1.0.4 • Published 6 months ago

saas-vs v1.0.4

Weekly downloads
-
License
MIT
Repository
-
Last release
6 months ago

SAAS-VS (Software as a service)

Software as a service is a multi-tenant software hosting model. Using it's functions you will be able to register multiple tenants for your product and able to create separate database and product related tables for each tenant.

It can be used in two ways:

1. Single produt multitenant approach:

If you have only single product and there are multiple tenants and you want to host your product for multiple tenants then you can use this package with isMultiProduct flag as false. Where you do not need to provide any unique product id for your product. This usecase is the default behaviour of package.

Architecture diagram for single product multitenant usecase:

architecture_diagram

Sequence diagram for single product multitenant usecase

sequence_diagram

2. Multiple produts multitenant approach:

If you have multiple products and there are multiple tenants and you want to host your products for multiple tenants then you can use this package by passing isMultiProduct flag as true . Where you need to provide a unique product id for each of your products while using the package functions.

Architecture diagram for multiple products multitenant usecase:

architecture_diagram

Sequence diagram for multiple products multitenant usecase

sequence_diagram


Installation

Using npm:

# Install package with npm

$  npm install saas-vs

Import module

import { MultiTenantController } from "saas-vs";
const multiTenantController = new MultiTenantController();

Requirements:

  • Create databse folder in root of your project and inside it schema folder and place file containing schema creation queries i.e .sql file for a product inside it.

For example create schema.sql inside database/schema folder path.

this can contain queries like this

   CREATE TABLE IF NOT EXISTS
        employee (
          autoUniqueId INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
          name VARCHAR(255) NOT NULL,
          code VARCHAR(255) NOT NULL,
          firstName VARCHAR(255) NOT NULL,
          middleName VARCHAR(255),
          lastName VARCHAR(255) NOT NULL,
          company INT(11) NOT NULL,
        }ENGINE = InnoDB;
  • There should be one db.config.json file present in your root directory with below required keys. You can take reference from db.config.sample file as well present inside root of this package.

For single product usecase(i.e default behaviour with isMultiProduct flag as false) db.config.json file will look like:

 {
    "MTS_MASTER_DB_PORT": 3306,
    "MTS_MASTER_DB_HOSTNAME": "YOUR_DB_HOSTNAME",
    "MTS_MASTER_DB_NAME": "YOUR_DB_NAME",
    "MTS_MASTER_DB_USERNAME": "YOUR_DB_USERNAME",
    "MTS_MASTER_DB_PASSWORD": "YOUR_DB_PASSWORD",
    "MTS_PRODUCT_DB_SCHEMA_PATH": "PRODUCT_SCHEMA_PATH",
}

This will include your master db details in which all registered organisations information is stored. There will be one more key MTS_PRODUCT_DB_SCHEMA_PATH which will be path of schema file stored in database/schema folder in your project for a registered product. All the queries listed in this file will get execute during post call.

For example for your product suppose schema file name is schema.sql(as specified in point one above) , then in db.config.json MTS_PRODUCT_DB_SCHEMA_PATH key will look like this

"MTS_PRODUCT_DB_SCHEMA_PATH" :  "schema.sql"

For multiple products usecase (i.e with isMultiProduct flag as true) db.config.json file will look like:

 {
    "MTS_MASTER_DB_PORT": 3306,
    "MTS_MASTER_DB_HOSTNAME": "YOUR_DB_HOSTNAME",
    "MTS_MASTER_DB_NAME": "YOUR_DB_NAME",
    "MTS_MASTER_DB_USERNAME": "YOUR_DB_USERNAME",
    "MTS_MASTER_DB_PASSWORD": "YOUR_DB_PASSWORD",
    "MTS_PRODUCT_CONFIG": { 
        "PRODUCT_1_ID": {
            "db_schema_path": "PRODUCT_1_SCHEMA_PATH"
        },
        "PRODUCT_2_ID": {
            "db_schema_path": "PRODUCT_2_SCHEMA_PATH"
        }
    }
}

