- Turborepo monorepo structure (packages: api, db, types, jobs; services: darkwatch) - Prisma schema: User, WatchListItem, Exposure, Alert, ScanJob models - WatchListService: CRUD with normalization, dedup, tier-based limits - HIBPService: API integration with severity scoring - MatchingEngine: exact-match with content hash dedup - AlertPipeline: dedup window, email notifications - ScanService: orchestrates watch list -> HIBP -> match -> alert flow - BullMQ job workers for scan and alert processing - Fastify API routes: watchlist, exposures, alerts, scan - Docker Compose: PostgreSQL 16 + Redis 7 - 15 unit tests passing - Implementation plan document uploaded
141 lines
3.0 KiB
Plaintext
141 lines
3.0 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
enum SubscriptionTier {
|
|
BASIC
|
|
PLUS
|
|
PREMIUM
|
|
}
|
|
|
|
enum IdentifierType {
|
|
EMAIL
|
|
PHONE
|
|
SSN
|
|
}
|
|
|
|
enum WatchListStatus {
|
|
ACTIVE
|
|
PAUSED
|
|
}
|
|
|
|
enum Severity {
|
|
INFO
|
|
WARNING
|
|
CRITICAL
|
|
}
|
|
|
|
enum AlertChannel {
|
|
EMAIL
|
|
PUSH
|
|
SMS
|
|
}
|
|
|
|
enum AlertStatus {
|
|
PENDING
|
|
SENT
|
|
READ
|
|
}
|
|
|
|
enum ScanJobStatus {
|
|
PENDING
|
|
RUNNING
|
|
COMPLETED
|
|
FAILED
|
|
}
|
|
|
|
enum DataSource {
|
|
HIBP
|
|
SECURITY_TRAILS
|
|
CENSYS
|
|
SHODAN
|
|
HONEYPOT
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
name String?
|
|
subscriptionTier SubscriptionTier @default(BASIC)
|
|
familyGroupId String?
|
|
watchListItems WatchListItem[]
|
|
alerts Alert[]
|
|
scanJobs ScanJob[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([email])
|
|
}
|
|
|
|
model WatchListItem {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
identifierType IdentifierType
|
|
identifierValue String
|
|
identifierHash String @unique
|
|
status WatchListStatus @default(ACTIVE)
|
|
exposures Exposure[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([identifierHash])
|
|
}
|
|
|
|
model Exposure {
|
|
id String @id @default(uuid())
|
|
watchListItemId String
|
|
watchListItem WatchListItem @relation(fields: [watchListItemId], references: [id], onDelete: Cascade)
|
|
dataSource DataSource
|
|
breachName String
|
|
exposedAt DateTime
|
|
dataType String[]
|
|
severity Severity
|
|
details String?
|
|
contentHash String @unique
|
|
alert Alert?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([watchListItemId])
|
|
@@index([contentHash])
|
|
@@index([dataSource])
|
|
}
|
|
|
|
model Alert {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
exposureId String @unique
|
|
exposure Exposure @relation(fields: [exposureId], references: [id], onDelete: Cascade)
|
|
severity Severity
|
|
channel AlertChannel
|
|
status AlertStatus @default(PENDING)
|
|
dedupKey String
|
|
sentAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId, status])
|
|
@@index([dedupKey])
|
|
}
|
|
|
|
model ScanJob {
|
|
id String @id @default(uuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
status ScanJobStatus @default(PENDING)
|
|
source DataSource?
|
|
resultCount Int @default(0)
|
|
errorMessage String?
|
|
completedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId, status])
|
|
@@index([createdAt])
|
|
}
|