FRE-4533: Merge apps/{api,web,mobile} and shared-db into ShieldAI repo

- Copy apps/api (Fastify server with spamshield/voiceprint/darkwatch services)
- Copy apps/web (SolidJS web app)
- Copy apps/mobile (SolidJS mobile app)
- Copy packages/shared-db (Prisma schema/models)
- Add apps/* to pnpm-workspace.yaml
This commit is contained in:
2026-05-02 10:16:18 -04:00
parent 1e42c4a5c2
commit 1197fe48f7
42 changed files with 5431 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './prisma/schema.prisma',
out: './migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
});

View File

@@ -0,0 +1,23 @@
{
"name": "@shieldsai/shared-db",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
"scripts": {
"db:generate": "prisma generate",
"db:push": "prisma db push",
"db:migrate": "prisma migrate deploy",
"db:studio": "prisma studio",
"db:format": "prisma format"
},
"dependencies": {
"@prisma/client": "^5.14.0",
"zod": "^4.3.6"
},
"devDependencies": {
"prisma": "^5.14.0",
"typescript": "^5.3.3"
}
}

View File

@@ -0,0 +1,437 @@
// 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[]
familyGroups FamilyGroupMember[]
familyGroupOwned FamilyGroup[] @relation("FamilyGroupOwner")
subscriptions Subscription[]
alerts Alert[]
voiceEnrollments VoiceEnrollment[]
voiceAnalyses VoiceAnalysis[]
spamFeedback SpamFeedback[]
spamRules SpamRule[]
// Audit
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
@@index([role])
}
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
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[]
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])
}
// ============================================
// 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])
}

View File

@@ -0,0 +1,50 @@
import { PrismaClient } from '@prisma/client';
// Singleton pattern for Prisma Client
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 types from generated client
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';

View File

@@ -0,0 +1,21 @@
// Re-export Prisma client
export { prisma } from './client';
// Export types
export type {
User,
Account,
Session,
FamilyGroup,
FamilyGroupMember,
Subscription,
WatchlistItem,
Exposure,
Alert,
VoiceEnrollment,
VoiceAnalysis,
SpamFeedback,
SpamRule,
AuditLog,
KPISnapshot,
} from './client';

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "prisma"]
}