From a6ec5991888a3bfb57c6f1c0c6c8fe83c4babfbe Mon Sep 17 00:00:00 2001 From: Simone Cavalli Date: Wed, 13 May 2026 22:46:54 +0200 Subject: [PATCH] chore(01-02): generate Drizzle migration from schema (0000_pretty_typhoid_mary.sql) - 10 tables: clients, phases, tasks, deliverables, comments, payments, documents, notes, service_catalog, quote_items - UNIQUE constraint on clients.token enforced at DB level - All FK cascades correct (deliverables->tasks->phases->clients) - approved_at: timestamp with time zone (nullable, immutable) - drizzle.config.ts already correct from Plan 01 (no changes needed) --- .../migrations/0000_pretty_typhoid_mary.sql | 95 +++ src/db/migrations/meta/0000_snapshot.json | 616 ++++++++++++++++++ src/db/migrations/meta/_journal.json | 13 + 3 files changed, 724 insertions(+) create mode 100644 src/db/migrations/0000_pretty_typhoid_mary.sql create mode 100644 src/db/migrations/meta/0000_snapshot.json create mode 100644 src/db/migrations/meta/_journal.json diff --git a/src/db/migrations/0000_pretty_typhoid_mary.sql b/src/db/migrations/0000_pretty_typhoid_mary.sql new file mode 100644 index 0000000..de3358c --- /dev/null +++ b/src/db/migrations/0000_pretty_typhoid_mary.sql @@ -0,0 +1,95 @@ +CREATE TABLE "clients" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "brand_name" text NOT NULL, + "brief" text NOT NULL, + "token" text NOT NULL, + "accepted_total" numeric(10, 2) DEFAULT '0', + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "clients_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "comments" ( + "id" text PRIMARY KEY NOT NULL, + "entity_type" text NOT NULL, + "entity_id" text NOT NULL, + "author" text NOT NULL, + "body" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "deliverables" ( + "id" text PRIMARY KEY NOT NULL, + "task_id" text NOT NULL, + "title" text NOT NULL, + "url" text, + "status" text DEFAULT 'pending' NOT NULL, + "approved_at" timestamp with time zone +); +--> statement-breakpoint +CREATE TABLE "documents" ( + "id" text PRIMARY KEY NOT NULL, + "client_id" text NOT NULL, + "label" text NOT NULL, + "url" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "notes" ( + "id" text PRIMARY KEY NOT NULL, + "client_id" text NOT NULL, + "body" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "payments" ( + "id" text PRIMARY KEY NOT NULL, + "client_id" text NOT NULL, + "label" text NOT NULL, + "amount" numeric(10, 2) NOT NULL, + "status" text DEFAULT 'da_saldare' NOT NULL, + "paid_at" timestamp with time zone +); +--> statement-breakpoint +CREATE TABLE "phases" ( + "id" text PRIMARY KEY NOT NULL, + "client_id" text NOT NULL, + "title" text NOT NULL, + "sort_order" integer DEFAULT 0 NOT NULL, + "status" text DEFAULT 'upcoming' NOT NULL +); +--> statement-breakpoint +CREATE TABLE "quote_items" ( + "id" text PRIMARY KEY NOT NULL, + "client_id" text NOT NULL, + "service_id" text NOT NULL, + "quantity" numeric(10, 2) NOT NULL, + "unit_price" numeric(10, 2) NOT NULL, + "subtotal" numeric(10, 2) NOT NULL +); +--> statement-breakpoint +CREATE TABLE "service_catalog" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "description" text, + "unit_price" numeric(10, 2) NOT NULL, + "active" boolean DEFAULT true NOT NULL +); +--> statement-breakpoint +CREATE TABLE "tasks" ( + "id" text PRIMARY KEY NOT NULL, + "phase_id" text NOT NULL, + "title" text NOT NULL, + "description" text, + "status" text DEFAULT 'todo' NOT NULL, + "sort_order" integer DEFAULT 0 NOT NULL +); +--> statement-breakpoint +ALTER TABLE "deliverables" ADD CONSTRAINT "deliverables_task_id_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "documents" ADD CONSTRAINT "documents_client_id_clients_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."clients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "notes" ADD CONSTRAINT "notes_client_id_clients_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."clients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "payments" ADD CONSTRAINT "payments_client_id_clients_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."clients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "phases" ADD CONSTRAINT "phases_client_id_clients_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."clients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "quote_items" ADD CONSTRAINT "quote_items_client_id_clients_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."clients"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "quote_items" ADD CONSTRAINT "quote_items_service_id_service_catalog_id_fk" FOREIGN KEY ("service_id") REFERENCES "public"."service_catalog"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "tasks" ADD CONSTRAINT "tasks_phase_id_phases_id_fk" FOREIGN KEY ("phase_id") REFERENCES "public"."phases"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/src/db/migrations/meta/0000_snapshot.json b/src/db/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..84c1c1d --- /dev/null +++ b/src/db/migrations/meta/0000_snapshot.json @@ -0,0 +1,616 @@ +{ + "id": "87c66332-bcb8-449c-8ab5-542d6179bd74", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.clients": { + "name": "clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "brand_name": { + "name": "brand_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "brief": { + "name": "brief", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accepted_total": { + "name": "accepted_total", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "clients_token_unique": { + "name": "clients_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.comments": { + "name": "comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deliverables": { + "name": "deliverables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "deliverables_task_id_tasks_id_fk": { + "name": "deliverables_task_id_tasks_id_fk", + "tableFrom": "deliverables", + "tableTo": "tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "documents_client_id_clients_id_fk": { + "name": "documents_client_id_clients_id_fk", + "tableFrom": "documents", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notes": { + "name": "notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "notes_client_id_clients_id_fk": { + "name": "notes_client_id_clients_id_fk", + "tableFrom": "notes", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payments": { + "name": "payments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'da_saldare'" + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "payments_client_id_clients_id_fk": { + "name": "payments_client_id_clients_id_fk", + "tableFrom": "payments", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.phases": { + "name": "phases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'upcoming'" + } + }, + "indexes": {}, + "foreignKeys": { + "phases_client_id_clients_id_fk": { + "name": "phases_client_id_clients_id_fk", + "tableFrom": "phases", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.quote_items": { + "name": "quote_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service_id": { + "name": "service_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "unit_price": { + "name": "unit_price", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "subtotal": { + "name": "subtotal", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "quote_items_client_id_clients_id_fk": { + "name": "quote_items_client_id_clients_id_fk", + "tableFrom": "quote_items", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "quote_items_service_id_service_catalog_id_fk": { + "name": "quote_items_service_id_service_catalog_id_fk", + "tableFrom": "quote_items", + "tableTo": "service_catalog", + "columnsFrom": [ + "service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.service_catalog": { + "name": "service_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit_price": { + "name": "unit_price", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "phase_id": { + "name": "phase_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'todo'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "tasks_phase_id_phases_id_fk": { + "name": "tasks_phase_id_phases_id_fk", + "tableFrom": "tasks", + "tableTo": "phases", + "columnsFrom": [ + "phase_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "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/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json new file mode 100644 index 0000000..bd0887f --- /dev/null +++ b/src/db/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1778705200462, + "tag": "0000_pretty_typhoid_mary", + "breakpoints": true + } + ] +} \ No newline at end of file