Files
livedash-node/prisma/schema.prisma
Kaj Kowalski 7f48a085bf feat: comprehensive security and architecture improvements
- Add Zod validation schemas with strong password requirements (12+ chars, complexity)
- Implement rate limiting for authentication endpoints (registration, password reset)
- Remove duplicate MetricCard component, consolidate to ui/metric-card.tsx
- Update README.md to use pnpm commands consistently
- Enhance authentication security with 12-round bcrypt hashing
- Add comprehensive input validation for all API endpoints
- Fix security vulnerabilities in user registration and password reset flows

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-28 01:52:53 +02:00

291 lines
8.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DATABASE_URL_DIRECT")
}
/// *
/// * COMPANY (multi-tenant root)
model Company {
id String @id @default(uuid())
name String
csvUrl String
csvUsername String?
csvPassword String?
sentimentAlert Float?
dashboardOpts Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
companyAiModels CompanyAIModel[]
sessions Session[]
imports SessionImport[]
users User[] @relation("CompanyUsers")
}
/// *
/// * USER (auth accounts)
model User {
id String @id @default(uuid())
email String @unique
password String
role UserRole @default(USER)
companyId String
resetToken String?
resetTokenExpiry DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade)
}
/// *
/// * 1. Normalised session ---------------------------
model Session {
id String @id @default(uuid())
companyId String
importId String? @unique
/// *
/// * session-level data (processed from SessionImport)
startTime DateTime
endTime DateTime
ipAddress String?
country String?
fullTranscriptUrl String?
avgResponseTime Float?
initialMsg String?
language String?
messagesSent Int?
sentiment SentimentCategory?
escalated Boolean?
forwardedHr Boolean?
category SessionCategory?
summary String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
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])
}
/// *
/// * 2. Raw CSV row (pure data storage) ----------
model SessionImport {
id String @id @default(uuid())
companyId String
externalSessionId String @unique
startTimeRaw String
endTimeRaw String
ipAddress String?
countryCode String?
language String?
messagesSent Int?
sentimentRaw String?
escalatedRaw String?
forwardedHrRaw String?
fullTranscriptUrl String?
avgResponseTimeSeconds Float?
tokens Int?
tokensEur Float?
category String?
initialMessage String?
rawTranscriptContent String?
createdAt DateTime @default(now())
session Session? @relation("ImportToSession")
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
@@unique([companyId, externalSessionId])
}
/// *
/// * MESSAGE (individual lines)
model Message {
id String @id @default(uuid())
sessionId String
timestamp DateTime?
role String
content String
order Int
createdAt DateTime @default(now())
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@unique([sessionId, order])
@@index([sessionId, order])
}
/// *
/// * UNIFIED PROCESSING STATUS TRACKING
model SessionProcessingStatus {
id String @id @default(uuid())
sessionId String
stage ProcessingStage
status ProcessingStatus @default(PENDING)
startedAt DateTime?
completedAt DateTime?
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])
}
/// *
/// * QUESTION MANAGEMENT (separate from Session for better analytics)
model Question {
id String @id @default(uuid())
content String @unique
createdAt DateTime @default(now())
sessionQuestions SessionQuestion[]
}
model SessionQuestion {
id String @id @default(uuid())
sessionId String
questionId String
order Int
createdAt DateTime @default(now())
question Question @relation(fields: [questionId], references: [id])
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@unique([sessionId, questionId])
@@unique([sessionId, order])
@@index([sessionId])
}
/// *
/// * AI PROCESSING COST TRACKING
model AIProcessingRequest {
id String @id @default(uuid())
sessionId String
openaiRequestId String?
model String
serviceTier String?
systemFingerprint String?
promptTokens Int
completionTokens Int
totalTokens Int
cachedTokens Int?
audioTokensPrompt Int?
reasoningTokens Int?
audioTokensCompletion Int?
acceptedPredictionTokens Int?
rejectedPredictionTokens Int?
promptTokenCost Float
completionTokenCost Float
totalCostEur Float
processingType String
success Boolean
errorMessage String?
requestedAt DateTime @default(now())
completedAt DateTime?
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@index([sessionId])
@@index([requestedAt])
@@index([model])
}
/// *
/// * AI Model definitions (without pricing)
model AIModel {
id String @id @default(uuid())
name String @unique
provider String
maxTokens Int?
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
pricing AIModelPricing[]
companyModels CompanyAIModel[]
@@index([provider, isActive])
}
/// *
/// * Time-based pricing for AI models
model AIModelPricing {
id String @id @default(uuid())
aiModelId String
promptTokenCost Float
completionTokenCost Float
effectiveFrom DateTime
effectiveUntil DateTime?
createdAt DateTime @default(now())
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())
aiModel AIModel @relation(fields: [aiModelId], references: [id], onDelete: Cascade)
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
@@unique([companyId, aiModelId])
@@index([companyId, isDefault])
}
/// *
/// * ENUMS fewer magic strings
enum UserRole {
ADMIN
USER
AUDITOR
}
enum SentimentCategory {
POSITIVE
NEUTRAL
NEGATIVE
}
enum SessionCategory {
SCHEDULE_HOURS
LEAVE_VACATION
SICK_LEAVE_RECOVERY
SALARY_COMPENSATION
CONTRACT_HOURS
ONBOARDING
OFFBOARDING
WORKWEAR_STAFF_PASS
TEAM_CONTACTS
PERSONAL_QUESTIONS
ACCESS_LOGIN
SOCIAL_QUESTIONS
UNRECOGNIZED_OTHER
}
enum ProcessingStage {
CSV_IMPORT
TRANSCRIPT_FETCH
SESSION_CREATION
AI_ANALYSIS
QUESTION_EXTRACTION
}
enum ProcessingStatus {
PENDING
IN_PROGRESS
COMPLETED
FAILED
SKIPPED
}