UNPKG

backstrap-server

Version:

Backstrap is a lightweight framework for building APIs

290 lines (280 loc) 10.3 kB
class SchemaControl { constructor() { this.dataAccess = null; this.accessControl = null; } init(da, ac) { this.dataAccess = da; this.accessControl = ac; Object.freeze(this); } update(name, user, pass, host, port) { return new Promise((resolve, reject) => { this.#checkDbExists(name, user, pass, host, port) .then(() => { return this.dataAccess.getDbConnection(); }) .then((connection) => { return Promise.all([connection, this.#createInitialTables(connection)]); }) .then(([connection, res]) => { return Promise.all([res, this.dataAccess.closeDbConnection(connection)]); }) .then(([commit_res]) => { return this.#createDefaultUser(); }) .then((res) => { resolve(res); }) .catch((err) => { if (err !== undefined && err !== null && typeof (err.AddToError) === 'function') { reject(err.AddToError(__filename, 'updateSchema')); } else { var errorObj = new ErrorObj(500, 'sc1001', __filename, 'updateSchema', 'error updating schema', 'Database error', err ); reject(errorObj); } }); }); } #createInitialTables(connection) { return new Promise((resolve, reject) => { let createCmds = []; let createUserTable = false; let createSessTable = false; this.#checkForTable('bs3_users', connection) .then((usrTblExists) => { if(!usrTblExists) { // WE ADD UNIQUE CONSTRAINTS ON username AND email LATER // SO WE CAN MAKE THEM CONDITIONAL ON deleted_at let usrTblCreate = `CREATE TABLE bs3_users ( id SERIAL PRIMARY KEY NOT NULL, account_type VARCHAR(16), username VARCHAR(128), email VARCHAR(256), locked BOOLEAN, roles JSONB, external_id VARCHAR(256), created_at TIMESTAMP, modified_at TIMESTAMP, deleted_at TIMESTAMP )`; createCmds.push(this.dataAccess.runSql(usrTblCreate, [], connection)); createUserTable = true; } return this.#checkForTable('bs3_credentials', connection); }) .then((credTblExists) => { if(!credTblExists) { let credTblCreate = `CREATE TABLE bs3_credentials ( id SERIAL PRIMARY KEY NOT NULL, salt VARCHAR(128), password TEXT, client_id VARCHAR(256) UNIQUE, client_secret VARCHAR(256), forgot_password JSONB, user_id INTEGER REFERENCES bs3_users(id), created_at TIMESTAMP, modified_at TIMESTAMP, deleted_at TIMESTAMP )`; createCmds.push(this.dataAccess.runSql(credTblCreate, [], connection)); } return this.#checkForTable('bs3_sessions', connection); }) .then((sessTblExists) => { if(!sessTblExists) { let sessTblCreate = `CREATE TABLE bs3_sessions ( id SERIAL PRIMARY KEY NOT NULL, token VARCHAR(256), client_info JSONB, created_at TIMESTAMP, ended_at TIMESTAMP, last_touch TIMESTAMP, user_id INTEGER REFERENCES bs3_users(id), anonymous BOOLEAN )`; createCmds.push(this.dataAccess.runSql(sessTblCreate, [], connection)); createSessTable = true; } return Promise.all(createCmds); }) .then((createRes) => { let idxCmds = []; if(createUserTable) { idxCmds.push(this.dataAccess.runSql('CREATE UNIQUE INDEX unq_users_username ON bs3_users(username) WHERE (deleted_at IS NULL)', [], connection)); idxCmds.push(this.dataAccess.runSql('CREATE UNIQUE INDEX unq_users_email ON bs3_users(email) WHERE (deleted_at IS NULL)', [], connection)); } if(createSessTable) { idxCmds.push(this.dataAccess.runSql('CREATE INDEX idx_token ON bs3_sessions(token)', [], connection)); idxCmds.push(this.dataAccess.runSql('CREATE INDEX idx_user_id ON bs3_sessions(user_id)', [], connection)); } if(idxCmds.length > 0) { Promise.all(idxCmds) .then((createIdxRes) => { resolve({success:true}); }) .catch((idxErr) => { if(idxErr && typeof(idxErr.AddToError) === 'function') { reject(idxErr.AddToError(__filename, 'createInitialTables')); } else { let errorObj = new ErrorObj(500, 'sc2001', __filename, 'createInitialTables', 'error creating indexes', 'Problem creating tables', idxErr); reject(errorObj); } }); } else { resolve({success: true}); } }) .catch((err) => { if(err && typeof(err.AddToError) === 'function') { reject(err.AddToError(__filename, 'createInitialTables')); } else { let errorObj = new ErrorObj(500, 'sc2000', __filename, 'createInitialTables', 'postgres error', 'Problem creating tables', err); reject(errorObj); } }) }); } #createDefaultUser() { return new Promise((resolve, reject) => { var qry = "SELECT COUNT(*) FROM bs3_users WHERE LOWER(username) = 'bsroot'"; var qry_params = []; this.dataAccess.ExecutePostgresQuery(qry, qry_params, null) .then((connection) => { if (parseInt(connection.results[0].count) === 0) { let userParams = { 'username': 'bsroot', 'first': 'bs3', 'last': 'admin', 'email': 'bsroot@backstrap.io', 'password': 'abcd@1234', 'roles': ['super-user'] }; this.accessControl.createUser('default', userParams, null, null) .then((createUserRes) => { resolve(createUserRes); }) .catch((createUserErr) => { if(createUserErr && typeof(createUserErr.AddToError) === 'function') { reject(createUserErr.AddToError(__filename, 'createDefaultUser')); } else { let errorObj = new ErrorObj(500, 'sc2002', __filename, 'createDefaultUser', 'error creating bsroot', 'There was problem creating the initial user.', createUserErr); reject(errorObj); } }); } else { resolve(true); } }) .catch((err) => { var errorObj = new ErrorObj(500, 'sc1008', __filename, 'createDefaultUser', 'error creating default user', 'Database error', err ); reject(errorObj); }); }); } #checkForTable(tableName, connection) { return new Promise((resolve, reject) => { var qry = "SELECT EXISTS ( " + "SELECT 1 " + "FROM pg_catalog.pg_class c " + "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " + "WHERE n.nspname = 'public' " + "AND c.relname = '" + tableName + "' " + "AND c.relkind = 'r' " + ");"; var qry_params = []; this.dataAccess.ExecutePostgresQuery(qry, qry_params, connection) .then((connection) => { resolve(connection.results[0].exists); }) .catch((err) => { var errorObj = new ErrorObj(500, 'sc0008', __filename, 'checkForTable', 'error querying postgres', 'Database error', err ); reject(errorObj); }); }); } #checkDbExists(db_name, db_user, db_pass, db_host, db_port) { return new Promise((resolve, reject) => { this.dataAccess.CreateDatabase(db_name, db_user, db_pass, db_host, db_port) .then((res) => { //BECAUSE events.js THROWS AN UNHANDLED EXCEPTION WHEN //QUERYING A DB THAT DOESNT EXIST, WE CANNOT DO SELECT 1 FROM pg_database WHERE datname = '" + db_name + "' //SO WE JUST CREATE AND IGNORE IF ERRORS BECAUSE IT EXISTS //42P04 == Datbase already exists // 42501 == User lacks create database privileges...assume the db is created if (res.results && !['42P04', '42501'].includes(res.results.code)) { var errorObj = new ErrorObj(500, 'sc0013', __filename, 'checkDbExists', 'error checking to see if database exists and creating if not', 'Database error', res ); reject(errorObj); } else { resolve(); } }) .catch((err) => { var errorObj = new ErrorObj(500, 'sc0014', __filename, 'getDatabase', 'error connecting to postgres', 'Database error', err ); reject(errorObj); }); }); } } var instance = new SchemaControl(); module.exports = instance;