ls-deno/db/migrations.ts

165 lines
4.2 KiB
TypeScript
Raw Normal View History

2022-10-08 00:00:45 -05:00
import { createNote, createUser, queryArray } from "@/db/mod.ts";
2022-09-27 15:49:41 -05:00
2022-10-07 17:23:43 -05:00
const id = "id uuid primary key default generate_ulid()";
2022-09-30 15:14:57 -05:00
2022-10-01 14:03:15 -05:00
interface TableSpec {
columns: string[];
2022-10-05 17:02:21 -05:00
additionalTableStatements?: string[];
2022-10-01 14:03:15 -05:00
additionalStatements?: string[];
prepStatements?: string[];
}
2022-10-07 17:09:13 -05:00
const createdAtTimestamp = "created_at timestamptz not null default now()";
const updatedAtTimestamp = "updated_at timestamptz not null default now()";
const timestamps = [createdAtTimestamp, updatedAtTimestamp];
2022-09-30 15:14:57 -05:00
2022-10-07 23:22:35 -05:00
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
`,
`
`,
];
2022-10-01 14:03:15 -05:00
const tables: Record<string, TableSpec> = {
2022-09-30 15:14:57 -05:00
"note": {
columns: [id, "content text not null", ...timestamps],
},
"user": {
2022-10-05 17:02:21 -05:00
prepStatements: [
"drop type if exists user_status",
2022-10-08 00:00:45 -05:00
"create type user_status as enum ('unverified', 'verified', 'superadmin')",
2022-10-05 17:02:21 -05:00
],
2022-09-30 15:14:57 -05:00
columns: [
id,
"username text not null unique",
2022-10-07 17:09:13 -05:00
"password_digest text not null",
2022-10-05 17:02:21 -05:00
"status user_status not null",
"display_name text",
2022-10-01 14:03:15 -05:00
...timestamps,
],
2022-10-07 17:09:13 -05:00
additionalTableStatements: [
"constraint valid_username check (username ~* '^[a-z\d\\-_]{2,38}$')",
],
2022-10-01 14:03:15 -05:00
},
2022-10-05 17:02:21 -05:00
"user_token": {
2022-10-07 17:09:13 -05:00
prepStatements: [
"drop type if exists user_token_type",
"create type user_token_type as enum ('session', 'reset')",
],
2022-10-05 17:02:21 -05:00
columns: [
2022-10-07 23:22:35 -05:00
"token_digest bytea unique",
2022-10-07 17:09:13 -05:00
"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)",
2022-10-05 17:02:21 -05:00
],
},
2022-10-01 14:03:15 -05:00
"team": {
columns: [
id,
2022-10-05 17:02:21 -05:00
"display_name text not null",
2022-10-01 14:03:15 -05:00
...timestamps,
],
additionalStatements: [
2022-10-05 17:02:21 -05:00
'create index display_name_idx on team ("display_name")',
2022-10-01 14:03:15 -05:00
],
},
"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",
2022-10-05 17:02:21 -05:00
"status team_user_status not null",
2022-09-30 15:14:57 -05:00
...timestamps,
],
2022-10-05 17:02:21 -05:00
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',
],
2022-10-01 14:03:15 -05:00
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)",
],
2022-09-30 15:14:57 -05:00
},
};
2022-09-27 15:49:41 -05:00
2022-10-07 23:22:35 -05:00
const createExtensions = extensions.map((s) =>
`create extension if not exists "${s.trim()}";`
2022-10-05 17:02:21 -05:00
).join("\n");
2022-10-07 23:22:35 -05:00
const createFunctions = functions.map((s) => s.trim() + ";").join("\n");
2022-10-05 17:02:21 -05:00
2022-10-07 23:22:35 -05:00
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 `
2022-10-05 17:02:21 -05:00
-- CREATE TABLE ${name}
2022-10-07 23:22:35 -05:00
${(meta.prepStatements || []).map((s) => `${s.trim()};`).join("\n")}
2022-10-01 14:03:15 -05:00
create table "${name}" (
2022-10-07 23:22:35 -05:00
${
meta.columns.concat(meta.additionalTableStatements || []).map((s) =>
s.trim()
).join(",\n ")
}
2022-10-01 14:03:15 -05:00
);
2022-10-07 23:22:35 -05:00
${(meta.additionalStatements || []).map((s) => `${s.trim()};`).join("\n")}
`;
}).map((s) => s.trim()).join("\n\n");
2022-09-30 15:14:57 -05:00
const queryString = `
2022-10-05 17:02:21 -05:00
begin;
${dropTables}
2022-10-07 23:22:35 -05:00
${createExtensions}
2022-10-07 17:23:43 -05:00
2022-10-07 23:22:35 -05:00
${createFunctions}
2022-10-05 17:02:21 -05:00
${createTables}
2022-10-07 17:09:13 -05:00
commit;
`;
console.log(queryString);
2022-10-07 23:22:35 -05:00
try {
const setupResult = await queryArray(queryString);
console.debug(setupResult);
} catch (err) {
console.log("Failed to run migration setup query:", { ...err });
throw err;
}
try {
2022-10-08 00:00:45 -05:00
await createNote({ content: "Hello, notes!" });
await createUser({
username: "lytedev",
passwordDigest:
"$2a$10$9fyDAOz6H4a393KHyjbvIe1WFxbhCJhq/CZmlXcEg4d1bE9Ey25WW",
});
2022-10-07 23:22:35 -05:00
} catch (err) {
2022-10-08 00:00:45 -05:00
console.log("Failed to run seed database:", { ...err });
2022-10-07 23:22:35 -05:00
throw err;
}