Skip to content
Snippets Groups Projects
Select Git revision
  • f0d798f9dd2ef9374ce283a0f83b1023de281ace
  • master default protected
  • deploy protected
  • citest
4 results

db.ts

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    db.ts 2.30 KiB
    /*
     * About migrations:
     *   File path: src/lib/server/database/migrations
     *   File name: \d+_.*\.sql
     *   Naming: <order>_<description>.sql
     *     <order> - number that represents the order in which the migration should be run
     *     <description> - short description of the migration
     *     The order is interpreted as integer, leading zeros are ignored.
     *   File content: SQL code
     *   Important: after execution/committing neither filename nor SQL code should be changed.
     *   The filename is used as key, so *any* to it change will be treated as a new migration.
     */
    
    import postgres, { type Sql } from "postgres";
    import fs from "node:fs/promises";
    import { env } from "$env/dynamic/private";
    import { building, dev } from "$app/environment";
    
    // fix for ARRAY_AGG on LEFT JOIN returning {NULL} instead of {null} or empty array
    (() => {
    	const original = JSON.parse;
    	JSON.parse = (text, reviver) => {
    		if (text === "NULL") return null;
    		else return original(text, reviver);
    	};
    })();
    
    export const sql = building ? null as unknown as Sql : await init(postgres({
    	host: env.POSTGRES_HOST || "localhost",
    	database: env.POSTGRES_DB,
    	user: env.POSTGRES_USER,
    	password: env.POSTGRES_PASSWORD,
    	port: parseInt(env.POSTGRES_PORT || "5432"),
    	transform: postgres.camel,
    	ssl: dev ? false : {
    		rejectUnauthorized: true,
    	},
    }));
    
    async function init(sql: Sql) {
    	await sql`
    	CREATE TABLE IF NOT EXISTS migrations (
    		id SERIAL PRIMARY KEY,
    		name TEXT,
    		dateMigrated TIMESTAMP NOT NULL DEFAULT NOW()
    	);
    	`;
    	const basePath = dev ? "src/lib/server/database/migrations" : "/app/migrations";
    	const migrationFiles = (await fs.readdir(basePath))
    		.filter(name => name.endsWith(".sql"))
    		.map(name => ({
    			filePath: `${basePath}/${name}`,
    			name: name.substring(0, name.length - 4),
    			sortKey: parseInt(name.split("_")[0]),
    		}))
    		.sort((a, b) => a.sortKey - b.sortKey);
    	const migrations = (await sql`SELECT name FROM migrations`).map(row => row.name as string);
    	for (const migration of migrationFiles) {
    		if (migrations.includes(migration.name)) continue;
    		console.log(`Running migration: ${migration.name}`);
    		// migrations must run in order
    		// eslint-disable-next-line no-await-in-loop
    		await sql.begin(sql => [
    			sql.file(migration.filePath),
    			sql`INSERT INTO migrations (name) VALUES (${migration.name})`,
    		]);
    	}
    	return sql;
    }