diff --git a/packages/api/src/__tests__/sms-classifier-race-condition.test.ts b/packages/api/src/__tests__/sms-classifier-race-condition.test.ts index 675c750..6595bec 100644 --- a/packages/api/src/__tests__/sms-classifier-race-condition.test.ts +++ b/packages/api/src/__tests__/sms-classifier-race-condition.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { SMSClassifierService } from '../services/spamshield/spamshield.service'; // Mock shared-db before anything else (Prisma client is not generated in test env) -vi.mock('@shieldsai/shared-db', () => ({ +vi.mock('@shieldai/db', () => ({ prisma: {}, SpamFeedback: {}, })); diff --git a/packages/api/src/routes/darkwatch.routes.ts b/packages/api/src/routes/darkwatch.routes.ts index 161b020..634fd5c 100644 --- a/packages/api/src/routes/darkwatch.routes.ts +++ b/packages/api/src/routes/darkwatch.routes.ts @@ -1,5 +1,5 @@ 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 { watchlistService, diff --git a/packages/api/src/services/darkwatch/alert.pipeline.ts b/packages/api/src/services/darkwatch/alert.pipeline.ts index c91a809..a3d2f1a 100644 --- a/packages/api/src/services/darkwatch/alert.pipeline.ts +++ b/packages/api/src/services/darkwatch/alert.pipeline.ts @@ -1,4 +1,4 @@ -import { prisma, AlertType, AlertSeverity } from '@shieldsai/shared-db'; +import { prisma, AlertType, AlertSeverity } from '@shieldai/db'; import { NotificationService, NotificationPriority, diff --git a/packages/api/src/services/darkwatch/scan.service.ts b/packages/api/src/services/darkwatch/scan.service.ts index d3b5182..61a3401 100644 --- a/packages/api/src/services/darkwatch/scan.service.ts +++ b/packages/api/src/services/darkwatch/scan.service.ts @@ -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'; function hashIdentifier(identifier: string): string { diff --git a/packages/api/src/services/darkwatch/scheduler.service.ts b/packages/api/src/services/darkwatch/scheduler.service.ts index e31725e..fefd4b8 100644 --- a/packages/api/src/services/darkwatch/scheduler.service.ts +++ b/packages/api/src/services/darkwatch/scheduler.service.ts @@ -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 { darkwatchScanQueue } from '@shieldsai/jobs'; import { randomUUID } from 'crypto'; diff --git a/packages/api/src/services/darkwatch/watchlist.service.ts b/packages/api/src/services/darkwatch/watchlist.service.ts index caaaf91..5fd8e89 100644 --- a/packages/api/src/services/darkwatch/watchlist.service.ts +++ b/packages/api/src/services/darkwatch/watchlist.service.ts @@ -1,4 +1,4 @@ -import { prisma, WatchlistType } from '@shieldsai/shared-db'; +import { prisma, WatchlistType } from '@shieldai/db'; import { createHash } from 'crypto'; export function normalizeValue(type: WatchlistType, value: string): string { diff --git a/packages/api/src/services/darkwatch/webhook.service.ts b/packages/api/src/services/darkwatch/webhook.service.ts index 256bd4e..419126c 100644 --- a/packages/api/src/services/darkwatch/webhook.service.ts +++ b/packages/api/src/services/darkwatch/webhook.service.ts @@ -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 { mixpanelService, EventType } from '@shieldsai/shared-analytics'; diff --git a/packages/api/src/services/spamshield/spamshield.service.ts b/packages/api/src/services/spamshield/spamshield.service.ts index 0f2f710..ce59e67 100644 --- a/packages/api/src/services/spamshield/spamshield.service.ts +++ b/packages/api/src/services/spamshield/spamshield.service.ts @@ -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 { createHash } from 'crypto'; import { spamAuditLogger, hashPhoneNumber } from './spamshield.audit-logger'; diff --git a/packages/api/src/services/voiceprint/voiceprint.service.ts b/packages/api/src/services/voiceprint/voiceprint.service.ts index 5f45dd3..9eb55b8 100644 --- a/packages/api/src/services/voiceprint/voiceprint.service.ts +++ b/packages/api/src/services/voiceprint/voiceprint.service.ts @@ -1,4 +1,4 @@ -import { prisma, VoiceEnrollment, VoiceAnalysis } from '@shieldsai/shared-db'; +import { prisma, VoiceEnrollment, VoiceAnalysis } from '@shieldai/db'; import { voicePrintEnv, AnalysisJobStatus, diff --git a/packages/db/package.json b/packages/db/package.json index c10555c..0b29155 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,21 +1,26 @@ { "name": "@shieldai/db", - "version": "0.1.0", - "main": "./dist/index.js", - "types": "./dist/index.js", + "version": "0.2.0", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", "scripts": { "build": "prisma generate && tsc", "db:migrate": "prisma migrate dev", "db:seed": "tsx prisma/seed.ts", "db:studio": "prisma studio", + "db:push": "prisma db push", + "db:format": "prisma format", "generate": "prisma generate" }, "dependencies": { "@prisma/client": "^6.2.0", - "prisma": "^6.2.0" + "prisma": "^6.2.0", + "zod": "^4.3.6" }, "devDependencies": { - "tsx": "^4.19.0" + "tsx": "^4.19.0", + "typescript": "^5.3.3" }, "exports": { ".": "./src/index.ts" diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 6d57397..edd32af 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -1,3 +1,6 @@ +// Prisma schema for ShieldAI +// All models for the multi-service SaaS platform + generator client { provider = "prisma-client-js" } @@ -7,406 +10,428 @@ datasource db { url = env("DATABASE_URL") } -enum SubscriptionTier { - BASIC - 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 -} +// ============================================ +// User & Authentication Models +// ============================================ model User { - id String @id @default(uuid()) - email String @unique - name String? - subscriptionTier SubscriptionTier @default(BASIC) - familyGroupId String? - watchListItems WatchListItem[] - alerts Alert[] - scanJobs ScanJob[] - scanSchedules ScanSchedule[] + id String @id @default(uuid()) + email String @unique + emailVerified DateTime? + name String? + image String? + role UserRole @default(user) + + // Relationships + accounts Account[] + sessions Session[] + familyGroups FamilyGroupMember[] + familyGroupOwned FamilyGroup[] @relation("FamilyGroupOwner") + subscriptions Subscription[] + alerts Alert[] voiceEnrollments VoiceEnrollment[] - analysisJobs AnalysisJob[] - spamFeedback SpamFeedback[] - spamCallAnalyses SpamCallAnalysis[] - spamAuditLogs SpamAuditLog[] - normalizedAlerts NormalizedAlert[] - correlationGroups CorrelationGroup[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + voiceAnalyses VoiceAnalysis[] + spamFeedback SpamFeedback[] + spamRules SpamRule[] + + // Audit + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@index([email]) + @@index([role]) } -model WatchListItem { - id String @id @default(uuid()) +enum UserRole { + 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 - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - identifierType IdentifierType - identifierValue String - identifierHash String @unique - status WatchListStatus @default(ACTIVE) + 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[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + alerts Alert[] + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt @@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 { 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? + 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([watchListItemId]) - @@index([contentHash]) - @@index([dataSource]) + @@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()) - 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? - 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()) + id String @id @default(uuid()) + subscriptionId String userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - intervalMinutes Int // minutes between scans - cronExpression String // cron expression for scheduling - status ScheduleStatus @default(ACTIVE) - lastScanAt DateTime? - nextScanAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + 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 - @@unique([userId]) - @@index([status]) -} - -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([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 - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - label String - embeddingVector Float[] - embeddingDim Int @default(192) - audioFilePath String? - sampleRate Int @default(16000) - durationSec Float? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + 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([embeddingDim]) + @@index([voiceHash]) } -model AnalysisJob { - id String @id @default(uuid()) - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - analysisType AnalysisType - audioFilePath String - status AnalysisJobStatus @default(PENDING) - result AnalysisResult? - errorMessage String? - completedAt DateTime? - createdAt DateTime @default(now()) +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, status]) - @@index([createdAt]) + @@index([userId]) + @@index([enrollmentId]) + @@index([audioHash]) } -model AnalysisResult { - id String @id @default(uuid()) - 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 -} +// ============================================ +// SpamShield Models (Spam Detection) +// ============================================ model SpamFeedback { - id String @id @default(uuid()) - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - phoneNumber String // AES-256 encrypted PII - phoneNumberHash String // SHA-256 hash for anonymized lookup - isSpam Boolean - label String? - metadata String? // Unbounded JSON - createdAt DateTime @default(now()) + 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([createdAt]) + @@index([isSpam]) } -model SpamCallAnalysis { - id String @id @default(uuid()) - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - phoneNumber String - 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]) +enum FeedbackType { + initial_detection + user_confirmation + user_rejection + auto_learned } model SpamRule { - id String @id @default(uuid()) - name String @unique - pattern String @db.VarChar(500) // Regex pattern - validated for ReDoS at application layer - decision SpamDecision - description String? - isActive Boolean @default(true) - priority Int @default(0) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - @@index([isActive]) - @@index([priority]) -} - -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()) + 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([createdAt]) - @@index([decision]) + @@index([isGlobal]) + @@index([ruleType]) } -enum AlertSource { - DARKWATCH - SPAMSHIELD - VOICEPRINT - CALL_ANALYSIS +enum RuleType { + phoneNumber + areaCode + prefix + pattern + reputation } -enum AlertCategory { - BREACH_EXPOSURE - SPAM_CALL - SPAM_SMS - SYNTHETIC_VOICE - VOICE_MISMATCH - CALL_QUALITY - CALL_ANOMALY - CALL_EVENT +enum RuleAction { + block + flag + allow + challenge } -enum CorrelationStatus { - ACTIVE - RESOLVED - FALSE_POSITIVE -} +// ============================================ +// Audit & Analytics Models +// ============================================ -enum EntityType { - PHONE_NUMBER - EMAIL - USER_ID - CALL_ID - IP_ADDRESS -} +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()) -model NormalizedAlert { - id String @id @default(uuid()) - source AlertSource - 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([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]) +} diff --git a/packages/db/prisma/seed.ts b/packages/db/prisma/seed.ts index 6ccb3a7..1251cc8 100644 --- a/packages/db/prisma/seed.ts +++ b/packages/db/prisma/seed.ts @@ -7,7 +7,7 @@ async function main() { create: { email: "dev@shieldai.local", name: "Dev User", - subscriptionTier: "PREMIUM", + role: "user", }, }); diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 56f55a3..6f6740f 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -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 { 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; + +// ============================================ +// Services (from @shieldai/db) +// ============================================ 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 }; diff --git a/packages/jobs/src/darkwatch.jobs.ts b/packages/jobs/src/darkwatch.jobs.ts index 1b7c4f2..1f26a0c 100644 --- a/packages/jobs/src/darkwatch.jobs.ts +++ b/packages/jobs/src/darkwatch.jobs.ts @@ -1,4 +1,4 @@ -import { prisma, SubscriptionTier } from '@shieldsai/shared-db'; +import { prisma, SubscriptionTier } from '@shieldai/db'; import { Queue, Worker, Job } from 'bullmq'; import { Redis } from 'ioredis'; import { tierConfig, getTierFeatures } from '@shieldsai/shared-billing'; diff --git a/services/darkwatch/src/alert.pipeline.ts b/services/darkwatch/src/alert.pipeline.ts index c91a809..a3d2f1a 100644 --- a/services/darkwatch/src/alert.pipeline.ts +++ b/services/darkwatch/src/alert.pipeline.ts @@ -1,4 +1,4 @@ -import { prisma, AlertType, AlertSeverity } from '@shieldsai/shared-db'; +import { prisma, AlertType, AlertSeverity } from '@shieldai/db'; import { NotificationService, NotificationPriority, diff --git a/services/darkwatch/src/scan.service.ts b/services/darkwatch/src/scan.service.ts index d3b5182..61a3401 100644 --- a/services/darkwatch/src/scan.service.ts +++ b/services/darkwatch/src/scan.service.ts @@ -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'; function hashIdentifier(identifier: string): string { diff --git a/services/darkwatch/src/scheduler.service.ts b/services/darkwatch/src/scheduler.service.ts index e31725e..fefd4b8 100644 --- a/services/darkwatch/src/scheduler.service.ts +++ b/services/darkwatch/src/scheduler.service.ts @@ -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 { darkwatchScanQueue } from '@shieldsai/jobs'; import { randomUUID } from 'crypto'; diff --git a/services/darkwatch/src/watchlist.service.ts b/services/darkwatch/src/watchlist.service.ts index caaaf91..5fd8e89 100644 --- a/services/darkwatch/src/watchlist.service.ts +++ b/services/darkwatch/src/watchlist.service.ts @@ -1,4 +1,4 @@ -import { prisma, WatchlistType } from '@shieldsai/shared-db'; +import { prisma, WatchlistType } from '@shieldai/db'; import { createHash } from 'crypto'; export function normalizeValue(type: WatchlistType, value: string): string { diff --git a/services/darkwatch/src/webhook.service.ts b/services/darkwatch/src/webhook.service.ts index 256bd4e..419126c 100644 --- a/services/darkwatch/src/webhook.service.ts +++ b/services/darkwatch/src/webhook.service.ts @@ -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 { mixpanelService, EventType } from '@shieldsai/shared-analytics'; diff --git a/services/voiceprint/src/voiceprint.service.ts b/services/voiceprint/src/voiceprint.service.ts index 5f45dd3..9eb55b8 100644 --- a/services/voiceprint/src/voiceprint.service.ts +++ b/services/voiceprint/src/voiceprint.service.ts @@ -1,4 +1,4 @@ -import { prisma, VoiceEnrollment, VoiceAnalysis } from '@shieldsai/shared-db'; +import { prisma, VoiceEnrollment, VoiceAnalysis } from '@shieldai/db'; import { voicePrintEnv, AnalysisJobStatus,