Files
livedash-node/prisma/schema.prisma
Kaj Kowalski 2f2c358e67 fix: resolve platform authentication cookie conflicts and session management
- Fix cookie isolation between regular and platform authentication systems
- Add custom cookie names for regular auth (app-auth.session-token) vs platform auth (platform-auth.session-token)
- Remove restrictive cookie path from platform auth to allow proper session access
- Create custom usePlatformSession hook to bypass NextAuth useSession routing issues
- Fix platform dashboard authentication and eliminate redirect loops
- Add proper NEXTAUTH_SECRET configuration
- Enhance platform login with autocomplete attributes
- Update TODO with PR #20 feedback actions and mark platform features complete

The platform management dashboard now has fully functional authentication
with proper session isolation between regular users and platform admins.
2025-06-28 14:24:33 +02:00

416 lines
14 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)
@@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)
companyAiModels CompanyAIModel[]
sessions Session[]
imports SessionImport[]
users User[] @relation("CompanyUsers")
@@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)
company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade)
@@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])
}
/// *
/// * 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])
}
/// *
/// * 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 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)
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@index([sessionId])
@@index([sessionId, requestedAt])
@@index([requestedAt])
@@index([model])
@@index([success, requestedAt])
}
/// *
/// * 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
}