diff --git a/TODO.md b/TODO.md deleted file mode 100644 index b4702c7..0000000 --- a/TODO.md +++ /dev/null @@ -1,108 +0,0 @@ -# TODO.md - -## Dashboard Integration - -- [ ] **Resolve `GeographicMap.tsx` and `ResponseTimeDistribution.tsx` data simulation** - - Investigate integrating real data sources with server-side analytics - - Replace simulated data mentioned in `docs/dashboard-components.md` - -## Component Specific - -- [ ] **Implement robust emailing of temporary passwords** - - - File: `pages/api/dashboard/users.ts` - - Set up proper email service integration - -- [x] **Session page improvements** ✅ - - File: `app/dashboard/sessions/page.tsx` - - Implemented pagination, advanced filtering, and sorting - -## File Cleanup - -- [x] **Remove backup files** ✅ - - Reviewed and removed `.bak` and `.new` files after integration - - Cleaned up `GeographicMap.tsx.bak`, `SessionDetails.tsx.bak`, `SessionDetails.tsx.new` - -## Database Schema Improvements - -- [ ] **Update EndTime field** - - - Make `endTime` field nullable in Prisma schema to match TypeScript interfaces - -- [ ] **Add database indices** - - - Add appropriate indices to improve query performance - - Focus on dashboard metrics and session listing queries - -- [ ] **Implement production email service** - - Replace console logging in `lib/sendEmail.ts` - - Consider providers: Nodemailer, SendGrid, AWS SES - -## General Enhancements & Features - -- [ ] **Real-time updates** - - - Implement for dashboard and session list - - Consider WebSockets or Server-Sent Events - -- [ ] **Data export functionality** - - - Allow users (especially admins) to export session data - - Support CSV format initially - -- [ ] **Customizable dashboard** - - Allow users to customize dashboard view - - Let users choose which metrics/charts are most important - -## Testing & Quality Assurance - -- [ ] **Comprehensive testing suite** - - - [ ] Unit tests for utility functions and API logic - - [ ] Integration tests for API endpoints with database - - [ ] End-to-end tests for user flows (Playwright or Cypress) - -- [ ] **Error monitoring and logging** - - - Integrate robust error monitoring service (Sentry) - - Enhance server-side logging - -- [ ] **Accessibility improvements** - - Review application against WCAG guidelines - - Improve keyboard navigation and screen reader compatibility - - Check color contrast ratios - -## Security Enhancements - -- [x] **Password reset functionality** ✅ - - - Implemented secure password reset mechanism - - Files: `app/forgot-password/page.tsx`, `app/reset-password/page.tsx`, `pages/api/forgot-password.ts`, `pages/api/reset-password.ts` - -- [ ] **Two-Factor Authentication (2FA)** - - - Consider adding 2FA, especially for admin accounts - -- [ ] **Input validation and sanitization** - - Review all user inputs (API request bodies, query parameters) - - Ensure proper validation and sanitization - -## Code Quality & Development - -- [ ] **Code review process** - - - Enforce code reviews for all changes - -- [ ] **Environment configuration** - - - Ensure secure management of environment-specific configurations - -- [ ] **Dependency management** - - - Periodically review dependencies for vulnerabilities - - Keep dependencies updated - -- [ ] **Documentation updates** - - [ ] Ensure `docs/dashboard-components.md` reflects actual implementations - - [ ] Verify "Dashboard Enhancements" are consistently applied - - [ ] Update documentation for improved layout and visual hierarchies diff --git a/package.json b/package.json index 3328dab..a26df5b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "prisma:migrate": "prisma migrate dev", "prisma:seed": "node prisma/seed.mjs", "prisma:push": "prisma db push", + "prisma:push:force": "prisma db push --force-reset", "prisma:studio": "prisma studio", "start": "node server.mjs", "lint:md": "markdownlint-cli2 \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cd31e47..0d93b3d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,74 +1,166 @@ -// Database schema, one company = one org, linked to users and CSV config generator client { provider = "prisma-client-js" } datasource db { - provider = "sqlite" + 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 // where to fetch CSV - csvUsername String? // for basic auth - csvPassword String? - sentimentAlert Float? // e.g. alert threshold for negative chats - dashboardOpts String? // JSON blob for per-company dashboard preferences - users User[] - sessions Session[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} + id String @id @default(uuid()) + name String + csvUrl String + csvUsername String? + csvPassword String? + sentimentAlert Float? + dashboardOpts Json? // JSON column instead of opaque string -model User { - id String @id @default(uuid()) - email String @unique - password String // hashed, use bcrypt - company Company @relation(fields: [companyId], references: [id]) - companyId String - role String // 'admin' | 'user' | 'auditor' - resetToken String? - resetTokenExpiry DateTime? -} + users User[] @relation("CompanyUsers") + sessions Session[] + imports SessionImport[] -model Session { - id String @id - company Company @relation(fields: [companyId], references: [id]) - companyId String - startTime DateTime - endTime DateTime - ipAddress String? - country String? - language String? - messagesSent Int? - sentiment Float? // Original sentiment score (float) - sentimentCategory String? // "positive", "neutral", "negative" from OpenAPI - escalated Boolean? - forwardedHr Boolean? - fullTranscriptUrl String? - avgResponseTime Float? - tokens Int? - tokensEur Float? - category String? - initialMsg String? - processed Boolean @default(false) // Flag for post-processing status - questions String? // JSON array of questions asked by user - summary String? // Brief summary of the conversation - messages Message[] // Relation to parsed messages - createdAt DateTime @default(now()) -} - -model Message { - id String @id @default(uuid()) - session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) - sessionId String - timestamp DateTime // When the message was sent - role String // "User", "Assistant", "System", etc. - content String // The message content - order Int // Order within the conversation (0, 1, 2, ...) createdAt DateTime @default(now()) - - @@index([sessionId, order]) // Index for efficient ordering queries + 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 … + */ + startTime DateTime + endTime DateTime + // … whatever other scalar fields you have here … + + /** + * ---------- 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? + + // ─── 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]) }