From abcbb5224ee2e4b4f80a0d295c163e57fe47a27c Mon Sep 17 00:00:00 2001 From: Simone Cavalli Date: Wed, 13 May 2026 22:47:51 +0200 Subject: [PATCH] =?UTF-8?q?feat(01-02):=20[BLOCKING]=20drizzle-kit=20push?= =?UTF-8?q?=20=E2=80=94=20schema=20live=20on=20Postgres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Connected to postgresql://178.104.27.55:5432/clienthub - 10 tables created: clients, phases, tasks, deliverables, comments, payments, documents, notes, service_catalog, quote_items - UNIQUE constraint on clients.token active at DB level - All FK constraints and CASCADE rules applied - Verified via information_schema.tables query --- src/db/migrations/relations.ts | 69 ++++++++++++++++ src/db/migrations/schema.ts | 139 +++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 src/db/migrations/relations.ts create mode 100644 src/db/migrations/schema.ts diff --git a/src/db/migrations/relations.ts b/src/db/migrations/relations.ts new file mode 100644 index 0000000..6d98a2a --- /dev/null +++ b/src/db/migrations/relations.ts @@ -0,0 +1,69 @@ +import { relations } from "drizzle-orm/relations"; +import { clients, notes, payments, phases, quoteItems, serviceCatalog, documents, tasks, deliverables } from "./schema"; + +export const notesRelations = relations(notes, ({one}) => ({ + client: one(clients, { + fields: [notes.clientId], + references: [clients.id] + }), +})); + +export const clientsRelations = relations(clients, ({many}) => ({ + notes: many(notes), + payments: many(payments), + phases: many(phases), + quoteItems: many(quoteItems), + documents: many(documents), +})); + +export const paymentsRelations = relations(payments, ({one}) => ({ + client: one(clients, { + fields: [payments.clientId], + references: [clients.id] + }), +})); + +export const phasesRelations = relations(phases, ({one, many}) => ({ + client: one(clients, { + fields: [phases.clientId], + references: [clients.id] + }), + tasks: many(tasks), +})); + +export const quoteItemsRelations = relations(quoteItems, ({one}) => ({ + client: one(clients, { + fields: [quoteItems.clientId], + references: [clients.id] + }), + serviceCatalog: one(serviceCatalog, { + fields: [quoteItems.serviceId], + references: [serviceCatalog.id] + }), +})); + +export const serviceCatalogRelations = relations(serviceCatalog, ({many}) => ({ + quoteItems: many(quoteItems), +})); + +export const documentsRelations = relations(documents, ({one}) => ({ + client: one(clients, { + fields: [documents.clientId], + references: [clients.id] + }), +})); + +export const tasksRelations = relations(tasks, ({one, many}) => ({ + phase: one(phases, { + fields: [tasks.phaseId], + references: [phases.id] + }), + deliverables: many(deliverables), +})); + +export const deliverablesRelations = relations(deliverables, ({one}) => ({ + task: one(tasks, { + fields: [deliverables.taskId], + references: [tasks.id] + }), +})); \ No newline at end of file diff --git a/src/db/migrations/schema.ts b/src/db/migrations/schema.ts new file mode 100644 index 0000000..10cfa70 --- /dev/null +++ b/src/db/migrations/schema.ts @@ -0,0 +1,139 @@ +import { pgTable, foreignKey, text, timestamp, unique, numeric, integer, boolean } from "drizzle-orm/pg-core" +import { sql } from "drizzle-orm" + + + +export const notes = pgTable("notes", { + id: text().primaryKey().notNull(), + clientId: text("client_id").notNull(), + body: text().notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), +}, (table) => [ + foreignKey({ + columns: [table.clientId], + foreignColumns: [clients.id], + name: "notes_client_id_clients_id_fk" + }).onDelete("cascade"), +]); + +export const comments = pgTable("comments", { + id: text().primaryKey().notNull(), + entityType: text("entity_type").notNull(), + entityId: text("entity_id").notNull(), + author: text().notNull(), + body: text().notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), +}); + +export const clients = pgTable("clients", { + id: text().primaryKey().notNull(), + name: text().notNull(), + brandName: text("brand_name").notNull(), + brief: text().notNull(), + token: text().notNull(), + acceptedTotal: numeric("accepted_total", { precision: 10, scale: 2 }).default('0'), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), +}, (table) => [ + unique("clients_token_unique").on(table.token), +]); + +export const payments = pgTable("payments", { + id: text().primaryKey().notNull(), + clientId: text("client_id").notNull(), + label: text().notNull(), + amount: numeric({ precision: 10, scale: 2 }).notNull(), + status: text().default('da_saldare').notNull(), + paidAt: timestamp("paid_at", { withTimezone: true, mode: 'string' }), +}, (table) => [ + foreignKey({ + columns: [table.clientId], + foreignColumns: [clients.id], + name: "payments_client_id_clients_id_fk" + }).onDelete("cascade"), +]); + +export const phases = pgTable("phases", { + id: text().primaryKey().notNull(), + clientId: text("client_id").notNull(), + title: text().notNull(), + sortOrder: integer("sort_order").default(0).notNull(), + status: text().default('upcoming').notNull(), +}, (table) => [ + foreignKey({ + columns: [table.clientId], + foreignColumns: [clients.id], + name: "phases_client_id_clients_id_fk" + }).onDelete("cascade"), +]); + +export const quoteItems = pgTable("quote_items", { + id: text().primaryKey().notNull(), + clientId: text("client_id").notNull(), + serviceId: text("service_id").notNull(), + quantity: numeric({ precision: 10, scale: 2 }).notNull(), + unitPrice: numeric("unit_price", { precision: 10, scale: 2 }).notNull(), + subtotal: numeric({ precision: 10, scale: 2 }).notNull(), +}, (table) => [ + foreignKey({ + columns: [table.clientId], + foreignColumns: [clients.id], + name: "quote_items_client_id_clients_id_fk" + }).onDelete("cascade"), + foreignKey({ + columns: [table.serviceId], + foreignColumns: [serviceCatalog.id], + name: "quote_items_service_id_service_catalog_id_fk" + }).onDelete("restrict"), +]); + +export const serviceCatalog = pgTable("service_catalog", { + id: text().primaryKey().notNull(), + name: text().notNull(), + description: text(), + unitPrice: numeric("unit_price", { precision: 10, scale: 2 }).notNull(), + active: boolean().default(true).notNull(), +}); + +export const documents = pgTable("documents", { + id: text().primaryKey().notNull(), + clientId: text("client_id").notNull(), + label: text().notNull(), + url: text().notNull(), + createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), +}, (table) => [ + foreignKey({ + columns: [table.clientId], + foreignColumns: [clients.id], + name: "documents_client_id_clients_id_fk" + }).onDelete("cascade"), +]); + +export const tasks = pgTable("tasks", { + id: text().primaryKey().notNull(), + phaseId: text("phase_id").notNull(), + title: text().notNull(), + description: text(), + status: text().default('todo').notNull(), + sortOrder: integer("sort_order").default(0).notNull(), +}, (table) => [ + foreignKey({ + columns: [table.phaseId], + foreignColumns: [phases.id], + name: "tasks_phase_id_phases_id_fk" + }).onDelete("cascade"), +]); + +export const deliverables = pgTable("deliverables", { + id: text().primaryKey().notNull(), + taskId: text("task_id").notNull(), + title: text().notNull(), + url: text(), + status: text().default('pending').notNull(), + approvedAt: timestamp("approved_at", { withTimezone: true, mode: 'string' }), +}, (table) => [ + foreignKey({ + columns: [table.taskId], + foreignColumns: [tasks.id], + name: "deliverables_task_id_tasks_id_fk" + }).onDelete("cascade"), +]);