Mirror of https://github.com/roostorg/coop github.com/roostorg/coop
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 158 lines 4.9 kB view raw
1import { readFileSync } from 'fs'; 2import { resolve as pathResolve } from 'path'; 3import { 4 makeSequelizeUmzugStorage, 5 SCRIPT_TEMPLATES_DIR_ABSOLUTE_PATH, 6 wrapMigration, 7 type DatabaseConfig, 8} from '@roostorg/db-migrator'; 9import { Kysely, PostgresDialect } from 'kysely'; 10import pg from 'pg'; 11import { 12 QueryTypes, 13 Sequelize, 14 type Options, 15 type QueryInterface, 16} from 'sequelize'; 17import { Umzug } from 'umzug'; 18 19const postgresSupportedScriptFormats = ['sql', 'cjs'] as const; 20type PostgresSupportedScriptFormats = 21 (typeof postgresSupportedScriptFormats)[number]; 22 23type PostgresContext = QueryInterface & { kysely: Kysely<unknown> }; 24 25export function makePostgresDatabaseConfig(opts: { 26 defaultScriptFormat: PostgresSupportedScriptFormats; 27 scriptsDirectory: string; 28 driverOpts: Options & { schema: string }; 29 maintenanceDatabase?: string; 30}): DatabaseConfig<PostgresSupportedScriptFormats, PostgresContext> { 31 const { driverOpts, scriptsDirectory, defaultScriptFormat } = opts; 32 // DB used for CREATE/DROP DATABASE (can't be the target DB itself). 33 // Defaults to `postgres`; some managed providers use a different name 34 // (e.g. `defaultdb`). 35 const maintenanceDatabase = opts.maintenanceDatabase ?? 'postgres'; 36 37 return { 38 supportedEnvironments: ['staging', 'prod'], 39 supportedScriptFormats: postgresSupportedScriptFormats, 40 defaultScriptFormat, 41 scriptsDirectory, 42 43 createStorage() { 44 return makeSequelizeUmzugStorage(new Sequelize(driverOpts), { 45 charset: 'utf8', 46 collate: 'utf8_unicode_ci', 47 schema: driverOpts.schema, 48 }); 49 }, 50 51 createContext() { 52 // For backwards compatibility w/ existing migrations, the context 53 // object that we give to umzug needs to continue to be a sequelize 54 // QueryInterface object. However, some migrations need to be able to 55 // use kysely, so we just attach a kysely instance directly onto the 56 // existing object, exploiting JS dynamism. 57 const sequelize = new Sequelize(driverOpts); 58 const queryInterface = sequelize.getQueryInterface() as QueryInterface & { 59 kysely: Kysely<unknown>; 60 }; 61 62 queryInterface.kysely = new Kysely({ 63 dialect: new PostgresDialect({ 64 pool: new pg.Pool({ 65 ...driverOpts, 66 user: driverOpts.username, 67 }), 68 }), 69 }); 70 return queryInterface; 71 }, 72 73 destroyContext(context) { 74 return context.sequelize.close(); 75 }, 76 77 resolveScript(params) { 78 const { path } = params; 79 const { sequelize } = params.context; 80 81 const baseResult = path.endsWith('.cjs') 82 ? Umzug.defaultResolver(params) 83 : { 84 name: params.name, 85 up() { 86 const sql = readFileSync(path).toString(); 87 return sequelize.query(sql); 88 }, 89 }; 90 91 async function validatePostgresViews() { 92 const viewNames = await sequelize.query<{ 93 viewName: string; 94 schemaName: string; 95 }>( 96 `select 97 table_schema as "schemaName", 98 table_name as "viewName" 99 from information_schema.views 100 where LOWER(table_schema) not in ('information_schema', 'pg_catalog')`, 101 { type: QueryTypes.SELECT }, 102 ); 103 104 await Promise.all( 105 viewNames.map(async (it) => 106 sequelize.query( 107 `SELECT * FROM "${it.schemaName}"."${it.viewName}" LIMIT 10`, 108 { type: QueryTypes.SELECT }, 109 ), 110 ), 111 ); 112 } 113 114 return wrapMigration({ runAfter: validatePostgresViews }, baseResult); 115 }, 116 117 getTemplate(filePath: string) { 118 return filePath.endsWith('.cjs') 119 ? readFileSync( 120 pathResolve(SCRIPT_TEMPLATES_DIR_ABSOLUTE_PATH, './sequelize.cjs'), 121 'utf8', 122 ) 123 : ''; 124 }, 125 126 async dropDbAndDisconnect() { 127 const targetDbconn = new Sequelize(driverOpts); 128 await targetDbconn.authenticate(); 129 await targetDbconn.close(); 130 131 const sequelize = new Sequelize({ 132 ...driverOpts, 133 database: maintenanceDatabase, 134 }); 135 await sequelize.query( 136 `DROP DATABASE "${driverOpts.database}" WITH (FORCE);`, 137 ); 138 await sequelize.close(); 139 }, 140 141 async prepareDbAndDisconnect() { 142 const sequelize = new Sequelize({ 143 ...driverOpts, 144 database: maintenanceDatabase, 145 }); 146 const existing = await sequelize.query( 147 `SELECT 1 FROM pg_database WHERE datname = ?`, 148 { replacements: [driverOpts.database], type: QueryTypes.SELECT }, 149 ); 150 if (existing.length === 0) { 151 // DDL identifiers can't be bound; driverOpts.database comes from 152 // trusted config (env vars), not user input. 153 await sequelize.query(`CREATE DATABASE "${driverOpts.database}";`); 154 } 155 await sequelize.close(); 156 }, 157 }; 158}