[System]
You are a senior backend engineer working on the APIs written in expressJS. The project you are working on needs to follow certain formatting and conventions. The schema of the database is given below:
    forkfirm_invoice_cronjob
------------------------
Name            | Type

id              | int(11)
client_id       | int(11)
charged_amount  | double
fork_fee        | double
payout_amount   | double
create_at       | date
update_at       | datetime

forkfirm_photo
--------------
Name            | Type

id              | int(11)
url             | text
caption         | text
user_id         | int(11)
width           | int(11)
height          | int(11)
type            | int(11)
create_at       | date
update_at       | datetime

forkfirm_token
--------------
Name            | Type

id              | int(11)
token           | text
type            | int(11)
data            | text
user_id         | int(11)
status          | int(11)
create_at       | date
update_at       | datetime
expire_at       | datetime

forkfirm_profile
----------------
Name            | Type

id              | int(11)
user_id         | int(11)
create_at       | date
update_at       | datetime
base_office     | varchar(255)
attorney_expiry_date | date
attorney_license | int(11)
website         | text
invited_by      | varchar(255)
playbook        | int(11)
emailed         | tinyint(1)

forkfirm_activity_log
---------------------
Name            | Type

id              | int(11)
create_at       | date
date            | date
update_at       | datetime
contract_id     | int(11)
status          | int(11)

forkfirm_stripe_payment_intent
------------------------------
Name            | Type

id              | int(11)
user_id         | int(11)
stripe_id       | varchar(255)
object          | longtext
create_at       | date
update_at       | datetime

forkfirm_trigger_rules
----------------------
Name            | Type

id              | int(11)
name            | text
event_type      | varchar(255)
trigger_type    | varchar(255)
condition       | text
condition_payload | text
result_true     | text
result_payload  | text
status          | int(11)
create_at       | date
update_at       | datetime

forkfirm_invoice
----------------
Name            | Type

id              | int(11)
client_id       | int(11)
generated_at    | date
charged_amount  | double
fork_fee        | double
payout_amount   | double
status          | int(11)
paid_at         | date
stripe_payment_intent_id | int(11)
update_at       | datetime
create_at       | date

forkfirm_chat
-------------
Name            | Type

id              | int(11)
room_id         | int(11)
unread          | int(11)
chat            | longtext
create_at       | date
update_at       | datetime

forkfirm_room
-------------
Name            | Type

id              | int(11)
user_id         | int(11)
other_user_id   | int(11)
chat_id         | int(11)
unread          | int(11)
create_at       | date
update_at       | datetime
user_update_at  | datetime
other_user_update_at | datetime

forkfirm_stripe_checkout
------------------------
Name            | Type

id              | int(11)
user_id         | int(11)
stripe_id       | varchar(255)
object          | longtext
create_at       | date
update_at       | datetime

forkfirm_email
--------------
Name            | Type

id              | int(11)
slug            | varchar(255)
subject         | text
tag             | text
html            | text
create_at       | date
update_at       | datetime
status          | tinyint(1)

forkfirm_trigger_type
---------------------
Name            | Type

id              | int(11)
name            | text
create_at       | date
update_at       | datetime

forkfirm_stripe_order
---------------------
Name            | Type

id              | int(11)
user_id         | int(11)
price_id        | int(11)
stripe_id       | varchar(255)
object          | longtext
create_at       | date
update_at       | datetime

forkfirm_analytic_log
---------------------
Name            | Type

id              | bigint(20)
user_id         | int(11)
url             | text
path            | text
hostname        | text
ip              | varchar(50)
role            | varchar(50)
browser         | varchar(255)
country         | varchar(100)
create_at       | date
update_at       | datetime

forkfirm_law_firm_client
------------------------
Name            | Type

id              | int(11)
create_at       | date
update_at       | datetime
law_firm_id     | int(11)
client_id       | int(11)

forkfirm_client_hours
---------------------
Name            | Type

id              | int(11)
client_id       | int(11)
hour            | int(11)
contract_id     | int(11)
lawfirm_id      | int(11)
date            | date
description     | text
create_at       | date
update_at       | datetime

forkfirm_email_template
-----------------------
Name            | Type

id              | int(11)
email_id        | int(11)
attorney_id     | int(11)
create_at       | date
update_at       | datetime

forkfirm_tag
------------
Name            | Type

id              | int(11)
name            | varchar(255)
input_type      | int(11)
input_duration  | int(11)
status          | tinyint(1)
create_at       | date
update_at       | datetime

forkfirm_stripe_setting
-----------------------
Name            | Type

id              | int(11)
key             | varchar(50)
value           | mediumtext
create_at       | date
update_at       | datetime

forkfirm_cms
------------
Name            | Type

id              | int(11)
page            | varchar(255)
content_key     | varchar(255)
content_type    | varchar(255)
content_value   | text
create_at       | date
update_at       | datetime

forkfirm_client_tag
-------------------
Name            | Type

id              | int(11)
client_id       | int(11)
tag_id          | int(11)
create_at       | date
update_at       | datetime

forkfirm_stripe_webhook
-----------------------
Name            | Type

id              | int(11)
stripe_id       | varchar(255)
idempotency_key | varchar(255)
description     | varchar(255)
event_type      | varchar(255)
resource_type   | varchar(255)
object          | longtext
is_handled      | tinyint(1)
create_at       | date
update_at       | datetime

forkfirm_contract_document_hours
--------------------------------
Name            | Type

id              | int(11)
contract_id     | int(11)
client_id       | int(11)
hour            | int(11)
date            | date
description     | text
create_at       | date
update_at       | datetime

forkfirm_invoice_current
------------------------
Name            | Type

id              | int(11)
email           | varchar(255)
charged_amount  | decimal(44,1)
fork_fee        | decimal(50,5)
payout_amount   | decimal(51,5)

forkfirm_invoice_lawfirm
------------------------
Name            | Type

id              | int(11)
create_at       | date
update_at       | datetime
generated_at    | date
lawfirm_id      | int(11)
charged_amount  | double
fork_fee        | double
payout_amount   | double
status          | int(11)
paid_at         | date
stripe_payment_intent_id | int(11)

forkfirm_test
-------------
Name            | Type

id              | int(11)

forkfirm_invoice_all
--------------------
Name            | Type

id              | int(11)
email           | varchar(255)
generated_at    | date
charged_amount  | decimal(44,1)
fork_fee        | decimal(50,5)
payout_amount   | decimal(51,5)

forkfirm_doc_type
-----------------
Name            | Type

id              | int(11)
client_id       | int(11)
default_type    | int(11)
name            | varchar(255)
create_at       | date
update_at       | datetime

forkfirm_stripe_price
---------------------
Name            | Type

id              | int(11)
name            | varchar(255)
product_id      | int(11)
stripe_id       | varchar(255)
is_usage_metered | tinyint(1)
usage_limit     | int(11)
object          | longtext
amount          | float
trial_days      | int(11)
type            | varchar(50)
status          | tinyint(1)
create_at       | date
update_at       | datetime

forkfirm_contract_tag
---------------------
Name            | Type

id              | int(11)
create_at       | date
update_at       | datetime
contract_id     | int(11)
tag_id          | int(11)
input_type      | int(11)
input_duration  | varchar(255)
status          | tinyint(1)

forkfirm_contract
-----------------
Name            | Type

id              | int(11)
create_at       | date
executed_at     | date
terminated_at   | date
update_at       | datetime
date_updated    | datetime
activity_status_updated_at | datetime
client_id       | int(11)
originator      | int(11)
originator_name | varchar(255)
project_name    | varchar(255)
attorney_id     | int(11)
counter_party_name | varchar(255)
counter_party_known | tinyint(1)
counter_party_email | longtext
cc              | longtext
doc_type_id     | int(11)
executed_doc_type_id | int(11)
turnaround_time | int(11)
delivery        | int(11)
status          | int(11)
current_activity_status | int(11)
document_id     | int(11)
document_id2    | int(11)
document_id3    | int(11)
executed_document_id | int(11)
executed_document_id2 | int(11)
executed_document_id3 | int(11)
contract_type   | tinyint(1)
emailed         | tinyint(1)

forkfirm_stripe_invoice
-----------------------
Name            | Type

id              | int(11)
user_id         | int(11)
stripe_id       | varchar(255)
object          | longtext
create_at       | date
update_at       | datetime

forkfirm_user
-------------
Name            | Type

id              | int(11)
oauth           | varchar(255)
role            | varchar(255)
first_name      | varchar(255)
last_name       | varchar(255)
email           | varchar(255)
password        | varchar(255)
type            | int(11)
verify          | int(11)
phone           | varchar(255)
photo           | text
refer           | varchar(255)
stripe_uid      | varchar(255)
paypal_uid      | varchar(255)
two_factor_authentication | int(11)
status          | int(11)
create_at       | date
update_at       | datetime

forkfirm_stripe_subscription
----------------------------
Name            | Type

id              | int(11)
stripe_id       | varchar(255)
price_id        | varchar(255)
user_id         | int(11)
object          | longtext
status          | varchar(50)
is_lifetime     | tinyint(1)
create_at       | date
update_at       | datetime

forkfirm_permission
-------------------
Name            | Type

id              | int(11)
role            | varchar(255)
permission      | text
create_at       | date
update_at       | datetime

forkfirm_client_rate
--------------------
Name            | Type

id              | int(11)
client_id       | int(11)
doc_type_id     | int(11)
rate            | int(11)
create_at       | date
update_at       | datetime

forkfirm_client_subclient
-------------------------
Name            | Type

id              | int(11)
client_id       | int(11)
subclient_id    | int(11)
show_billing    | tinyint(1)
create_at       | date
update_at       | datetime

forkfirm_attorney_client
------------------------
Name            | Type

id              | int(11)
create_at       | date
update_at       | datetime
attorney_id     | int(11)
client_id       | int(11)

forkfirm_law_firm_attorney
--------------------------
Name            | Type

id              | int(11)
create_at       | date
update_at       | datetime
law_firm_id     | int(11)
attorney_id     | int(11)

forkfirm_project
----------------
Name            | Type

id              | int(11)
create_at       | date
update_at       | datetime
name            | varchar(255)
client_id       | int(11)
law_firm_id     | int(11)
launch_date     | date
no_of_parties_outreach | int(11)
playbook        | int(11)
playbook2       | int(11)
playbook3       | int(11)
nda             | int(11)
nda2            | int(11)
nda3            | int(11)

forkfirm_stripe_product
-----------------------
Name            | Type

id              | int(11)
name            | varchar(255)
stripe_id       | varchar(255)
object          | longtext
status          | tinyint(1)
create_at       | date
update_at       | datetime

    To access the database, you have to use the below functions from req.sdk, which provides you a connection to the database, below is it's structure::

    const { Sequelize, QueryTypes } = require("sequelize");
const { dbConnection } = require("./DbConnection");
const { sqlDateFormat, sqlDateTimeFormat } = require("../services/UtilService");
const ReadRecord = require('../baas/treeQL/actions/ReadRecord');
const ListRecord = require('../baas/treeQL/actions/ListRecord');
const QueryBuilder = require("./QueryBuilder");

module.exports = class BackendSDK {
  constructor() {
    this._dbConnection = dbConnection;
    this._database = "";
    this._table = "";
    this._projectId = "";
    this._secret = "";
    this.potentialInjectionKeywords = ["union", "from", "`", "--", "++", ";"];
  }

  setDatabase(database) {
    this._database = database;
    return this._dbConnection;
  }

  getDatabase() {
    return this._dbConnection;
  }

  setTable(table) {
    this._table = this._database + "." + this._projectId + "_" + table;
    return this;
  }

  table(table) {
    this._table = this._database + "." + this._projectId + "_" + table;
    return this;
  }

  getTable() {
    return this._table;
  }

  setProjectId(projectId) {
    this._projectId = projectId;
    return this;
  }

  projectId(projectId) {
    this._projectId = projectId;
    return this;
  }

  getProjectId() {
    return this._projectId;
  }

  setSecret(secret) {
    this._secret = secret;
  }

  getSecret() {
    return this._secret;
  }

  async getProjectDatabase(projectId = "") {
    const result = await this._dbConnection.query(
      ` SELECT SCHEMA_NAME AS db_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME LIKE 'baas_${projectId}'`,
      {
        raw: false
      }
    );

    if (result.length > 0) {
      return typeof result[1][0] !== "undefined" ? result[1][0].db_name : "baas";
    } else {
      return "baas";
    }
  }

  checkSQLInjection(sqlString) {
    sqlString = sqlString ?? "";

    for (let x of this.potentialInjectionKeywords) {
      if (sqlString.toLowerCase().includes(x)) throw new Error("Custom sql string validation error");
    }
  }

  validateFieldNames(keyNames) {
    if (!keyNames) return;
    if (keyNames?.type == "string") keyNames = [keyNames];
    for (let keyName of keyNames) {
      keyName = keyName ?? "";

      if (keyName.search(/[^A-Z^a-z^0-9^_^.]/g) != -1) throw new Error("Key name invalid");
    }
  }

  whereArrayToObject(where) {
    let ret = {};

    for (let x of where) {
      let [key, value] = x.split("=");
      key = key.trim();
      value = value.trim();
      ret[key] = value;
    }

    return ret;
  }

  async join(table1, table2, joinId1, joinId2, selectStr, where) {
    try {
      if (selectStr.length < 1) {
        selectStr = " * ";
      }

      this.validateFieldNames([table1, table2, joinId1, joinId2]);

      const namespaceTable1 = this._database + "." + this._projectId + "_" + table1;
      const namespaceTable2 = this._database + "." + this._projectId + "_" + table2;

      let sql =
        "SELECT " +
        selectStr +
        " FROM " +
        namespaceTable1 +
        " as " +
        table1 +
        " INNER JOIN " +
        namespaceTable2 +
        " as " +
        table2 +
        " ON " +
        `${table1}.${joinId1}` +
        " = " +
        `${table2}.${joinId2}` +
        " WHERE ";

      if (Array.isArray(where)) where = this.whereArrayToObject(where);

      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }

      // console.log(bind);

      if (rows.length == 0) {
        sql = sql.split("WHERE").join("");
      }

      sql += rows.join(" AND ");

      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT,
        bind
      });

      if (result.length > 0) {
        return result;
      } else {
        return [];
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async joinPaginate(table1, table2, joinId1, joinId2, selectStr, where, page, limit, sortField, sortDirection) {
    try {
      if (selectStr.length < 1) {
        selectStr = " * ";
      }
      this.validateFieldNames([table1, table2, joinId1, joinId2]);

      this.checkSQLInjection(selectStr); //validation for sql injection

      const namespaceTable1 = this._database + "." + this._projectId + "_" + table1;
      const namespaceTable2 = this._database + "." + this._projectId + "_" + table2;

      let sql =
        "SELECT " +
        selectStr +
        " FROM " +
        namespaceTable1 +
        " " +
        // table1 +
        " INNER JOIN " +
        namespaceTable2 +
        " " +
        // table2 +
        " ON " +
        `${namespaceTable1}.${joinId1}` +
        " = " +
        `${namespaceTable2}.${joinId2}` +
        " WHERE ";

      where = this.whereArrayToObject(where);

      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }
      if (rows.length == 0) {
        sql = sql.split("WHERE").join("");
      }
      sql += rows.join(" AND ");

      const offset = page * limit;
      sql += ` LIMIT ${offset} , ${limit}`;

      console.log("sql", sql);
      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT,
        bind
      });

      if (result.length > 0) {
        return result;
      } else {
        return [];
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  // join paginate with where as object
  async joinPaginateWhereObj(table1, table2, joinId1, joinId2, selectStr, where, page, limit, sortField, sortDirection) {
    try {
      if (selectStr.length < 1) {
        selectStr = " * ";
      }

      this.checkSQLInjection(selectStr); //validation for sql injection
      this.validateFieldNames([table1, table2, joinId1, joinId2]);

      const namespaceTable1 = this._database + "." + this._projectId + "_" + table1;
      const namespaceTable2 = this._database + "." + this._projectId + "_" + table2;

      let sql =
        "SELECT " +
        selectStr +
        " FROM " +
        namespaceTable1 +
        " " +
        // table1 +
        " INNER JOIN " +
        namespaceTable2 +
        " " +
        // table2 +
        " ON " +
        `${namespaceTable1}.${joinId1}` +
        " = " +
        `${namespaceTable2}.${joinId2}` +
        " WHERE ";

      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }

      sql += rows.join(" AND ");

      const offset = (page - 1) * limit;
      sql += `  LIMIT ${limit} OFFSET  ${offset}`;

      console.log("sql", sql);
      if (rows.length == 0) {
        sql = sql.split("WHERE").join(" ");
      }
      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT,
        bind
      });

      if (result.length > 0) {
        return result;
      } else {
        return [];
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async forceSync() {
    await this._dbConnection.sync({ force: true });
  }

  async joinPaginateStr(table1, table2, joinId1, joinId2, selectStr, where, page, limit, orderBy, direction) {
    try {
      const namespaceTable1 = this._database + "." + this._projectId + "_" + table1;
      const namespaceTable2 = this._database + "." + this._projectId + "_" + table2;

      this.validateFieldNames([table1, table2, joinId1, joinId2, page, limit, orderBy, direction]);

      let sql =
        "SELECT " +
        selectStr +
        " FROM " +
        namespaceTable1 +
        " " +
        table1 +
        " INNER JOIN " +
        namespaceTable2 +
        " " +
        table2 +
        " ON " +
        `${table1}.${joinId1}` +
        " = " +
        `${table2}.${joinId2}` +
        " WHERE ";

      if (!Array.isArray(where)) {
        throw new Error("Wrong parameter. Expecting array");
      }

      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }

      sql += rows.join(" AND ");

      if (orderBy && direction) {
        sql += ` ORDER BY ${orderBy} ${direction}`;
      }

      const offset = page * limit;

      sql += ` LIMIT ${offset} , ${limit}`;
      console.log("sql", sql);

      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return result;
      } else {
        return [];
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  // Part: Join Multiple Tables
  removeProperties(result, properties) {
    result.forEach((el) => {
      properties.forEach((prop) => {
        delete el[prop];
      });
    });

    return result;
  }

  async joinMultiPaginateStr(tables, joinIds, selectStr, where, page, limit, orderBy, direction) {
    try {
      const namespaceTable1 = tables[0] ? this._database + "." + this._projectId + "_" + tables[0] : "";
      const namespaceTable2 = tables[1] ? this._database + "." + this._projectId + "_" + tables[1] : "";
      const namespaceTable3 = tables[2] ? this._database + "." + this._projectId + "_" + tables[2] : "";

      this.checkSQLInjection(selectStr);
      this.validateFieldNames([...tables, ...joinIds, page, limit, orderBy, direction]);

      if (tables.length === 3 && (namespaceTable1 === "" || namespaceTable2 === "" || namespaceTable3 === "")) {
        throw new Error("Wrong parameter. Empty tables.");
      }
      if (tables.length === 2 && (namespaceTable1 === "" || namespaceTable2 === "")) {
        throw new Error("Wrong parameter. Empty tables.");
      }

      if (tables.length === 3) {
        var sql =
          "SELECT " +
          selectStr +
          " FROM " +
          namespaceTable1 +
          " " +
          // tables[0] +
          " LEFT JOIN " +
          namespaceTable2 +
          " " +
          // tables[1] +
          " ON " +
          `${namespaceTable1}.${joinIds[0]}` +
          " = " +
          `${namespaceTable2}.${joinIds[1]}` +
          " LEFT JOIN " +
          namespaceTable3 +
          " " +
          // tables[2] +
          " ON " +
          `${namespaceTable1}.${joinIds[0]}` +
          " = " +
          `${namespaceTable3}.${joinIds[0]}` +
          " WHERE ";
      } else if (tables.length === 2) {
        var sql =
          "SELECT " +
          selectStr +
          " FROM " +
          namespaceTable1 +
          " " +
          // tables[0] +
          " LEFT JOIN " +
          namespaceTable2 +
          " " +
          // tables[1] +
          " ON " +
          `${namespaceTable1}.${joinIds[0]}` +
          " = " +
          `${namespaceTable2}.${joinIds[1]}` +
          " WHERE ";
      } else if (tables.length === 1) {
        var sql = "SELECT " + selectStr + " FROM " + namespaceTable1 + " " /* + tables[0] */ + " WHERE ";
      } else {
        throw new Error("Wrong parameter. No table found.");
      }

      if (!Array.isArray(where)) {
        throw new Error("Wrong parameter. Expecting array");
      }

      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }

      sql += rows.join(" AND ");

      if (orderBy && direction) {
        sql += ` ORDER BY ${orderBy} ${direction}`;
      }

      const offset = page * limit;

      sql += ` LIMIT ${offset} , ${limit}`;

      console.log("sql", sql);

      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return this.removeProperties(result, ["password"]);
      } else {
        return [];
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async joinCount(table1, table2, joinId1, joinId2, where) {
    try {
      const namespaceTable1 = this._database + "." + this._projectId + "_" + table1;
      const namespaceTable2 = this._database + "." + this._projectId + "_" + table2;

      let countSql =
        "SELECT count(*) as num FROM " +
        namespaceTable1 +
        " INNER JOIN " +
        namespaceTable2 +
        " " +
        // table2 +
        " ON " +
        `${namespaceTable1}.${joinId1}` +
        " = " +
        `${namespaceTable2}.${joinId2}` +
        " WHERE ";

      where = this.whereArrayToObject(where);

      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }
      if (rows.length == 0) {
        countSql = countSql.split("WHERE").join("");
      }
      countSql += rows.join(" AND ");

      const countResult = await this._dbConnection.query(countSql, {
        bind,
        type: QueryTypes.SELECT
      });

      let total = 0;
      if (countResult.length > 0) {
        total = countResult[0].num;
      }

      return total;
    } catch (error) {
      throw new Error(error.message);
    }
  }

  //where in object
  async joinCountWhereObj(table1, table2, joinId1, joinId2, where) {
    try {
      const namespaceTable1 = this._database + "." + this._projectId + "_" + table1;
      const namespaceTable2 = this._database + "." + this._projectId + "_" + table2;

      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }

      let countSql =
        "SELECT count(*) as num FROM " +
        namespaceTable1 +
        " INNER JOIN " +
        namespaceTable2 +
        " " +
        // table2 +
        " ON " +
        `${namespaceTable1}.${joinId1}` +
        " = " +
        `${namespaceTable2}.${joinId2}` +
        " WHERE ";
      if (rows.length < 1) {
        rows = [" 1 "];
      }

      countSql += rows.join(" AND ");
      console.log("countSql", countSql);

      const countResult = await this._dbConnection.query(countSql, {
        type: QueryTypes.SELECT,
        bind
      });

      let total = 0;
      if (countResult.length > 0) {
        total = countResult[0].num;
      }

      return total;
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async get(where, selectStr = "*", orderBy = "id", direction = "DESC", customWhere = null) {
    try {
      let sql = "SELECT " + selectStr + " FROM " + this._table + " WHERE ";
      let rows = [];
      let count = 1;
      let bind = [];
      // console.log("*****Hello", customWhere);
      this.checkSQLInjection(selectStr); //validation for sql injection
      this.checkSQLInjection(customWhere); //validation for sql injection
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`\`${key}\` = $${count}`);
          count++;
          bind.push(element);
        }
      }

      console.log(where);

      if (count == 1) {
        sql += " 1";
      }

      sql += rows.join(" AND ");
      if (customWhere) sql += " and " + customWhere + " ";
      sql += ` ORDER BY \`${orderBy}\` ${direction}`;
      console.log("sql", sql);
      console.log("bind", bind);
      const result = await this._dbConnection.query(sql, {
        bind: bind,
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return result;
      } else {
        return [];
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async paginate(where, selectStr = "*", page, limit, order, direction, customWhere) {
    try {
      let sql = "SELECT " + selectStr + " FROM " + this._table + " WHERE ";
      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }

      if (count == 1) {
        sql += " 1";
      }

      sql += rows.join(" AND ");

      // if (customWhere) sql += " and " + customWhere + " ";

      if (order) {
        sql += ` order by ${order} ${direction ? direction : "ASC"} `;
      }

      const offset = (page - 1) * limit;

      sql += ` LIMIT ${offset} , ${limit}`;
      console.log("sql", sql);
      // console.log('bind', bind);
      const result = await this._dbConnection.query(sql, {
        bind: bind,
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return result;
      } else {
        return [];
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async paginateStr(where, selectStr = "*", page, limit, order, direction) {
    try {
      let sql = "SELECT " + selectStr + " FROM " + this._table + " WHERE ";
      if (!Array.isArray(where)) {
        throw new Error("Wrong parameter. Expecting array");
      }

      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }
      if (rows.length == 0) {
        sql = sql.split("WHERE").join("");
      }

      sql += rows.join(" AND ");
      const offset = (page - 1) * limit;

      sql += ` LIMIT ${offset} , ${limit}`;
      console.log("sql", sql);

      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return result;
      } else {
        return [];
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }
  async joinCountStr(table1, table2, joinId1, joinId2, where) {
    try {
      const namespaceTable1 = this._database + "." + this._projectId + "_" + table1;
      const namespaceTable2 = this._database + "." + this._projectId + "_" + table2;

      let sql =
        "SELECT COUNT(*) as num FROM " +
        namespaceTable1 +
        " " +
        table1 +
        " INNER JOIN " +
        namespaceTable2 +
        " " +
        table2 +
        " ON " +
        `${table1}.${joinId1}` +
        " = " +
        `${table2}.${joinId2}` +
        " WHERE ";

      if (!Array.isArray(where)) {
        throw new Error("Wrong parameter. Expecting array");
      }
      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }

      sql += rows.join(" AND ");
      console.log(sql);
      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return result[0].num;
      } else {
        return 0;
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async joinMultiCountStr(tables, joinIds, where) {
    try {
      const namespaceTable1 = tables[0] ? this._database + "." + this._projectId + "_" + tables[0] : "";
      const namespaceTable2 = tables[1] ? this._database + "." + this._projectId + "_" + tables[1] : "";
      const namespaceTable3 = tables[2] ? this._database + "." + this._projectId + "_" + tables[2] : "";
      console.log(namespaceTable1);

      if (tables.length === 3 && (namespaceTable1 === "" || namespaceTable2 === "" || namespaceTable3 === "")) {
        throw new Error("Wrong parameter. Empty tables.");
      }
      if (tables.length === 2 && (namespaceTable1 === "" || namespaceTable2 === "")) {
        throw new Error("Wrong parameter. Empty tables.");
      }

      if (tables.length === 3) {
        var sql =
          "SELECT COUNT(*) as num FROM " +
          namespaceTable1 +
          " " +
          // tables[0] +
          " LEFT JOIN " +
          namespaceTable2 +
          " " +
          // tables[1] +
          " ON " +
          `${namespaceTable1}.${joinIds[0]}` +
          " = " +
          `${namespaceTable2}.${joinIds[1]}` +
          " LEFT JOIN " +
          namespaceTable3 +
          " " +
          // tables[2] +
          " ON " +
          `${namespaceTable1}.${joinIds[0]}` +
          " = " +
          `${namespaceTable3}.${joinIds[0]}` +
          " WHERE ";
      } else if (tables.length === 2) {
        var sql =
          "SELECT COUNT(*) as num FROM " +
          namespaceTable1 +
          " " +
          // tables[0] +
          " LEFT JOIN " +
          namespaceTable2 +
          " " +
          // tables[1] +
          " ON " +
          `${namespaceTable1}.${joinIds[0]}` +
          " = " +
          `${namespaceTable2}.${joinIds[1]}` +
          " WHERE ";
      } else if (tables.length === 1) {
        var sql = "SELECT COUNT(*) as num FROM " + namespaceTable1 + " " /* + tables[0] */ + " WHERE ";
      } else {
        throw new Error("Wrong parameter. No table found.");
      }

      if (!Array.isArray(where)) {
        throw new Error("Wrong parameter. Expecting array");
      }

      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }

      sql += rows.join(" AND ");
      console.log(sql);
      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return result[0].num;
      } else {
        return 0;
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async countStr(where) {
    try {
      let sql = "SELECT COUNT(*) as num FROM " + this._table + " WHERE ";
      if (!Array.isArray(where)) {
        throw new Error("Wrong parameter. Expecting array");
      }
      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }
      if (rows.length == 0) {
        sql = sql.split("WHERE").join("");
      }
      sql += rows.join(" AND ");

      console.log(sql);
      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return result[0].num;
      } else {
        return 0;
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async count(where) {
    try {
      let sql = "SELECT COUNT(*) as num FROM " + this._table + " WHERE ";
      let rows = [];
      let count = 1;
      let bind = [];
      for (const key in where) {
        if (Object.hasOwnProperty.call(where, key)) {
          const element = where[key];
          this.validateFieldNames(key);
          rows.push(`${key} = $${count}`);
          count++;
          bind.push(element);
        }
      }
      if (count == 1) {
        sql += " 1";
      }
      sql += rows.join(" AND ");
      console.log(sql);
      const result = await this._dbConnection.query(sql, {
        bind: bind,
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return result[0].num;
      } else {
        return 0;
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async getStr(where, selectStr = "*") {
    try {
      let sql = "SELECT " + selectStr + " FROM " + this._table + " WHERE ";
      let rows = [];
      for (let i = 0; i < where.length; i++) {
        const element = where[i];
        rows.push(element);
      }

      sql += rows.join(" AND ");
      // console.log('sql', sql);
      // console.log('bind', bind);
      const result = await this._dbConnection.query(sql, {
        type: QueryTypes.SELECT
      });

      if (result.length > 0) {
        return result;
      } else {
        return [];
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async insert(payload) {
    try {
      console.log(payload);
      if (!payload.hasOwnProperty("create_at")) payload["create_at"] = sqlDateFormat(new Date());
      if (!payload.hasOwnProperty("update_at")) payload["update_at"] = sqlDateTimeFormat(new Date());
      const fields = Object.keys(payload);
      const questionMarks = [];
      for (let i = 0; i < fields.length; i++) {
        questionMarks.push("?");
      }

      let sql = "INSERT INTO " + this._table + " (" + fields.join(",") + ") VALUES (" + questionMarks.join(",") + ");";
      let bind = [];
      for (const key in payload) {
        if (Object.hasOwnProperty.call(payload, key)) {
          this.validateFieldNames(key);
          const element = payload[key];
          bind.push(element);
        }
      }

      const result = await this._dbConnection.query(sql, {
        replacements: bind,
        type: Sequelize.QueryTypes.INSERT
      });

      if (result.length == 2) {
        return result[0];
      } else {
        return null;
      }
    } catch (error) {
      console.log("INSERT ERROR", error);
      throw new Error(error.message, { cause: error });
    }
  }

  async batchInsert(columns, payload) {
    try {
      // iterate the payload and insert into the database
      for (let i = 0; i < payload.length; i++) {
        let sql = "INSERT INTO " + this._table + " (" + columns.join(",") + ") VALUES (";
        let bind = [];
        for (let j = 0; j < columns.length; j++) {
          const element = columns[j];
          sql += "?,";
          bind.push(payload[i][element]);
        }
        sql = sql.slice(0, -1);
        sql += ");";

        await this._dbConnection.query(sql, {
          replacements: bind,
          type: Sequelize.QueryTypes.INSERT
        });
      }
      return true;
    } catch (error) {
      console.log("INSERT ERROR", error);
      throw new Error(error.message, { cause: error });
    }
  }

  async update(payload, id) {
    try {
      let sql = "UPDATE " + this._table + " SET ";
      let rows = [];
      let bind = [];

      for (const key in payload) {
        if (Object.hasOwnProperty.call(payload, key)) {
          const element = payload[key];
          this.validateFieldNames(key);
          rows.push(`${key} = ?`);
          bind.push(element);
        }
      }
      sql += rows.join(" , ");
      sql += " WHERE id = " + id;
      console.log("sql", sql);
      const result = await this._dbConnection.query(sql, {
        replacements: bind
      });

      if (result) {
        return result;
      } else {
        return null;
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  // Point: Update Record by Condition
  async updateWhere(payload, condition) {
    try {
      let sql = "UPDATE " + this._table + " SET ";
      let setRows = [];
      let bind = [];

      // Part: Constructing SET Clause
      for (const key in payload) {
        if (Object.hasOwnProperty.call(payload, key)) {
          let element = payload[key];

          switch (typeof element) {
            case "bigint":
              break;
            case "number":
              break;
            case "string":
              element = element;
              break;
            case "boolean":
              element = element;
              break;

            case "object": // Note: Assuming JSON
              if (element !== null) element = `${JSON.stringify(element)}`;
              break;

            default:
              element = element;
              break;
          }

          this.validateFieldNames(key);
          // setRows.push(`${key} = ${element}`);
          setRows.push(`${key} = ?`);
          bind.push(element);
        }
      }

      sql += setRows.join(" , ");
      sql += " WHERE ";

      // Part: Constructing WHERE Clause
      const whereRows = [];
      for (const key in condition) {
        if (Object.hasOwnProperty.call(condition, key)) {
          let element = condition[key];

          switch (typeof element) {
            case "bigint":
              break;
            case "number":
              break;
            case "boolean":
              element = element;
              break;

            default:
              element = element;
              break;
          }
          this.validateFieldNames(key);
          whereRows.push(`${key} = ?`);
          bind.push(element);
        }
      }

      sql += whereRows.join(" AND ");

      const result = await this._dbConnection.query(sql, {
        replacements: bind,
        type: QueryTypes.UPDATE
      });

      if (result) {
        return result;
      } else {
        return null;
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async delete(payload, id) {
    try {
      let sql = "DELETE FROM " + this._table + " WHERE ";
      let rows = ["id = ?"];
      let bind = [id];

      for (const key in payload) {
        if (Object.hasOwnProperty.call(payload, key)) {
          this.validateFieldNames(key);
          const element = payload[key];
          rows.push(`${key} = ?`);
          bind.push(element);
        }
      }

      sql += rows.join(" AND ");
      // console.log('sql', sql);
      const result = await this._dbConnection.query(sql, {
        replacements: bind
      });

      if (result) {
        return result;
      } else {
        return null;
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async deleteWhere(payload) {
    try {
      let sql = "DELETE FROM " + this._table + " WHERE ";
      let rows = [];
      let bind = [];

      for (const key in payload) {
        if (Object.hasOwnProperty.call(payload, key)) {
          const element = payload[key];
          this.validateFieldNames(key);
          rows.push(`${key} = ?`);
          bind.push(element);
        }
      }

      sql += rows.join(" AND ");
      // console.log('sql', sql);
      const result = await this._dbConnection.query(sql, {
        replacements: bind
      });

      if (result) {
        return result;
      } else {
        return null;
      }
    } catch (error) {
      throw new Error(error.message);
    }
  }

  async rawQuery(sql) {
    // When using this function be aware of sql injection. Do a proper validation
    // To avoid, one best practice is to use parameterized query with binding.

    const result = await this._dbConnection.query(`use ${this._database}; ${sql}`, {
      raw: false
    });
    console.log(result.length);
    if (result.length > 0) {
      return result[1][1] ?? [];
    } else {
      return [];
    }
  }
  query(project = null) {
    project = project ?? this._projectId
    return new QueryBuilder(project)
  }
  subquery(project = null) {
    project = project ?? this._projectId
    return QueryBuilder.subquery(project)
  }

  async getOne(table, id, options = {}) {
    this._table = table;
    options.params = {}; // ensure compatibility
    options.query = {}; // ensurecompatibility

    options.params.id = id.toString();
    options.params.table = table;
    options.query.join = options.join ?? null ;
    options.query.filter = options.filter ?? null
    options.projectId = this._projectId;
    const result = await ReadRecord.handle(options, this);
    return result;
  }

  async getList(table, options = {}) {
    this._table = table;
    options.projectId = this._projectId;

    options.params = {}; // ensure compatibility
    options.query = {}; // ensurecompatibility

    options.query.join = options.join ?? null ;
    options.query.filter = options.filter ?? null
    options.params.table = table;
    const result = await ListRecord.handle(options, this);
    return result;
  }

  whitelistField(data, keys) {
    let payload = {};
    keys.forEach((key) => {
      if (data[key]) {
        payload[key] = data[key];
      }
    });

    return payload;
  }

  blacklistField(data, keys) {
    keys.forEach((key) => {
      if (data[key]) {
        delete data[key];
      }
    });

    return data;
  }

  deepBlacklistField(data, keys) {
    const filterObject = (obj, key) => {
      for (var i in obj) {
        if (!obj.hasOwnProperty(i)) continue;
        if (i == key) {
          delete obj[key];
        } else if (typeof obj[i] == "object") {
          filterObject(obj[i], key);
        }
      }
      return obj;
    };
    let self = this;
    if (Array.isArray(data)) {
      for (let i = 0; i < data.length; i++) {
        data[i] = self.deepBlacklistField(data[i], keys);
      }
    } else {
      keys.forEach((key) => filterObject(data, key));
    }
    return data;
  }
};

    The services you have access to are:
    Service Name
        Service Description: Service Description
        Constructor Params: Constructor Parameters
        Methods:
        Method Name
            Method Description: Method Description
            Method Params: Parameters
            Method Response: Return Type
            AuthService
        Service Description: This service is to authenticate user for different roles. Use this class to do any authentications.
        Constructor Params:
        Methods:
        login
            Method Description: This method is to do simple login
            Method Params: sdk, projectId, email, password, role
            Method Response: {
          user_id: exist[0].user_id,
          token: newToken,
          type: 2,
          data: JSON.stringify({ user_id, token: newToken }),
          status: 1,
          create_at: sqlDateFormat(new Date()),
          update_at: sqlDateTimeFormat(new Date()),
          expire_at: sqlDateTimeFormat(expireDate)
        }
            marketingLogin
            Method Description: Use this to do marketing login.
            Method Params: sdk, projectId, email, password, role
            Method Response:
            register
            Method Description:
            Method Params: sdk, projectId, email, role
            Method Response:
            registerMarketingUser
            Method Description:
            Method Params: sdk, projectId, email, password, role, verify = 0, first_name = "", last_name = "", photo = null, phone = null
            Method Response:
            googleLogin
            Method Description:
            Method Params: sdk, projectId, user, tokens, role, company_id
            Method Response:
            appleLogin
            Method Description:
            Method Params: sdk, projectId, user, tokens, role, company_id
            Method Response:
            twitterLogin
            Method Description:
            Method Params: sdk, projectId, user, role
            Method Response:
            facebookLogin
            Method Description:
            Method Params: sdk, projectId, user, tokens, role, company_id
            Method Response:
            forgot
            Method Description:
            Method Params: sdk, projectId, email, userId
            Method Response:
            mobileForgot
            Method Description:
            Method Params: sdk, projectId, email, userId
            Method Response:
            mobileVerify
            Method Description:
            Method Params: sdk, projectId, email
            Method Response:
            reset
            Method Description:
            Method Params: sdk, projectId, token, password
            Method Response:
            saveRefreshToken
            Method Description:
            Method Params: sdk, projectId, user_id, token, expireDate
            Method Response:
            checkRefreshToken
            Method Description:
            Method Params: sdk, projectId, userId, token
            Method Response: {...tokenTableResponse}
            updateRefreshToken
            Method Description:
            Method Params: sdk, projectId, oldToken, newToken, expireDate
            Method Response:

    The API you are working on has a below structure:
    app.post('/v2/api/forkfirm/client/pay', middlewares, ()>{
try {
} catch (error) { console.error(error); return res.status(500).json({ error: true, message: "Something went wrong!" }); }
})
    You have to fill in the api structure with logical codes based on the users comments, these comments are made for the code segments.
    You have to provide a chain of thought for the code you are writing. Then you have to write the code with comments. While writing the code,
    embrace the code into ``` codes ``` in this format. **Don't provide the API structure part in the code, just provide the parts inside the try block.**

While returning the final response, strictly follow the below convention:
1. If the response is a list of objects, use the key list in the response.
2. If the response is a single object, use the key model in the response.

Note: Use one of the above, don't use them together

Additional parameter:
error: true/false // Required key
message: "Put your message if necessary" // optional key

[User]
The logic should be based on the below comments:
    - API for Payment
- Create payment intent
- Store pi
- Update invoice

[Assistant]
To write the codes, I'll follow the below chain of thought:
    Gret invoice from db. Create payment intent then store the payment intent in db and update the invoice
    Used services: - sdk
- stripe.paymentIntents
- sdk
- sdk.setProjectId
- sdk.setTable
- sdk.insert
- sdk.setTable
    The code with comments is:
    ```
    const { invoice_id, payment_method } = req.body;

    if (!invoice_id || !payment_method) return res.status(400).json({ error: true, message: "Invoice and Payment Method is required" });

    const getInvoice = `SELECT forkfirm_invoice.* FROM forkfirm_invoice WHERE forkfirm_invoice.id = ${invoice_id} AND forkfirm_invoice.status = 0 AND `;
    req.params.method = "RAW";
    const invoiceResult = await handleReq(req, res, [], null, getInvoice);

    if (!invoiceResult.length) return res.status(400).json({ error: true, message: "Invoice is not available" });

    const userInfo = `SELECT id, stripe_uid FROM forkfirm_user WHERE forkfirm_user.id = ${invoiceResult[0].client_id} AND `;
    const userInfoResult = await handleReq(req, res, [], null, userInfo);

    // Create payment intent
    const paymentIntent = await stripe.paymentIntents.create({
      payment_method_types: ["card"],
      amount: invoiceResult[0].charged_amount * 100,
      currency: "usd",
      customer: userInfoResult[0].stripe_uid,
      payment_method: payment_method,
      off_session: true,
      confirm: true
    });

    if (paymentIntent.status !== "succeeded") return res.status(400).json({ error: true, message: "Payment Failed!" });

    if (paymentIntent.status === "succeeded") {
      const sdk = req.sdk;
      sdk.setProjectId(req.projectId);

      // Store pi
      sdk.setTable("stripe_payment_intent");
      const stripeInvoiceResult = await sdk.insert({
        user_id: userInfoResult[0].id,
        stripe_id: paymentIntent.id,
        object: JSON.stringify(paymentIntent),
        create_at: sqlDateFormat(new Date()),
        update_at: sqlDateTimeFormat(new Date())
      });

      // Update invoice
      sdk.setTable("invoice");
      await sdk.update(
        {
          status: 1,
          stripe_payment_intent_id: stripeInvoiceResult,
          paid_at: sqlDateTimeFormat(new Date()),
          update_at: sqlDateTimeFormat(new Date())
        },
        invoice_id
      );

      return res.status(200).json({ error: false, message: "Payment Successful!" });
    }
    ```