This will include your master db details in which all registered organisation information is stored. There will be one more key MTS_PRODUCT_CONFIG which will be an object containing db schema path for requested products. You can specify schema path for multiple products in it.

db_schema_path : It will be path of schema file stored in database/schema folder in your project for a registered product. All the queries listed in this file will get execute during post call.

For example for product with id 41e27793-4d90-11ef-8910-0a9df3751e43 , suppose schema file name is schema.sql(as specified in point one above) , then in db.config.json MTS_PRODUCT_CONFIG key will look like this

"MTS_PRODUCT_CONFIG" : {"41e27793-4d90-11ef-8910-0a9df3751e43": {"db_schema_path": "schema.sql"}}

1. post : To register a particular organisation for a product

post(orgInformation,isMultiProduct) function creates database , schema for registered product and also creates customers table inside master db if not exists and inserts record in it for registered organisation with newly created db details. In response it will return newly created orgId along with all the details for registered product.

post(orgInformation: IOrgInformation, isMultiProduct:boolean =false): Promise<ICustomer>;

Usage if single product multitenant approach:

customers table with below schema will be created automatically if not exists in your master db. It will contain details of all registered organisations.

CREATE TABLE `customers` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `org_id` varchar(40) NOT NULL DEFAULT (uuid()),
  `org_name` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `first_name` varchar(255) NOT NULL,
  `last_name` varchar(255) DEFAULT NULL,
  `contact_number` varchar(50) NOT NULL,
  `contact_email` varchar(255) NOT NULL,
  `phone_country_code` varchar(20) DEFAULT NULL,
  `website` varchar(255) DEFAULT NULL,
  `license` text,
  `license_expiry` date DEFAULT NULL,
  `address` text,
  `db_credentials` json DEFAULT NULL,
  `env_vars` json DEFAULT NULL,
  `status` enum('ACTIVE','INACTIVE') NOT NULL,
  `created_at` datetime NOT NULL DEFAULT (utc_timestamp()),
  `updated_at` datetime NOT NULL DEFAULT (utc_timestamp()),
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_domain__customers` (`domain`),
  UNIQUE KEY `unique_org_id__customers` (`org_id`),
  INDEX `domain_and_status_idx` (`domain`,`status`)
) ENGINE=InnoDB;
    const orgInformation = {
        first_name: "abc",
        email: "abc@org.com",
        org_name: "org pvt. ltd.",
        contact_number: "9999999999",
        last_name: "abc",   //optional
        phone_country_code: "+91", //optional
        website: "abc.com", //optional
        license: "abc", //optional
        license_expiry: "2024-11-1", //optional
        address: "abc", //optional
        status: "ACTIVE" //optional
        env_vars: {var1:'abc'} //optional
    }
    const response = await multiTenantController.post(orgInformation)
    or 
    const response = await multiTenantController.post(orgInformation, false)
    // response will look like
    {
    "org_id": "e3afd9ae-177c-4809-b39e-39c1167bc7c3",
    "id": 1,
    "first_name": "abc",
    "last_name": "abc",
    "contact_number": "9999999999",
    "contact_email": "abc@org.com",
    "org_name": "org pvt. ltd.",
    "phone_country_code": "+91",
    "domain": "org.com",
    "status": "ACTIVE",
    "website": "org.com",
    "license": "abc",
    "license_expiry": "2024-10-31T18:30:00.000Z",
    "address": "abc",
    "env_vars": {var1:'abc'},
    "db_credentials": {
        "RDS_PORT": 3306,
        "RDS_DATABASE": "org",
        "RDS_HOSTNAME": "abc",
        "RDS_PASSWORD": "abc",
        "RDS_USERNAME": "abc"
    }
  }
    

Usage if multiple products multitenant approach:

customers table with below schema will be created automatically if not exists in your master db. It will contain details of all registered organisations.

   CREATE TABLE `customers` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `org_id` varchar(40) NOT NULL DEFAULT (uuid()),
  `org_name` varchar(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  `product_id` varchar(40) NOT NULL,
  `first_name` varchar(255) NOT NULL,
  `last_name` varchar(255) DEFAULT NULL,
  `contact_number` varchar(50) NOT NULL,
  `contact_email` varchar(255) NOT NULL,
  `phone_country_code` varchar(20) DEFAULT NULL,
  `website` varchar(255) DEFAULT NULL,
  `license` text,
  `license_expiry` date DEFAULT NULL,
  `address` text,
  `db_credentials` json DEFAULT NULL,
  `env_vars` json DEFAULT NULL,
  `status` enum('ACTIVE','INACTIVE') NOT NULL,
  `created_at` datetime NOT NULL DEFAULT (utc_timestamp()),
  `updated_at` datetime NOT NULL DEFAULT (utc_timestamp()),
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_domain_and_product_id__customers` (`domain`,`product_id`),
  UNIQUE KEY `unique_org_id_and_product_id__customers` (`org_id`,`product_id`),
  INDEX `domain_product_id_and_status_idx` (`domain`,`product_id`,`status`)
) ENGINE=InnoDB;

