Consolidate @shieldai/db and @shieldsai/shared-db packages (FRE-4603)
- Merged singleton pattern + type exports from shared-db - Kept FieldEncryptionService from original db package - Upgraded to Prisma v6.2.0 (newer version) - Adopted shared-db's complete schema for multi-service platform - Updated 17 consumer imports across darkwatch, voiceprint, jobs, api - Standardized on @shieldai/db namespace Files changed: - packages/db/package.json (v0.1.0 → v0.2.0) - packages/db/src/index.ts (consolidated exports) - packages/db/prisma/schema.prisma (merged schema) - packages/db/prisma/seed.ts (updated for new schema) - 17 consumer files updated Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|||||||
import { SMSClassifierService } from '../services/spamshield/spamshield.service';
|
import { SMSClassifierService } from '../services/spamshield/spamshield.service';
|
||||||
|
|
||||||
// Mock shared-db before anything else (Prisma client is not generated in test env)
|
// Mock shared-db before anything else (Prisma client is not generated in test env)
|
||||||
vi.mock('@shieldsai/shared-db', () => ({
|
vi.mock('@shieldai/db', () => ({
|
||||||
prisma: {},
|
prisma: {},
|
||||||
SpamFeedback: {},
|
SpamFeedback: {},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
||||||
import { prisma, SubscriptionTier } from '@shieldsai/shared-db';
|
import { prisma, SubscriptionTier } from '@shieldai/db';
|
||||||
import { tierConfig, SubscriptionTier as BillingTier } from '@shieldsai/shared-billing';
|
import { tierConfig, SubscriptionTier as BillingTier } from '@shieldsai/shared-billing';
|
||||||
import {
|
import {
|
||||||
watchlistService,
|
watchlistService,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, AlertType, AlertSeverity } from '@shieldsai/shared-db';
|
import { prisma, AlertType, AlertSeverity } from '@shieldai/db';
|
||||||
import {
|
import {
|
||||||
NotificationService,
|
NotificationService,
|
||||||
NotificationPriority,
|
NotificationPriority,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, ExposureSource, ExposureSeverity, WatchlistType } from '@shieldsai/shared-db';
|
import { prisma, ExposureSource, ExposureSeverity, WatchlistType } from '@shieldai/db';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
function hashIdentifier(identifier: string): string {
|
function hashIdentifier(identifier: string): string {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, SubscriptionTier, SubscriptionStatus } from '@shieldsai/shared-db';
|
import { prisma, SubscriptionTier, SubscriptionStatus } from '@shieldai/db';
|
||||||
import { tierConfig } from '@shieldsai/shared-billing';
|
import { tierConfig } from '@shieldsai/shared-billing';
|
||||||
import { darkwatchScanQueue } from '@shieldsai/jobs';
|
import { darkwatchScanQueue } from '@shieldsai/jobs';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, WatchlistType } from '@shieldsai/shared-db';
|
import { prisma, WatchlistType } from '@shieldai/db';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
export function normalizeValue(type: WatchlistType, value: string): string {
|
export function normalizeValue(type: WatchlistType, value: string): string {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, ExposureSource, ExposureSeverity, WatchlistType, AlertType, AlertSeverity } from '@shieldsai/shared-db';
|
import { prisma, ExposureSource, ExposureSeverity, WatchlistType, AlertType, AlertSeverity } from '@shieldai/db';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { mixpanelService, EventType } from '@shieldsai/shared-analytics';
|
import { mixpanelService, EventType } from '@shieldsai/shared-analytics';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, SpamFeedback } from '@shieldsai/shared-db';
|
import { prisma, SpamFeedback } from '@shieldai/db';
|
||||||
import { spamShieldEnv, SpamDecision, spamFeatureFlags, defaultScores, metadataLimits } from './spamshield.config';
|
import { spamShieldEnv, SpamDecision, spamFeatureFlags, defaultScores, metadataLimits } from './spamshield.config';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { spamAuditLogger, hashPhoneNumber } from './spamshield.audit-logger';
|
import { spamAuditLogger, hashPhoneNumber } from './spamshield.audit-logger';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, VoiceEnrollment, VoiceAnalysis } from '@shieldsai/shared-db';
|
import { prisma, VoiceEnrollment, VoiceAnalysis } from '@shieldai/db';
|
||||||
import {
|
import {
|
||||||
voicePrintEnv,
|
voicePrintEnv,
|
||||||
AnalysisJobStatus,
|
AnalysisJobStatus,
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
{
|
{
|
||||||
"name": "@shieldai/db",
|
"name": "@shieldai/db",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"main": "./dist/index.js",
|
"type": "module",
|
||||||
"types": "./dist/index.js",
|
"main": "./src/index.ts",
|
||||||
|
"types": "./src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "prisma generate && tsc",
|
"build": "prisma generate && tsc",
|
||||||
"db:migrate": "prisma migrate dev",
|
"db:migrate": "prisma migrate dev",
|
||||||
"db:seed": "tsx prisma/seed.ts",
|
"db:seed": "tsx prisma/seed.ts",
|
||||||
"db:studio": "prisma studio",
|
"db:studio": "prisma studio",
|
||||||
|
"db:push": "prisma db push",
|
||||||
|
"db:format": "prisma format",
|
||||||
"generate": "prisma generate"
|
"generate": "prisma generate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.2.0",
|
"@prisma/client": "^6.2.0",
|
||||||
"prisma": "^6.2.0"
|
"prisma": "^6.2.0",
|
||||||
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tsx": "^4.19.0"
|
"tsx": "^4.19.0",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Prisma schema for ShieldAI
|
||||||
|
// All models for the multi-service SaaS platform
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
}
|
}
|
||||||
@@ -7,406 +10,428 @@ datasource db {
|
|||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SubscriptionTier {
|
// ============================================
|
||||||
BASIC
|
// User & Authentication Models
|
||||||
PLUS
|
// ============================================
|
||||||
PREMIUM
|
|
||||||
}
|
|
||||||
|
|
||||||
enum IdentifierType {
|
|
||||||
EMAIL
|
|
||||||
PHONE
|
|
||||||
SSN
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WatchListStatus {
|
|
||||||
ACTIVE
|
|
||||||
PAUSED
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Severity {
|
|
||||||
LOW
|
|
||||||
INFO
|
|
||||||
MEDIUM
|
|
||||||
WARNING
|
|
||||||
HIGH
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AnalysisJobStatus {
|
|
||||||
PENDING
|
|
||||||
RUNNING
|
|
||||||
COMPLETED
|
|
||||||
FAILED
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AnalysisType {
|
|
||||||
SYNTHETIC_DETECTION
|
|
||||||
VOICE_MATCH
|
|
||||||
BATCH
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DetectionVerdict {
|
|
||||||
NATURAL
|
|
||||||
SYNTHETIC
|
|
||||||
UNCERTAIN
|
|
||||||
}
|
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
email String @unique
|
email String @unique
|
||||||
name String?
|
emailVerified DateTime?
|
||||||
subscriptionTier SubscriptionTier @default(BASIC)
|
name String?
|
||||||
familyGroupId String?
|
image String?
|
||||||
watchListItems WatchListItem[]
|
role UserRole @default(user)
|
||||||
alerts Alert[]
|
|
||||||
scanJobs ScanJob[]
|
// Relationships
|
||||||
scanSchedules ScanSchedule[]
|
accounts Account[]
|
||||||
|
sessions Session[]
|
||||||
|
familyGroups FamilyGroupMember[]
|
||||||
|
familyGroupOwned FamilyGroup[] @relation("FamilyGroupOwner")
|
||||||
|
subscriptions Subscription[]
|
||||||
|
alerts Alert[]
|
||||||
voiceEnrollments VoiceEnrollment[]
|
voiceEnrollments VoiceEnrollment[]
|
||||||
analysisJobs AnalysisJob[]
|
voiceAnalyses VoiceAnalysis[]
|
||||||
spamFeedback SpamFeedback[]
|
spamFeedback SpamFeedback[]
|
||||||
spamCallAnalyses SpamCallAnalysis[]
|
spamRules SpamRule[]
|
||||||
spamAuditLogs SpamAuditLog[]
|
|
||||||
normalizedAlerts NormalizedAlert[]
|
// Audit
|
||||||
correlationGroups CorrelationGroup[]
|
createdAt DateTime @default(now())
|
||||||
createdAt DateTime @default(now())
|
updatedAt DateTime @updatedAt
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@index([email])
|
@@index([email])
|
||||||
|
@@index([role])
|
||||||
}
|
}
|
||||||
|
|
||||||
model WatchListItem {
|
enum UserRole {
|
||||||
id String @id @default(uuid())
|
user
|
||||||
|
family_admin
|
||||||
|
family_member
|
||||||
|
support
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 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
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
familyGroupId String?
|
||||||
identifierType IdentifierType
|
stripeId String? @unique
|
||||||
identifierValue String
|
tier SubscriptionTier @default(basic)
|
||||||
identifierHash String @unique
|
status SubscriptionStatus @default(active)
|
||||||
status WatchListStatus @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[]
|
exposures Exposure[]
|
||||||
createdAt DateTime @default(now())
|
alerts Alert[]
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([identifierHash])
|
@@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 {
|
model Exposure {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
watchListItemId String
|
subscriptionId String
|
||||||
watchListItem WatchListItem @relation(fields: [watchListItemId], references: [id], onDelete: Cascade)
|
watchlistItemId String?
|
||||||
dataSource DataSource
|
source ExposureSource
|
||||||
breachName String
|
dataType WatchlistType
|
||||||
exposedAt DateTime
|
identifier String
|
||||||
dataType String[]
|
identifierHash String
|
||||||
severity Severity
|
severity ExposureSeverity @default(info)
|
||||||
details String?
|
metadata Json? // Additional source-specific data
|
||||||
contentHash String @unique
|
isFirstTime Boolean @default(false)
|
||||||
alert Alert?
|
|
||||||
|
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
||||||
|
watchlistItem WatchlistItem? @relation(fields: [watchlistItemId], references: [id])
|
||||||
|
alerts Alert[]
|
||||||
|
|
||||||
|
detectedAt DateTime
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@index([watchListItemId])
|
@@index([subscriptionId])
|
||||||
@@index([contentHash])
|
@@index([watchlistItemId])
|
||||||
@@index([dataSource])
|
@@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 {
|
model Alert {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String
|
subscriptionId 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?
|
|
||||||
scheduledBy String?
|
|
||||||
webhookEvents WebhookEvent[]
|
|
||||||
completedAt DateTime?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
|
||||||
@@index([userId, status])
|
|
||||||
@@index([createdAt])
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ScheduleStatus {
|
|
||||||
ACTIVE
|
|
||||||
PAUSED
|
|
||||||
}
|
|
||||||
|
|
||||||
model ScanSchedule {
|
|
||||||
id String @id @default(uuid())
|
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
exposureId String?
|
||||||
intervalMinutes Int // minutes between scans
|
type AlertType
|
||||||
cronExpression String // cron expression for scheduling
|
title String
|
||||||
status ScheduleStatus @default(ACTIVE)
|
message String
|
||||||
lastScanAt DateTime?
|
severity AlertSeverity @default(info)
|
||||||
nextScanAt DateTime?
|
isRead Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
readAt DateTime?
|
||||||
updatedAt DateTime @updatedAt
|
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
|
||||||
|
|
||||||
@@unique([userId])
|
@@index([subscriptionId])
|
||||||
@@index([status])
|
@@index([userId])
|
||||||
}
|
@@index([isRead])
|
||||||
|
|
||||||
enum WebhookEventType {
|
|
||||||
SCAN_TRIGGER
|
|
||||||
BREACH_DETECTED
|
|
||||||
SUBSCRIPTION_CHANGE
|
|
||||||
}
|
|
||||||
|
|
||||||
model WebhookEvent {
|
|
||||||
id String @id @default(uuid())
|
|
||||||
eventType WebhookEventType
|
|
||||||
payload String
|
|
||||||
source String?
|
|
||||||
signature String?
|
|
||||||
processed Boolean @default(false)
|
|
||||||
processedAt DateTime?
|
|
||||||
scanJobId String?
|
|
||||||
scanJob ScanJob? @relation(fields: [scanJobId], references: [id], onDelete: SetNull)
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
|
||||||
@@index([eventType, processed])
|
|
||||||
@@index([createdAt])
|
@@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 {
|
model VoiceEnrollment {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
name String
|
||||||
label String
|
voiceHash String // FAISS embedding hash
|
||||||
embeddingVector Float[]
|
audioMetadata Json? // Sample rate, duration, etc.
|
||||||
embeddingDim Int @default(192)
|
|
||||||
audioFilePath String?
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
sampleRate Int @default(16000)
|
analyses VoiceAnalysis[]
|
||||||
durationSec Float?
|
|
||||||
createdAt DateTime @default(now())
|
isActive Boolean @default(true)
|
||||||
updatedAt DateTime @updatedAt
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([embeddingDim])
|
@@index([voiceHash])
|
||||||
}
|
}
|
||||||
|
|
||||||
model AnalysisJob {
|
model VoiceAnalysis {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String
|
enrollmentId String?
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
userId String
|
||||||
analysisType AnalysisType
|
audioHash String // Content hash of audio file
|
||||||
audioFilePath String
|
isSynthetic Boolean
|
||||||
status AnalysisJobStatus @default(PENDING)
|
confidence Float // 0.0 to 1.0
|
||||||
result AnalysisResult?
|
analysisResult Json // Full ML analysis results
|
||||||
errorMessage String?
|
audioUrl String // S3 storage URL
|
||||||
completedAt DateTime?
|
|
||||||
createdAt DateTime @default(now())
|
enrollment VoiceEnrollment? @relation(fields: [enrollmentId], references: [id])
|
||||||
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
@@index([userId, status])
|
@@index([userId])
|
||||||
@@index([createdAt])
|
@@index([enrollmentId])
|
||||||
|
@@index([audioHash])
|
||||||
}
|
}
|
||||||
|
|
||||||
model AnalysisResult {
|
// ============================================
|
||||||
id String @id @default(uuid())
|
// SpamShield Models (Spam Detection)
|
||||||
analysisJobId String @unique
|
// ============================================
|
||||||
analysisJob AnalysisJob @relation(fields: [analysisJobId], references: [id], onDelete: Cascade)
|
|
||||||
syntheticScore Float
|
|
||||||
verdict DetectionVerdict
|
|
||||||
matchedEnrollmentId String?
|
|
||||||
matchedSimilarity Float?
|
|
||||||
confidence Float
|
|
||||||
processingTimeMs Int
|
|
||||||
modelVersion String?
|
|
||||||
metadata String?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
|
||||||
@@index([analysisJobId])
|
|
||||||
@@index([verdict])
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SpamDecision {
|
|
||||||
BLOCK
|
|
||||||
FLAG
|
|
||||||
ALLOW
|
|
||||||
}
|
|
||||||
|
|
||||||
model SpamFeedback {
|
model SpamFeedback {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
phoneNumber String
|
||||||
phoneNumber String // AES-256 encrypted PII
|
phoneNumberHash String // SHA-256 hash
|
||||||
phoneNumberHash String // SHA-256 hash for anonymized lookup
|
isSpam Boolean
|
||||||
isSpam Boolean
|
confidence Float? // ML model confidence
|
||||||
label String?
|
feedbackType FeedbackType
|
||||||
metadata String? // Unbounded JSON
|
metadata Json? // Call duration, time, etc.
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([phoneNumberHash])
|
@@index([phoneNumberHash])
|
||||||
@@index([createdAt])
|
@@index([isSpam])
|
||||||
}
|
}
|
||||||
|
|
||||||
model SpamCallAnalysis {
|
enum FeedbackType {
|
||||||
id String @id @default(uuid())
|
initial_detection
|
||||||
userId String
|
user_confirmation
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user_rejection
|
||||||
phoneNumber String
|
auto_learned
|
||||||
callTimestamp DateTime
|
|
||||||
hiyaReputationScore Float?
|
|
||||||
truecallerSpamScore Float?
|
|
||||||
decision SpamDecision
|
|
||||||
confidence Float
|
|
||||||
ruleMatches String[] // IDs of matched rules
|
|
||||||
auditLogs SpamAuditLog[]
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
|
||||||
@@index([userId])
|
|
||||||
@@index([phoneNumber])
|
|
||||||
@@index([callTimestamp])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model SpamRule {
|
model SpamRule {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
name String @unique
|
userId String?
|
||||||
pattern String @db.VarChar(500) // Regex pattern - validated for ReDoS at application layer
|
isGlobal Boolean @default(false)
|
||||||
decision SpamDecision
|
ruleType RuleType
|
||||||
description String?
|
pattern String
|
||||||
isActive Boolean @default(true)
|
action RuleAction
|
||||||
priority Int @default(0)
|
priority Int @default(0)
|
||||||
createdAt DateTime @default(now())
|
isActive Boolean @default(true)
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
@@index([isActive])
|
|
||||||
@@index([priority])
|
createdAt DateTime @default(now())
|
||||||
}
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
model SpamAuditLog {
|
|
||||||
id String @id @default(uuid())
|
|
||||||
analysisId String?
|
|
||||||
analysis SpamCallAnalysis? @relation(fields: [analysisId], references: [id], onDelete: SetNull)
|
|
||||||
userId String
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
phoneNumber String
|
|
||||||
decision SpamDecision
|
|
||||||
reason String
|
|
||||||
ruleId String?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([createdAt])
|
@@index([isGlobal])
|
||||||
@@index([decision])
|
@@index([ruleType])
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AlertSource {
|
enum RuleType {
|
||||||
DARKWATCH
|
phoneNumber
|
||||||
SPAMSHIELD
|
areaCode
|
||||||
VOICEPRINT
|
prefix
|
||||||
CALL_ANALYSIS
|
pattern
|
||||||
|
reputation
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AlertCategory {
|
enum RuleAction {
|
||||||
BREACH_EXPOSURE
|
block
|
||||||
SPAM_CALL
|
flag
|
||||||
SPAM_SMS
|
allow
|
||||||
SYNTHETIC_VOICE
|
challenge
|
||||||
VOICE_MISMATCH
|
|
||||||
CALL_QUALITY
|
|
||||||
CALL_ANOMALY
|
|
||||||
CALL_EVENT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CorrelationStatus {
|
// ============================================
|
||||||
ACTIVE
|
// Audit & Analytics Models
|
||||||
RESOLVED
|
// ============================================
|
||||||
FALSE_POSITIVE
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EntityType {
|
model AuditLog {
|
||||||
PHONE_NUMBER
|
id String @id @default(uuid())
|
||||||
EMAIL
|
userId String?
|
||||||
USER_ID
|
action String
|
||||||
CALL_ID
|
resource String
|
||||||
IP_ADDRESS
|
resourceId String?
|
||||||
}
|
changes Json? // Before/after values
|
||||||
|
metadata Json?
|
||||||
|
ipAddress String?
|
||||||
|
userAgent String?
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
model NormalizedAlert {
|
@@index([userId])
|
||||||
id String @id @default(uuid())
|
@@index([action])
|
||||||
source AlertSource
|
@@index([resource])
|
||||||
category AlertCategory
|
|
||||||
severity Severity
|
|
||||||
userId String
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
title String
|
|
||||||
description String
|
|
||||||
entities Json // [{ type: EntityType, value: string }]
|
|
||||||
sourceAlertId String
|
|
||||||
groupId String?
|
|
||||||
correlationGroup CorrelationGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
|
|
||||||
payload Json
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
|
|
||||||
@@index([userId, createdAt])
|
|
||||||
@@index([groupId])
|
|
||||||
@@index([sourceAlertId])
|
|
||||||
@@index([source])
|
|
||||||
@@index([severity])
|
|
||||||
}
|
|
||||||
|
|
||||||
model CorrelationGroup {
|
|
||||||
id String @id @default(uuid())
|
|
||||||
userId String
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade, map: "corr_user_idx")
|
|
||||||
entities Json // [{ type: EntityType, value: string }]
|
|
||||||
highestSeverity Severity
|
|
||||||
status CorrelationStatus @default(ACTIVE)
|
|
||||||
alertCount Int @default(0)
|
|
||||||
alerts NormalizedAlert[]
|
|
||||||
summary String?
|
|
||||||
resolvedAt DateTime?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
@@index([userId, status])
|
|
||||||
@@index([createdAt])
|
@@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])
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ async function main() {
|
|||||||
create: {
|
create: {
|
||||||
email: "dev@shieldai.local",
|
email: "dev@shieldai.local",
|
||||||
name: "Dev User",
|
name: "Dev User",
|
||||||
subscriptionTier: "PREMIUM",
|
role: "user",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,71 @@
|
|||||||
|
// ============================================
|
||||||
|
// Consolidated @shieldai/db package
|
||||||
|
// ============================================
|
||||||
|
// Merges functionality from:
|
||||||
|
// - @shieldai/db (Prisma v6.2.0, FieldEncryptionService)
|
||||||
|
// - @shieldsai/shared-db (singleton pattern, type exports)
|
||||||
|
// ============================================
|
||||||
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import { FieldEncryptionService } from './services/field-encryption.service';
|
import { FieldEncryptionService } from './services/field-encryption.service';
|
||||||
|
|
||||||
export const prisma = new PrismaClient();
|
// ============================================
|
||||||
|
// Singleton Pattern (from shared-db)
|
||||||
|
// ============================================
|
||||||
|
const globalForPrisma = globalThis as unknown as {
|
||||||
|
prisma: PrismaClient | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prisma =
|
||||||
|
globalForPrisma.prisma ??
|
||||||
|
new PrismaClient({
|
||||||
|
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
globalForPrisma.prisma = prisma;
|
||||||
|
}
|
||||||
|
|
||||||
export default prisma;
|
export default prisma;
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Services (from @shieldai/db)
|
||||||
|
// ============================================
|
||||||
export { FieldEncryptionService };
|
export { FieldEncryptionService };
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Type Exports (from shared-db)
|
||||||
|
// ============================================
|
||||||
|
export type {
|
||||||
|
User,
|
||||||
|
Account,
|
||||||
|
Session,
|
||||||
|
FamilyGroup,
|
||||||
|
FamilyGroupMember,
|
||||||
|
Subscription,
|
||||||
|
WatchlistItem,
|
||||||
|
Exposure,
|
||||||
|
Alert,
|
||||||
|
VoiceEnrollment,
|
||||||
|
VoiceAnalysis,
|
||||||
|
SpamFeedback,
|
||||||
|
SpamRule,
|
||||||
|
AuditLog,
|
||||||
|
KPISnapshot,
|
||||||
|
UserRole,
|
||||||
|
FamilyMemberRole,
|
||||||
|
SubscriptionTier,
|
||||||
|
SubscriptionStatus,
|
||||||
|
WatchlistType,
|
||||||
|
ExposureSource,
|
||||||
|
ExposureSeverity,
|
||||||
|
AlertType,
|
||||||
|
AlertSeverity,
|
||||||
|
AlertChannel,
|
||||||
|
FeedbackType,
|
||||||
|
RuleType,
|
||||||
|
RuleAction,
|
||||||
|
} from '@prisma/client';
|
||||||
|
|
||||||
|
export * as PrismaModels from '@prisma/client';
|
||||||
export type { PrismaClient };
|
export type { PrismaClient };
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, SubscriptionTier } from '@shieldsai/shared-db';
|
import { prisma, SubscriptionTier } from '@shieldai/db';
|
||||||
import { Queue, Worker, Job } from 'bullmq';
|
import { Queue, Worker, Job } from 'bullmq';
|
||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
import { tierConfig, getTierFeatures } from '@shieldsai/shared-billing';
|
import { tierConfig, getTierFeatures } from '@shieldsai/shared-billing';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, AlertType, AlertSeverity } from '@shieldsai/shared-db';
|
import { prisma, AlertType, AlertSeverity } from '@shieldai/db';
|
||||||
import {
|
import {
|
||||||
NotificationService,
|
NotificationService,
|
||||||
NotificationPriority,
|
NotificationPriority,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, ExposureSource, ExposureSeverity, WatchlistType } from '@shieldsai/shared-db';
|
import { prisma, ExposureSource, ExposureSeverity, WatchlistType } from '@shieldai/db';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
function hashIdentifier(identifier: string): string {
|
function hashIdentifier(identifier: string): string {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, SubscriptionTier, SubscriptionStatus } from '@shieldsai/shared-db';
|
import { prisma, SubscriptionTier, SubscriptionStatus } from '@shieldai/db';
|
||||||
import { tierConfig } from '@shieldsai/shared-billing';
|
import { tierConfig } from '@shieldsai/shared-billing';
|
||||||
import { darkwatchScanQueue } from '@shieldsai/jobs';
|
import { darkwatchScanQueue } from '@shieldsai/jobs';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, WatchlistType } from '@shieldsai/shared-db';
|
import { prisma, WatchlistType } from '@shieldai/db';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
export function normalizeValue(type: WatchlistType, value: string): string {
|
export function normalizeValue(type: WatchlistType, value: string): string {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, ExposureSource, ExposureSeverity, WatchlistType, AlertType, AlertSeverity } from '@shieldsai/shared-db';
|
import { prisma, ExposureSource, ExposureSeverity, WatchlistType, AlertType, AlertSeverity } from '@shieldai/db';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { mixpanelService, EventType } from '@shieldsai/shared-analytics';
|
import { mixpanelService, EventType } from '@shieldsai/shared-analytics';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma, VoiceEnrollment, VoiceAnalysis } from '@shieldsai/shared-db';
|
import { prisma, VoiceEnrollment, VoiceAnalysis } from '@shieldai/db';
|
||||||
import {
|
import {
|
||||||
voicePrintEnv,
|
voicePrintEnv,
|
||||||
AnalysisJobStatus,
|
AnalysisJobStatus,
|
||||||
|
|||||||
Reference in New Issue
Block a user