/*****************************************************************************\ | ( )_ _ | | _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | | ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | | | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | | | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | | | | ( )_) | | | (_) \___/' | | | | General Bots Copyright (c) Pragmatismo.io. All rights reserved. | | Licensed under the AGPL-3.0. | | | | According to our dual licensing model, this program can be used either | | under the terms of the GNU Affero General Public License, version 3, | | or under a proprietary license. | | | | The texts of the GNU Affero General Public License with an additional | | permission and of our proprietary license can be found at and | | in the LICENSE file you have received along with this program. | | | | This program is distributed in the hope that it will be useful, | | but WITHOUT ANY WARRANTY; without even the implied warranty of | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | | GNU Affero General Public License for more details. | | | | "General Bots" is a registered trademark of Pragmatismo.io. | | The licensing of the program under the AGPLv3 does not imply a | | trademark license. Therefore any rights, title and interest in | | our trademarks remain entirely with us. | | | \*****************************************************************************/ "use strict"; const logger = require("../../../src/logger"); import { Sequelize } from "sequelize-typescript"; import { GBConfigService } from "./GBConfigService"; import { IGBInstance, IGBCoreService } from "botlib"; import { GuaribasInstance } from "../models/GBModel"; /** * Core service layer. */ export class GBCoreService implements IGBCoreService { /** * Data access layer instance. */ public sequelize: Sequelize; /** * Allows filtering on SQL generated before send to the database. */ private queryGenerator: any; /** * Custom create table query. */ private createTableQuery: (tableName, attributes, options) => string; /** * Custom change column query. */ private changeColumnQuery: (tableName, attributes) => string; /** * Dialect used. Tested: mssql and sqlite. */ private dialect: string; /** * Constructor retrieves default values. */ constructor() { this.dialect = GBConfigService.get("DATABASE_DIALECT"); } /** * Gets database config and connect to storage. */ async initDatabase() { return new Promise((resolve, reject) => { try { let host: string | undefined; let database: string | undefined; let username: string | undefined; let password: string | undefined; let storage: string | undefined; if (this.dialect === "mssql") { host = GBConfigService.get("DATABASE_HOST"); database = GBConfigService.get("DATABASE_NAME"); username = GBConfigService.get("DATABASE_USERNAME"); password = GBConfigService.get("DATABASE_PASSWORD"); } else if (this.dialect === "sqlite") { storage = GBConfigService.get("DATABASE_STORAGE"); } let logging = GBConfigService.get("DATABASE_LOGGING") === "true" ? (str: string) => { logger.info(str); } : false; let encrypt = GBConfigService.get("DATABASE_ENCRYPT") === "true"; this.sequelize = new Sequelize({ host: host, database: database, username: username, password: password, logging: logging, operatorsAliases: false, dialect: this.dialect, storage: storage, dialectOptions: { encrypt: encrypt }, pool: { max: 32, min: 8, idle: 40000, evict: 40000, acquire: 40000 } }); if (this.dialect === "mssql") { this.queryGenerator = this.sequelize.getQueryInterface().QueryGenerator; this.createTableQuery = this.queryGenerator.createTableQuery; this.queryGenerator.createTableQuery = ( tableName, attributes, options ) => this.createTableQueryOverride(tableName, attributes, options); this.changeColumnQuery = this.queryGenerator.changeColumnQuery; this.queryGenerator.changeColumnQuery = (tableName, attributes) => this.changeColumnQueryOverride(tableName, attributes); } resolve(); } catch (error) { reject(error); } }); } /** * SQL: * * // let sql: string = '' + * // 'IF OBJECT_ID(\'[UserGroup]\', \'U\') IS NULL\n' + * // 'CREATE TABLE [UserGroup] (\n' + * // ' [id] INTEGER NOT NULL IDENTITY(1,1),\n' + * // ' [userId] INTEGER NULL,\n' + * // ' [groupId] INTEGER NULL,\n' + * // ' [instanceId] INTEGER NULL,\n' + * // ' PRIMARY KEY ([id1], [id2]),\n' + * // ' FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + * // ' FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId1]) ON DELETE NO ACTION,\n' + * // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION);'; */ private createTableQueryOverride(tableName, attributes, options): string { let sql: string = this.createTableQuery.apply(this.queryGenerator, [ tableName, attributes, options ]); const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/; const matches = re1.exec(sql); if (matches) { const table = matches[1]; const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/; sql = sql.replace( re2, (match: string, ...args: any[]): string => { return "CONSTRAINT [" + table + "_pk] " + match; } ); const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re4 = /\[([^\]]*)\]/g; sql = sql.replace( re3, (match: string, ...args: any[]): string => { const fkcols = args[0]; let fkname = table; let matches = re4.exec(fkcols); while (matches != null) { fkname += "_" + matches[1]; matches = re4.exec(fkcols); } return "CONSTRAINT [" + fkname + "_fk] FOREIGN KEY (" + fkcols + ")"; } ); } return sql; } /** * SQL: * let sql = '' + * 'ALTER TABLE [UserGroup]\n' + * ' ADD CONSTRAINT [invalid1] FOREIGN KEY ([userId1], [userId2], [userId3]) REFERENCES [User] ([userId1], [userId2], [userId3]) ON DELETE NO ACTION,\n' + * ' CONSTRAINT [invalid2] FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId2]) ON DELETE NO ACTION, \n' + * ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION;\n'; */ private changeColumnQueryOverride(tableName, attributes): string { let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [ tableName, attributes ]); const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/; const matches = re1.exec(sql); if (matches) { const table = matches[1]; const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g; const re3 = /\[([^\]]*)\]/g; sql = sql.replace( re2, (match: string, ...args: any[]): string => { const fkcols = args[2]; let fkname = table; let matches = re3.exec(fkcols); while (matches != null) { fkname += "_" + matches[1]; matches = re3.exec(fkcols); } return ( (args[0] ? args[0] : "") + "CONSTRAINT [" + fkname + "_fk] FOREIGN KEY (" + fkcols + ")" ); } ); } return sql; } async syncDatabaseStructure() { return new Promise((resolve, reject) => { if (GBConfigService.get("DATABASE_SYNC") === "true") { const alter = GBConfigService.get("DATABASE_SYNC_ALTER") === "true"; const force = GBConfigService.get("DATABASE_SYNC_FORCE") === "true"; logger.info("Syncing database..."); this.sequelize .sync({ alter: alter, force: force }) .then( value => { logger.info("Database synced."); resolve(value); }, err => reject(err) ); } else { logger.info("Database synchronization is disabled."); resolve(); } }); } /** * Loads all items to start several listeners. */ async loadInstances(): Promise { return new Promise((resolve, reject) => { GuaribasInstance.findAll({}) .then((items: IGBInstance[]) => { if (!items) items = []; if (items.length == 0) { resolve([]); } else { resolve(items); } }) .catch(reason => { if (reason.message.indexOf("no such table: GuaribasInstance") != -1) { resolve([]); } else { logger.info(`GuaribasServiceError: ${reason}`); reject(reason); } }); }); } /** * Loads just one Bot instance. */ async loadInstance(botId: string): Promise { return new Promise((resolve, reject) => { let options = { where: {} }; if (botId != "[default]") { options.where = { botId: botId }; } GuaribasInstance.findOne(options) .then((instance: IGBInstance) => { if (instance) { resolve(instance); } else { resolve(null); } }) .catch(err => { logger.info(`GuaribasServiceError: ${err}`); reject(err); }); }); } }