P0 fixes: - Add CANCELLED status to RemovalStatus enum (types + Prisma schema) - Use CANCELLED instead of REJECTED for user-initiated cancellations - Add null guard for req.broker?.name in GET /request/:id - Remove unsafe 'as any' casts in RemoveBrokersService.ts - Add type-safe toPersonalInfo() validator for JSON deserialization - Type RemovalRequestWithBroker properly in getRemovalStatus() - Fix alert: any to NormalizedAlertInput in BrokerAlertPipeline P1 fixes: - Fix admin role check: remove non-existent 'admin', only check 'support' - Fix BrokerDefinition.category type from string to BrokerCategory - Add complete OpenAPI spec for all removebrokers routes and schemas
919 lines
22 KiB
Plaintext
919 lines
22 KiB
Plaintext
// Prisma schema for ShieldAI
|
|
// All models for the multi-service SaaS platform
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// ============================================
|
|
// User & Authentication Models
|
|
// ============================================
|
|
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
emailVerified DateTime?
|
|
name String?
|
|
image String?
|
|
role UserRole @default(user)
|
|
|
|
// Relationships
|
|
accounts Account[]
|
|
sessions Session[]
|
|
deviceTokens DeviceToken[]
|
|
familyGroups FamilyGroupMember[]
|
|
familyGroupOwned FamilyGroup[] @relation("FamilyGroupOwner")
|
|
subscriptions Subscription[]
|
|
alerts Alert[]
|
|
voiceEnrollments VoiceEnrollment[]
|
|
voiceAnalyses VoiceAnalysis[]
|
|
spamFeedback SpamFeedback[]
|
|
spamRules SpamRule[]
|
|
normalizedAlerts NormalizedAlert[]
|
|
correlationGroups CorrelationGroup[]
|
|
securityReports SecurityReport[]
|
|
analysisJobs AnalysisJob[]
|
|
|
|
// Audit
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([email])
|
|
@@index([role])
|
|
}
|
|
|
|
enum UserRole {
|
|
user
|
|
family_admin
|
|
family_member
|
|
support
|
|
}
|
|
|
|
enum DetectionVerdict {
|
|
NATURAL
|
|
SYNTHETIC
|
|
UNCERTAIN
|
|
}
|
|
|
|
enum AnalysisType {
|
|
SYNTHETIC_DETECTION
|
|
VOICE_MATCH
|
|
BATCH
|
|
}
|
|
|
|
enum AnalysisJobStatus {
|
|
PENDING
|
|
RUNNING
|
|
COMPLETED
|
|
FAILED
|
|
}
|
|
|
|
model Account {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
provider String
|
|
providerAccountId String
|
|
access_token String?
|
|
refresh_token String?
|
|
expires_at Int?
|
|
token_type String?
|
|
scope String?
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([userId, provider, providerAccountId])
|
|
@@index([userId])
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
sessionToken String @unique
|
|
expires DateTime
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([sessionToken])
|
|
@@index([userId])
|
|
}
|
|
|
|
model DeviceToken {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
deviceType DeviceType
|
|
token String @unique
|
|
platform Platform
|
|
appName String?
|
|
appVersion String?
|
|
osVersion String?
|
|
model String?
|
|
isActive Boolean @default(true)
|
|
lastUsedAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([deviceType])
|
|
@@index([platform])
|
|
@@index([isActive])
|
|
}
|
|
|
|
enum DeviceType {
|
|
mobile
|
|
web
|
|
desktop
|
|
}
|
|
|
|
enum Platform {
|
|
ios
|
|
android
|
|
web
|
|
}
|
|
|
|
// ============================================
|
|
// Family & Subscription Models
|
|
// ============================================
|
|
|
|
model FamilyGroup {
|
|
id String @id @default(uuid())
|
|
name String
|
|
ownerId String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
owner User @relation("FamilyGroupOwner", fields: [ownerId], references: [id])
|
|
members FamilyGroupMember[]
|
|
subscriptions Subscription[]
|
|
|
|
@@index([ownerId])
|
|
@@index([name])
|
|
}
|
|
|
|
model FamilyGroupMember {
|
|
id String @id @default(uuid())
|
|
groupId String
|
|
userId String
|
|
role FamilyMemberRole @default(member)
|
|
joinedAt DateTime @default(now())
|
|
|
|
group FamilyGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([groupId, userId])
|
|
@@index([groupId])
|
|
@@index([userId])
|
|
}
|
|
|
|
enum FamilyMemberRole {
|
|
owner
|
|
admin
|
|
member
|
|
}
|
|
|
|
model Subscription {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
familyGroupId String?
|
|
stripeId String? @unique
|
|
tier SubscriptionTier @default(basic)
|
|
status SubscriptionStatus @default(active)
|
|
currentPeriodStart DateTime
|
|
currentPeriodEnd DateTime
|
|
cancelAtPeriodEnd Boolean @default(false)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
familyGroup FamilyGroup? @relation(fields: [familyGroupId], references: [id])
|
|
|
|
watchlistItems WatchlistItem[]
|
|
exposures Exposure[]
|
|
alerts Alert[]
|
|
propertyWatchlistItems PropertyWatchlistItem[]
|
|
removalRequests RemovalRequest[]
|
|
brokerListings BrokerListing[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([familyGroupId])
|
|
@@index([stripeId])
|
|
@@index([tier])
|
|
}
|
|
|
|
enum SubscriptionTier {
|
|
basic
|
|
plus
|
|
premium
|
|
}
|
|
|
|
enum SubscriptionStatus {
|
|
active
|
|
past_due
|
|
canceled
|
|
unpaid
|
|
trialing
|
|
}
|
|
|
|
// ============================================
|
|
// DarkWatch Models (Dark Web Monitoring)
|
|
// ============================================
|
|
|
|
model WatchlistItem {
|
|
id String @id @default(uuid())
|
|
subscriptionId String
|
|
type WatchlistType
|
|
value String
|
|
hash String // SHA-256 hash for deduplication
|
|
isActive Boolean @default(true)
|
|
|
|
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
exposures Exposure[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([subscriptionId, type, hash])
|
|
@@index([subscriptionId])
|
|
@@index([type])
|
|
@@index([hash])
|
|
}
|
|
|
|
enum WatchlistType {
|
|
email
|
|
phoneNumber
|
|
ssn
|
|
address
|
|
domain
|
|
}
|
|
|
|
model Exposure {
|
|
id String @id @default(uuid())
|
|
subscriptionId String
|
|
watchlistItemId String?
|
|
source ExposureSource
|
|
dataType WatchlistType
|
|
identifier String
|
|
identifierHash String
|
|
severity ExposureSeverity @default(info)
|
|
metadata Json? // Additional source-specific data
|
|
isFirstTime Boolean @default(false)
|
|
|
|
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
watchlistItem WatchlistItem? @relation(fields: [watchlistItemId], references: [id])
|
|
alerts Alert[]
|
|
|
|
detectedAt DateTime
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([subscriptionId])
|
|
@@index([watchlistItemId])
|
|
@@index([source])
|
|
@@index([severity])
|
|
@@index([detectedAt])
|
|
}
|
|
|
|
enum ExposureSource {
|
|
hibp // Have I Been Pwned
|
|
securityTrails
|
|
censys
|
|
darkWebForum
|
|
shodan
|
|
honeypot
|
|
}
|
|
|
|
enum ExposureSeverity {
|
|
info
|
|
warning
|
|
critical
|
|
}
|
|
|
|
// ============================================
|
|
// Notification & Alert Models
|
|
// ============================================
|
|
|
|
model Alert {
|
|
id String @id @default(uuid())
|
|
subscriptionId String
|
|
userId String
|
|
exposureId String?
|
|
type AlertType
|
|
title String
|
|
message String
|
|
severity AlertSeverity @default(info)
|
|
isRead Boolean @default(false)
|
|
readAt DateTime?
|
|
channel AlertChannel[] // Array of notification channels
|
|
|
|
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
exposure Exposure? @relation(fields: [exposureId], references: [id])
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([subscriptionId])
|
|
@@index([userId])
|
|
@@index([isRead])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
enum AlertType {
|
|
exposure_detected
|
|
exposure_resolved
|
|
scan_complete
|
|
subscription_changed
|
|
system_warning
|
|
}
|
|
|
|
enum AlertSeverity {
|
|
info
|
|
warning
|
|
critical
|
|
}
|
|
|
|
enum AlertChannel {
|
|
email
|
|
push
|
|
sms
|
|
}
|
|
|
|
// ============================================
|
|
// VoicePrint Models (Voice Cloning Detection)
|
|
// ============================================
|
|
|
|
model VoiceEnrollment {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
name String
|
|
voiceHash String // FAISS embedding hash
|
|
audioMetadata Json? // Sample rate, duration, etc.
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
analyses VoiceAnalysis[]
|
|
|
|
isActive Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([voiceHash])
|
|
}
|
|
|
|
model VoiceAnalysis {
|
|
id String @id @default(uuid())
|
|
enrollmentId String?
|
|
userId String
|
|
audioHash String // Content hash of audio file
|
|
isSynthetic Boolean
|
|
confidence Float // 0.0 to 1.0
|
|
analysisResult Json // Full ML analysis results
|
|
audioUrl String // S3 storage URL
|
|
|
|
enrollment VoiceEnrollment? @relation(fields: [enrollmentId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
@@index([enrollmentId])
|
|
@@index([audioHash])
|
|
}
|
|
|
|
model AnalysisJob {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
analysisType AnalysisType
|
|
audioFilePath String
|
|
status AnalysisJobStatus
|
|
errorMessage String?
|
|
completedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id])
|
|
result AnalysisResult?
|
|
|
|
@@index([userId])
|
|
@@index([status])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model AnalysisResult {
|
|
id String @id @default(uuid())
|
|
analysisJobId String @unique
|
|
syntheticScore Float
|
|
verdict DetectionVerdict
|
|
confidence Float
|
|
processingTimeMs Int
|
|
matchedEnrollmentId String?
|
|
matchedSimilarity Float?
|
|
modelVersion String?
|
|
|
|
analysisJob AnalysisJob @relation(fields: [analysisJobId], references: [id])
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([analysisJobId])
|
|
@@index([syntheticScore])
|
|
@@index([verdict])
|
|
}
|
|
|
|
// ============================================
|
|
// SpamShield Models (Spam Detection)
|
|
// ============================================
|
|
|
|
model SpamFeedback {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
phoneNumber String
|
|
phoneNumberHash String // SHA-256 hash
|
|
isSpam Boolean
|
|
confidence Float? // ML model confidence
|
|
feedbackType FeedbackType
|
|
metadata Json? // Call duration, time, etc.
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([phoneNumberHash])
|
|
@@index([isSpam])
|
|
}
|
|
|
|
enum FeedbackType {
|
|
initial_detection
|
|
user_confirmation
|
|
user_rejection
|
|
auto_learned
|
|
}
|
|
|
|
model SpamRule {
|
|
id String @id @default(uuid())
|
|
userId String?
|
|
isGlobal Boolean @default(false)
|
|
ruleType RuleType
|
|
pattern String
|
|
action RuleAction
|
|
priority Int @default(0)
|
|
isActive Boolean @default(true)
|
|
|
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([isGlobal])
|
|
@@index([ruleType])
|
|
}
|
|
|
|
enum RuleType {
|
|
phoneNumber
|
|
areaCode
|
|
prefix
|
|
pattern
|
|
reputation
|
|
}
|
|
|
|
enum RuleAction {
|
|
block
|
|
flag
|
|
allow
|
|
challenge
|
|
}
|
|
|
|
// ============================================
|
|
// Audit & Analytics Models
|
|
// ============================================
|
|
|
|
model AuditLog {
|
|
id String @id @default(uuid())
|
|
userId String?
|
|
action String
|
|
resource String
|
|
resourceId String?
|
|
changes Json? // Before/after values
|
|
metadata Json?
|
|
ipAddress String?
|
|
userAgent String?
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
@@index([action])
|
|
@@index([resource])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model KPISnapshot {
|
|
id String @id @default(uuid())
|
|
date DateTime @unique
|
|
metricName String
|
|
metricValue Float
|
|
metadata Json?
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([metricName])
|
|
@@index([date])
|
|
}
|
|
|
|
// ============================================
|
|
// Cross-Service Alert Correlation Models
|
|
// ============================================
|
|
|
|
enum AlertSource {
|
|
DARKWATCH
|
|
SPAMSHIELD
|
|
VOICEPRINT
|
|
CALL_ANALYSIS
|
|
HOME_TITLE
|
|
INFO_BROKER
|
|
}
|
|
|
|
enum AlertCategory {
|
|
BREACH_EXPOSURE
|
|
SPAM_CALL
|
|
SPAM_SMS
|
|
SYNTHETIC_VOICE
|
|
VOICE_MISMATCH
|
|
CALL_ANOMALY
|
|
CALL_QUALITY
|
|
CALL_EVENT
|
|
HOME_TITLE
|
|
INFO_BROKER_LISTING
|
|
INFO_BROKER_REMOVAL
|
|
}
|
|
|
|
enum NormalizedAlertSeverity {
|
|
LOW
|
|
INFO
|
|
MEDIUM
|
|
WARNING
|
|
HIGH
|
|
CRITICAL
|
|
}
|
|
|
|
enum CorrelationStatus {
|
|
ACTIVE
|
|
RESOLVED
|
|
FALSE_POSITIVE
|
|
}
|
|
|
|
model NormalizedAlert {
|
|
id String @id @default(uuid())
|
|
source AlertSource
|
|
category AlertCategory
|
|
severity NormalizedAlertSeverity
|
|
userId String
|
|
title String
|
|
description String
|
|
entities Json
|
|
sourceAlertId String
|
|
groupId String?
|
|
payload Json?
|
|
createdAt DateTime
|
|
updatedAt DateTime @default(now()) @updatedAt
|
|
|
|
correlationGroup CorrelationGroup? @relation(fields: [groupId], references: [id])
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([sourceAlertId])
|
|
@@index([userId])
|
|
@@index([groupId])
|
|
@@index([source])
|
|
@@index([severity])
|
|
@@index([createdAt])
|
|
@@index([userId, createdAt])
|
|
}
|
|
|
|
model CorrelationGroup {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
entities Json
|
|
highestSeverity NormalizedAlertSeverity
|
|
status CorrelationStatus @default(ACTIVE)
|
|
alertCount Int @default(0)
|
|
summary String?
|
|
resolvedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @default(now()) @updatedAt
|
|
|
|
alerts NormalizedAlert[]
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([status])
|
|
@@index([userId, status])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
// ============================================
|
|
// Report Generation Models
|
|
// ============================================
|
|
|
|
enum ReportType {
|
|
MONTHLY_PLUS
|
|
ANNUAL_PREMIUM
|
|
WEEKLY_DIGEST
|
|
}
|
|
|
|
enum ReportStatus {
|
|
PENDING
|
|
GENERATING
|
|
COMPLETED
|
|
FAILED
|
|
DELIVERED
|
|
}
|
|
|
|
model SecurityReport {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
subscriptionId String
|
|
reportType ReportType
|
|
status ReportStatus @default(PENDING)
|
|
periodStart DateTime
|
|
periodEnd DateTime
|
|
title String
|
|
summary String?
|
|
htmlContent String?
|
|
pdfUrl String?
|
|
dataPayload Json?
|
|
error String?
|
|
scheduledFor DateTime?
|
|
deliveredAt DateTime?
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @default(now()) @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([subscriptionId])
|
|
@@index([reportType])
|
|
@@index([status])
|
|
@@index([periodStart, periodEnd])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
// ============================================
|
|
// Waitlist & Marketing Models
|
|
// ============================================
|
|
|
|
model WaitlistEntry {
|
|
id String @id @default(uuid())
|
|
email String
|
|
name String?
|
|
source String? // landing_page, blog, referral, social, paid_search
|
|
tier SubscriptionTier? // interest level
|
|
utmSource String?
|
|
utmMedium String?
|
|
utmCampaign String?
|
|
metadata Json? // Browser, device, location, etc.
|
|
|
|
// Conversion tracking
|
|
convertedAt DateTime?
|
|
convertedToUserId String?
|
|
convertedToSubscriptionId String?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([email])
|
|
@@index([source])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model BlogPost {
|
|
id String @id @default(uuid())
|
|
slug String @unique
|
|
title String
|
|
excerpt String?
|
|
content String
|
|
authorName String?
|
|
coverImageUrl String?
|
|
tags String[] // Array of tag strings
|
|
published Boolean @default(false)
|
|
publishedAt DateTime?
|
|
viewCount Int @default(0)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([slug])
|
|
@@index([published, publishedAt])
|
|
@@index([tags])
|
|
}
|
|
|
|
// ============================================
|
|
// Home Title Service Models
|
|
// ============================================
|
|
|
|
enum PropertyChangeType {
|
|
tax_change
|
|
deed_change
|
|
ownership_transfer
|
|
lien_filing
|
|
metadata_change
|
|
}
|
|
|
|
enum PropertyChangeSeverity {
|
|
info
|
|
warning
|
|
critical
|
|
}
|
|
|
|
model PropertyWatchlistItem {
|
|
id String @id @default(uuid())
|
|
subscriptionId String
|
|
address String
|
|
parcelId String?
|
|
ownerName String?
|
|
streetAddress String
|
|
city String? @default("")
|
|
state String? @default("")
|
|
zipCode String? @default("")
|
|
latitude Float?
|
|
longitude Float?
|
|
isActive Boolean @default(true)
|
|
|
|
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
snapshots PropertySnapshot[]
|
|
changes PropertyChange[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([subscriptionId, parcelId])
|
|
@@index([subscriptionId])
|
|
@@index([parcelId])
|
|
@@index([address])
|
|
}
|
|
|
|
model PropertySnapshot {
|
|
id String @id @default(uuid())
|
|
propertyWatchlistItemId String
|
|
subscriptionId String
|
|
capturedAt DateTime
|
|
ownerName String
|
|
address Json // { streetNumber, streetName, streetType, unit, city, state, zip, latitude?, longitude? }
|
|
deedDate String?
|
|
taxId String?
|
|
propertyType String @default("residential")
|
|
taxAmount Float?
|
|
lienCount Int @default(0)
|
|
|
|
propertyWatchlistItem PropertyWatchlistItem @relation(fields: [propertyWatchlistItemId], references: [id], onDelete: Cascade)
|
|
changes PropertyChange[] @relation("SnapshotChanges")
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([propertyWatchlistItemId])
|
|
@@index([subscriptionId])
|
|
@@index([capturedAt])
|
|
}
|
|
|
|
model PropertyChange {
|
|
id String @id @default(uuid())
|
|
propertyWatchlistItemId String
|
|
snapshotId String?
|
|
changeType PropertyChangeType
|
|
severity PropertyChangeSeverity @default(info)
|
|
details Json?
|
|
detectedAt DateTime @default(now())
|
|
|
|
propertyWatchlistItem PropertyWatchlistItem @relation(fields: [propertyWatchlistItemId], references: [id], onDelete: Cascade)
|
|
snapshot PropertySnapshot? @relation("SnapshotChanges", fields: [snapshotId], references: [id])
|
|
|
|
@@index([propertyWatchlistItemId])
|
|
@@index([snapshotId])
|
|
@@index([changeType])
|
|
}
|
|
|
|
// ============================================
|
|
// Info Broker Removal Models
|
|
// ============================================
|
|
|
|
enum BrokerCategory {
|
|
PEOPLE_SEARCH
|
|
BACKGROUND_CHECK
|
|
PUBLIC_RECORDS
|
|
REVERSE_LOOKUP
|
|
SOCIAL_MEDIA
|
|
}
|
|
|
|
enum RemovalMethod {
|
|
AUTOMATED
|
|
MANUAL_FORM
|
|
EMAIL
|
|
PHONE
|
|
MAIL
|
|
NONE
|
|
}
|
|
|
|
enum RemovalStatus {
|
|
PENDING
|
|
SUBMITTED
|
|
IN_PROGRESS
|
|
COMPLETED
|
|
FAILED
|
|
REJECTED
|
|
CANCELLED
|
|
}
|
|
|
|
model InfoBroker {
|
|
id String @id @default(uuid())
|
|
name String
|
|
domain String @unique
|
|
category BrokerCategory
|
|
removalMethod RemovalMethod
|
|
removalUrl String?
|
|
requiresAccount Boolean @default(false)
|
|
requiresVerification Boolean @default(false)
|
|
estimatedDays Int @default(14)
|
|
isActive Boolean @default(true)
|
|
|
|
removalRequests RemovalRequest[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @default(now()) @updatedAt
|
|
|
|
@@index([category])
|
|
@@index([isActive])
|
|
@@index([removalMethod])
|
|
}
|
|
|
|
model RemovalRequest {
|
|
id String @id @default(uuid())
|
|
subscriptionId String
|
|
brokerId String
|
|
status RemovalStatus @default(PENDING)
|
|
personalInfo Json // { fullName, email?, phone?, address?, dob? }
|
|
method RemovalMethod
|
|
attempts Int @default(0)
|
|
nextRetryAt DateTime?
|
|
submittedAt DateTime?
|
|
completedAt DateTime?
|
|
error String?
|
|
notes String?
|
|
metadata Json? // Broker response data, tracking info
|
|
|
|
broker InfoBroker @relation(fields: [brokerId], references: [id])
|
|
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
brokerListings BrokerListing[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @default(now()) @updatedAt
|
|
|
|
@@index([subscriptionId])
|
|
@@index([brokerId])
|
|
@@index([status])
|
|
@@index([submittedAt])
|
|
@@index([subscriptionId, status])
|
|
}
|
|
|
|
model BrokerListing {
|
|
id String @id @default(uuid())
|
|
subscriptionId String
|
|
brokerId String
|
|
removalRequestId String?
|
|
url String
|
|
dataFound Json // Fields found on the listing
|
|
screenshotUrl String?
|
|
isRemoved Boolean @default(false)
|
|
removedAt DateTime?
|
|
|
|
removalRequest RemovalRequest? @relation(fields: [removalRequestId], references: [id])
|
|
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
|
|
scannedAt DateTime @default(now())
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @default(now()) @updatedAt
|
|
|
|
@@index([subscriptionId])
|
|
@@index([brokerId])
|
|
@@index([removalRequestId])
|
|
@@index([isRemoved])
|
|
@@index([subscriptionId, isRemoved])
|
|
}
|