db rearch

This commit is contained in:
2026-05-25 20:50:45 -04:00
parent 3ccaeaa2e3
commit 89822dedb8
19 changed files with 339 additions and 324 deletions

View File

@@ -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`

View File

@@ -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();
});
});

View File

@@ -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();
});

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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];

View File

@@ -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),

View File

@@ -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()),
});

View File

@@ -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),

View File

@@ -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()),
});

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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),

View File

@@ -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";