feat(11-01): add tags table to schema with migration and push script

- Add polymorphic tags table (entity_type/entity_id/name) with unique
  index on (entity_type, entity_id, name) and lookup index on
  (entity_type, entity_id) — pattern follows comments table (D-05/D-06/D-07)
- Add Tag/NewTag types and tagsRelations (no direct FK, query-time join)
- Hand-write src/db/migrations/0006_add_tags_table.sql following the
  established project convention (drizzle-kit generate is unusable here —
  meta/_journal.json snapshots out of sync since 0001, pre-existing since
  Phase 8) and add journal entry for 0006_add_tags_table
- Add scripts/push-11-tags-migration.ts (idempotent CREATE TABLE/INDEX IF
  NOT EXISTS) with production deploy note for manual SSH+docker exec apply
This commit is contained in:
2026-06-13 15:26:14 +02:00
parent d1b1047368
commit 4773487d0c
4 changed files with 108 additions and 0 deletions
+35
View File
@@ -6,6 +6,8 @@ import {
timestamp,
boolean,
primaryKey,
uniqueIndex,
index,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { nanoid } from "nanoid";
@@ -110,6 +112,33 @@ export const comments = pgTable("comments", {
.defaultNow(),
});
// ============ TAGS (polymorphic — services now, leads in Phase 14) ============
// entity_type scopes the tag pool (D-06): "services" tags and "leads" tags are
// separate pools even though they share this table. No `color` column — badge
// color is derived deterministically from `name` via hash (D-07).
export const tags = pgTable(
"tags",
{
id: text("id")
.primaryKey()
.$defaultFn(() => nanoid()),
entity_type: text("entity_type").notNull(), // "services" | "leads" (Phase 14)
entity_id: text("entity_id").notNull(),
name: text("name").notNull(),
created_at: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
},
(t) => ({
entityTagUnique: uniqueIndex("tags_entity_name_unique").on(
t.entity_type,
t.entity_id,
t.name
),
entityIdx: index("tags_entity_idx").on(t.entity_type, t.entity_id),
})
);
// ============ PAYMENTS ============
export const payments = pgTable("payments", {
id: text("id")
@@ -460,6 +489,10 @@ export const commentsRelations = relations(comments, (_) => ({
// Polymorphic: no direct FK relation — entity_type + entity_id used at query time
}));
export const tagsRelations = relations(tags, (_) => ({
// Polymorphic: no direct FK relation — entity_type + entity_id used at query time
}));
export const paymentsRelations = relations(payments, ({ one }) => ({
project: one(projects, {
fields: [payments.project_id],
@@ -578,6 +611,8 @@ export type Deliverable = typeof deliverables.$inferSelect;
export type NewDeliverable = typeof deliverables.$inferInsert;
export type Comment = typeof comments.$inferSelect;
export type NewComment = typeof comments.$inferInsert;
export type Tag = typeof tags.$inferSelect;
export type NewTag = typeof tags.$inferInsert;
export type Payment = typeof payments.$inferSelect;
export type NewPayment = typeof payments.$inferInsert;
export type Document = typeof documents.$inferSelect;