feat: migrate full Prisma schema to Drizzle ORM (29 tables, 28 enums, 25 relations)

- Install drizzle-orm, drizzle-kit, pg, @types/pg in web/
- Create split schema directory with domain files:
  - auth (users, accounts, sessions, deviceTokens)
  - subscription (familyGroups, familyGroupMembers, subscriptions)
  - darkwatch (watchlistItems, exposures)
  - alerts
  - voiceprint (voiceEnrollments, voiceAnalyses, analysisJobs, analysisResults)
  - spamshield (spamFeedback, spamRules)
  - audit (auditLogs, kpiSnapshots)
  - correlation (normalizedAlerts, correlationGroups)
  - reports (securityReports)
  - marketing (waitlistEntries, blogPosts)
  - hometitle (propertyWatchlistItems, propertySnapshots, propertyChanges)
  - removebrokers (infoBrokers, removalRequests, brokerListings)
- Define all 28 PostgreSQL enums via pgEnum()
- Define all indexes, unique constraints, and foreign keys
- Define all 25 relation definitions via relations() helper
- Update drizzle.config.ts for PostgreSQL dialect
- Update db/index.ts for node-postgres connection
- Replace old placeholder schema.ts with barrel re-export
- Add 38 comprehensive schema tests
This commit is contained in:
2026-05-25 15:35:10 -04:00
parent 9dc55517b1
commit bc20aeaeb6
21 changed files with 1780 additions and 23 deletions

View File

@@ -0,0 +1,67 @@
import { pgTable, text, timestamp, index, uuid, boolean, jsonb, integer } from "drizzle-orm/pg-core";
import { subscriptions } from "./subscription";
import { brokerCategory, removalMethod, removalStatus } from "./enums";
export const infoBrokers = pgTable("info_brokers", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
domain: text("domain").notNull().unique(),
category: brokerCategory("category").notNull(),
removalMethod: removalMethod("removal_method").notNull(),
removalUrl: text("removal_url"),
requiresAccount: boolean("requires_account").default(false).notNull(),
requiresVerification: boolean("requires_verification").default(false).notNull(),
estimatedDays: integer("estimated_days").default(14).notNull(),
isActive: boolean("is_active").default(true).notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()),
}, (table) => ({
categoryIdx: index("info_brokers_category_idx").on(table.category),
isActiveIdx: index("info_brokers_is_active_idx").on(table.isActive),
removalMethodIdx: index("info_brokers_removal_method_idx").on(table.removalMethod),
}));
export const removalRequests = pgTable("removal_requests", {
id: uuid("id").defaultRandom().primaryKey(),
subscriptionId: uuid("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }),
brokerId: uuid("broker_id").notNull().references(() => infoBrokers.id),
status: removalStatus("status").default("PENDING").notNull(),
personalInfo: jsonb("personal_info").notNull(),
method: removalMethod("method").notNull(),
attempts: integer("attempts").default(0).notNull(),
nextRetryAt: timestamp("next_retry_at", { withTimezone: true, mode: "date" }),
submittedAt: timestamp("submitted_at", { withTimezone: true, mode: "date" }),
completedAt: timestamp("completed_at", { withTimezone: true, mode: "date" }),
error: text("error"),
notes: text("notes"),
metadata: jsonb("metadata"),
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()),
}, (table) => ({
subscriptionIdIdx: index("removal_requests_subscription_id_idx").on(table.subscriptionId),
brokerIdIdx: index("removal_requests_broker_id_idx").on(table.brokerId),
statusIdx: index("removal_requests_status_idx").on(table.status),
submittedAtIdx: index("removal_requests_submitted_at_idx").on(table.submittedAt),
subscriptionIdStatusIdx: index("removal_requests_sub_id_status_idx").on(table.subscriptionId, table.status),
}));
export const brokerListings = pgTable("broker_listings", {
id: uuid("id").defaultRandom().primaryKey(),
subscriptionId: uuid("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }),
brokerId: uuid("broker_id").notNull(),
removalRequestId: uuid("removal_request_id").references(() => removalRequests.id),
url: text("url").notNull(),
dataFound: jsonb("data_found").notNull(),
screenshotUrl: text("screenshot_url"),
isRemoved: boolean("is_removed").default(false).notNull(),
removedAt: timestamp("removed_at", { withTimezone: true, mode: "date" }),
scannedAt: timestamp("scanned_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()),
}, (table) => ({
subscriptionIdIdx: index("broker_listings_subscription_id_idx").on(table.subscriptionId),
brokerIdIdx: index("broker_listings_broker_id_idx").on(table.brokerId),
removalRequestIdIdx: index("broker_listings_removal_request_id_idx").on(table.removalRequestId),
isRemovedIdx: index("broker_listings_is_removed_idx").on(table.isRemoved),
subscriptionIdIsRemovedIdx: index("broker_listings_sub_id_is_removed_idx").on(table.subscriptionId, table.isRemoved),
}));