import { createNote, createUser, queryArray } from "@/db/mod.ts"; const id = "id uuid primary key default generate_ulid()"; interface TableSpec { columns: string[]; additionalTableStatements?: string[]; additionalStatements?: string[]; prepStatements?: string[]; } const createdAtTimestamp = "created_at timestamptz not null default now()"; const updatedAtTimestamp = "updated_at timestamptz not null default now()"; const timestamps = [createdAtTimestamp, updatedAtTimestamp]; const extensions = [ "uuid-ossp", "pgcrypto", ]; const functions = [ ` create or replace function generate_ulid() returns uuid as $$ select (lpad(to_hex(floor(extract(epoch from clock_timestamp()) * 1000)::bigint), 12, '0') || encode(gen_random_bytes(10), 'hex'))::uuid; $$ language sql `, ` `, ]; const tables: Record = { "user": { columns: [ id, "username text not null unique", "password_digest text not null", "display_name text", ...timestamps, ], additionalTableStatements: [ "constraint valid_username check (username ~* '^[a-z\\d\\-_]{2,38}$')", ], }, "user_token": { columns: [ "digest bytea not null unique", "user_id uuid not null", "data jsonb", createdAtTimestamp, ], additionalStatements: [ "create index team_data_type on user_token using hash ((data->'type'))", ], additionalTableStatements: [ 'constraint fk_user foreign key(user_id) references "user"(id) on delete cascade', ], }, "team": { columns: [ id, "display_name text not null", ...timestamps, ], additionalStatements: [ 'create index display_name_idx on team ("display_name")', ], }, "team_user": { prepStatements: [ "drop type if exists team_user_status", "create type team_user_status as enum ('invited', 'accepted', 'owner')", ], columns: [ "team_id uuid", "user_id uuid", "status team_user_status not null", ...timestamps, ], additionalTableStatements: [ 'constraint fk_team foreign key(team_id) references "team"(id) on delete cascade', 'constraint fk_user foreign key(user_id) references "user"(id) on delete cascade', ], additionalStatements: [ "create index team_user_idx on team_user (team_id) include (user_id)", "create index team_idx on team_user (team_id)", "create index user_idx on team_user (user_id)", "create index status_idx on team_user (status)", ], }, "note": { columns: [ id, "user_id uuid default null", "content text not null", ...timestamps, ], additionalTableStatements: [ 'constraint fk_user foreign key(user_id) references "user"(id) on delete cascade', ], }, }; const createExtensions = extensions.map((s) => `create extension if not exists "${s.trim()}";` ).join("\n"); const createFunctions = functions.map((s) => s.trim() + ";").join("\n"); const dropTables = Object.entries(tables).reverse().map(([name, _meta]) => `drop table if exists "${name.trim()}";` ).join("\n"); const createTables = Object.entries(tables).map(([rawName, meta]) => { const name = rawName.trim(); return ` -- CREATE TABLE ${name} ${(meta.prepStatements || []).map((s) => `${s.trim()};`).join("\n")} create table "${name}" ( ${ meta.columns.concat(meta.additionalTableStatements || []).map((s) => s.trim() ).join(",\n ") } ); ${(meta.additionalStatements || []).map((s) => `${s.trim()};`).join("\n")} `; }).map((s) => s.trim()).join("\n\n"); const queryString = ` begin; ${dropTables} ${createExtensions} ${createFunctions} ${createTables} commit; `; console.log(queryString); try { const setupResult = await queryArray(queryString); console.debug(setupResult); } catch (err) { console.log("Failed to run migration setup query:", { ...err }); throw err; } try { console.debug( await Promise.all([ createNote({ content: "Hello, notes!" }), createUser({ username: "lytedev", passwordDigest: "$2a$10$9fyDAOz6H4a393KHyjbvIe1WFxbhCJhq/CZmlXcEg4d1bE9Ey25WW", }), ]), ); } catch (err) { console.log("Failed to run seed database:", { ...err }); throw err; }