diff --git a/labelapp/Dockerfile b/labelapp/Dockerfile index e132340..d30d4c0 100644 --- a/labelapp/Dockerfile +++ b/labelapp/Dockerfile @@ -28,21 +28,22 @@ WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 -# Standalone server + static assets +# Standalone server + static assets (includes postgres + drizzle-orm via serverExternalPackages) COPY --from=builder /app/labelapp/.next/standalone ./ COPY --from=builder /app/labelapp/.next/static ./labelapp/.next/static COPY --from=builder /app/labelapp/public ./labelapp/public -# Drizzle migration tooling -COPY --from=deps /app/node_modules ./node_modules -COPY --from=deps /app/labelapp/node_modules ./labelapp/node_modules -COPY --from=builder /app/labelapp/drizzle.config.ts ./labelapp/ +# Entrypoint scripts need drizzle-orm (migrator) + postgres beyond what standalone traces. +# Standalone copies the full package.json; swap it for a clean one so bun add works. +RUN cd labelapp && mv package.json package.json.bak && echo '{}' > package.json \ + && bun add postgres drizzle-orm \ + && mv package.json.bak package.json + +# Drizzle migrations + schema (migrate.ts uses drizzle-orm programmatically — no drizzle-kit needed) COPY --from=builder /app/labelapp/drizzle/ ./labelapp/drizzle/ COPY --from=builder /app/labelapp/db/ ./labelapp/db/ -COPY --from=builder /app/packages/schemas/ ./packages/schemas/ -COPY --from=builder /app/package.json ./ -# Seed/sample/assign scripts +# Seed/sample/assign scripts + lib utilities COPY --from=builder /app/labelapp/scripts/ ./labelapp/scripts/ COPY --from=builder /app/labelapp/lib/ ./labelapp/lib/ diff --git a/labelapp/drizzle/0001_add-active-ms.sql b/labelapp/drizzle/0001_add-active-ms.sql new file mode 100644 index 0000000..d92b94f --- /dev/null +++ b/labelapp/drizzle/0001_add-active-ms.sql @@ -0,0 +1 @@ +ALTER TABLE "human_labels" ADD COLUMN IF NOT EXISTS "active_ms" integer; \ No newline at end of file diff --git a/labelapp/drizzle/meta/0001_snapshot.json b/labelapp/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..9bf7c85 --- /dev/null +++ b/labelapp/drizzle/meta/0001_snapshot.json @@ -0,0 +1,510 @@ +{ + "id": "392c9bd4-1b68-4e32-86b0-be7abc632b44", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.adjudications": { + "name": "adjudications", + "schema": "", + "columns": { + "paragraph_id": { + "name": "paragraph_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "final_category": { + "name": "final_category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "final_specificity": { + "name": "final_specificity", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "adjudicator_id": { + "name": "adjudicator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "adjudications_paragraph_id_paragraphs_id_fk": { + "name": "adjudications_paragraph_id_paragraphs_id_fk", + "tableFrom": "adjudications", + "tableTo": "paragraphs", + "columnsFrom": [ + "paragraph_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.annotators": { + "name": "annotators", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "onboarded_at": { + "name": "onboarded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assignments": { + "name": "assignments", + "schema": "", + "columns": { + "paragraph_id": { + "name": "paragraph_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "annotator_id": { + "name": "annotator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_warmup": { + "name": "is_warmup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "assignments_paragraph_id_paragraphs_id_fk": { + "name": "assignments_paragraph_id_paragraphs_id_fk", + "tableFrom": "assignments", + "tableTo": "paragraphs", + "columnsFrom": [ + "paragraph_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assignments_annotator_id_annotators_id_fk": { + "name": "assignments_annotator_id_annotators_id_fk", + "tableFrom": "assignments", + "tableTo": "annotators", + "columnsFrom": [ + "annotator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "assignments_paragraph_id_annotator_id_unique": { + "name": "assignments_paragraph_id_annotator_id_unique", + "nullsNotDistinct": false, + "columns": [ + "paragraph_id", + "annotator_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.human_labels": { + "name": "human_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "human_labels_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "paragraph_id": { + "name": "paragraph_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "annotator_id": { + "name": "annotator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_category": { + "name": "content_category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "specificity_level": { + "name": "specificity_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "labeled_at": { + "name": "labeled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "active_ms": { + "name": "active_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "human_labels_paragraph_id_paragraphs_id_fk": { + "name": "human_labels_paragraph_id_paragraphs_id_fk", + "tableFrom": "human_labels", + "tableTo": "paragraphs", + "columnsFrom": [ + "paragraph_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "human_labels_annotator_id_annotators_id_fk": { + "name": "human_labels_annotator_id_annotators_id_fk", + "tableFrom": "human_labels", + "tableTo": "annotators", + "columnsFrom": [ + "annotator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "human_labels_paragraph_id_annotator_id_unique": { + "name": "human_labels_paragraph_id_annotator_id_unique", + "nullsNotDistinct": false, + "columns": [ + "paragraph_id", + "annotator_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paragraphs": { + "name": "paragraphs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "word_count": { + "name": "word_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "paragraph_index": { + "name": "paragraph_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "company_name": { + "name": "company_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cik": { + "name": "cik", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ticker": { + "name": "ticker", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "filing_type": { + "name": "filing_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filing_date": { + "name": "filing_date", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fiscal_year": { + "name": "fiscal_year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "accession_number": { + "name": "accession_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sec_item": { + "name": "sec_item", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stage1_category": { + "name": "stage1_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stage1_specificity": { + "name": "stage1_specificity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "stage1_method": { + "name": "stage1_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stage1_confidence": { + "name": "stage1_confidence", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.quiz_sessions": { + "name": "quiz_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "annotator_id": { + "name": "annotator_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "passed": { + "name": "passed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_questions": { + "name": "total_questions", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "answers": { + "name": "answers", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + } + }, + "indexes": {}, + "foreignKeys": { + "quiz_sessions_annotator_id_annotators_id_fk": { + "name": "quiz_sessions_annotator_id_annotators_id_fk", + "tableFrom": "quiz_sessions", + "tableTo": "annotators", + "columnsFrom": [ + "annotator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/labelapp/drizzle/meta/_journal.json b/labelapp/drizzle/meta/_journal.json index 88bcca8..8c70ae5 100644 --- a/labelapp/drizzle/meta/_journal.json +++ b/labelapp/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1774815564792, "tag": "0000_baseline", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1774822800000, + "tag": "0001_add-active-ms", + "breakpoints": true } ] } \ No newline at end of file diff --git a/labelapp/entrypoint.sh b/labelapp/entrypoint.sh index 6b678e7..45fae3b 100644 --- a/labelapp/entrypoint.sh +++ b/labelapp/entrypoint.sh @@ -7,7 +7,7 @@ echo "==> Ensuring migration baseline..." bun run scripts/ensure-migration-baseline.ts echo "==> Running Drizzle migrations..." -bunx drizzle-kit migrate +bun run scripts/migrate.ts echo "==> Checking if database needs seeding..." ROW_COUNT=$(bun --eval " diff --git a/labelapp/next.config.ts b/labelapp/next.config.ts index 6ebc36f..1149636 100644 --- a/labelapp/next.config.ts +++ b/labelapp/next.config.ts @@ -4,6 +4,7 @@ import path from "node:path"; const nextConfig: NextConfig = { output: "standalone", outputFileTracingRoot: path.join(import.meta.dirname, "../"), + serverExternalPackages: ["postgres", "drizzle-orm", "drizzle-kit"], }; export default nextConfig; diff --git a/labelapp/package.json b/labelapp/package.json index 6ea4001..a1ed1bf 100644 --- a/labelapp/package.json +++ b/labelapp/package.json @@ -9,7 +9,7 @@ "typecheck": "tsc --noEmit", "lint": "eslint", "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit migrate", + "db:migrate": "bun run scripts/migrate.ts", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", "seed": "bun run scripts/seed.ts", diff --git a/labelapp/scripts/migrate.ts b/labelapp/scripts/migrate.ts new file mode 100644 index 0000000..f6ffc41 --- /dev/null +++ b/labelapp/scripts/migrate.ts @@ -0,0 +1,19 @@ +/** + * Programmatic Drizzle migration runner. + * Uses drizzle-orm/postgres-js/migrator instead of drizzle-kit CLI, + * so drizzle-kit is not needed at runtime. + */ +import { drizzle } from "drizzle-orm/postgres-js"; +import { migrate } from "drizzle-orm/postgres-js/migrator"; +import postgres from "postgres"; +import { resolve } from "path"; + +const sql = postgres(process.env.DATABASE_URL!, { max: 1 }); +const db = drizzle(sql); + +await migrate(db, { + migrationsFolder: resolve(import.meta.dirname!, "../drizzle"), +}); + +console.log("Migrations applied."); +await sql.end();