import { newPgConnectionConfig } from "joist-utils" ; import pgMigrate from "node-pg-migrate" ; import { Client } from "pg" ; import pgStructure , { Db , Table } from "pg-structure" ; const productionDirectory = "/home/node/app/migrations" ; export async function runMigrationsIfNeeded ( dir = productionDirectory ) { const client = new Client ( newPgConnectionConfig ( ) ) ; await client . connect ( ) ; try { await pgMigrate ( { dbClient : client , migrationsTable : "migrations" , dir , count : undefined as any as number , direction : "up" , ignorePattern : "(.*.d.ts)|(.*utils.[jt]s)|(migrate.[jt]s)|(migrate.test.[jt]s)" , decamelize : true , } ) ; const db = await pgStructure ( newPgConnectionConfig ( ) ) ; if ( process . env . ADD_FLUSH_DATABASE === "true" ) { console . log ( "Creating flush_database() function" ) ; await createFlushDbFunction ( db , client ) ; } } finally { await client . end ( ) ; } } /** Creates a `flush_database` stored procedure to truncate all of the tables between tests. */ async function createFlushDbFunction ( db , client ) { await client . query ( generateFlushFunction ( db ) ) ; } function generateFlushFunction ( db ) { const tables = db . tables . filter ( ( t ) => isEntityTable ( t ) || isJoinTable ( t ) ) . map ( ( t ) => t . name ) ; const statements = tables . map ( ( t ) => `DELETE FROM "${ t }"; ALTER SEQUENCE IF EXISTS "${ t }_id_seq" RESTART WITH 1 INCREMENT BY 1;` ) . join ( "\n" ) ; return `CREATE OR REPLACE FUNCTION flush_database() RETURNS void AS $$
    BEGIN
    ${ statements }
    END;
   $$ LANGUAGE
    'plpgsql'` ; } export function isEntityTable ( t ) { const columnNames = t . columns . map ( ( c ) => c . name ) ; return includesAllOf ( columnNames , [ "id" , "created_at" , "updated_at" ] ) ; } export function isJoinTable ( t ) { const { columns } = t ; const hasOnePk = columns . filter ( ( c ) => c . isPrimaryKey ) . length === 1 ; const hasTwoFks = columns . filter ( ( c ) => c . isForeignKey ) . length === 2 ; const hasThreeColumns = columns . length === 3 ; const hasFourColumnsOneIsCreatedAt = columns . length === 4 && columns . filter ( ( c ) => c . name === "created_at" ) . length === 1 ; return hasOnePk && hasTwoFks && ( hasThreeColumns || hasFourColumnsOneIsCreatedAt ) ; } function includesAllOf ( set , subset ) { return subset . find ( ( e ) => ! set . includes ( e ) ) === undefined ; }