If domain already exists then it will create new entry with same orgId for new product. product_id is required in this case.

    const orgInformation = {
        product_id: "41e27793-4d90-11ef-8910-0a9df3751e43",
        first_name: "abc",
        email: "abc@org.com",
        org_name: "org pvt. ltd.",
        contact_number: "9999999999",
        last_name: "abc",   //optional
        phone_country_code: "+91", //optional
        website: "abc.com", //optional
        license: "abc", //optional
        license_expiry: "2024-11-1", //optional
        address: "abc", //optional
        status: "ACTIVE" //optional
        env_vars: {var1:'abc'} //optional
    }
    const response = await multiTenantController.post(orgInformation, true)
    
    // response will look like
    {
    "org_id": "e3afd9ae-177c-4809-b39e-39c1167bc7c3",
    "id": 1,
    "first_name": "abc",
    "last_name": "abc",
    "contact_number": "9999999999",
    "contact_email": "abc@org.com",
    "org_name": "org pvt. ltd.",
    "phone_country_code": "+91",
    "domain": "org.com",
    "product_id": "41e27793-4d90-11ef-8910-0a9df3751e43",
    "status": "ACTIVE",
    "website": "org.com",
    "license": "abc",
    "license_expiry": "2024-10-31T18:30:00.000Z",
    "address": "abc",
    "env_vars": {var1:'abc'},
    "db_credentials": {
        "RDS_PORT": 3306,
        "RDS_DATABASE": "org",
        "RDS_HOSTNAME": "abc",
        "RDS_PASSWORD": "abc",
        "RDS_USERNAME": "abc"
    }
  }
    

2. get : To get the registered organisation details for a product

get(domainName,isMultiProduct,productId) function fetches the registered organisation details. If no registered organisation is found then it will return null.

get(domainName: string, isMultiProduct: boolean = false, productId?: string): Promise<ICustomer | null>;

Usage if single product multitenant approach:

   const domainName = "abc";
   
   const response = await multiTenantController.get(domainName);
   or
   const response = await multiTenantController.get(domainName, false);
   // response will be same as in case of post
    

Usage if multiple products multitenant approach:

productId is required in this case

   const domainName = "abc";
   const productId = "41e27793-4d90-11ef-8910-0a9df3751e43";
   
   const response = await multiTenantController.get(domainName, true, productId);
   
   // response will be same as in case of post
    

3. isValid : To check if domain has been registered for a product and it's status is currently active or not

