mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
fix(mana-media): commit initial schema migration + run on startup
The media schema/tables were never created on fresh deploys because mana-media only shipped a `db:push` script and nothing ever ran it in the container. Result: every upload returned 500 the moment a new environment came up (just hit prod again on mana.how). - Add `db:generate` + `db:migrate` scripts and a migrate.ts runner - Generate the initial migration covering media/media_references/ media_thumbnails (matches what was already on local + prod, which were stamped manually so the migrator skips on existing deploys) - Call runMigrations() at startup in src/index.ts so future fresh containers self-bootstrap. Idempotent — drizzle tracks state in drizzle.__drizzle_migrations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
80b23dd9ff
commit
64b8ab30ad
6 changed files with 697 additions and 0 deletions
|
|
@ -7,6 +7,8 @@
|
|||
"start": "bun run src/index.ts",
|
||||
"type-check": "tsc --noEmit",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "bun run src/db/migrate.ts",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
46
services/mana-media/apps/api/src/db/migrate.ts
Normal file
46
services/mana-media/apps/api/src/db/migrate.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Drizzle migration runner.
|
||||
*
|
||||
* Run on every container startup (and as `pnpm db:migrate` for local use)
|
||||
* so that fresh deployments end up with the `media` schema and tables
|
||||
* without any manual SQL. Drizzle's migrator tracks applied migrations
|
||||
* in `drizzle.__drizzle_migrations`, so re-runs are no-ops.
|
||||
*/
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import { migrate } from 'drizzle-orm/postgres-js/migrator';
|
||||
import postgres from 'postgres';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
export async function runMigrations(databaseUrl: string): Promise<void> {
|
||||
// Migrations live next to this file at runtime, regardless of cwd.
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const migrationsFolder = resolve(here, 'migrations');
|
||||
|
||||
// `max: 1` is required by drizzle's migrator.
|
||||
const sql = postgres(databaseUrl, { max: 1 });
|
||||
try {
|
||||
const db = drizzle(sql);
|
||||
await migrate(db, { migrationsFolder });
|
||||
} finally {
|
||||
await sql.end({ timeout: 5 });
|
||||
}
|
||||
}
|
||||
|
||||
// Allow `bun run src/db/migrate.ts` for manual / CI use.
|
||||
if (import.meta.main) {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
if (!databaseUrl) {
|
||||
console.error('DATABASE_URL is required');
|
||||
process.exit(1);
|
||||
}
|
||||
runMigrations(databaseUrl)
|
||||
.then(() => {
|
||||
console.log('Migrations applied');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Migration failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
CREATE SCHEMA "media";
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "media"."media" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"content_hash" text NOT NULL,
|
||||
"original_name" text,
|
||||
"mime_type" text NOT NULL,
|
||||
"size" bigint NOT NULL,
|
||||
"original_key" text NOT NULL,
|
||||
"status" text DEFAULT 'uploading' NOT NULL,
|
||||
"width" integer,
|
||||
"height" integer,
|
||||
"format" text,
|
||||
"has_alpha" boolean,
|
||||
"exif_data" jsonb,
|
||||
"date_taken" timestamp with time zone,
|
||||
"camera_make" text,
|
||||
"camera_model" text,
|
||||
"focal_length" text,
|
||||
"aperture" text,
|
||||
"iso" integer,
|
||||
"exposure_time" text,
|
||||
"gps_latitude" text,
|
||||
"gps_longitude" text,
|
||||
"thumbnail_key" text,
|
||||
"medium_key" text,
|
||||
"large_key" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "media_content_hash_unique" UNIQUE("content_hash")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "media"."media_references" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"media_id" uuid NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"app" text NOT NULL,
|
||||
"source_url" text,
|
||||
"metadata" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "media"."media_thumbnails" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"media_id" uuid NOT NULL,
|
||||
"width" integer NOT NULL,
|
||||
"height" integer NOT NULL,
|
||||
"fit" text DEFAULT 'cover' NOT NULL,
|
||||
"format" text DEFAULT 'webp' NOT NULL,
|
||||
"quality" integer DEFAULT 80 NOT NULL,
|
||||
"storage_key" text NOT NULL,
|
||||
"size" integer NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "media"."media_references" ADD CONSTRAINT "media_references_media_id_media_id_fk" FOREIGN KEY ("media_id") REFERENCES "media"."media"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "media"."media_thumbnails" ADD CONSTRAINT "media_thumbnails_media_id_media_id_fk" FOREIGN KEY ("media_id") REFERENCES "media"."media"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "media_content_hash_idx" ON "media"."media" USING btree ("content_hash");--> statement-breakpoint
|
||||
CREATE INDEX "media_status_idx" ON "media"."media" USING btree ("status");--> statement-breakpoint
|
||||
CREATE INDEX "media_created_at_idx" ON "media"."media" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "media_date_taken_idx" ON "media"."media" USING btree ("date_taken");--> statement-breakpoint
|
||||
CREATE INDEX "media_camera_idx" ON "media"."media" USING btree ("camera_make","camera_model");--> statement-breakpoint
|
||||
CREATE INDEX "media_ref_media_id_idx" ON "media"."media_references" USING btree ("media_id");--> statement-breakpoint
|
||||
CREATE INDEX "media_ref_user_id_idx" ON "media"."media_references" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "media_ref_app_idx" ON "media"."media_references" USING btree ("app");--> statement-breakpoint
|
||||
CREATE INDEX "media_ref_user_app_idx" ON "media"."media_references" USING btree ("user_id","app");--> statement-breakpoint
|
||||
CREATE INDEX "media_thumb_media_id_idx" ON "media"."media_thumbnails" USING btree ("media_id");--> statement-breakpoint
|
||||
CREATE INDEX "media_thumb_params_idx" ON "media"."media_thumbnails" USING btree ("media_id","width","height","fit","format");
|
||||
|
|
@ -0,0 +1,561 @@
|
|||
{
|
||||
"id": "270c9891-ddcf-4b1e-b987-d91f033e0767",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"media.media": {
|
||||
"name": "media",
|
||||
"schema": "media",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"content_hash": {
|
||||
"name": "content_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"original_name": {
|
||||
"name": "original_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"mime_type": {
|
||||
"name": "mime_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"size": {
|
||||
"name": "size",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"original_key": {
|
||||
"name": "original_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'uploading'"
|
||||
},
|
||||
"width": {
|
||||
"name": "width",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"height": {
|
||||
"name": "height",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"format": {
|
||||
"name": "format",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"has_alpha": {
|
||||
"name": "has_alpha",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"exif_data": {
|
||||
"name": "exif_data",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"date_taken": {
|
||||
"name": "date_taken",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"camera_make": {
|
||||
"name": "camera_make",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"camera_model": {
|
||||
"name": "camera_model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"focal_length": {
|
||||
"name": "focal_length",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"aperture": {
|
||||
"name": "aperture",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"iso": {
|
||||
"name": "iso",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"exposure_time": {
|
||||
"name": "exposure_time",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gps_latitude": {
|
||||
"name": "gps_latitude",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"gps_longitude": {
|
||||
"name": "gps_longitude",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"thumbnail_key": {
|
||||
"name": "thumbnail_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"medium_key": {
|
||||
"name": "medium_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"large_key": {
|
||||
"name": "large_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"media_content_hash_idx": {
|
||||
"name": "media_content_hash_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "content_hash",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"media_status_idx": {
|
||||
"name": "media_status_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "status",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"media_created_at_idx": {
|
||||
"name": "media_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"media_date_taken_idx": {
|
||||
"name": "media_date_taken_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "date_taken",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"media_camera_idx": {
|
||||
"name": "media_camera_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "camera_make",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "camera_model",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"media_content_hash_unique": {
|
||||
"name": "media_content_hash_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["content_hash"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"media.media_references": {
|
||||
"name": "media_references",
|
||||
"schema": "media",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"media_id": {
|
||||
"name": "media_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"app": {
|
||||
"name": "app",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source_url": {
|
||||
"name": "source_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"media_ref_media_id_idx": {
|
||||
"name": "media_ref_media_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "media_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"media_ref_user_id_idx": {
|
||||
"name": "media_ref_user_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"media_ref_app_idx": {
|
||||
"name": "media_ref_app_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "app",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"media_ref_user_app_idx": {
|
||||
"name": "media_ref_user_app_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "app",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"media_references_media_id_media_id_fk": {
|
||||
"name": "media_references_media_id_media_id_fk",
|
||||
"tableFrom": "media_references",
|
||||
"tableTo": "media",
|
||||
"schemaTo": "media",
|
||||
"columnsFrom": ["media_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"media.media_thumbnails": {
|
||||
"name": "media_thumbnails",
|
||||
"schema": "media",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"media_id": {
|
||||
"name": "media_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"width": {
|
||||
"name": "width",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"height": {
|
||||
"name": "height",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fit": {
|
||||
"name": "fit",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'cover'"
|
||||
},
|
||||
"format": {
|
||||
"name": "format",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'webp'"
|
||||
},
|
||||
"quality": {
|
||||
"name": "quality",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 80
|
||||
},
|
||||
"storage_key": {
|
||||
"name": "storage_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"size": {
|
||||
"name": "size",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"media_thumb_media_id_idx": {
|
||||
"name": "media_thumb_media_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "media_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"media_thumb_params_idx": {
|
||||
"name": "media_thumb_params_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "media_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "width",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "height",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "fit",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "format",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"media_thumbnails_media_id_media_id_fk": {
|
||||
"name": "media_thumbnails_media_id_media_id_fk",
|
||||
"tableFrom": "media_thumbnails",
|
||||
"tableTo": "media",
|
||||
"schemaTo": "media",
|
||||
"columnsFrom": ["media_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {
|
||||
"media": "media"
|
||||
},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1775759324563,
|
||||
"tag": "0000_marvelous_micromacro",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import { cors } from 'hono/cors';
|
|||
import { Queue, Worker, Job } from 'bullmq';
|
||||
import { collectDefaultMetrics, Registry, Counter, Histogram } from 'prom-client';
|
||||
import { getDb, closeConnection } from './db';
|
||||
import { runMigrations } from './db/migrate';
|
||||
import { StorageService } from './services/storage';
|
||||
import { UploadService } from './services/upload';
|
||||
import { ProcessService } from './services/process';
|
||||
|
|
@ -16,6 +17,12 @@ const port = parseInt(process.env.PORT || '3015');
|
|||
// Database
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
if (!databaseUrl) throw new Error('DATABASE_URL is required');
|
||||
|
||||
// Apply pending Drizzle migrations before opening the pool. Idempotent —
|
||||
// drizzle tracks applied migrations in drizzle.__drizzle_migrations, so
|
||||
// existing deployments are unaffected.
|
||||
await runMigrations(databaseUrl);
|
||||
|
||||
const db = getDb(databaseUrl);
|
||||
|
||||
// Services
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue