UNPKG

@launchql/core

Version:
145 lines (142 loc) 4.38 kB
import { Logger } from '@launchql/logger'; import { readFileSync } from 'fs'; import { join } from 'path'; import { getPgPool } from 'pg-cache'; const log = new Logger('init'); export class LaunchQLInit { pool; pgConfig; constructor(config) { this.pgConfig = config; this.pool = getPgPool(this.pgConfig); } /** * Bootstrap standard roles (anonymous, authenticated, administrator) */ async bootstrapRoles() { try { log.info('Bootstrapping LaunchQL roles...'); const sqlPath = join(__dirname, 'sql', 'bootstrap-roles.sql'); const sql = readFileSync(sqlPath, 'utf-8'); await this.pool.query(sql); log.success('Successfully bootstrapped LaunchQL roles'); } catch (error) { log.error('Failed to bootstrap roles:', error); throw error; } } /** * Bootstrap test roles (roles only, no users) */ async bootstrapTestRoles() { try { log.warn('WARNING: This command creates test roles and should NEVER be run on a production database!'); log.info('Bootstrapping LaunchQL test roles...'); const sqlPath = join(__dirname, 'sql', 'bootstrap-test-roles.sql'); const sql = readFileSync(sqlPath, 'utf-8'); await this.pool.query(sql); log.success('Successfully bootstrapped LaunchQL test roles'); } catch (error) { log.error('Failed to bootstrap test roles:', error); throw error; } } /** * Bootstrap database roles with custom username and password */ async bootstrapDbRoles(username, password) { try { log.info(`Bootstrapping LaunchQL database roles for user: ${username}...`); const sql = ` BEGIN; DO $do$ DECLARE v_username TEXT := '${username.replace(/'/g, "''")}'; v_password TEXT := '${password.replace(/'/g, "''")}'; BEGIN BEGIN EXECUTE format('CREATE ROLE %I LOGIN PASSWORD %L', v_username, v_password); EXCEPTION WHEN duplicate_object THEN -- Role already exists; optionally sync attributes here with ALTER ROLE NULL; END; END $do$; -- Robust GRANTs under concurrency: GRANT can race on pg_auth_members unique index. -- Catch unique_violation (23505) and continue so CI/CD concurrent jobs don't fail. DO $do$ DECLARE v_username TEXT := '${username.replace(/'/g, "''")}'; BEGIN BEGIN EXECUTE format('GRANT %I TO %I', 'anonymous', v_username); EXCEPTION WHEN unique_violation THEN -- Membership was granted concurrently; ignore. NULL; WHEN undefined_object THEN -- One of the roles doesn't exist yet; order operations as needed. RAISE NOTICE 'Missing role when granting % to %', 'anonymous', v_username; END; BEGIN EXECUTE format('GRANT %I TO %I', 'authenticated', v_username); EXCEPTION WHEN unique_violation THEN -- Membership was granted concurrently; ignore. NULL; WHEN undefined_object THEN RAISE NOTICE 'Missing role when granting % to %', 'authenticated', v_username; END; END $do$; COMMIT; `; await this.pool.query(sql); log.success(`Successfully bootstrapped LaunchQL database roles for user: ${username}`); } catch (error) { log.error(`Failed to bootstrap database roles for user ${username}:`, error); throw error; } } /** * Remove database roles and revoke grants */ async removeDbRoles(username) { try { log.info(`Removing LaunchQL database roles for user: ${username}...`); const sql = ` BEGIN; DO $do$ BEGIN IF EXISTS ( SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '${username}') THEN REVOKE anonymous FROM ${username}; REVOKE authenticated FROM ${username}; DROP ROLE ${username}; END IF; END $do$; COMMIT; `; await this.pool.query(sql); log.success(`Successfully removed LaunchQL database roles for user: ${username}`); } catch (error) { log.error(`Failed to remove database roles for user ${username}:`, error); throw error; } } /** * Close the database connection */ async close() { } }