isValid(domainName,isMultiProduct,productId) function finds the active org on the basis of domain name and product id (if it's a multiple producs multitenant use case) from customers table. If it exists then it will return true otherwise false.

isValid(domainName: string, isMultiProduct: boolean = false, productId?: string): Promise<Boolean>;

Usage if single product multitenant approach:

   const domainName = "abc";
   
   const response = await multiTenantController.isValid(domainName);
   or 
   const response = await multiTenantController.isValid(domainName, false);
   // response will be true or false
    

Usage if multiple products multitenant approach:

productId is required in this case

   const domainName = "abc";
   const productId = "41e27793-4d90-11ef-8910-0a9df3751e43";
   
   const response = await multiTenantController.isValid(domainName, true, productId);
   
   // response will be true or false
    

4. getConnectionDetails : To get db connecton details for a registered product

getConnectionDetails(domainName,isMultiProduct,productId) function returns the db details on the basis of domain name and product id(if it's a multiple producs multitenant use case) from customers table.

getConnectionDetails(domainName: string, isMultiProduct: boolean = false, productId?: string): Promise<IDbCredentials | null>;

Usage if single product multitenant approach:

   const domainName = "abc";
   
   const response = await multiTenantController.getConnectionDetails(domainName);
   or
   const response = await multiTenantController.getConnectionDetails(domainName, false);
   // response will look like
   
   {
    "RDS_PORT": 3306,
    "RDS_DATABASE": "abc",
    "RDS_HOSTNAME": "abc",
    "RDS_PASSWORD": "abc",
    "RDS_USERNAME": "abc"
 }
    

Usage if multiple products multitenant approach:

productId is required in this case

   const domainName = "abc";
   const productId = "41e27793-4d90-11ef-8910-0a9df3751e43";
   
   const response = await multiTenantController.getConnectionDetails(domainName, true, productId);
   
   // response will look like
   
   {
    "RDS_PORT": 3306,
    "RDS_DATABASE": "abc",
    "RDS_HOSTNAME": "abc",
    "RDS_PASSWORD": "abc",
    "RDS_USERNAME": "abc"
 }
    

5. put : To update registered organisation information

put(orgInformation, isMultiProduct) function updates the information of registered organisation of a product in customers table

put(orgInformation: IOrgUpdateInformation, isMultiProduct: boolean = false): Promise<void>;

Usage if single product multitenant approach:

    const orgInformation = {
        domain_name: "abc",
        first_name: "abc", //optional
        org_name: "org pvt. ltd.", //optional
        contact_number: "9999999999", //optional
        last_name: "abc",   //optional
        phone_country_code: "+91", //optional
        website: "abc.com", //optional
        license: "abc", //optional
        license_expiry: "2024-11-1", //optional
        address: "abc", //optional
        status: "ACTIVE", //optional
        env_vars: {var1:'abc'}, //optional
        db_credentials: {RDS_PORT: 123, RDS_DATABASE: 'abc',RDS_HOSTNAME: 'abc', RDS_PASSWORD: 'abc',RDS_USERNAME: 'abc'} //optional
    };
    
    await multiTenantController.put(orgInformation)
    or
    await multiTenantController.put(orgInformation, false)
    

Usage if multiple products multitenant approach:

product_id is required in this case

    const orgInformation = {
        product_id: "41e27793-4d90-11ef-8910-0a9df3751e43",
        domain_name: "abc",
        first_name: "abc", //optional
        org_name: "org pvt. ltd.", //optional
        contact_number: "9999999999", //optional
        last_name: "abc",   //optional
        phone_country_code: "+91", //optional
        website: "abc.com", //optional
        license: "abc", //optional
        license_expiry: "2024-11-1", //optional
        address: "abc", //optional
        status: "ACTIVE", //optional
        env_vars: {var1:'abc'}, //optional
        db_credentials: {RDS_PORT: 123, RDS_DATABASE: 'abc',RDS_HOSTNAME: 'abc', RDS_PASSWORD: 'abc',RDS_USERNAME: 'abc'} //optional
    };
    
    await multiTenantController.put(orgInformation, true)
    
1.0.4

6 months ago

1.0.3

6 months ago

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago