Source: saltcorn-data/models/tenant.js

/**
 * Tenant Management Data Layer Access
 *
 */
const db = require("../db");
const reset = require("../db/reset_schema");
const { contract, is } = require("contractis");
const { sqlsanitize } = require("../db/internal");
const { setConfig } = require("./config");
/**
 * List all Tenants
 * @type {*|(function(...[*]=): *)}
 */
const getAllTenants = contract(
  is.fun([], is.promise(is.array(is.str))),
  async () => {
    const tens = await db.select("_sc_tenants");
    return tens.map(({ subdomain }) => subdomain);
  }
);
/**
 * Create Tenant and switch to It:
 * - normalize domain name
 * - create db schema
 * - reset db schema (create required )
 * - change current base_url
 * @type {*|(function(...[*]=): *)}
 *
 * Arguments:
 * subdomain - tenant name (subdomain)
 * newurl - base url of tenant
 * email - email of creator
 * description - description of tenant
 */
const createTenant = contract(
  is.fun([is.str, is.maybe(is.str), is.maybe(is.str), is.maybe(is.str)], is.promise(is.undefined)),
  // TODO how to set names for arguments
  async ( subdomain, newurl, email, description) => {
      // normalize domain name
    const saneDomain = domain_sanitize(subdomain);

    // add email
    const saneEmail = typeof email !== "undefined" ? email : "";
    // add description
    const saneDescription = description !== "undefined" ? description : null;
    // add info about tenant into main site
    const id = await db.insert(
      "_sc_tenants",
      { subdomain: saneDomain, email: saneEmail, description: saneDescription },
      { noid: true }
    );
    //create schema
    await db.query(`CREATE SCHEMA "${saneDomain}";`);

    // set continuation storage
    //db.tenantNamespace.set("tenant", saneDomain);
    await db.runWithTenant(saneDomain, async () => {
      //reset schema
      await reset(true, saneDomain);
      if (newurl) await setConfig("base_url", newurl);
    });
  }
);
/**
 * Delete Tenant
 * Note! This is deleting all tenant data in database!
 * @type {*|(function(...[*]=): *)}
 */
const deleteTenant = contract(
  is.fun(is.str, is.promise(is.undefined)),
  async (sub) => {
    const subdomain = domain_sanitize(sub);
    // drop tenant db schema
    await db.query(`drop schema if exists "${subdomain}" CASCADE `);
    // delete information about tenant from main site
    await db.deleteWhere("_sc_tenants", { subdomain });
  }
);
/**
 * Sanitize Domain (Normalize domain name).
 * - force to lower case
 * - remove . in name
 * @type {*|(function(...[*]=): *)}
 */
const domain_sanitize = contract(is.fun(is.str, is.str), (s) =>
  sqlsanitize(s.replace(".", "").toLowerCase())
);
/**
 * Call fuction f for each Tenant
 * @param f - called function
 * @returns {Promise<void>} no result
 */
const eachTenant = async (f) => {
  await f();
  if (db.is_it_multi_tenant()) {
    const tenantList = await getAllTenants();
    for (const domain of tenantList) await db.runWithTenant(domain, f);
  }
};

module.exports = {
  getAllTenants,
  createTenant,
  domain_sanitize,
  deleteTenant,
  eachTenant,
};