import { 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 = { "note": { columns: [id, "content text not null", ...timestamps], }, "user": { prepStatements: [ "drop type if exists user_status", "create type user_status as enum ('unverified', 'verified', 'owner', 'superadmin')", ], columns: [ id, "username text not null unique", "password_digest text not null", "status user_status not null", "display_name text", ...timestamps, ], additionalTableStatements: [ "constraint valid_username check (username ~* '^[a-z\d\\-_]{2,38}$')", ], }, "user_token": { prepStatements: [ "drop type if exists user_token_type", "create type user_token_type as enum ('session', 'reset')", ], columns: [ "token_digest bytea unique", "type user_token_type not null", "sent_to text not null", createdAtTimestamp, ], additionalStatements: [ "create index team_user_type on user_token (type)", "create index team_user_sent_to on user_token (sent_to)", ], }, "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)", ], }, }; 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; } const seedQuery = ` insert into note (content) values ('Hello, notes!'); -- TODO: create reserved usernames? with new_user as ( insert into "user" (username, password_digest, status) values ('lytedev', '$2a$10$9fyDAOz6H4a393KHyjbvIe1WFxbhCJhq/CZmlXcEg4d1bE9Ey25WW', 'superadmin') returning id as user_id ), new_team as ( insert into "team" (display_name) values ('superadmins') returning id as team_id ) insert into "team_user" (user_id, team_id, status) values ( (select user_id from new_user), (select team_id from new_team), 'owner' ) returning user_id; `; console.log(seedQuery); try { const seedResult = await queryArray(seedQuery); console.debug(seedResult); } catch (err) { console.log("Failed to run migration seed query:", { ...err }); throw err; }