Files
livedash-node/prisma/schema.prisma
Kaj Kowalski 1eea2cc3e4 refactor: fix biome linting issues and update project documentation
- Fix 36+ biome linting issues reducing errors/warnings from 227 to 191
- Replace explicit 'any' types with proper TypeScript interfaces
- Fix React hooks dependencies and useCallback patterns
- Resolve unused variables and parameter assignment issues
- Improve accessibility with proper label associations
- Add comprehensive API documentation for admin and security features
- Update README.md with accurate PostgreSQL setup and current tech stack
- Create complete documentation for audit logging, CSP monitoring, and batch processing
- Fix outdated project information and missing developer workflows
2025-07-12 00:28:09 +02:00

617 lines
21 KiB
Plaintext

generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DATABASE_URL_DIRECT")
}
/// *
/// * PLATFORM USER (super-admin for Notso AI)
/// * Platform-level users who can manage companies and platform-wide settings
/// * Separate from Company users for platform management isolation
model PlatformUser {
id String @id @default(uuid())
/// Platform user email address
email String @unique @db.VarChar(255)
/// Hashed password for platform authentication
password String @db.VarChar(255)
/// Platform permission level
role PlatformUserRole @default(ADMIN)
/// Display name for platform user
name String @db.VarChar(255)
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
/// Relations
auditLogs SecurityAuditLog[]
@@index([email])
}
/// *
/// * COMPANY (multi-tenant root)
/// * Root entity for multi-tenant architecture
/// * Each company has isolated data with own users, sessions, and AI model configurations
model Company {
id String @id @default(uuid())
/// Company name for display and filtering
name String @db.VarChar(255)
/// Company status for suspension/activation
status CompanyStatus @default(ACTIVE)
/// URL endpoint for CSV data import
csvUrl String
/// Optional HTTP auth username for CSV endpoint
csvUsername String? @db.VarChar(255)
/// Optional HTTP auth password for CSV endpoint
csvPassword String? @db.VarChar(255)
/// Company-specific dashboard configuration (theme, layout, etc.)
dashboardOpts Json?
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
/// Maximum number of users allowed for this company
maxUsers Int @default(10)
companyAiModels CompanyAIModel[]
sessions Session[]
imports SessionImport[]
users User[] @relation("CompanyUsers")
aiBatchRequests AIBatchRequest[]
auditLogs SecurityAuditLog[]
@@index([name])
@@index([status])
}
/// *
/// * USER (authentication accounts)
/// * Application users with role-based access control
/// * Each user belongs to exactly one company for data isolation
model User {
id String @id @default(uuid())
/// User email address, must be unique across all companies
email String @unique @db.VarChar(255)
/// Hashed password for authentication
password String @db.VarChar(255)
/// User permission level within their company
role UserRole @default(USER)
/// Foreign key to Company - enforces data isolation
companyId String
/// Temporary token for password reset functionality
resetToken String? @db.VarChar(255)
/// Expiration time for reset token
resetTokenExpiry DateTime? @db.Timestamptz(6)
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
/// Display name for the user
name String? @db.VarChar(255)
/// When this user was invited
invitedAt DateTime? @db.Timestamptz(6)
/// Email of the user who invited this user (for audit trail)
invitedBy String? @db.VarChar(255)
company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade)
auditLogs SecurityAuditLog[]
@@index([companyId])
@@index([email])
}
/// *
/// * SESSION (processed conversation data)
/// * Normalized session data derived from raw CSV imports
/// * Contains AI-enhanced data like sentiment analysis and categorization
/// * 1:1 relationship with SessionImport via importId
model Session {
id String @id @default(uuid())
/// Foreign key to Company for data isolation
companyId String
/// Optional 1:1 link to source SessionImport record
importId String? @unique
/// Session timing and basic data
/// When the conversation started
startTime DateTime @db.Timestamptz(6)
/// When the conversation ended
endTime DateTime @db.Timestamptz(6)
/// Client IP address (IPv4/IPv6)
ipAddress String? @db.Inet
/// ISO 3166-1 alpha-3 country code
country String? @db.VarChar(3)
/// URL to external transcript source
fullTranscriptUrl String?
/// Average response time in seconds
avgResponseTime Float? @db.Real
/// First message in the conversation
initialMsg String?
/// ISO 639 language code
language String? @db.VarChar(10)
/// Total number of messages in session
messagesSent Int?
/// AI-enhanced analysis fields
/// AI-determined overall sentiment
sentiment SentimentCategory?
/// Whether session was escalated to human
escalated Boolean?
/// Whether session was forwarded to HR
forwardedHr Boolean?
/// AI-determined conversation category
category SessionCategory?
/// AI-generated session summary
summary String?
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
aiProcessingRequests AIProcessingRequest[]
messages Message[]
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
import SessionImport? @relation("ImportToSession", fields: [importId], references: [id])
processingStatus SessionProcessingStatus[]
sessionQuestions SessionQuestion[]
@@index([companyId, startTime])
@@index([companyId, sentiment])
@@index([companyId, category])
@@index([companyId, escalated])
@@index([companyId, forwardedHr])
@@index([companyId, language])
@@index([companyId, messagesSent])
@@index([companyId, avgResponseTime])
}
/// *
/// * 2. Raw CSV row (pure data storage) ----------
model SessionImport {
id String @id @default(uuid())
companyId String
externalSessionId String
startTimeRaw String @db.VarChar(255)
endTimeRaw String @db.VarChar(255)
ipAddress String? @db.VarChar(45)
countryCode String? @db.VarChar(3)
language String? @db.VarChar(10)
messagesSent Int?
sentimentRaw String? @db.VarChar(50)
escalatedRaw String? @db.VarChar(50)
forwardedHrRaw String? @db.VarChar(50)
fullTranscriptUrl String?
avgResponseTimeSeconds Float? @db.Real
tokens Int?
tokensEur Float? @db.Real
category String? @db.VarChar(255)
initialMessage String?
rawTranscriptContent String?
createdAt DateTime @default(now()) @db.Timestamptz(6)
session Session? @relation("ImportToSession")
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
@@unique([companyId, externalSessionId])
@@index([companyId])
@@index([companyId, createdAt])
}
/// *
/// * MESSAGE (individual lines)
model Message {
id String @id @default(uuid())
sessionId String
timestamp DateTime? @db.Timestamptz(6)
role String @db.VarChar(50)
content String
order Int
createdAt DateTime @default(now()) @db.Timestamptz(6)
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@unique([sessionId, order])
@@index([sessionId, order])
@@index([sessionId, timestamp])
@@index([sessionId, role])
}
/// *
/// * UNIFIED PROCESSING STATUS TRACKING
model SessionProcessingStatus {
id String @id @default(uuid())
sessionId String
stage ProcessingStage
status ProcessingStatus @default(PENDING)
startedAt DateTime? @db.Timestamptz(6)
completedAt DateTime? @db.Timestamptz(6)
errorMessage String?
retryCount Int @default(0)
metadata Json?
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@unique([sessionId, stage])
@@index([stage, status])
@@index([sessionId])
@@index([status, startedAt])
}
/// *
/// * QUESTION MANAGEMENT (separate from Session for better analytics)
model Question {
id String @id @default(uuid())
content String @unique
createdAt DateTime @default(now()) @db.Timestamptz(6)
sessionQuestions SessionQuestion[]
}
model SessionQuestion {
id String @id @default(uuid())
sessionId String
questionId String
order Int
createdAt DateTime @default(now()) @db.Timestamptz(6)
question Question @relation(fields: [questionId], references: [id])
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@unique([sessionId, questionId])
@@unique([sessionId, order])
@@index([sessionId])
@@index([questionId])
}
/// *
/// * AI BATCH REQUEST TRACKING (OpenAI Batch API)
/// * Tracks batch jobs submitted to OpenAI for cost-efficient processing
model AIBatchRequest {
id String @id @default(cuid())
companyId String
company Company @relation(fields: [companyId], references: [id])
/// OpenAI specific IDs
openaiBatchId String @unique
inputFileId String
outputFileId String?
errorFileId String?
/// Our internal status tracking
status AIBatchRequestStatus @default(PENDING)
/// Timestamps
createdAt DateTime @default(now()) @db.Timestamptz(6)
completedAt DateTime? @db.Timestamptz(6)
processedAt DateTime? @db.Timestamptz(6) // When we finished processing the results
/// Relation to the individual requests included in this batch
processingRequests AIProcessingRequest[]
@@index([companyId, status])
}
/// *
/// * AI PROCESSING COST TRACKING
model AIProcessingRequest {
id String @id @default(uuid())
sessionId String
openaiRequestId String? @db.VarChar(255)
model String @db.VarChar(100)
serviceTier String? @db.VarChar(50)
systemFingerprint String? @db.VarChar(255)
promptTokens Int
completionTokens Int
totalTokens Int
cachedTokens Int?
audioTokensPrompt Int?
reasoningTokens Int?
audioTokensCompletion Int?
acceptedPredictionTokens Int?
rejectedPredictionTokens Int?
promptTokenCost Float @db.Real
completionTokenCost Float @db.Real
totalCostEur Float @db.Real
processingType String @db.VarChar(100)
success Boolean
errorMessage String?
requestedAt DateTime @default(now()) @db.Timestamptz(6)
completedAt DateTime? @db.Timestamptz(6)
/// === NEW BATCH API FIELDS ===
processingStatus AIRequestStatus @default(PENDING_BATCHING)
batchId String?
batch AIBatchRequest? @relation(fields: [batchId], references: [id])
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@index([sessionId])
@@index([sessionId, requestedAt])
@@index([requestedAt])
@@index([model])
@@index([success, requestedAt])
@@index([processingStatus]) // Add this index for efficient querying
@@index([processingStatus, requestedAt]) // Optimize time-based status queries
@@index([batchId]) // Optimize batch-related queries
@@index([processingStatus, batchId]) // Composite index for batch status filtering
}
/// *
/// * AI Model definitions (without pricing)
model AIModel {
id String @id @default(uuid())
name String @unique @db.VarChar(100)
provider String @db.VarChar(50)
maxTokens Int?
isActive Boolean @default(true)
createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
pricing AIModelPricing[]
companyModels CompanyAIModel[]
@@index([provider, isActive])
@@index([name])
}
/// *
/// * Time-based pricing for AI models
model AIModelPricing {
id String @id @default(uuid())
aiModelId String
promptTokenCost Float @db.Real
completionTokenCost Float @db.Real
effectiveFrom DateTime @db.Timestamptz(6)
effectiveUntil DateTime? @db.Timestamptz(6)
createdAt DateTime @default(now()) @db.Timestamptz(6)
aiModel AIModel @relation(fields: [aiModelId], references: [id], onDelete: Cascade)
@@index([aiModelId, effectiveFrom])
@@index([effectiveFrom, effectiveUntil])
}
/// *
/// * Company-specific AI model assignments
model CompanyAIModel {
id String @id @default(uuid())
companyId String
aiModelId String
isDefault Boolean @default(false)
createdAt DateTime @default(now()) @db.Timestamptz(6)
aiModel AIModel @relation(fields: [aiModelId], references: [id], onDelete: Cascade)
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
@@unique([companyId, aiModelId])
@@index([companyId, isDefault])
}
/// Platform-level user roles for Notso AI team
enum PlatformUserRole {
/// Full platform access, can create/suspend companies
SUPER_ADMIN
/// Platform administration, company management
ADMIN
/// Customer support access, read-only company access
SUPPORT
}
/// User permission levels within a company
enum UserRole {
/// Full access to company data and settings
ADMIN
/// Standard access to view and interact with data
USER
/// Read-only access for compliance and auditing
AUDITOR
}
/// Company operational status
enum CompanyStatus {
/// Company is operational and can access all features
ACTIVE
/// Company access is temporarily disabled
SUSPENDED
/// Company is in trial period with potential limitations
TRIAL
/// Company is archived and data is read-only
ARCHIVED
}
/// AI-determined sentiment categories for sessions
enum SentimentCategory {
/// Customer expressed satisfaction or positive emotions
POSITIVE
/// Neutral tone or mixed emotions
NEUTRAL
/// Customer expressed frustration or negative emotions
NEGATIVE
}
/// AI-determined conversation categories based on content analysis
enum SessionCategory {
/// Questions about work schedules and hours
SCHEDULE_HOURS
/// Vacation requests and leave policies
LEAVE_VACATION
/// Sick leave and recovery-related discussions
SICK_LEAVE_RECOVERY
/// Salary, benefits, and compensation questions
SALARY_COMPENSATION
/// Contract terms and working hours
CONTRACT_HOURS
/// New employee onboarding processes
ONBOARDING
/// Employee departure and offboarding
OFFBOARDING
/// Equipment, uniforms, and access cards
WORKWEAR_STAFF_PASS
/// Team directory and contact information
TEAM_CONTACTS
/// Personal HR matters and private concerns
PERSONAL_QUESTIONS
/// System access and login issues
ACCESS_LOGIN
/// Social events and company culture
SOCIAL_QUESTIONS
/// Conversations that don't fit other categories
UNRECOGNIZED_OTHER
}
/// Processing pipeline stages for session data transformation
enum ProcessingStage {
/// Initial import of raw CSV data into SessionImport
CSV_IMPORT
/// Fetching transcript content from external URLs
TRANSCRIPT_FETCH
/// Converting SessionImport to normalized Session
SESSION_CREATION
/// AI processing for sentiment, categorization, summaries
AI_ANALYSIS
/// Extracting questions from conversation content
QUESTION_EXTRACTION
}
/// Status of each processing stage
enum ProcessingStatus {
/// Stage is queued for processing
PENDING
/// Stage is currently being processed
IN_PROGRESS
/// Stage completed successfully
COMPLETED
/// Stage failed with errors
FAILED
/// Stage was intentionally skipped
SKIPPED
}
/// OpenAI Batch Request Status
enum AIBatchRequestStatus {
/// Batch request created, waiting to upload file to OpenAI
PENDING
/// Currently uploading file to OpenAI
UPLOADING
/// OpenAI is validating the uploaded file
VALIDATING
/// Batch is queued or running on OpenAI's side
IN_PROGRESS
/// OpenAI is finalizing the batch results
FINALIZING
/// Batch completed on OpenAI, ready for processing
COMPLETED
/// We have processed the results from the completed batch
PROCESSED
/// Batch failed during processing
FAILED
/// Batch was cancelled
CANCELLED
}
/// Status for individual AI processing requests within a batch
enum AIRequestStatus {
/// Request is waiting to be included in a batch
PENDING_BATCHING
/// Request is currently part of a batch being processed
BATCHING_IN_PROGRESS
/// Processing completed successfully
PROCESSING_COMPLETE
/// Processing failed
PROCESSING_FAILED
}
/// *
/// * SECURITY AUDIT LOG (comprehensive security event tracking)
/// * Tracks all security-critical events for compliance and incident investigation
/// * Immutable records with structured metadata for analysis
model SecurityAuditLog {
id String @id @default(uuid())
/// Event category for filtering and analysis
eventType SecurityEventType
/// High-level action description
action String @db.VarChar(255)
/// Detailed event outcome (success, failure, blocked)
outcome AuditOutcome
/// User who performed the action (if authenticated)
userId String?
/// Company context for multi-tenant filtering
companyId String?
/// Platform user who performed the action (for admin events)
platformUserId String?
/// Client IP address for geographic analysis
ipAddress String? @db.Inet
/// User agent string for device/browser analysis
userAgent String?
/// ISO 3166-1 alpha-3 country code derived from IP
country String? @db.VarChar(3)
/// Structured metadata with additional context
metadata Json?
/// Error message if action failed
errorMessage String?
/// Severity level for alerting and prioritization
severity AuditSeverity @default(INFO)
/// Session ID for correlation with user sessions
sessionId String? @db.VarChar(255)
/// Request ID for tracing across system boundaries
requestId String? @db.VarChar(255)
/// Immutable timestamp for chronological ordering
timestamp DateTime @default(now()) @db.Timestamptz(6)
/// Relations
user User? @relation(fields: [userId], references: [id])
company Company? @relation(fields: [companyId], references: [id])
platformUser PlatformUser? @relation(fields: [platformUserId], references: [id])
@@index([eventType, timestamp])
@@index([companyId, eventType, timestamp])
@@index([userId, timestamp])
@@index([platformUserId, timestamp])
@@index([outcome, severity, timestamp])
@@index([ipAddress, timestamp])
@@index([timestamp])
@@index([sessionId])
@@index([requestId])
}
/// Security event categories for audit logging
enum SecurityEventType {
/// Authentication events (login, logout, password changes)
AUTHENTICATION
/// Authorization events (permission checks, access denied)
AUTHORIZATION
/// User management events (create, update, delete, invite)
USER_MANAGEMENT
/// Company management events (create, suspend, settings changes)
COMPANY_MANAGEMENT
/// Rate limiting and abuse prevention
RATE_LIMITING
/// CSRF protection violations
CSRF_PROTECTION
/// Security header violations
SECURITY_HEADERS
/// Password reset flows
PASSWORD_RESET
/// Platform admin activities
PLATFORM_ADMIN
/// Data export and privacy events
DATA_PRIVACY
/// System configuration changes
SYSTEM_CONFIG
/// API security events
API_SECURITY
}
/// Outcome classification for audit events
enum AuditOutcome {
/// Action completed successfully
SUCCESS
/// Action failed due to user error or invalid input
FAILURE
/// Action was blocked by security controls
BLOCKED
/// Action triggered rate limiting
RATE_LIMITED
/// Action was suspicious but not blocked
SUSPICIOUS
}
/// Severity levels for audit events
enum AuditSeverity {
/// Informational events for compliance tracking
INFO
/// Low-impact security events
LOW
/// Medium-impact security events requiring attention
MEDIUM
/// High-impact security events requiring immediate attention
HIGH
/// Critical security events requiring urgent response
CRITICAL
}