mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 06:32:10 +01:00
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.
This commit is contained in:
39
TODO
39
TODO
@ -10,10 +10,10 @@
|
|||||||
- [x] Add company creation workflows
|
- [x] Add company creation workflows
|
||||||
- [x] Add basic platform API endpoints with tests
|
- [x] Add basic platform API endpoints with tests
|
||||||
- [x] Create stunning SaaS landing page with modern design
|
- [x] Create stunning SaaS landing page with modern design
|
||||||
- [ ] Add company editing/management workflows
|
- [x] Add company editing/management workflows
|
||||||
- [ ] Create company suspension/activation UI features
|
- [x] Create company suspension/activation UI features
|
||||||
- [ ] Add proper SEO metadata and OpenGraph tags
|
- [x] Add proper SEO metadata and OpenGraph tags
|
||||||
- [ ] Add user management within companies from platform
|
- [x] Add user management within companies from platform
|
||||||
- [ ] Add AI model management UI
|
- [ ] Add AI model management UI
|
||||||
- [ ] Add cost tracking/quotas UI
|
- [ ] Add cost tracking/quotas UI
|
||||||
|
|
||||||
@ -61,6 +61,37 @@
|
|||||||
|
|
||||||
## High Priority
|
## High Priority
|
||||||
|
|
||||||
|
### PR #20 Feedback Actions (Code Review)
|
||||||
|
- [ ] **Fix Environment Variable Testing**
|
||||||
|
- [ ] Replace process.env access with proper environment mocking in tests
|
||||||
|
- [ ] Update existing tests to avoid direct environment variable dependencies
|
||||||
|
- [ ] Add environment validation tests for critical config values
|
||||||
|
|
||||||
|
- [ ] **Enforce Zero Accessibility Violations**
|
||||||
|
- [ ] Set Playwright accessibility tests to fail on any violations (not just warn)
|
||||||
|
- [ ] Add accessibility regression tests for all major components
|
||||||
|
- [ ] Implement accessibility checklist for new components
|
||||||
|
|
||||||
|
- [ ] **Improve Error Handling with Custom Error Classes**
|
||||||
|
- [ ] Create custom error classes for different error types (ValidationError, AuthError, etc.)
|
||||||
|
- [ ] Replace generic Error throws with specific error classes
|
||||||
|
- [ ] Add proper error logging and monitoring integration
|
||||||
|
|
||||||
|
- [ ] **Refactor Long className Strings**
|
||||||
|
- [ ] Extract complex className combinations into utility functions
|
||||||
|
- [ ] Consider using cn() utility from utils for cleaner class composition
|
||||||
|
- [ ] Break down overly complex className props into semantic components
|
||||||
|
|
||||||
|
- [ ] **Add Dark Mode Accessibility Tests**
|
||||||
|
- [ ] Create comprehensive test suite for dark mode color contrast
|
||||||
|
- [ ] Verify focus indicators work properly in both light and dark modes
|
||||||
|
- [ ] Test screen reader compatibility with theme switching
|
||||||
|
|
||||||
|
- [ ] **Fix Platform Login Authentication Issue**
|
||||||
|
- [ ] NEXTAUTH_SECRET was using placeholder value (FIXED)
|
||||||
|
- [ ] Investigate platform cookie path restrictions in /platform auth
|
||||||
|
- [ ] Test platform login flow end-to-end after fixes
|
||||||
|
|
||||||
### Testing & Quality Assurance
|
### Testing & Quality Assurance
|
||||||
- [ ] Add comprehensive test coverage for API endpoints (currently minimal)
|
- [ ] Add comprehensive test coverage for API endpoints (currently minimal)
|
||||||
- [ ] Implement integration tests for the data processing pipeline
|
- [ ] Implement integration tests for the data processing pipeline
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useSession } from "next-auth/react";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
@ -48,8 +47,39 @@ interface DashboardData {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom hook for platform session
|
||||||
|
function usePlatformSession() {
|
||||||
|
const [session, setSession] = useState<any>(null);
|
||||||
|
const [status, setStatus] = useState<"loading" | "authenticated" | "unauthenticated">("loading");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSession = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/platform/auth/session");
|
||||||
|
const sessionData = await response.json();
|
||||||
|
|
||||||
|
if (sessionData?.user?.isPlatformUser) {
|
||||||
|
setSession(sessionData);
|
||||||
|
setStatus("authenticated");
|
||||||
|
} else {
|
||||||
|
setSession(null);
|
||||||
|
setStatus("unauthenticated");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Platform session fetch error:", error);
|
||||||
|
setSession(null);
|
||||||
|
setStatus("unauthenticated");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchSession();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { data: session, status };
|
||||||
|
}
|
||||||
|
|
||||||
export default function PlatformDashboard() {
|
export default function PlatformDashboard() {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = usePlatformSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
|
const [dashboardData, setDashboardData] = useState<DashboardData | null>(null);
|
||||||
@ -67,7 +97,7 @@ export default function PlatformDashboard() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === "loading") return;
|
if (status === "loading") return;
|
||||||
|
|
||||||
if (!session?.user?.isPlatformUser) {
|
if (status === "unauthenticated" || !session?.user?.isPlatformUser) {
|
||||||
router.push("/platform/login");
|
router.push("/platform/login");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -155,7 +185,7 @@ export default function PlatformDashboard() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session?.user?.isPlatformUser) {
|
if (status === "unauthenticated" || !session?.user?.isPlatformUser) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export default function PlatformLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SessionProvider>
|
<SessionProvider basePath="/api/platform/auth">
|
||||||
{children}
|
{children}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
|
|||||||
@ -31,14 +31,9 @@ export default function PlatformLoginPage() {
|
|||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
setError("Invalid credentials");
|
setError("Invalid credentials");
|
||||||
} else {
|
} else if (result?.ok) {
|
||||||
// Verify the session has platform access
|
// Login successful, redirect to dashboard
|
||||||
const session = await getSession();
|
router.push("/platform/dashboard");
|
||||||
if (session?.user?.isPlatformUser) {
|
|
||||||
router.push("/platform/dashboard");
|
|
||||||
} else {
|
|
||||||
setError("Platform access required");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError("An error occurred during login");
|
setError("An error occurred during login");
|
||||||
@ -73,6 +68,7 @@ export default function PlatformLoginPage() {
|
|||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
required
|
required
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
autoComplete="email"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -85,6 +81,7 @@ export default function PlatformLoginPage() {
|
|||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
required
|
required
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
autoComplete="current-password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
12
lib/auth.ts
12
lib/auth.ts
@ -80,6 +80,18 @@ export const authOptions: NextAuthOptions = {
|
|||||||
],
|
],
|
||||||
session: {
|
session: {
|
||||||
strategy: "jwt",
|
strategy: "jwt",
|
||||||
|
maxAge: 24 * 60 * 60, // 24 hours for regular users
|
||||||
|
},
|
||||||
|
cookies: {
|
||||||
|
sessionToken: {
|
||||||
|
name: `app-auth.session-token`,
|
||||||
|
options: {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: "lax",
|
||||||
|
path: "/",
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token, user }) {
|
async jwt({ token, user }) {
|
||||||
|
|||||||
@ -79,7 +79,7 @@ export const platformAuthOptions: NextAuthOptions = {
|
|||||||
options: {
|
options: {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: "lax",
|
sameSite: "lax",
|
||||||
path: "/platform",
|
path: "/",
|
||||||
secure: process.env.NODE_ENV === "production",
|
secure: process.env.NODE_ENV === "production",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,56 +10,72 @@ datasource db {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * PLATFORM USER (super-admin for Notso AI)
|
/// * PLATFORM USER (super-admin for Notso AI)
|
||||||
/// * Platform-level users who can manage companies and platform-wide settings
|
/// * Platform-level users who can manage companies and platform-wide settings
|
||||||
/// * Separate from Company users for platform management isolation
|
/// * Separate from Company users for platform management isolation
|
||||||
model PlatformUser {
|
model PlatformUser {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
email String @unique @db.VarChar(255) /// Platform user email address
|
/// Platform user email address
|
||||||
password String @db.VarChar(255) /// Hashed password for platform authentication
|
email String @unique @db.VarChar(255)
|
||||||
role PlatformUserRole @default(ADMIN) /// Platform permission level
|
/// Hashed password for platform authentication
|
||||||
name String @db.VarChar(255) /// Display name for platform user
|
password String @db.VarChar(255)
|
||||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
/// Platform permission level
|
||||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
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])
|
@@index([email])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * COMPANY (multi-tenant root)
|
/// * COMPANY (multi-tenant root)
|
||||||
/// * Root entity for multi-tenant architecture
|
/// * Root entity for multi-tenant architecture
|
||||||
/// * Each company has isolated data with own users, sessions, and AI model configurations
|
/// * Each company has isolated data with own users, sessions, and AI model configurations
|
||||||
model Company {
|
model Company {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
name String @db.VarChar(255) /// Company name for display and filtering
|
/// Company name for display and filtering
|
||||||
status CompanyStatus @default(ACTIVE) /// Company status for suspension/activation
|
name String @db.VarChar(255)
|
||||||
csvUrl String @db.Text /// URL endpoint for CSV data import
|
/// Company status for suspension/activation
|
||||||
csvUsername String? @db.VarChar(255) /// Optional HTTP auth username for CSV endpoint
|
status CompanyStatus @default(ACTIVE)
|
||||||
csvPassword String? @db.VarChar(255) /// Optional HTTP auth password for CSV endpoint
|
/// URL endpoint for CSV data import
|
||||||
dashboardOpts Json? @db.JsonB /// Company-specific dashboard configuration (theme, layout, etc.)
|
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)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
companyAiModels CompanyAIModel[] /// AI models assigned to this company
|
companyAiModels CompanyAIModel[]
|
||||||
sessions Session[] /// All processed sessions for this company
|
sessions Session[]
|
||||||
imports SessionImport[] /// Raw CSV import data for this company
|
imports SessionImport[]
|
||||||
users User[] @relation("CompanyUsers") /// Users belonging to this company
|
users User[] @relation("CompanyUsers")
|
||||||
|
|
||||||
@@index([name])
|
@@index([name])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * USER (authentication accounts)
|
/// * USER (authentication accounts)
|
||||||
/// * Application users with role-based access control
|
/// * Application users with role-based access control
|
||||||
/// * Each user belongs to exactly one company for data isolation
|
/// * Each user belongs to exactly one company for data isolation
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
email String @unique @db.VarChar(255) /// User email address, must be unique across all companies
|
/// User email address, must be unique across all companies
|
||||||
password String @db.VarChar(255) /// Hashed password for authentication
|
email String @unique @db.VarChar(255)
|
||||||
role UserRole @default(USER) /// User permission level within their company
|
/// Hashed password for authentication
|
||||||
companyId String /// Foreign key to Company - enforces data isolation
|
password String @db.VarChar(255)
|
||||||
resetToken String? @db.VarChar(255) /// Temporary token for password reset functionality
|
/// User permission level within their company
|
||||||
resetTokenExpiry DateTime? @db.Timestamptz(6) /// Expiration time for reset token
|
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)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade)
|
company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade)
|
||||||
@ -69,47 +85,62 @@ model User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * SESSION (processed conversation data)
|
/// * SESSION (processed conversation data)
|
||||||
/// * Normalized session data derived from raw CSV imports
|
/// * Normalized session data derived from raw CSV imports
|
||||||
/// * Contains AI-enhanced data like sentiment analysis and categorization
|
/// * Contains AI-enhanced data like sentiment analysis and categorization
|
||||||
/// * 1:1 relationship with SessionImport via importId
|
/// * 1:1 relationship with SessionImport via importId
|
||||||
model Session {
|
model Session {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
companyId String /// Foreign key to Company for data isolation
|
/// Foreign key to Company for data isolation
|
||||||
importId String? @unique /// Optional 1:1 link to source SessionImport record
|
companyId String
|
||||||
|
/// Optional 1:1 link to source SessionImport record
|
||||||
|
importId String? @unique
|
||||||
/// Session timing and basic data
|
/// Session timing and basic data
|
||||||
startTime DateTime @db.Timestamptz(6) /// When the conversation started
|
/// When the conversation started
|
||||||
endTime DateTime @db.Timestamptz(6) /// When the conversation ended
|
startTime DateTime @db.Timestamptz(6)
|
||||||
ipAddress String? @db.Inet /// Client IP address (IPv4/IPv6)
|
/// When the conversation ended
|
||||||
country String? @db.VarChar(3) /// ISO 3166-1 alpha-3 country code
|
endTime DateTime @db.Timestamptz(6)
|
||||||
fullTranscriptUrl String? @db.Text /// URL to external transcript source
|
/// Client IP address (IPv4/IPv6)
|
||||||
avgResponseTime Float? @db.Real /// Average response time in seconds
|
ipAddress String? @db.Inet
|
||||||
initialMsg String? @db.Text /// First message in the conversation
|
/// ISO 3166-1 alpha-3 country code
|
||||||
language String? @db.VarChar(10) /// ISO 639 language code
|
country String? @db.VarChar(3)
|
||||||
messagesSent Int? /// Total number of messages in session
|
/// 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-enhanced analysis fields
|
||||||
sentiment SentimentCategory? /// AI-determined overall sentiment
|
/// AI-determined overall sentiment
|
||||||
escalated Boolean? /// Whether session was escalated to human
|
sentiment SentimentCategory?
|
||||||
forwardedHr Boolean? /// Whether session was forwarded to HR
|
/// Whether session was escalated to human
|
||||||
category SessionCategory? /// AI-determined conversation category
|
escalated Boolean?
|
||||||
summary String? @db.Text /// AI-generated session summary
|
/// 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)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
/// Related data
|
aiProcessingRequests AIProcessingRequest[]
|
||||||
aiProcessingRequests AIProcessingRequest[] /// All AI API calls made for this session
|
messages Message[]
|
||||||
messages Message[] /// Individual messages in conversation order
|
|
||||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||||
import SessionImport? @relation("ImportToSession", fields: [importId], references: [id])
|
import SessionImport? @relation("ImportToSession", fields: [importId], references: [id])
|
||||||
processingStatus SessionProcessingStatus[] /// Pipeline stage tracking
|
processingStatus SessionProcessingStatus[]
|
||||||
sessionQuestions SessionQuestion[] /// Questions extracted from conversation
|
sessionQuestions SessionQuestion[]
|
||||||
|
|
||||||
@@index([companyId, startTime]) /// Primary query pattern: company sessions by time
|
@@index([companyId, startTime])
|
||||||
@@index([companyId, sentiment]) /// Filter sessions by sentiment within company
|
@@index([companyId, sentiment])
|
||||||
@@index([companyId, category]) /// Filter sessions by category within company
|
@@index([companyId, category])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * 2. Raw CSV row (pure data storage) ----------
|
/// * 2. Raw CSV row (pure data storage) ----------
|
||||||
model SessionImport {
|
model SessionImport {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
companyId String
|
companyId String
|
||||||
@ -123,13 +154,13 @@ model SessionImport {
|
|||||||
sentimentRaw String? @db.VarChar(50)
|
sentimentRaw String? @db.VarChar(50)
|
||||||
escalatedRaw String? @db.VarChar(50)
|
escalatedRaw String? @db.VarChar(50)
|
||||||
forwardedHrRaw String? @db.VarChar(50)
|
forwardedHrRaw String? @db.VarChar(50)
|
||||||
fullTranscriptUrl String? @db.Text
|
fullTranscriptUrl String?
|
||||||
avgResponseTimeSeconds Float? @db.Real
|
avgResponseTimeSeconds Float? @db.Real
|
||||||
tokens Int?
|
tokens Int?
|
||||||
tokensEur Float? @db.Real
|
tokensEur Float? @db.Real
|
||||||
category String? @db.VarChar(255)
|
category String? @db.VarChar(255)
|
||||||
initialMessage String? @db.Text
|
initialMessage String?
|
||||||
rawTranscriptContent String? @db.Text
|
rawTranscriptContent String?
|
||||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
session Session? @relation("ImportToSession")
|
session Session? @relation("ImportToSession")
|
||||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||||
@ -140,13 +171,13 @@ model SessionImport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * MESSAGE (individual lines)
|
/// * MESSAGE (individual lines)
|
||||||
model Message {
|
model Message {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
sessionId String
|
sessionId String
|
||||||
timestamp DateTime? @db.Timestamptz(6)
|
timestamp DateTime? @db.Timestamptz(6)
|
||||||
role String @db.VarChar(50)
|
role String @db.VarChar(50)
|
||||||
content String @db.Text
|
content String
|
||||||
order Int
|
order Int
|
||||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||||
@ -157,7 +188,7 @@ model Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * UNIFIED PROCESSING STATUS TRACKING
|
/// * UNIFIED PROCESSING STATUS TRACKING
|
||||||
model SessionProcessingStatus {
|
model SessionProcessingStatus {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
sessionId String
|
sessionId String
|
||||||
@ -165,9 +196,9 @@ model SessionProcessingStatus {
|
|||||||
status ProcessingStatus @default(PENDING)
|
status ProcessingStatus @default(PENDING)
|
||||||
startedAt DateTime? @db.Timestamptz(6)
|
startedAt DateTime? @db.Timestamptz(6)
|
||||||
completedAt DateTime? @db.Timestamptz(6)
|
completedAt DateTime? @db.Timestamptz(6)
|
||||||
errorMessage String? @db.Text
|
errorMessage String?
|
||||||
retryCount Int @default(0)
|
retryCount Int @default(0)
|
||||||
metadata Json? @db.JsonB
|
metadata Json?
|
||||||
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@unique([sessionId, stage])
|
@@unique([sessionId, stage])
|
||||||
@ -177,10 +208,10 @@ model SessionProcessingStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * QUESTION MANAGEMENT (separate from Session for better analytics)
|
/// * QUESTION MANAGEMENT (separate from Session for better analytics)
|
||||||
model Question {
|
model Question {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
content String @unique @db.Text
|
content String @unique
|
||||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
sessionQuestions SessionQuestion[]
|
sessionQuestions SessionQuestion[]
|
||||||
}
|
}
|
||||||
@ -201,7 +232,7 @@ model SessionQuestion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * AI PROCESSING COST TRACKING
|
/// * AI PROCESSING COST TRACKING
|
||||||
model AIProcessingRequest {
|
model AIProcessingRequest {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
sessionId String
|
sessionId String
|
||||||
@ -223,7 +254,7 @@ model AIProcessingRequest {
|
|||||||
totalCostEur Float @db.Real
|
totalCostEur Float @db.Real
|
||||||
processingType String @db.VarChar(100)
|
processingType String @db.VarChar(100)
|
||||||
success Boolean
|
success Boolean
|
||||||
errorMessage String? @db.Text
|
errorMessage String?
|
||||||
requestedAt DateTime @default(now()) @db.Timestamptz(6)
|
requestedAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
completedAt DateTime? @db.Timestamptz(6)
|
completedAt DateTime? @db.Timestamptz(6)
|
||||||
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||||
@ -236,7 +267,7 @@ model AIProcessingRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * AI Model definitions (without pricing)
|
/// * AI Model definitions (without pricing)
|
||||||
model AIModel {
|
model AIModel {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
name String @unique @db.VarChar(100)
|
name String @unique @db.VarChar(100)
|
||||||
@ -253,7 +284,7 @@ model AIModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * Time-based pricing for AI models
|
/// * Time-based pricing for AI models
|
||||||
model AIModelPricing {
|
model AIModelPricing {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
aiModelId String
|
aiModelId String
|
||||||
@ -269,7 +300,7 @@ model AIModelPricing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
/// *
|
||||||
/// * Company-specific AI model assignments
|
/// * Company-specific AI model assignments
|
||||||
model CompanyAIModel {
|
model CompanyAIModel {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
companyId String
|
companyId String
|
||||||
@ -283,70 +314,102 @@ model CompanyAIModel {
|
|||||||
@@index([companyId, isDefault])
|
@@index([companyId, isDefault])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// *
|
|
||||||
/// * ENUMS – typed constants for better data integrity
|
|
||||||
///
|
|
||||||
|
|
||||||
/// Platform-level user roles for Notso AI team
|
/// Platform-level user roles for Notso AI team
|
||||||
enum PlatformUserRole {
|
enum PlatformUserRole {
|
||||||
SUPER_ADMIN /// Full platform access, can create/suspend companies
|
/// Full platform access, can create/suspend companies
|
||||||
ADMIN /// Platform administration, company management
|
SUPER_ADMIN
|
||||||
SUPPORT /// Customer support access, read-only company access
|
/// Platform administration, company management
|
||||||
|
ADMIN
|
||||||
|
/// Customer support access, read-only company access
|
||||||
|
SUPPORT
|
||||||
}
|
}
|
||||||
|
|
||||||
/// User permission levels within a company
|
/// User permission levels within a company
|
||||||
enum UserRole {
|
enum UserRole {
|
||||||
ADMIN /// Full access to company data and settings
|
/// Full access to company data and settings
|
||||||
USER /// Standard access to view and interact with data
|
ADMIN
|
||||||
AUDITOR /// Read-only access for compliance and auditing
|
/// Standard access to view and interact with data
|
||||||
|
USER
|
||||||
|
/// Read-only access for compliance and auditing
|
||||||
|
AUDITOR
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Company operational status
|
/// Company operational status
|
||||||
enum CompanyStatus {
|
enum CompanyStatus {
|
||||||
ACTIVE /// Company is operational and can access all features
|
/// Company is operational and can access all features
|
||||||
SUSPENDED /// Company access is temporarily disabled
|
ACTIVE
|
||||||
TRIAL /// Company is in trial period with potential limitations
|
/// Company access is temporarily disabled
|
||||||
ARCHIVED /// Company is archived and data is read-only
|
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
|
/// AI-determined sentiment categories for sessions
|
||||||
enum SentimentCategory {
|
enum SentimentCategory {
|
||||||
POSITIVE /// Customer expressed satisfaction or positive emotions
|
/// Customer expressed satisfaction or positive emotions
|
||||||
NEUTRAL /// Neutral tone or mixed emotions
|
POSITIVE
|
||||||
NEGATIVE /// Customer expressed frustration or negative emotions
|
/// Neutral tone or mixed emotions
|
||||||
|
NEUTRAL
|
||||||
|
/// Customer expressed frustration or negative emotions
|
||||||
|
NEGATIVE
|
||||||
}
|
}
|
||||||
|
|
||||||
/// AI-determined conversation categories based on content analysis
|
/// AI-determined conversation categories based on content analysis
|
||||||
enum SessionCategory {
|
enum SessionCategory {
|
||||||
SCHEDULE_HOURS /// Questions about work schedules and hours
|
/// Questions about work schedules and hours
|
||||||
LEAVE_VACATION /// Vacation requests and leave policies
|
SCHEDULE_HOURS
|
||||||
SICK_LEAVE_RECOVERY /// Sick leave and recovery-related discussions
|
/// Vacation requests and leave policies
|
||||||
SALARY_COMPENSATION /// Salary, benefits, and compensation questions
|
LEAVE_VACATION
|
||||||
CONTRACT_HOURS /// Contract terms and working hours
|
/// Sick leave and recovery-related discussions
|
||||||
ONBOARDING /// New employee onboarding processes
|
SICK_LEAVE_RECOVERY
|
||||||
OFFBOARDING /// Employee departure and offboarding
|
/// Salary, benefits, and compensation questions
|
||||||
WORKWEAR_STAFF_PASS /// Equipment, uniforms, and access cards
|
SALARY_COMPENSATION
|
||||||
TEAM_CONTACTS /// Team directory and contact information
|
/// Contract terms and working hours
|
||||||
PERSONAL_QUESTIONS /// Personal HR matters and private concerns
|
CONTRACT_HOURS
|
||||||
ACCESS_LOGIN /// System access and login issues
|
/// New employee onboarding processes
|
||||||
SOCIAL_QUESTIONS /// Social events and company culture
|
ONBOARDING
|
||||||
UNRECOGNIZED_OTHER /// Conversations that don't fit other categories
|
/// 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
|
/// Processing pipeline stages for session data transformation
|
||||||
enum ProcessingStage {
|
enum ProcessingStage {
|
||||||
CSV_IMPORT /// Initial import of raw CSV data into SessionImport
|
/// Initial import of raw CSV data into SessionImport
|
||||||
TRANSCRIPT_FETCH /// Fetching transcript content from external URLs
|
CSV_IMPORT
|
||||||
SESSION_CREATION /// Converting SessionImport to normalized Session
|
/// Fetching transcript content from external URLs
|
||||||
AI_ANALYSIS /// AI processing for sentiment, categorization, summaries
|
TRANSCRIPT_FETCH
|
||||||
QUESTION_EXTRACTION /// Extracting questions from conversation content
|
/// 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
|
/// Status of each processing stage
|
||||||
enum ProcessingStatus {
|
enum ProcessingStatus {
|
||||||
PENDING /// Stage is queued for processing
|
/// Stage is queued for processing
|
||||||
IN_PROGRESS /// Stage is currently being processed
|
PENDING
|
||||||
COMPLETED /// Stage completed successfully
|
/// Stage is currently being processed
|
||||||
FAILED /// Stage failed with errors
|
IN_PROGRESS
|
||||||
SKIPPED /// Stage was intentionally skipped
|
/// Stage completed successfully
|
||||||
|
COMPLETED
|
||||||
|
/// Stage failed with errors
|
||||||
|
FAILED
|
||||||
|
/// Stage was intentionally skipped
|
||||||
|
SKIPPED
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user