mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 06:32:10 +01:00
- 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.
416 lines
14 KiB
Plaintext
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
|
|
}
|