From c31900d94af4162e75cf62848637cef9a3d212e3 Mon Sep 17 00:00:00 2001 From: Lars Hampe Date: Tue, 1 Oct 2024 20:39:12 +0200 Subject: [PATCH] feat(api): database migrations on startup --- apps/api/src/utils/startup.ts | 4 + packages/database/src/index.ts | 3 +- packages/database/src/migration.ts | 8 + .../src/migrations/0000_crazy_mindworm.sql | 86 ++++ .../src/migrations/meta/0000_snapshot.json | 383 ++++++++++++++++++ .../src/migrations/meta/_journal.json | 13 + packages/database/src/schema/changelog.ts | 8 +- 7 files changed, 502 insertions(+), 3 deletions(-) create mode 100644 packages/database/src/migration.ts create mode 100644 packages/database/src/migrations/0000_crazy_mindworm.sql create mode 100644 packages/database/src/migrations/meta/0000_snapshot.json create mode 100644 packages/database/src/migrations/meta/_journal.json diff --git a/apps/api/src/utils/startup.ts b/apps/api/src/utils/startup.ts index a9214db..6e47574 100644 --- a/apps/api/src/utils/startup.ts +++ b/apps/api/src/utils/startup.ts @@ -1,3 +1,5 @@ +import { migrateDatabase } from '@boring.tools/database' + declare module 'bun' { interface Env { POSTGRES_URL: string @@ -27,4 +29,6 @@ export const startup = () => { process.exit(0) } }) + + migrateDatabase() } diff --git a/packages/database/src/index.ts b/packages/database/src/index.ts index 3b6f494..f36b3a3 100644 --- a/packages/database/src/index.ts +++ b/packages/database/src/index.ts @@ -1,12 +1,13 @@ import { drizzle } from 'drizzle-orm/node-postgres' import { Client } from 'pg' +export * from './migration' import * as schema from './schema' export * from './schema' const POSTGRES_URL = import.meta.env.POSTGRES_URL ?? process.env.POSTGRES_URL -const client = new Client({ connectionString: POSTGRES_URL }) +export const client = new Client({ connectionString: POSTGRES_URL }) await client.connect() export const db = drizzle(client, { schema }) diff --git a/packages/database/src/migration.ts b/packages/database/src/migration.ts new file mode 100644 index 0000000..616a342 --- /dev/null +++ b/packages/database/src/migration.ts @@ -0,0 +1,8 @@ +import path from 'node:path' +import { migrate } from 'drizzle-orm/postgres-js/migrator' +import { client, db } from './' + +export const migrateDatabase = async () => { + await migrate(db, { migrationsFolder: path.join(__dirname, 'migrations') }) + await client.end() +} diff --git a/packages/database/src/migrations/0000_crazy_mindworm.sql b/packages/database/src/migrations/0000_crazy_mindworm.sql new file mode 100644 index 0000000..e21dbbe --- /dev/null +++ b/packages/database/src/migrations/0000_crazy_mindworm.sql @@ -0,0 +1,86 @@ +DO $$ BEGIN + CREATE TYPE "public"."status" AS ENUM('draft', 'review', 'published'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "user" ( + "id" varchar(32) PRIMARY KEY NOT NULL, + "name" text, + "email" text NOT NULL, + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "access_token" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "userId" varchar(32), + "token" text NOT NULL, + "name" text NOT NULL, + "createdAt" timestamp DEFAULT now() NOT NULL, + "lastUsedOn" timestamp +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "changelog" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "createdAt" timestamp DEFAULT now(), + "updatedAt" timestamp, + "userId" varchar(32), + "title" varchar(256), + "description" text, + "isSemver" boolean DEFAULT true +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "changelog_commit" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "createdAt" timestamp, + "changelogId" uuid, + "versionId" uuid, + "shortHash" varchar(8) NOT NULL, + "author" json, + "body" text, + "message" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "changelog_version" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "createdAt" timestamp DEFAULT now(), + "updatedAt" timestamp, + "releasedAt" timestamp, + "changelogId" uuid NOT NULL, + "version" varchar(32) NOT NULL, + "markdown" text NOT NULL, + "status" "status" DEFAULT 'draft' NOT NULL, + "shortHash" varchar(8) NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "access_token" ADD CONSTRAINT "access_token_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "changelog" ADD CONSTRAINT "changelog_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "changelog_commit" ADD CONSTRAINT "changelog_commit_changelogId_changelog_id_fk" FOREIGN KEY ("changelogId") REFERENCES "public"."changelog"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "changelog_commit" ADD CONSTRAINT "changelog_commit_versionId_changelog_version_id_fk" FOREIGN KEY ("versionId") REFERENCES "public"."changelog_version"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "changelog_version" ADD CONSTRAINT "changelog_version_changelogId_changelog_id_fk" FOREIGN KEY ("changelogId") REFERENCES "public"."changelog"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "unique" ON "changelog_commit" USING btree ("changelogId","shortHash"); \ No newline at end of file diff --git a/packages/database/src/migrations/meta/0000_snapshot.json b/packages/database/src/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..964fdfe --- /dev/null +++ b/packages/database/src/migrations/meta/0000_snapshot.json @@ -0,0 +1,383 @@ +{ + "id": "33a139be-9cb3-4dfd-a14c-c196e17b053d", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + } + }, + "public.access_token": { + "name": "access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "userId": { + "name": "userId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lastUsedOn": { + "name": "lastUsedOn", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "access_token_userId_user_id_fk": { + "name": "access_token_userId_user_id_fk", + "tableFrom": "access_token", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.changelog": { + "name": "changelog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isSemver": { + "name": "isSemver", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": { + "changelog_userId_user_id_fk": { + "name": "changelog_userId_user_id_fk", + "tableFrom": "changelog", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.changelog_commit": { + "name": "changelog_commit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "changelogId": { + "name": "changelogId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "versionId": { + "name": "versionId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "shortHash": { + "name": "shortHash", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true + }, + "author": { + "name": "author", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "unique": { + "name": "unique", + "columns": [ + { + "expression": "changelogId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shortHash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "changelog_commit_changelogId_changelog_id_fk": { + "name": "changelog_commit_changelogId_changelog_id_fk", + "tableFrom": "changelog_commit", + "tableTo": "changelog", + "columnsFrom": [ + "changelogId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "changelog_commit_versionId_changelog_version_id_fk": { + "name": "changelog_commit_versionId_changelog_version_id_fk", + "tableFrom": "changelog_commit", + "tableTo": "changelog_version", + "columnsFrom": [ + "versionId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.changelog_version": { + "name": "changelog_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "releasedAt": { + "name": "releasedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "changelogId": { + "name": "changelogId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "shortHash": { + "name": "shortHash", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "changelog_version_changelogId_changelog_id_fk": { + "name": "changelog_version_changelogId_changelog_id_fk", + "tableFrom": "changelog_version", + "tableTo": "changelog", + "columnsFrom": [ + "changelogId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "draft", + "review", + "published" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/database/src/migrations/meta/_journal.json b/packages/database/src/migrations/meta/_journal.json new file mode 100644 index 0000000..1350f7a --- /dev/null +++ b/packages/database/src/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1727807735760, + "tag": "0000_crazy_mindworm", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/packages/database/src/schema/changelog.ts b/packages/database/src/schema/changelog.ts index ae83dd1..7a8354c 100644 --- a/packages/database/src/schema/changelog.ts +++ b/packages/database/src/schema/changelog.ts @@ -17,7 +17,7 @@ export const changelog = pgTable('changelog', { createdAt: timestamp('createdAt').defaultNow(), updatedAt: timestamp('updatedAt'), - userId: text('userId').references(() => user.id, { + userId: varchar('userId', { length: 32 }).references(() => user.id, { onDelete: 'cascade', }), @@ -26,9 +26,13 @@ export const changelog = pgTable('changelog', { isSemver: boolean('isSemver').default(true), }) -export const changelog_relation = relations(changelog, ({ many }) => ({ +export const changelog_relation = relations(changelog, ({ many, one }) => ({ versions: many(changelog_version), commits: many(changelog_commit), + user: one(user, { + fields: [changelog.userId], + references: [user.id], + }), })) export const changelog_version_status = pgEnum('status', [