From 89822dedb81c5d6d9e1c33c8a7f35be64be0f174 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Mon, 25 May 2026 20:50:45 -0400 Subject: [PATCH] db rearch --- tasks/shieldai-unified-restructure/README.md | 30 +++---- web/src/server/db/db.test.ts | 4 +- web/src/server/db/index.ts | 4 +- web/src/server/db/schema/alerts.ts | 27 +++--- web/src/server/db/schema/audit.ts | 26 +++--- web/src/server/db/schema/correlation.ts | 43 +++++---- web/src/server/db/schema/darkwatch.ts | 41 +++++---- web/src/server/db/schema/enums.ts | 90 ++++++++++++------- web/src/server/db/schema/hometitle.ts | 53 ++++++----- web/src/server/db/schema/invitation.ts | 23 +++-- web/src/server/db/schema/marketing.ts | 31 ++++--- web/src/server/db/schema/notifications.ts | 18 ++-- web/src/server/db/schema/removebrokers.ts | 69 +++++++------- web/src/server/db/schema/report-schedules.ts | 21 +++-- web/src/server/db/schema/reports.ts | 29 +++--- web/src/server/db/schema/spamshield.ts | 39 ++++---- web/src/server/db/schema/subscription.ts | 51 ++++++----- web/src/server/db/schema/voiceprint.ts | 63 +++++++------ web/src/server/services/correlation/engine.ts | 1 - 19 files changed, 339 insertions(+), 324 deletions(-) diff --git a/tasks/shieldai-unified-restructure/README.md b/tasks/shieldai-unified-restructure/README.md index 809659d..648ab69 100644 --- a/tasks/shieldai-unified-restructure/README.md +++ b/tasks/shieldai-unified-restructure/README.md @@ -27,21 +27,21 @@ Tasks - [x] 20 — Backend Router — Alert Correlation & Normalization Engine → `20-alert-correlation-router.md` - [x] 21 — Backend Router — Security Report Generation → `21-report-generation-router.md` - [x] 22 — Background Jobs — Scheduler, Scan Workers, and Reminders → `22-background-jobs.md` -- [ ] 23 — Frontend Integration — Wire All Pages to tRPC APIs → `23-frontend-api-integration.md` -- [ ] 24 — Dashboard — Unified Widgets for All Services → `24-dashboard-widgets.md` -- [ ] 25 — Real-Time Alerts — WebSocket Push Notifications → `25-realtime-alerts.md` -- [ ] 26 — Polish — Error Boundaries, Loading States, Skeletons, and Transitions → `26-error-loading-states.md` -- [ ] 27 — Browser Extension — Move to browser-ext/ and Update API Client → `27-browser-extension-move.md` -- [ ] 28 — iOS App — SwiftUI Foundation, Navigation, and Shared Theme → `28-ios-app-foundation.md` -- [ ] 29 — iOS App — Design System Components Matching Web Theme → `29-ios-design-system.md` -- [ ] 30 — iOS App — Authentication, Onboarding, and Account Setup → `30-ios-auth-onboarding.md` -- [ ] 31 — iOS App — API Client, tRPC Bridge, and Offline Support → `31-ios-api-client.md` -- [ ] 32 — iOS App — Dashboard and Service Screens (DarkWatch, VoicePrint, SpamShield, etc.) → `32-ios-service-screens.md` -- [ ] 33 — iOS App — Push Notifications, Biometrics, Voice Enrollment, Camera → `33-ios-native-features.md` -- [ ] 34 — Android App — Jetpack Compose Foundation, Navigation, and Shared Theme → `34-android-app-foundation.md` -- [ ] 35 — Android App — Design System Components Matching Web Theme → `35-android-design-system.md` -- [ ] 36 — Android App — Authentication, Onboarding, and Account Setup → `36-android-auth-onboarding.md` -- [ ] 37 — Android App — API Client, tRPC Bridge, and Offline Support → `37-android-api-client.md` +- [x] 23 — Frontend Integration — Wire All Pages to tRPC APIs → `23-frontend-api-integration.md` +- [x] 24 — Dashboard — Unified Widgets for All Services → `24-dashboard-widgets.md` +- [x] 25 — Real-Time Alerts — WebSocket Push Notifications → `25-realtime-alerts.md` +- [x] 26 — Polish — Error Boundaries, Loading States, Skeletons, and Transitions → `26-error-loading-states.md` +- [x] 27 — Browser Extension — Move to browser-ext/ and Update API Client → `27-browser-extension-move.md` +- [x] 28 — iOS App — SwiftUI Foundation, Navigation, and Shared Theme → `28-ios-app-foundation.md` +- [x] 29 — iOS App — Design System Components Matching Web Theme → `29-ios-design-system.md` +- [x] 30 — iOS App — Authentication, Onboarding, and Account Setup → `30-ios-auth-onboarding.md` +- [x] 31 — iOS App — API Client, tRPC Bridge, and Offline Support → `31-ios-api-client.md` +- [x] 32 — iOS App — Dashboard and Service Screens (DarkWatch, VoicePrint, SpamShield, etc.) → `32-ios-service-screens.md` +- [x] 33 — iOS App — Push Notifications, Biometrics, Voice Enrollment, Camera → `33-ios-native-features.md` +- [x] 34 — Android App — Jetpack Compose Foundation, Navigation, and Shared Theme → `34-android-app-foundation.md` +- [x] 35 — Android App — Design System Components Matching Web Theme → `35-android-design-system.md` +- [x] 36 — Android App — Authentication, Onboarding, and Account Setup → `36-android-auth-onboarding.md` +- [x] 37 — Android App — API Client, tRPC Bridge, and Offline Support → `37-android-api-client.md` - [ ] 38 — Android App — Dashboard and Service Screens → `38-android-service-screens.md` - [ ] 39 — Android App — Push Notifications, Biometrics, Voice Enrollment, Call Screening → `39-android-native-features.md` - [ ] 40 — Shared Mobile Assets — Icons, Colors, Typography, and Brand Guidelines → `40-shared-mobile-assets.md` diff --git a/web/src/server/db/db.test.ts b/web/src/server/db/db.test.ts index 0cca27b..bed9b8d 100644 --- a/web/src/server/db/db.test.ts +++ b/web/src/server/db/db.test.ts @@ -1,10 +1,10 @@ import { describe, it, expect } from "vitest"; describe("db module", () => { - it("exports db and pool", async () => { + it("exports db and client", async () => { const mod = await import("./index"); expect(mod.db).toBeDefined(); - expect(mod.pool).toBeDefined(); + expect(mod.client).toBeDefined(); }); }); diff --git a/web/src/server/db/index.ts b/web/src/server/db/index.ts index 3c8e835..753dc5f 100644 --- a/web/src/server/db/index.ts +++ b/web/src/server/db/index.ts @@ -12,9 +12,9 @@ export const db = drizzle(client, { schema }); export { client }; process.on("SIGTERM", () => { - client.close().catch(() => process.exit(1)); + client.close(); }); process.on("SIGINT", () => { - client.close().catch(() => process.exit(1)); + client.close(); }); diff --git a/web/src/server/db/schema/alerts.ts b/web/src/server/db/schema/alerts.ts index b2038c5..c74cf24 100644 --- a/web/src/server/db/schema/alerts.ts +++ b/web/src/server/db/schema/alerts.ts @@ -1,23 +1,22 @@ -import { pgTable, text, timestamp, index, uuid, boolean } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; import { users } from "./auth"; import { subscriptions } from "./subscription"; import { exposures } from "./darkwatch"; -import { alertType, alertSeverity, alertChannel } from "./enums"; -export const alerts = pgTable("alerts", { - id: uuid("id").defaultRandom().primaryKey(), - subscriptionId: uuid("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), - userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), - exposureId: uuid("exposure_id").references(() => exposures.id), - type: alertType("type").notNull(), +export const alerts = sqliteTable("alerts", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + subscriptionId: text("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), + userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), + exposureId: text("exposure_id").references(() => exposures.id), + type: text("type").notNull(), title: text("title").notNull(), message: text("message").notNull(), - severity: alertSeverity("severity").default("info").notNull(), - isRead: boolean("is_read").default(false).notNull(), - readAt: timestamp("read_at", { withTimezone: true, mode: "date" }), - channel: alertChannel("channel").array().notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + severity: text("severity").default("info").notNull(), + isRead: integer("is_read", { mode: "boolean" }).default(false).notNull(), + readAt: integer("read_at", { mode: "timestamp_ms" }), + channel: text("channel", { mode: "json" }).notNull().$defaultFn(() => []), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ subscriptionIdIdx: index("alerts_subscription_id_idx").on(table.subscriptionId), userIdIdx: index("alerts_user_id_idx").on(table.userId), diff --git a/web/src/server/db/schema/audit.ts b/web/src/server/db/schema/audit.ts index 02a87cb..9c2c7a3 100644 --- a/web/src/server/db/schema/audit.ts +++ b/web/src/server/db/schema/audit.ts @@ -1,16 +1,16 @@ -import { pgTable, text, timestamp, index, uuid, jsonb, doublePrecision } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, real, index } from "drizzle-orm/sqlite-core"; -export const auditLogs = pgTable("audit_logs", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id"), +export const auditLogs = sqliteTable("audit_logs", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id"), action: text("action").notNull(), resource: text("resource").notNull(), resourceId: text("resource_id"), - changes: jsonb("changes"), - metadata: jsonb("metadata"), + changes: text("changes", { mode: "json" }), + metadata: text("metadata", { mode: "json" }), ipAddress: text("ip_address"), userAgent: text("user_agent"), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), }, (table) => ({ userIdIdx: index("audit_logs_user_id_idx").on(table.userId), actionIdx: index("audit_logs_action_idx").on(table.action), @@ -18,13 +18,13 @@ export const auditLogs = pgTable("audit_logs", { createdAtIdx: index("audit_logs_created_at_idx").on(table.createdAt), })); -export const kpiSnapshots = pgTable("kpi_snapshots", { - id: uuid("id").defaultRandom().primaryKey(), - date: timestamp("date", { withTimezone: true, mode: "date" }).notNull().unique(), +export const kpiSnapshots = sqliteTable("kpi_snapshots", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + date: integer("date", { mode: "timestamp_ms" }).notNull().unique(), metricName: text("metric_name").notNull(), - metricValue: doublePrecision("metric_value").notNull(), - metadata: jsonb("metadata"), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), + metricValue: real("metric_value").notNull(), + metadata: text("metadata", { mode: "json" }), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), }, (table) => ({ metricNameIdx: index("kpi_snapshots_metric_name_idx").on(table.metricName), dateIdx: index("kpi_snapshots_date_idx").on(table.date), diff --git a/web/src/server/db/schema/correlation.ts b/web/src/server/db/schema/correlation.ts index e7d2724..8141bf0 100644 --- a/web/src/server/db/schema/correlation.ts +++ b/web/src/server/db/schema/correlation.ts @@ -1,18 +1,17 @@ -import { pgTable, text, timestamp, uniqueIndex, index, uuid, jsonb, integer } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, uniqueIndex, index } from "drizzle-orm/sqlite-core"; import { users } from "./auth"; -import { alertSource, alertCategory, normalizedAlertSeverity, correlationStatus } from "./enums"; -export const correlationGroups = pgTable("correlation_groups", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), - entities: jsonb("entities").notNull(), - highestSeverity: normalizedAlertSeverity("highest_severity").notNull(), - status: correlationStatus("status").default("ACTIVE").notNull(), +export const correlationGroups = sqliteTable("correlation_groups", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), + entities: text("entities", { mode: "json" }).notNull(), + highestSeverity: text("highest_severity").notNull(), + status: text("status").default("ACTIVE").notNull(), alertCount: integer("alert_count").default(0).notNull(), summary: text("summary"), - resolvedAt: timestamp("resolved_at", { withTimezone: true, mode: "date" }), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + resolvedAt: integer("resolved_at", { mode: "timestamp_ms" }), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ userIdIdx: index("correlation_groups_user_id_idx").on(table.userId), statusIdx: index("correlation_groups_status_idx").on(table.status), @@ -20,20 +19,20 @@ export const correlationGroups = pgTable("correlation_groups", { createdAtIdx: index("correlation_groups_created_at_idx").on(table.createdAt), })); -export const normalizedAlerts = pgTable("normalized_alerts", { - id: uuid("id").defaultRandom().primaryKey(), - source: alertSource("source").notNull(), - category: alertCategory("category").notNull(), - severity: normalizedAlertSeverity("severity").notNull(), - userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), +export const normalizedAlerts = sqliteTable("normalized_alerts", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + source: text("source").notNull(), + category: text("category").notNull(), + severity: text("severity").notNull(), + userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), title: text("title").notNull(), description: text("description").notNull(), - entities: jsonb("entities").notNull(), + entities: text("entities", { mode: "json" }).notNull(), sourceAlertId: text("source_alert_id").notNull(), - groupId: uuid("group_id").references(() => correlationGroups.id), - payload: jsonb("payload"), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + groupId: text("group_id").references(() => correlationGroups.id), + payload: text("payload", { mode: "json" }), + createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ sourceAlertIdUnique: uniqueIndex("normalized_alerts_source_alert_id_unique").on(table.sourceAlertId), userIdIdx: index("normalized_alerts_user_id_idx").on(table.userId), diff --git a/web/src/server/db/schema/darkwatch.ts b/web/src/server/db/schema/darkwatch.ts index d6fb622..7168ecb 100644 --- a/web/src/server/db/schema/darkwatch.ts +++ b/web/src/server/db/schema/darkwatch.ts @@ -1,16 +1,15 @@ -import { pgTable, text, timestamp, uniqueIndex, index, uuid, boolean, jsonb } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, uniqueIndex, index } from "drizzle-orm/sqlite-core"; import { subscriptions } from "./subscription"; -import { watchlistType, exposureSource, exposureSeverity } from "./enums"; -export const watchlistItems = pgTable("watchlist_items", { - id: uuid("id").defaultRandom().primaryKey(), - subscriptionId: uuid("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), - type: watchlistType("type").notNull(), +export const watchlistItems = sqliteTable("watchlist_items", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + subscriptionId: text("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), + type: text("type").notNull(), value: text("value").notNull(), hash: text("hash").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()), + isActive: integer("is_active", { mode: "boolean" }).default(true).notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ subTypeHashUnique: uniqueIndex("watchlist_items_sub_type_hash_unique").on(table.subscriptionId, table.type, table.hash), subscriptionIdIdx: index("watchlist_items_subscription_id_idx").on(table.subscriptionId), @@ -18,20 +17,20 @@ export const watchlistItems = pgTable("watchlist_items", { hashIdx: index("watchlist_items_hash_idx").on(table.hash), })); -export const exposures = pgTable("exposures", { - id: uuid("id").defaultRandom().primaryKey(), - subscriptionId: uuid("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), - watchlistItemId: uuid("watchlist_item_id").references(() => watchlistItems.id), - source: exposureSource("source").notNull(), - dataType: watchlistType("data_type").notNull(), +export const exposures = sqliteTable("exposures", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + subscriptionId: text("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), + watchlistItemId: text("watchlist_item_id").references(() => watchlistItems.id), + source: text("source").notNull(), + dataType: text("data_type").notNull(), identifier: text("identifier").notNull(), identifierHash: text("identifier_hash").notNull(), - severity: exposureSeverity("severity").default("info").notNull(), - metadata: jsonb("metadata"), - isFirstTime: boolean("is_first_time").default(false).notNull(), - detectedAt: timestamp("detected_at", { withTimezone: true, mode: "date" }).notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + severity: text("severity").default("info").notNull(), + metadata: text("metadata", { mode: "json" }), + isFirstTime: integer("is_first_time", { mode: "boolean" }).default(false).notNull(), + detectedAt: integer("detected_at", { mode: "timestamp_ms" }).notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ subscriptionIdIdx: index("exposures_subscription_id_idx").on(table.subscriptionId), watchlistItemIdIdx: index("exposures_watchlist_item_id_idx").on(table.watchlistItemId), diff --git a/web/src/server/db/schema/enums.ts b/web/src/server/db/schema/enums.ts index a64986f..a4d71d2 100644 --- a/web/src/server/db/schema/enums.ts +++ b/web/src/server/db/schema/enums.ts @@ -1,31 +1,61 @@ -import { pgEnum } from "drizzle-orm/pg-core"; +export const userRoleValues = ["user", "family_admin", "family_member", "support"] as const; +export const deviceTypeValues = ["mobile", "web", "desktop"] as const; +export const platformValues = ["ios", "android", "web"] as const; +export const familyMemberRoleValues = ["owner", "admin", "member"] as const; +export const subscriptionTierValues = ["basic", "plus", "premium"] as const; +export const subscriptionStatusValues = ["active", "past_due", "canceled", "unpaid", "trialing"] as const; +export const watchlistTypeValues = ["email", "phoneNumber", "ssn", "address", "domain"] as const; +export const exposureSourceValues = ["hibp", "securityTrails", "censys", "darkWebForum", "shodan", "honeypot"] as const; +export const exposureSeverityValues = ["info", "warning", "critical"] as const; +export const alertTypeValues = ["exposure_detected", "exposure_resolved", "scan_complete", "subscription_changed", "system_warning", "property_change"] as const; +export const alertSeverityValues = ["info", "warning", "critical"] as const; +export const alertChannelValues = ["email", "push", "sms"] as const; +export const detectionVerdictValues = ["NATURAL", "SYNTHETIC", "UNCERTAIN"] as const; +export const analysisTypeValues = ["SYNTHETIC_DETECTION", "VOICE_MATCH", "BATCH"] as const; +export const analysisJobStatusValues = ["PENDING", "RUNNING", "COMPLETED", "FAILED"] as const; +export const feedbackTypeValues = ["initial_detection", "user_confirmation", "user_rejection", "auto_learned"] as const; +export const ruleTypeValues = ["phoneNumber", "areaCode", "prefix", "pattern", "reputation"] as const; +export const ruleActionValues = ["block", "flag", "allow", "challenge"] as const; +export const alertSourceValues = ["DARKWATCH", "SPAMSHIELD", "VOICEPRINT", "CALL_ANALYSIS", "HOME_TITLE", "INFO_BROKER"] as const; +export const alertCategoryValues = ["BREACH_EXPOSURE", "SPAM_CALL", "SPAM_SMS", "SYNTHETIC_VOICE", "VOICE_MISMATCH", "CALL_ANOMALY", "CALL_QUALITY", "CALL_EVENT", "HOME_TITLE", "INFO_BROKER_LISTING", "INFO_BROKER_REMOVAL"] as const; +export const normalizedAlertSeverityValues = ["LOW", "INFO", "MEDIUM", "WARNING", "HIGH", "CRITICAL"] as const; +export const correlationStatusValues = ["ACTIVE", "RESOLVED", "FALSE_POSITIVE"] as const; +export const reportTypeValues = ["MONTHLY_PLUS", "ANNUAL_PREMIUM", "WEEKLY_DIGEST"] as const; +export const reportStatusValues = ["PENDING", "GENERATING", "COMPLETED", "FAILED", "DELIVERED"] as const; +export const propertyChangeTypeValues = ["tax_change", "deed_change", "ownership_transfer", "lien_filing", "metadata_change"] as const; +export const propertyChangeSeverityValues = ["info", "warning", "critical"] as const; +export const brokerCategoryValues = ["PEOPLE_SEARCH", "BACKGROUND_CHECK", "PUBLIC_RECORDS", "REVERSE_LOOKUP", "SOCIAL_MEDIA"] as const; +export const removalMethodValues = ["AUTOMATED", "MANUAL_FORM", "EMAIL", "PHONE", "MAIL", "NONE"] as const; +export const removalStatusValues = ["PENDING", "SUBMITTED", "IN_PROGRESS", "COMPLETED", "FAILED", "REJECTED", "CANCELLED"] as const; +export const invitationStatusValues = ["pending", "accepted", "expired", "cancelled"] as const; -export const userRole = pgEnum("user_role", ["user", "family_admin", "family_member", "support"]); -export const deviceType = pgEnum("device_type", ["mobile", "web", "desktop"]); -export const platform = pgEnum("platform", ["ios", "android", "web"]); -export const familyMemberRole = pgEnum("family_member_role", ["owner", "admin", "member"]); -export const subscriptionTier = pgEnum("subscription_tier", ["basic", "plus", "premium"]); -export const subscriptionStatus = pgEnum("subscription_status", ["active", "past_due", "canceled", "unpaid", "trialing"]); -export const watchlistType = pgEnum("watchlist_type", ["email", "phoneNumber", "ssn", "address", "domain"]); -export const exposureSource = pgEnum("exposure_source", ["hibp", "securityTrails", "censys", "darkWebForum", "shodan", "honeypot"]); -export const exposureSeverity = pgEnum("exposure_severity", ["info", "warning", "critical"]); -export const alertType = pgEnum("alert_type", ["exposure_detected", "exposure_resolved", "scan_complete", "subscription_changed", "system_warning", "property_change"]); -export const alertSeverity = pgEnum("alert_severity", ["info", "warning", "critical"]); -export const alertChannel = pgEnum("alert_channel", ["email", "push", "sms"]); -export const detectionVerdict = pgEnum("detection_verdict", ["NATURAL", "SYNTHETIC", "UNCERTAIN"]); -export const analysisType = pgEnum("analysis_type", ["SYNTHETIC_DETECTION", "VOICE_MATCH", "BATCH"]); -export const analysisJobStatus = pgEnum("analysis_job_status", ["PENDING", "RUNNING", "COMPLETED", "FAILED"]); -export const feedbackType = pgEnum("feedback_type", ["initial_detection", "user_confirmation", "user_rejection", "auto_learned"]); -export const ruleType = pgEnum("rule_type", ["phoneNumber", "areaCode", "prefix", "pattern", "reputation"]); -export const ruleAction = pgEnum("rule_action", ["block", "flag", "allow", "challenge"]); -export const alertSource = pgEnum("alert_source", ["DARKWATCH", "SPAMSHIELD", "VOICEPRINT", "CALL_ANALYSIS", "HOME_TITLE", "INFO_BROKER"]); -export const alertCategory = pgEnum("alert_category", ["BREACH_EXPOSURE", "SPAM_CALL", "SPAM_SMS", "SYNTHETIC_VOICE", "VOICE_MISMATCH", "CALL_ANOMALY", "CALL_QUALITY", "CALL_EVENT", "HOME_TITLE", "INFO_BROKER_LISTING", "INFO_BROKER_REMOVAL"]); -export const normalizedAlertSeverity = pgEnum("normalized_alert_severity", ["LOW", "INFO", "MEDIUM", "WARNING", "HIGH", "CRITICAL"]); -export const correlationStatus = pgEnum("correlation_status", ["ACTIVE", "RESOLVED", "FALSE_POSITIVE"]); -export const reportType = pgEnum("report_type", ["MONTHLY_PLUS", "ANNUAL_PREMIUM", "WEEKLY_DIGEST"]); -export const reportStatus = pgEnum("report_status", ["PENDING", "GENERATING", "COMPLETED", "FAILED", "DELIVERED"]); -export const propertyChangeType = pgEnum("property_change_type", ["tax_change", "deed_change", "ownership_transfer", "lien_filing", "metadata_change"]); -export const propertyChangeSeverity = pgEnum("property_change_severity", ["info", "warning", "critical"]); -export const brokerCategory = pgEnum("broker_category", ["PEOPLE_SEARCH", "BACKGROUND_CHECK", "PUBLIC_RECORDS", "REVERSE_LOOKUP", "SOCIAL_MEDIA"]); -export const removalMethod = pgEnum("removal_method", ["AUTOMATED", "MANUAL_FORM", "EMAIL", "PHONE", "MAIL", "NONE"]); -export const removalStatus = pgEnum("removal_status", ["PENDING", "SUBMITTED", "IN_PROGRESS", "COMPLETED", "FAILED", "REJECTED", "CANCELLED"]); +export type UserRole = typeof userRoleValues[number]; +export type DeviceType = typeof deviceTypeValues[number]; +export type Platform = typeof platformValues[number]; +export type FamilyMemberRole = typeof familyMemberRoleValues[number]; +export type SubscriptionTier = typeof subscriptionTierValues[number]; +export type SubscriptionStatus = typeof subscriptionStatusValues[number]; +export type WatchlistType = typeof watchlistTypeValues[number]; +export type ExposureSource = typeof exposureSourceValues[number]; +export type ExposureSeverity = typeof exposureSeverityValues[number]; +export type AlertType = typeof alertTypeValues[number]; +export type AlertSeverity = typeof alertSeverityValues[number]; +export type AlertChannel = typeof alertChannelValues[number]; +export type DetectionVerdict = typeof detectionVerdictValues[number]; +export type AnalysisType = typeof analysisTypeValues[number]; +export type AnalysisJobStatus = typeof analysisJobStatusValues[number]; +export type FeedbackType = typeof feedbackTypeValues[number]; +export type RuleType = typeof ruleTypeValues[number]; +export type RuleAction = typeof ruleActionValues[number]; +export type AlertSource = typeof alertSourceValues[number]; +export type AlertCategory = typeof alertCategoryValues[number]; +export type NormalizedAlertSeverity = typeof normalizedAlertSeverityValues[number]; +export type CorrelationStatus = typeof correlationStatusValues[number]; +export type ReportType = typeof reportTypeValues[number]; +export type ReportStatus = typeof reportStatusValues[number]; +export type PropertyChangeType = typeof propertyChangeTypeValues[number]; +export type PropertyChangeSeverity = typeof propertyChangeSeverityValues[number]; +export type BrokerCategory = typeof brokerCategoryValues[number]; +export type RemovalMethod = typeof removalMethodValues[number]; +export type RemovalStatus = typeof removalStatusValues[number]; +export type InvitationStatus = typeof invitationStatusValues[number]; diff --git a/web/src/server/db/schema/hometitle.ts b/web/src/server/db/schema/hometitle.ts index 4ebdd2d..61de752 100644 --- a/web/src/server/db/schema/hometitle.ts +++ b/web/src/server/db/schema/hometitle.ts @@ -1,10 +1,9 @@ -import { pgTable, text, timestamp, uniqueIndex, index, uuid, boolean, jsonb, doublePrecision, integer } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, real, uniqueIndex, index } from "drizzle-orm/sqlite-core"; import { subscriptions } from "./subscription"; -import { propertyChangeType, propertyChangeSeverity } from "./enums"; -export const propertyWatchlistItems = pgTable("property_watchlist_items", { - id: uuid("id").defaultRandom().primaryKey(), - subscriptionId: uuid("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), +export const propertyWatchlistItems = sqliteTable("property_watchlist_items", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + subscriptionId: text("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), address: text("address").notNull(), parcelId: text("parcel_id"), ownerName: text("owner_name"), @@ -12,11 +11,11 @@ export const propertyWatchlistItems = pgTable("property_watchlist_items", { city: text("city").default(""), state: text("state").default(""), zipCode: text("zip_code").default(""), - latitude: doublePrecision("latitude"), - longitude: doublePrecision("longitude"), - 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()), + latitude: real("latitude"), + longitude: real("longitude"), + isActive: integer("is_active", { mode: "boolean" }).default(true).notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ subParcelIdUnique: uniqueIndex("property_watchlist_items_sub_parcel_unique").on(table.subscriptionId, table.parcelId), subscriptionIdIdx: index("property_watchlist_items_subscription_id_idx").on(table.subscriptionId), @@ -24,34 +23,34 @@ export const propertyWatchlistItems = pgTable("property_watchlist_items", { addressIdx: index("property_watchlist_items_address_idx").on(table.address), })); -export const propertySnapshots = pgTable("property_snapshots", { - id: uuid("id").defaultRandom().primaryKey(), - propertyWatchlistItemId: uuid("property_watchlist_item_id").notNull().references(() => propertyWatchlistItems.id, { onDelete: "cascade" }), - subscriptionId: uuid("subscription_id").notNull(), - capturedAt: timestamp("captured_at", { withTimezone: true, mode: "date" }).notNull(), +export const propertySnapshots = sqliteTable("property_snapshots", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + propertyWatchlistItemId: text("property_watchlist_item_id").notNull().references(() => propertyWatchlistItems.id, { onDelete: "cascade" }), + subscriptionId: text("subscription_id").notNull(), + capturedAt: integer("captured_at", { mode: "timestamp_ms" }).notNull(), ownerName: text("owner_name").notNull(), - address: jsonb("address").notNull(), + address: text("address", { mode: "json" }).notNull(), deedDate: text("deed_date"), taxId: text("tax_id"), propertyType: text("property_type").default("residential").notNull(), - taxAmount: doublePrecision("tax_amount"), + taxAmount: real("tax_amount"), lienCount: integer("lien_count").default(0).notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ propertyWatchlistItemIdIdx: index("property_snapshots_property_watchlist_item_id_idx").on(table.propertyWatchlistItemId), subscriptionIdIdx: index("property_snapshots_subscription_id_idx").on(table.subscriptionId), capturedAtIdx: index("property_snapshots_captured_at_idx").on(table.capturedAt), })); -export const propertyChanges = pgTable("property_changes", { - id: uuid("id").defaultRandom().primaryKey(), - propertyWatchlistItemId: uuid("property_watchlist_item_id").notNull().references(() => propertyWatchlistItems.id, { onDelete: "cascade" }), - snapshotId: uuid("snapshot_id").references(() => propertySnapshots.id), - changeType: propertyChangeType("change_type").notNull(), - severity: propertyChangeSeverity("severity").default("info").notNull(), - details: jsonb("details"), - detectedAt: timestamp("detected_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), +export const propertyChanges = sqliteTable("property_changes", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + propertyWatchlistItemId: text("property_watchlist_item_id").notNull().references(() => propertyWatchlistItems.id, { onDelete: "cascade" }), + snapshotId: text("snapshot_id").references(() => propertySnapshots.id), + changeType: text("change_type").notNull(), + severity: text("severity").default("info").notNull(), + details: text("details", { mode: "json" }), + detectedAt: integer("detected_at", { mode: "timestamp_ms" }).defaultNow().notNull(), }, (table) => ({ propertyWatchlistItemIdIdx: index("property_changes_property_watchlist_item_id_idx").on(table.propertyWatchlistItemId), snapshotIdIdx: index("property_changes_snapshot_id_idx").on(table.snapshotId), diff --git a/web/src/server/db/schema/invitation.ts b/web/src/server/db/schema/invitation.ts index 7a85aa3..680f881 100644 --- a/web/src/server/db/schema/invitation.ts +++ b/web/src/server/db/schema/invitation.ts @@ -1,18 +1,15 @@ -import { pgTable, text, timestamp, uuid, pgEnum } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; import { users } from "./auth"; import { familyGroups } from "./subscription"; -import { familyMemberRole } from "./enums"; -export const invitationStatus = pgEnum("invitation_status", ["pending", "accepted", "expired", "cancelled"]); - -export const invitations = pgTable("invitations", { - id: uuid("id").defaultRandom().primaryKey(), - groupId: uuid("group_id").notNull().references(() => familyGroups.id, { onDelete: "cascade" }), +export const invitations = sqliteTable("invitations", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + groupId: text("group_id").notNull().references(() => familyGroups.id, { onDelete: "cascade" }), email: text("email").notNull(), - role: familyMemberRole("role").default("member").notNull(), - invitedBy: uuid("invited_by").notNull().references(() => users.id), - status: invitationStatus("status").default("pending").notNull(), - expiresAt: timestamp("expires_at", { withTimezone: true, mode: "date" }).notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + role: text("role").default("member").notNull(), + invitedBy: text("invited_by").notNull().references(() => users.id), + status: text("status").default("pending").notNull(), + expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }); diff --git a/web/src/server/db/schema/marketing.ts b/web/src/server/db/schema/marketing.ts index ec96889..f3431c3 100644 --- a/web/src/server/db/schema/marketing.ts +++ b/web/src/server/db/schema/marketing.ts @@ -1,41 +1,40 @@ -import { pgTable, text, timestamp, index, uuid, boolean, integer, jsonb } from "drizzle-orm/pg-core"; -import { subscriptionTier } from "./enums"; +import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; -export const waitlistEntries = pgTable("waitlist_entries", { - id: uuid("id").defaultRandom().primaryKey(), +export const waitlistEntries = sqliteTable("waitlist_entries", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), email: text("email").notNull(), name: text("name"), source: text("source"), - tier: subscriptionTier("tier"), + tier: text("tier"), utmSource: text("utm_source"), utmMedium: text("utm_medium"), utmCampaign: text("utm_campaign"), - metadata: jsonb("metadata"), - convertedAt: timestamp("converted_at", { withTimezone: true, mode: "date" }), + metadata: text("metadata", { mode: "json" }), + convertedAt: integer("converted_at", { mode: "timestamp_ms" }), convertedToUserId: text("converted_to_user_id"), convertedToSubscriptionId: text("converted_to_subscription_id"), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ emailIdx: index("waitlist_entries_email_idx").on(table.email), sourceIdx: index("waitlist_entries_source_idx").on(table.source), createdAtIdx: index("waitlist_entries_created_at_idx").on(table.createdAt), })); -export const blogPosts = pgTable("blog_posts", { - id: uuid("id").defaultRandom().primaryKey(), +export const blogPosts = sqliteTable("blog_posts", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), slug: text("slug").notNull().unique(), title: text("title").notNull(), excerpt: text("excerpt"), content: text("content").notNull(), authorName: text("author_name"), coverImageUrl: text("cover_image_url"), - tags: text("tags").array().notNull(), - published: boolean("published").default(false).notNull(), - publishedAt: timestamp("published_at", { withTimezone: true, mode: "date" }), + tags: text("tags", { mode: "json" }).notNull().$defaultFn(() => []), + published: integer("published", { mode: "boolean" }).default(false).notNull(), + publishedAt: integer("published_at", { mode: "timestamp_ms" }), viewCount: integer("view_count").default(0).notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ slugIdx: index("blog_posts_slug_idx").on(table.slug), publishedIdx: index("blog_posts_published_idx").on(table.published, table.publishedAt), diff --git a/web/src/server/db/schema/notifications.ts b/web/src/server/db/schema/notifications.ts index a11f8de..05a0509 100644 --- a/web/src/server/db/schema/notifications.ts +++ b/web/src/server/db/schema/notifications.ts @@ -1,15 +1,15 @@ -import { pgTable, uuid, boolean, timestamp } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; import { users } from "./auth"; -export const notificationPreferences = pgTable("notification_preferences", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id") +export const notificationPreferences = sqliteTable("notification_preferences", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }) .unique(), - emailEnabled: boolean("email_enabled").default(true).notNull(), - pushEnabled: boolean("push_enabled").default(true).notNull(), - smsEnabled: boolean("sms_enabled").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()), + emailEnabled: integer("email_enabled", { mode: "boolean" }).default(true).notNull(), + pushEnabled: integer("push_enabled", { mode: "boolean" }).default(true).notNull(), + smsEnabled: integer("sms_enabled", { mode: "boolean" }).default(true).notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }); diff --git a/web/src/server/db/schema/removebrokers.ts b/web/src/server/db/schema/removebrokers.ts index fddf163..645551f 100644 --- a/web/src/server/db/schema/removebrokers.ts +++ b/web/src/server/db/schema/removebrokers.ts @@ -1,42 +1,41 @@ -import { pgTable, text, timestamp, index, uuid, boolean, jsonb, integer } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; import { subscriptions } from "./subscription"; -import { brokerCategory, removalMethod, removalStatus } from "./enums"; -export const infoBrokers = pgTable("info_brokers", { - id: uuid("id").defaultRandom().primaryKey(), +export const infoBrokers = sqliteTable("info_brokers", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), name: text("name").notNull(), domain: text("domain").notNull().unique(), - category: brokerCategory("category").notNull(), - removalMethod: removalMethod("removal_method").notNull(), + category: text("category").notNull(), + removalMethod: text("removal_method").notNull(), removalUrl: text("removal_url"), - requiresAccount: boolean("requires_account").default(false).notNull(), - requiresVerification: boolean("requires_verification").default(false).notNull(), + requiresAccount: integer("requires_account", { mode: "boolean" }).default(false).notNull(), + requiresVerification: integer("requires_verification", { mode: "boolean" }).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()), + isActive: integer("is_active", { mode: "boolean" }).default(true).notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).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(), +export const removalRequests = sqliteTable("removal_requests", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + subscriptionId: text("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), + brokerId: text("broker_id").notNull().references(() => infoBrokers.id), + status: text("status").default("PENDING").notNull(), + personalInfo: text("personal_info", { mode: "json" }).notNull(), + method: text("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" }), + nextRetryAt: integer("next_retry_at", { mode: "timestamp_ms" }), + submittedAt: integer("submitted_at", { mode: "timestamp_ms" }), + completedAt: integer("completed_at", { mode: "timestamp_ms" }), 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()), + metadata: text("metadata", { mode: "json" }), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).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), @@ -45,19 +44,19 @@ export const removalRequests = pgTable("removal_requests", { 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), +export const brokerListings = sqliteTable("broker_listings", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + subscriptionId: text("subscription_id").notNull().references(() => subscriptions.id, { onDelete: "cascade" }), + brokerId: text("broker_id").notNull(), + removalRequestId: text("removal_request_id").references(() => removalRequests.id), url: text("url").notNull(), - dataFound: jsonb("data_found").notNull(), + dataFound: text("data_found", { mode: "json" }).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()), + isRemoved: integer("is_removed", { mode: "boolean" }).default(false).notNull(), + removedAt: integer("removed_at", { mode: "timestamp_ms" }), + scannedAt: integer("scanned_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).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), diff --git a/web/src/server/db/schema/report-schedules.ts b/web/src/server/db/schema/report-schedules.ts index a1c8c1b..8878866 100644 --- a/web/src/server/db/schema/report-schedules.ts +++ b/web/src/server/db/schema/report-schedules.ts @@ -1,17 +1,16 @@ -import { pgTable, text, timestamp, index, uuid, boolean } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; import { users } from "./auth"; -import { reportType } from "./enums"; -export const reportSchedules = pgTable("report_schedules", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), - enabled: boolean("enabled").default(true).notNull(), +export const reportSchedules = sqliteTable("report_schedules", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), + enabled: integer("enabled", { mode: "boolean" }).default(true).notNull(), frequency: text("frequency").notNull(), - reportType: reportType("report_type").notNull(), - lastGeneratedAt: timestamp("last_generated_at", { withTimezone: true, mode: "date" }), - nextScheduledAt: timestamp("next_scheduled_at", { withTimezone: true, mode: "date" }), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + reportType: text("report_type").notNull(), + lastGeneratedAt: integer("last_generated_at", { mode: "timestamp_ms" }), + nextScheduledAt: integer("next_scheduled_at", { mode: "timestamp_ms" }), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ userIdIdx: index("report_schedules_user_id_idx").on(table.userId), enabledIdx: index("report_schedules_enabled_idx").on(table.enabled), diff --git a/web/src/server/db/schema/reports.ts b/web/src/server/db/schema/reports.ts index e507a2e..7b23ebd 100644 --- a/web/src/server/db/schema/reports.ts +++ b/web/src/server/db/schema/reports.ts @@ -1,25 +1,24 @@ -import { pgTable, text, timestamp, index, uuid, jsonb } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core"; import { users } from "./auth"; -import { reportType, reportStatus } from "./enums"; -export const securityReports = pgTable("security_reports", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), - subscriptionId: uuid("subscription_id").notNull(), - reportType: reportType("report_type").notNull(), - status: reportStatus("status").default("PENDING").notNull(), - periodStart: timestamp("period_start", { withTimezone: true, mode: "date" }).notNull(), - periodEnd: timestamp("period_end", { withTimezone: true, mode: "date" }).notNull(), +export const securityReports = sqliteTable("security_reports", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), + subscriptionId: text("subscription_id").notNull(), + reportType: text("report_type").notNull(), + status: text("status").default("PENDING").notNull(), + periodStart: integer("period_start", { mode: "timestamp_ms" }).notNull(), + periodEnd: integer("period_end", { mode: "timestamp_ms" }).notNull(), title: text("title").notNull(), summary: text("summary"), htmlContent: text("html_content"), pdfUrl: text("pdf_url"), - dataPayload: jsonb("data_payload"), + dataPayload: text("data_payload", { mode: "json" }), error: text("error"), - scheduledFor: timestamp("scheduled_for", { withTimezone: true, mode: "date" }), - deliveredAt: timestamp("delivered_at", { withTimezone: true, mode: "date" }), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + scheduledFor: integer("scheduled_for", { mode: "timestamp_ms" }), + deliveredAt: integer("delivered_at", { mode: "timestamp_ms" }), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ userIdIdx: index("security_reports_user_id_idx").on(table.userId), subscriptionIdIdx: index("security_reports_subscription_id_idx").on(table.subscriptionId), diff --git a/web/src/server/db/schema/spamshield.ts b/web/src/server/db/schema/spamshield.ts index b916f9a..247846a 100644 --- a/web/src/server/db/schema/spamshield.ts +++ b/web/src/server/db/schema/spamshield.ts @@ -1,35 +1,34 @@ -import { pgTable, text, timestamp, index, uuid, boolean, jsonb, doublePrecision, integer } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, real, index } from "drizzle-orm/sqlite-core"; import { users } from "./auth"; -import { feedbackType, ruleType, ruleAction } from "./enums"; -export const spamFeedback = pgTable("spam_feedback", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), +export const spamFeedback = sqliteTable("spam_feedback", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), phoneNumber: text("phone_number").notNull(), phoneNumberHash: text("phone_number_hash").notNull(), - isSpam: boolean("is_spam").notNull(), - confidence: doublePrecision("confidence"), - feedbackType: feedbackType("feedback_type").notNull(), - 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()), + isSpam: integer("is_spam", { mode: "boolean" }).notNull(), + confidence: real("confidence"), + feedbackType: text("feedback_type").notNull(), + metadata: text("metadata", { mode: "json" }), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ userIdIdx: index("spam_feedback_user_id_idx").on(table.userId), phoneNumberHashIdx: index("spam_feedback_phone_number_hash_idx").on(table.phoneNumberHash), isSpamIdx: index("spam_feedback_is_spam_idx").on(table.isSpam), })); -export const spamRules = pgTable("spam_rules", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }), - isGlobal: boolean("is_global").default(false).notNull(), - ruleType: ruleType("rule_type").notNull(), +export const spamRules = sqliteTable("spam_rules", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id").references(() => users.id, { onDelete: "cascade" }), + isGlobal: integer("is_global", { mode: "boolean" }).default(false).notNull(), + ruleType: text("rule_type").notNull(), pattern: text("pattern").notNull(), - action: ruleAction("action").notNull(), + action: text("action").notNull(), priority: integer("priority").default(0).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()), + isActive: integer("is_active", { mode: "boolean" }).default(true).notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ userIdIdx: index("spam_rules_user_id_idx").on(table.userId), isGlobalIdx: index("spam_rules_is_global_idx").on(table.isGlobal), diff --git a/web/src/server/db/schema/subscription.ts b/web/src/server/db/schema/subscription.ts index f3efb49..01bf80c 100644 --- a/web/src/server/db/schema/subscription.ts +++ b/web/src/server/db/schema/subscription.ts @@ -1,44 +1,43 @@ -import { pgTable, text, timestamp, uniqueIndex, index, uuid, boolean } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, uniqueIndex, index } from "drizzle-orm/sqlite-core"; import { users } from "./auth"; -import { familyMemberRole, subscriptionTier, subscriptionStatus } from "./enums"; -export const familyGroups = pgTable("family_groups", { - id: uuid("id").defaultRandom().primaryKey(), +export const familyGroups = sqliteTable("family_groups", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), name: text("name").notNull(), - ownerId: uuid("owner_id").notNull().references(() => users.id), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + ownerId: text("owner_id").notNull().references(() => users.id), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ ownerIdIdx: index("family_groups_owner_id_idx").on(table.ownerId), nameIdx: index("family_groups_name_idx").on(table.name), })); -export const familyGroupMembers = pgTable("family_group_members", { - id: uuid("id").defaultRandom().primaryKey(), - groupId: uuid("group_id").notNull().references(() => familyGroups.id, { onDelete: "cascade" }), - userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), - role: familyMemberRole("role").default("member").notNull(), - joinedAt: timestamp("joined_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()), +export const familyGroupMembers = sqliteTable("family_group_members", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + groupId: text("group_id").notNull().references(() => familyGroups.id, { onDelete: "cascade" }), + userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), + role: text("role").default("member").notNull(), + joinedAt: integer("joined_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ groupUserUnique: uniqueIndex("family_group_members_group_user_unique").on(table.groupId, table.userId), groupIdIdx: index("family_group_members_group_id_idx").on(table.groupId), userIdIdx: index("family_group_members_user_id_idx").on(table.userId), })); -export const subscriptions = pgTable("subscriptions", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), - familyGroupId: uuid("family_group_id").references(() => familyGroups.id), +export const subscriptions = sqliteTable("subscriptions", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), + familyGroupId: text("family_group_id").references(() => familyGroups.id), stripeId: text("stripe_id").unique(), - tier: subscriptionTier("tier").default("basic").notNull(), - status: subscriptionStatus("status").default("active").notNull(), - currentPeriodStart: timestamp("current_period_start", { withTimezone: true, mode: "date" }).notNull(), - currentPeriodEnd: timestamp("current_period_end", { withTimezone: true, mode: "date" }).notNull(), - cancelAtPeriodEnd: boolean("cancel_at_period_end").default(false).notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updated_at", { withTimezone: true, mode: "date" }).defaultNow().notNull().$onUpdate(() => new Date()), + tier: text("tier").default("basic").notNull(), + status: text("status").default("active").notNull(), + currentPeriodStart: integer("current_period_start", { mode: "timestamp_ms" }).notNull(), + currentPeriodEnd: integer("current_period_end", { mode: "timestamp_ms" }).notNull(), + cancelAtPeriodEnd: integer("cancel_at_period_end", { mode: "boolean" }).default(false).notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ userIdIdx: index("subscriptions_user_id_idx").on(table.userId), familyGroupIdIdx: index("subscriptions_family_group_id_idx").on(table.familyGroupId), diff --git a/web/src/server/db/schema/voiceprint.ts b/web/src/server/db/schema/voiceprint.ts index 5ba2bfc..5b3f2d8 100644 --- a/web/src/server/db/schema/voiceprint.ts +++ b/web/src/server/db/schema/voiceprint.ts @@ -1,63 +1,62 @@ -import { pgTable, text, timestamp, index, uuid, boolean, jsonb, doublePrecision, integer } from "drizzle-orm/pg-core"; +import { sqliteTable, text, integer, real, index } from "drizzle-orm/sqlite-core"; import { users } from "./auth"; -import { detectionVerdict, analysisType, analysisJobStatus } from "./enums"; -export const voiceEnrollments = pgTable("voice_enrollments", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), +export const voiceEnrollments = sqliteTable("voice_enrollments", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), name: text("name").notNull(), voiceHash: text("voice_hash").notNull(), - audioMetadata: jsonb("audio_metadata"), - 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()), + audioMetadata: text("audio_metadata", { mode: "json" }), + isActive: integer("is_active", { mode: "boolean" }).default(true).notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }).defaultNow().notNull().$onUpdate(() => new Date()), }, (table) => ({ userIdIdx: index("voice_enrollments_user_id_idx").on(table.userId), voiceHashIdx: index("voice_enrollments_voice_hash_idx").on(table.voiceHash), })); -export const voiceAnalyses = pgTable("voice_analyses", { - id: uuid("id").defaultRandom().primaryKey(), - enrollmentId: uuid("enrollment_id").references(() => voiceEnrollments.id), - userId: uuid("user_id").notNull().references(() => users.id), +export const voiceAnalyses = sqliteTable("voice_analyses", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + enrollmentId: text("enrollment_id").references(() => voiceEnrollments.id), + userId: text("user_id").notNull().references(() => users.id), audioHash: text("audio_hash").notNull(), - isSynthetic: boolean("is_synthetic").notNull(), - confidence: doublePrecision("confidence").notNull(), - analysisResult: jsonb("analysis_result").notNull(), + isSynthetic: integer("is_synthetic", { mode: "boolean" }).notNull(), + confidence: real("confidence").notNull(), + analysisResult: text("analysis_result", { mode: "json" }).notNull(), audioUrl: text("audio_url").notNull(), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), }, (table) => ({ userIdIdx: index("voice_analyses_user_id_idx").on(table.userId), enrollmentIdIdx: index("voice_analyses_enrollment_id_idx").on(table.enrollmentId), audioHashIdx: index("voice_analyses_audio_hash_idx").on(table.audioHash), })); -export const analysisJobs = pgTable("analysis_jobs", { - id: uuid("id").defaultRandom().primaryKey(), - userId: uuid("user_id").notNull().references(() => users.id), - analysisType: analysisType("analysis_type").notNull(), +export const analysisJobs = sqliteTable("analysis_jobs", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text("user_id").notNull().references(() => users.id), + analysisType: text("analysis_type").notNull(), audioFilePath: text("audio_file_path").notNull(), - status: analysisJobStatus("status").notNull(), + status: text("status").notNull(), errorMessage: text("error_message"), - completedAt: timestamp("completed_at", { withTimezone: true, mode: "date" }), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), + completedAt: integer("completed_at", { mode: "timestamp_ms" }), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), }, (table) => ({ userIdIdx: index("analysis_jobs_user_id_idx").on(table.userId), statusIdx: index("analysis_jobs_status_idx").on(table.status), createdAtIdx: index("analysis_jobs_created_at_idx").on(table.createdAt), })); -export const analysisResults = pgTable("analysis_results", { - id: uuid("id").defaultRandom().primaryKey(), - analysisJobId: uuid("analysis_job_id").notNull().unique().references(() => analysisJobs.id), - syntheticScore: doublePrecision("synthetic_score").notNull(), - verdict: detectionVerdict("verdict").notNull(), - confidence: doublePrecision("confidence").notNull(), +export const analysisResults = sqliteTable("analysis_results", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + analysisJobId: text("analysis_job_id").notNull().unique().references(() => analysisJobs.id), + syntheticScore: real("synthetic_score").notNull(), + verdict: text("verdict").notNull(), + confidence: real("confidence").notNull(), processingTimeMs: integer("processing_time_ms").notNull(), matchedEnrollmentId: text("matched_enrollment_id"), - matchedSimilarity: doublePrecision("matched_similarity"), + matchedSimilarity: real("matched_similarity"), modelVersion: text("model_version"), - createdAt: timestamp("created_at", { withTimezone: true, mode: "date" }).defaultNow().notNull(), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), }, (table) => ({ analysisJobIdIdx: index("analysis_results_analysis_job_id_idx").on(table.analysisJobId), syntheticScoreIdx: index("analysis_results_synthetic_score_idx").on(table.syntheticScore), diff --git a/web/src/server/services/correlation/engine.ts b/web/src/server/services/correlation/engine.ts index cbee197..b573ca5 100644 --- a/web/src/server/services/correlation/engine.ts +++ b/web/src/server/services/correlation/engine.ts @@ -1,5 +1,4 @@ import { and, eq, gte, inArray, not, sql } from "drizzle-orm"; -import type { NodePgDatabase } from "drizzle-orm/node-postgres"; import { db } from "~/server/db"; import { normalizedAlerts, correlationGroups } from "~/server/db/schema"; import type { NormalizedAlertInput, EntitySet } from "./normalizer";