mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 08:32:09 +01:00
feat: enhance server initialization with environment validation and import processing scheduler test: add Jest setup for unit tests and mock console methods test: implement unit tests for environment management and validation test: create unit tests for transcript fetcher functionality
190 lines
4.9 KiB
Plaintext
190 lines
4.9 KiB
Plaintext
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "sqlite" // still OK for local/dev; use Postgres in prod
|
||
url = "file:./dev.db"
|
||
}
|
||
|
||
/**
|
||
* ENUMS – fewer magic strings
|
||
*/
|
||
enum UserRole {
|
||
ADMIN
|
||
USER
|
||
AUDITOR
|
||
}
|
||
|
||
enum SentimentCategory {
|
||
POSITIVE
|
||
NEUTRAL
|
||
NEGATIVE
|
||
}
|
||
|
||
/**
|
||
* COMPANY (multi-tenant root)
|
||
*/
|
||
model Company {
|
||
id String @id @default(uuid())
|
||
name String
|
||
csvUrl String
|
||
csvUsername String?
|
||
csvPassword String?
|
||
sentimentAlert Float?
|
||
dashboardOpts Json? // JSON column instead of opaque string
|
||
|
||
users User[] @relation("CompanyUsers")
|
||
sessions Session[]
|
||
imports SessionImport[]
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
/**
|
||
* USER (auth accounts)
|
||
*/
|
||
model User {
|
||
id String @id @default(uuid())
|
||
email String @unique
|
||
password String
|
||
role UserRole @default(USER)
|
||
|
||
company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade)
|
||
companyId String
|
||
|
||
resetToken String?
|
||
resetTokenExpiry DateTime?
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
}
|
||
|
||
/**
|
||
* SESSION ↔ SESSIONIMPORT (1-to-1)
|
||
*/
|
||
|
||
/**
|
||
* 1. Normalised session ---------------------------
|
||
*/
|
||
model Session {
|
||
id String @id @default(uuid())
|
||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||
companyId String
|
||
|
||
/**
|
||
* 1-to-1 link back to the import row
|
||
*/
|
||
import SessionImport? @relation("ImportToSession", fields: [importId], references: [id])
|
||
importId String? @unique
|
||
|
||
/**
|
||
* session-level data (processed from SessionImport)
|
||
*/
|
||
startTime DateTime
|
||
endTime DateTime
|
||
|
||
// Processed fields from SessionImport data
|
||
ipAddress String?
|
||
country String? // processed from countryCode
|
||
language String? // processed from language
|
||
messagesSent Int?
|
||
sentiment Float? // processed from sentimentRaw
|
||
sentimentCategory SentimentCategory?
|
||
escalated Boolean?
|
||
forwardedHr Boolean?
|
||
fullTranscriptUrl String?
|
||
avgResponseTime Float? // processed from avgResponseTimeSeconds
|
||
tokens Int?
|
||
tokensEur Float?
|
||
category String?
|
||
initialMsg String? // processed from initialMessage
|
||
|
||
// Processing metadata
|
||
processed Boolean @default(false)
|
||
questions String? // JSON array of extracted questions
|
||
summary String? // AI-generated summary
|
||
|
||
/**
|
||
* ---------- the missing opposite side ----------
|
||
*/
|
||
messages Message[] // <-- satisfies Message.session
|
||
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
@@index([companyId, startTime])
|
||
}
|
||
|
||
/**
|
||
* 2. Raw CSV row waiting to be processed ----------
|
||
*/
|
||
enum ImportStatus {
|
||
QUEUED
|
||
PROCESSING
|
||
DONE
|
||
ERROR
|
||
}
|
||
|
||
model SessionImport {
|
||
id String @id @default(uuid())
|
||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||
companyId String
|
||
|
||
/**
|
||
* 1-to-1 back-relation; NO fields/references here
|
||
*/
|
||
session Session? @relation("ImportToSession")
|
||
|
||
// ─── 16 CSV columns 1-to-1 ────────────────────────
|
||
externalSessionId String @unique // value from CSV column 1
|
||
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?
|
||
|
||
// ─── Raw transcript content ─────────────────────────
|
||
rawTranscriptContent String? // Fetched content from fullTranscriptUrl
|
||
|
||
// ─── bookkeeping ─────────────────────────────────
|
||
status ImportStatus @default(QUEUED)
|
||
errorMsg String?
|
||
processedAt DateTime?
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([companyId, externalSessionId]) // idempotent re-imports
|
||
@@index([status])
|
||
}
|
||
|
||
/**
|
||
* MESSAGE (individual lines)
|
||
*/
|
||
model Message {
|
||
id String @id @default(uuid())
|
||
|
||
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||
sessionId String
|
||
|
||
timestamp DateTime?
|
||
role String // "user" | "assistant" | "system" – free-form keeps migration easy
|
||
content String
|
||
order Int
|
||
|
||
createdAt DateTime @default(now())
|
||
|
||
@@unique([sessionId, order]) // guards against duplicate order values
|
||
@@index([sessionId, order])
|
||
}
|