diff --git a/README.md b/README.md index 1456cf2..d06d957 100644 --- a/README.md +++ b/README.md @@ -72,15 +72,12 @@ The EMS follows a **3-tier architecture** for scalability and maintainability: ## πŸ—οΈ Services Created -- **auth-service**: NestJS microservice with health endpoints -- **user-service**: NestJS microservice with health endpoints -- **event-service**: NestJS microservice with health endpoints -- **booking-service**: NestJS microservice with health endpoints -- **ticketing-service**: NestJS microservice with health endpoints -- **speaker-service**: NestJS microservice with health endpoints -- **feedback-service**: NestJS microservice with health endpoints -- **notification-service**: NestJS microservice with health endpoints -- **reporting-analytics-service**: NestJS microservice with health endpoints +- **auth-service**: Node.js microservice with health endpoints +- **event-service**: Node.js microservice with health endpoints +- **booking-service**: Node.js microservice with health endpoints +- **speaker-service**: Node.js microservice with health endpoints +- **feedback-service**: Node.js microservice with health endpoints +- **notification-service**: Node.js microservice with health endpoints ## πŸš€ Running Services @@ -98,14 +95,11 @@ docker-compose up -d ``` β”œβ”€β”€ ems-client/ # Next.js frontend with health endpoint β”œβ”€β”€ ems-gateway/ # NGINX configuration -└── ems-services/ # NestJS microservices +└── ems-services/ # Node.js microservices β”œβ”€β”€ auth-service/ - β”œβ”€β”€ user-service/ β”œβ”€β”€ event-service/ β”œβ”€β”€ booking-service/ - β”œβ”€β”€ ticketing-service/ β”œβ”€β”€ speaker-service/ β”œβ”€β”€ feedback-service/ - β”œβ”€β”€ notification-service/ - └── reporting-analytics-service/ + └── notification-service/ ``` diff --git a/docker-compose.test-db.yaml b/docker-compose.test-db.yaml index 4e25a25..3015a4a 100644 --- a/docker-compose.test-db.yaml +++ b/docker-compose.test-db.yaml @@ -15,6 +15,10 @@ services: interval: 10s timeout: 5s retries: 5 + deploy: + resources: + limits: + memory: 512M volumes: ems-db-test-data: \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 592ac18..bfb3462 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -12,6 +12,10 @@ services: ./ems-client/.env.production networks: - event-net + deploy: + resources: + limits: + memory: 512M ems-gateway: build: @@ -31,6 +35,10 @@ services: - booking-service - speaker-service - notification-service + deploy: + resources: + limits: + memory: 512M ######################################### # BACKEND MICROSERVICES @@ -52,6 +60,10 @@ services: condition: service_healthy rabbitmq: condition: service_started + deploy: + resources: + limits: + memory: 512M event-service: <<: *base-service @@ -74,6 +86,10 @@ services: condition: service_started auth-service: condition: service_started + deploy: + resources: + limits: + memory: 512M booking-service: <<: *base-service @@ -92,6 +108,10 @@ services: condition: service_healthy rabbitmq: condition: service_started + deploy: + resources: + limits: + memory: 512M feedback-service: <<: *base-service @@ -103,6 +123,8 @@ services: container_name: feedback-service env_file: ./ems-services/feedback-service/.env.production + environment: + - JWT_SECRET=supersecretkey networks: - event-net depends_on: @@ -110,6 +132,10 @@ services: condition: service_healthy rabbitmq: condition: service_started + deploy: + resources: + limits: + memory: 512M notification-service: build: @@ -123,6 +149,10 @@ services: depends_on: rabbitmq: condition: service_started + deploy: + resources: + limits: + memory: 512M speaker-service: <<: *base-service @@ -146,6 +176,10 @@ services: condition: service_healthy rabbitmq: condition: service_started + deploy: + resources: + limits: + memory: 512M ######################################### # DATABASES FOR MICROSERVICES @@ -167,6 +201,10 @@ services: interval: 10s timeout: 5s retries: 5 + deploy: + resources: + limits: + memory: 512M event-service-db: <<: *base-service-db @@ -177,6 +215,10 @@ services: - POSTGRES_DB=event_db volumes: - event-service-data:/var/lib/postgresql/data + deploy: + resources: + limits: + memory: 512M booking-service-db: <<: *base-service-db @@ -187,6 +229,10 @@ services: - POSTGRES_DB=booking_db volumes: - booking-service-data:/var/lib/postgresql/data + deploy: + resources: + limits: + memory: 512M feedback-service-db: <<: *base-service-db @@ -197,6 +243,10 @@ services: - POSTGRES_DB=feedback_db volumes: - feedback-service-data:/var/lib/postgresql/data + deploy: + resources: + limits: + memory: 512M speaker-service-db: <<: *base-service-db @@ -207,6 +257,10 @@ services: - POSTGRES_DB=speaker_db volumes: - speaker-service-data:/var/lib/postgresql/data + deploy: + resources: + limits: + memory: 512M ######################################### # INFRASTRUCTURE SERVICES @@ -231,6 +285,10 @@ services: timeout: 10s retries: 5 start_period: 20s # Give RabbitMQ time to start up before checking + deploy: + resources: + limits: + memory: 512M # This section defines the custom network for our services to communicate. networks: diff --git a/docs/MESSAGING_SYSTEM_IMPLEMENTATION.md b/docs/MESSAGING_SYSTEM_IMPLEMENTATION.md new file mode 100644 index 0000000..85feaa1 --- /dev/null +++ b/docs/MESSAGING_SYSTEM_IMPLEMENTATION.md @@ -0,0 +1,227 @@ +# Messaging System Implementation Summary + +## Overview +A comprehensive two-way messaging system has been implemented to enable communication between speakers and administrators. The system includes real-time message delivery, read receipts, message threading, and event association. + +## Backend Implementation + +### 1. Database Schema Enhancements (`ems-services/speaker-service/prisma/schema.prisma`) +- **Added `MessageStatus` enum**: `SENT`, `DELIVERED`, `READ` +- **Enhanced `Message` model** with: + - `eventId` (optional): Associate messages with events + - `status`: MessageStatus enum (default: SENT) + - `deliveredAt`: Timestamp when message was delivered + - `attachmentUrl`, `attachmentName`, `attachmentType`: Support for file attachments + - `updatedAt`: Track message updates + - Added indexes for performance: `fromUserId`, `toUserId`, `threadId`, `eventId`, `status` + +### 2. Message Service (`ems-services/speaker-service/src/services/message.service.ts`) +- Enhanced `createMessage()` to support new fields (eventId, attachments, status) +- Added `markMessageAsDelivered()` method +- Updated `markMessageAsRead()` to update status to 'READ' +- **New admin-specific methods**: + - `getAllSpeakerMessages()`: Get all messages from speakers + - `getMessagesByEvent()`: Filter messages by event ID + - `getMessagesBySpeaker()`: Get all messages from a specific speaker + - `getThreadsBySpeaker()`: Get message threads organized by speaker + - `getUnreadSpeakerMessageCount()`: Count unread messages for admins + +### 3. Message Routes (`ems-services/speaker-service/src/routes/message.routes.ts`) +- **Authentication**: All routes now require authentication via `authMiddleware` +- **Authorization**: Users can only access their own messages unless they're admins +- **Enhanced POST /**: Now uses authenticated user ID, supports eventId and attachments +- **Admin-only routes**: + - `GET /admin/all-speaker-messages`: Get all speaker messages + - `GET /admin/event/:eventId`: Get messages by event + - `GET /admin/speaker/:speakerUserId`: Get messages by speaker + - `GET /admin/speaker/:speakerUserId/threads`: Get threads by speaker + - `GET /admin/unread-count`: Get unread message count + +### 4. WebSocket Service (`ems-services/speaker-service/src/services/websocket.service.ts`) +- **Real-time message delivery** using Socket.IO +- **Features**: + - JWT authentication for WebSocket connections + - User-specific rooms (`user:${userId}`) + - Admin room (`admins`) for broadcasting speaker messages + - Events: + - `message:sent`: When a message is created + - `message:received`: Real-time delivery to recipient + - `message:read`: Read receipt notifications + - `message:typing`: Typing indicators + - `message:new_speaker_message`: Notify all admins of new speaker messages + - Automatic status updates (SENT β†’ DELIVERED β†’ READ) + +### 5. Server Integration (`ems-services/speaker-service/src/server.ts`) +- Integrated WebSocket service with HTTP server +- Graceful shutdown handling for WebSocket connections + +### 6. Dependencies (`ems-services/speaker-service/package.json`) +- Added `socket.io` (^4.7.5) +- Added `@types/socket.io` (^3.0.2) + +## Frontend Implementation + +### 1. API Client Methods (`ems-client/lib/api/admin.api.ts`) +- Added `Message` and `MessageThread` interfaces +- **New methods**: + - `getAllSpeakerMessages()`: Fetch all speaker messages + - `getMessagesByEvent()`: Filter by event + - `getMessagesBySpeaker()`: Filter by speaker + - `getThreadsBySpeaker()`: Get threads by speaker + - `getUnreadSpeakerMessageCount()`: Get unread count + - `sendMessage()`: Send a message + - `markMessageAsRead()`: Mark message as read + +### 2. Admin Messaging Center (`ems-client/app/dashboard/admin/messages/page.tsx`) +- **Features**: + - View all speaker messages + - Filter by search query, speaker, or event + - Message detail view with read receipts + - Compose and reply to messages + - Unread message count badge + - Real-time updates (via polling every 30 seconds) + - Mobile-responsive design + - Event association display + +### 3. Admin Dashboard Integration (`ems-client/app/dashboard/admin/page.tsx`) +- Added "Messages" quick action button linking to `/dashboard/admin/messages` + +## Features Implemented + +βœ… **Speakers can send messages to admins** +- Speakers can compose and send messages from their dashboard +- Messages are automatically associated with events (optional) +- Support for file attachments (optional) + +βœ… **Admins can view all messages** +- Centralized messaging center at `/dashboard/admin/messages` +- Filter messages by speaker, event, or search query +- View message threads organized by speaker + +βœ… **Real-time message delivery** +- WebSocket server implemented for real-time updates +- Message status tracking (SENT β†’ DELIVERED β†’ READ) +- Read receipts sent to message senders + +βœ… **Message history persistence** +- All messages stored in PostgreSQL database +- Message threading support +- Event association for organizing messages + +βœ… **Notification system** +- WebSocket events notify admins of new speaker messages +- Unread message count displayed in admin dashboard +- Real-time notifications via Socket.IO + +βœ… **Message thread organization** +- Messages grouped by threadId +- Threads organized by speaker and event +- Conversation history maintained + +βœ… **Mobile-responsive interface** +- Responsive grid layout +- Touch-friendly buttons and interactions +- Optimized for mobile devices + +## Pending Tasks + +### 1. Database Migration +**Action Required**: Run Prisma migration to apply schema changes +```bash +cd ems-services/speaker-service +npx prisma migrate dev --name add_message_enhancements +``` + +### 2. WebSocket Client Integration (Frontend) +**Status**: Pending +- Install `socket.io-client` in frontend +- Create WebSocket hook (`useWebSocket.ts`) +- Integrate real-time updates in messaging components +- Replace polling with WebSocket events + +### 3. Speaker Message Interface Enhancement +**Status**: Pending +- Enhance existing `MessageCenter` component with event context +- Add event selection when composing messages +- Display event information in message threads + +### 4. File Upload Support +**Status**: Partially Implemented +- Backend supports attachment fields +- Frontend upload UI needs to be added +- File storage integration required + +### 5. Notification Service Integration +**Status**: Pending +- Create RabbitMQ publisher for message events +- Add consumer in notification-service +- Send email notifications for new messages (optional) + +## Testing Checklist + +- [ ] Test message creation from speaker dashboard +- [ ] Test admin viewing all speaker messages +- [ ] Test message filtering (by speaker, event, search) +- [ ] Test read receipt functionality +- [ ] Test WebSocket real-time delivery +- [ ] Test message threading +- [ ] Test event association +- [ ] Test mobile responsiveness +- [ ] Test authentication and authorization + +## API Endpoints + +### Speaker Endpoints +- `POST /api/messages` - Send message (authenticated) +- `GET /api/messages/inbox/:userId` - Get inbox messages +- `GET /api/messages/sent/:userId` - Get sent messages +- `GET /api/messages/threads/:userId` - Get message threads +- `GET /api/messages/unread/:userId/count` - Get unread count +- `PUT /api/messages/:id/read` - Mark as read + +### Admin Endpoints +- `GET /api/messages/admin/all-speaker-messages` - Get all speaker messages +- `GET /api/messages/admin/event/:eventId` - Get messages by event +- `GET /api/messages/admin/speaker/:speakerUserId` - Get messages by speaker +- `GET /api/messages/admin/speaker/:speakerUserId/threads` - Get threads by speaker +- `GET /api/messages/admin/unread-count` - Get unread count + +## WebSocket Events + +### Client β†’ Server +- `message:sent` - Notify server of new message +- `message:read` - Mark message as read +- `message:typing` - Send typing indicator + +### Server β†’ Client +- `message:received` - New message received +- `message:delivered` - Message delivery confirmation +- `message:read_receipt` - Read receipt notification +- `message:new_speaker_message` - New speaker message (admins only) +- `message:typing` - Typing indicator received + +## Environment Variables + +No new environment variables required. Uses existing: +- `JWT_SECRET` - For WebSocket authentication +- `CLIENT_URL` - For CORS configuration +- `DATABASE_URL` - For Prisma +- `RABBITMQ_URL` - For future notification integration + +## Next Steps + +1. **Run database migration** to apply schema changes +2. **Install dependencies**: `npm install` in `speaker-service` +3. **Test WebSocket connection** from frontend +4. **Add WebSocket client** to frontend components +5. **Enhance speaker interface** with event context +6. **Add file upload UI** for attachments +7. **Integrate notification service** for email alerts + +## Notes + +- WebSocket server is ready but frontend client integration is pending +- Current implementation uses polling (30s interval) for unread count +- File attachment support is partially implemented (backend ready, frontend pending) +- Notification service integration can be added for email alerts + diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..c7f2d9c --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,146 @@ +# Testing Guide + +## Overview + +Tests are **NOT** run during Docker container builds or deployments. The build process only compiles TypeScript code using `tsc`. Tests must be run manually using npm scripts. + +## Build Process + +The Docker build process: +1. Installs dependencies (`npm ci`) +2. Generates Prisma client (`npx prisma generate`) +3. Compiles TypeScript (`npm run build` which runs `tsc`) +4. **Does NOT run tests** + +## Running Tests Manually + +### Available Test Scripts + +All services support the following npm test scripts: + +- `npm test` - Run all tests once +- `npm run test:watch` - Run tests in watch mode +- `npm run test:coverage` - Run tests with coverage report +- `npm run test:ci` - Run tests in CI mode (no watch, with coverage) +- `npm run test:debug` - Run tests with debug output +- `npm run test:verbose` - Run tests with verbose output +- `npm run test:update-snapshots` - Update test snapshots + +### Running Tests for a Specific Service + +Navigate to the service directory and run the test command: + +```bash +# Example: Run tests for event-service +cd ems-services/event-service +npm test + +# Example: Run tests with coverage +npm run test:coverage + +# Example: Run tests in watch mode +npm run test:watch +``` + +### Running Tests for All Services + +You can run tests for all services from the root directory: + +```bash +# Run tests for all services +cd ems-services/event-service && npm test && cd ../.. +cd ems-services/speaker-service && npm test && cd ../.. +cd ems-services/booking-service && npm test && cd ../.. +cd ems-services/auth-service && npm test && cd ../.. +cd ems-services/notification-service && npm test && cd ../.. +cd ems-services/feedback-service && npm test && cd ../.. +``` + +Or use a script to run all tests: + +```bash +# Create a script to run all tests +./scripts/run-all-tests.sh +``` + +## Test Configuration + +### Jest Configuration + +Each service has its own `jest.config.ts` file that configures: +- Test file patterns +- Coverage thresholds +- TypeScript support +- Test environment setup + +### Test Environment + +Tests use a separate environment configuration: +- Test environment variables are loaded from `.env.test` files +- Test setup files are in `src/test/` directories +- Mocks are defined in `src/test/mocks-simple.ts` + +## Test Structure + +Tests are organized as follows: + +``` +ems-services/{service}/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ services/ +β”‚ β”‚ └── __test__/ +β”‚ β”‚ └── *.test.ts +β”‚ β”œβ”€β”€ routes/ +β”‚ β”‚ └── __test__/ +β”‚ β”‚ └── *.test.ts +β”‚ └── test/ +β”‚ β”œβ”€β”€ setup.ts +β”‚ β”œβ”€β”€ env-setup.ts +β”‚ └── mocks-simple.ts +└── jest.config.ts +``` + +## Important Notes + +1. **Tests are NOT run during Docker builds** - The build process only compiles TypeScript +2. **Tests require dev dependencies** - Make sure to install dependencies with `npm install` (not `npm ci --omit=dev`) +3. **Test environment variables** - Ensure `.env.test` files are configured for each service +4. **Database setup** - Some tests may require a test database connection + +## CI/CD Integration + +If you want to run tests in CI/CD pipelines, you can: + +1. Add a separate test stage in your CI/CD pipeline +2. Run tests before building Docker images +3. Use `npm run test:ci` for CI environments + +Example CI/CD step: + +```yaml +# Example GitHub Actions step +- name: Run tests + run: | + cd ems-services/event-service + npm install + npm run test:ci +``` + +## Troubleshooting + +### Tests fail to run + +1. Ensure dev dependencies are installed: `npm install` +2. Check that `.env.test` files exist and are configured +3. Verify Jest is installed: `npm list jest` + +### TypeScript compilation errors in tests + +- Tests are compiled during `npm test` execution +- Ensure all test files have proper imports and types +- Check `tsconfig.json` includes test files + +### Coverage reports + +Coverage reports are generated in the `coverage/` directory after running `npm run test:coverage`. + diff --git a/docs/UNIT_TESTS_SUMMARY.md b/docs/UNIT_TESTS_SUMMARY.md new file mode 100644 index 0000000..c16cd18 --- /dev/null +++ b/docs/UNIT_TESTS_SUMMARY.md @@ -0,0 +1,161 @@ +# Unit Tests Summary + +This document summarizes the unit tests added for the messaging system and admin event update functionality. + +## Test Files Created + +### Speaker Service Tests + +1. **`ems-services/speaker-service/src/services/__test__/message.service.test.ts`** + - Tests for `MessageService.createMessage()` with new fields (eventId, status, attachment details) + - Tests for admin methods: + - `getAllSpeakerMessages()` + - `getMessagesByEvent()` + - `getMessagesBySpeaker()` + - `getThreadsBySpeaker()` + - `getUnreadSpeakerMessageCount()` + - Tests for message status updates: + - `markMessageAsDelivered()` + - `markMessageAsRead()` + +2. **`ems-services/speaker-service/src/routes/__test__/message.routes.test.ts`** + - Tests for authentication enforcement on all message routes + - Tests for authorization: + - User-specific access (users can only access their own messages) + - Admin-only access (admin methods) + - Message creation authorization (speakers can send to admins, admins can send to anyone) + +3. **`ems-services/speaker-service/src/services/__test__/websocket.service.test.ts`** + - Tests for JWT authentication of WebSocket connections + - Tests for room assignment: + - User-specific rooms (`user:`) + - Admin room (`admins`) + - Tests for `message:sent` event processing: + - Marking messages as delivered if recipient is online + - Emitting `message:received` to recipients + - Emitting `message:new_speaker_message` to admins when message is from speaker + - Emitting `message:delivered` confirmation to sender + - Tests for `message:read` event processing: + - Marking messages as read + - Emitting `message:read_receipt` to sender + - Tests for connection management + +### Event Service Tests + +4. **`ems-services/event-service/src/services/__test__/event.service.updateEventAsAdmin.test.ts`** + - Tests for `updateEventAsAdmin()`: + - Successfully updates event when called by admin + - Publishes `event.updated` message if event is PUBLISHED + - Does not publish message if event is not PUBLISHED + - Tests for venue availability validation: + - Validates venue availability for PUBLISHED events + - Throws error if overlapping events exist + - Allows update if no overlapping events + - Does not validate for non-PUBLISHED events + - Tests for booking date validation: + - Ensures booking start date is before end date + - Allows valid booking dates + - Uses existing dates if only one date is provided + - Error handling tests: + - Event not found + - Venue not found + +5. **`ems-services/event-service/src/routes/__test__/admin.routes.updateEvent.test.ts`** + - Tests for `PUT /admin/events/:id` endpoint: + - Requires admin authentication + - Validates request body fields: + - `name` (cannot be empty) + - `description` (cannot be empty) + - `category` (cannot be empty) + - `venueId` (must be valid number) + - `bookingStartDate` (must be valid date) + - `bookingEndDate` (must be valid date) + - Booking dates (start must be before end) + - Calls `updateEventAsAdmin` with correct parameters + - Returns success response with updated event + +## Test Setup Files + +### Speaker Service + +- **`ems-services/speaker-service/jest.config.ts`** - Jest configuration +- **`ems-services/speaker-service/src/test/env-setup.ts`** - Environment setup +- **`ems-services/speaker-service/src/test/mocks-simple.ts`** - Mock definitions +- **`ems-services/speaker-service/src/test/setup.ts`** - Test setup and teardown + +## Test Coverage + +### MessageService Tests +- βœ… `createMessage()` with all new fields +- βœ… Admin methods for retrieving and filtering messages +- βœ… Message status tracking (SENT, DELIVERED, READ) +- βœ… Thread management +- βœ… Pagination support + +### Message Routes Tests +- βœ… Authentication enforcement +- βœ… User-specific authorization +- βœ… Admin-only authorization +- βœ… Message creation authorization + +### WebSocketService Tests +- βœ… JWT authentication +- βœ… Room assignment (user-specific and admin rooms) +- βœ… `message:sent` event processing +- βœ… `message:read` event processing +- βœ… Real-time message delivery +- βœ… Admin notifications for speaker messages + +### EventService.updateEventAsAdmin Tests +- βœ… Successful event updates by admin +- βœ… Message publishing for PUBLISHED events +- βœ… Venue availability validation +- βœ… Booking date validation +- βœ… Error handling + +### Admin Routes Tests +- βœ… Admin authentication requirement +- βœ… Request body validation for all fields +- βœ… Route handler functionality + +## Running the Tests + +### Speaker Service +```bash +cd ems-services/speaker-service +npm test +``` + +### Event Service +```bash +cd ems-services/event-service +npm test +``` + +## Test Environment Setup + +Before running tests, ensure you have: +1. Created `.env.test` files in both services with required environment variables: + - `DATABASE_URL` + - `RABBITMQ_URL` + - `JWT_SECRET` + - `PORT` + - `CLIENT_URL` (for speaker-service) + +2. Installed dependencies: + ```bash + npm install + ``` + +3. Generated Prisma client: + ```bash + npm run prisma:generate + ``` + +## Notes + +- All tests use mocked Prisma client and external services +- Tests are isolated and do not require a running database or RabbitMQ +- Mock data factories are provided for creating test objects +- Tests follow the existing test patterns in the codebase + diff --git a/docs/implementation-plans/session-lite-roadmap.md b/docs/implementation-plans/session-lite-roadmap.md new file mode 100644 index 0000000..a9c0b79 --- /dev/null +++ b/docs/implementation-plans/session-lite-roadmap.md @@ -0,0 +1,30 @@ +# Session Lite Rollout Plan + +Reference roadmap for implementing the session-lite LLD incrementally and validating each step from the client side. + +## Phase 0 – Schema & Contracts +- Align Prisma schema/data model with `session-lite-lld.md`. +- Add missing identifiers (`sessionLiteId`, `eventId`, `speakerId`, `invitationId`, etc.). +- Update DTOs/response contracts so clients receive every identifier needed for UI logic and conditionals. + +## Phase 1 – Session Lite CRUD APIs +- Implement minimal create/read/update/delete flows on the backend. +- Ensure responses include full identifier sets for downstream consumers. +- Cover the new service endpoints with integration tests. +- Manually hit the APIs from the client (or temporary tooling) to confirm payloads. + +## Phase 2 – Admin UI Integration +- Wire the new APIs into the admin dashboard (feature flag if necessary). +- Reflect IDs in the UI components (rows, actions, statuses). +- Add loading/error states and verify the payload contracts against the UI bindings. + +## Phase 3 – Invitation Alignment +- Update invitation flows to reference session-lite IDs directly (no message parsing). +- Surface invite state in the client using the explicit identifiers. +- Remove legacy logic that inferred session links from free-form text. + +## Phase 4 – Validation & Logging +- Add guardrails for state transitions (pending/accepted/rejected) keyed by explicit IDs. +- Ensure structured logging and telemetry reference the new identifiers. +- Final regression pass through the client to confirm no contradictions remain between modal and session table states. + diff --git a/docs/low-level-design/session-lite-lld.md b/docs/low-level-design/session-lite-lld.md new file mode 100644 index 0000000..a578114 --- /dev/null +++ b/docs/low-level-design/session-lite-lld.md @@ -0,0 +1,77 @@ +## Session Lite Low-Level Design + +### 1. Goal +Provide a minimal, production-ready way to represent multiple sessions per event, assign speakers with availability checks, and expose schedule + materials status across admin, speaker, and attendee views without overhauling existing services. + +### 2. Data Model Changes +- `Session` (new table) + - `id` (UUID) + - `event_id` (FK β†’ `Event`) + - `title` + - `description` (optional) + - `starts_at`, `ends_at` + - `stage_location` (optional) + - `created_at`, `updated_at` + - Index on (`event_id`, `starts_at`) +- `SessionSpeaker` (join table) + - `id` (UUID) + - `session_id` (FK β†’ `Session`) + - `speaker_id` (FK β†’ `Speaker`) + - `materials_asset_id` (optional FK β†’ global `SpeakerMaterial`) + - `materials_status` (enum: `requested`, `uploaded`, `acknowledged`; defaults to `requested`) + - `speaker_checkin_confirmed` (boolean; required before β€œJoin Event” unlocks) + - `created_at`, `updated_at` + - Unique index on (`session_id`, `speaker_id`) +- `SpeakerMaterial` (existing/global) + - Reuse current storage; session assignments only keep references so deleting sessions never removes shared speaker assets. + +### 3. Core Service Logic +- **Create/Update session** + - Accepts session metadata and retains event-level validation (timeframe within event dates). + - Enforces non-overlapping sessions per stage when configured. +- **Assign speaker to session** + - Validates that the speaker has no accepted sessions overlapping `starts_at`/`ends_at`. + - Stores material requirements and initializes `materials_status = requested`. +- **Material upload flow** + - Speakers upload files via existing storage; API stores `materials_asset_id`, flips status to `uploaded`. + - Speakers must mark `speaker_checkin_confirmed = true` before the join button enables. +- **Delete session** + - Removes session + assignments in a transaction; materials remain intact in the global library. + +### 4. API Touchpoints +- `GET /events/:id` + - Returns `sessions` array with nested `speakers` (name, session assignment metadata, materials status, check-in flag). +- `POST /events/:id/sessions` + - Creates session and (optionally) accepts a `speakers` array with availability validation. +- `PUT /sessions/:id` + - Updates session details; warns if schedule changes impact assigned speakers. +- `POST /sessions/:id/speakers` + - Adds speaker assignment after availability check; returns conflict info if double-booked. +- `PATCH /sessions/:id/speakers/:speakerId/materials` + - Updates materials status/check-in flags for that assignment. +- `POST /sessions/:id/speakers/:speakerId/materials/upload` + - Handles file upload and links to global materials. +- `DELETE /sessions/:id` + - Removes session and assignments. + +### 5. Client Updates +- **Admin dashboard** + - Session list for an event with inline forms (title, time, location). + - Speaker picker with conflict warnings; materials status shown per speaker. + - Timing edits display admin-only warnings; no automatic speaker notifications. +- **Speaker dashboard** + - Shows upcoming sessions with timeframe, materials checklist, upload controls, and join button gated by `speaker_checkin_confirmed`. +- **Attendee views** + - Event cards and details display session title, start/end time, and speaker names. + +### 6. Validation & Constraints +- Sessions must fall within the parent event timeframe. +- Speakers cannot be assigned to overlapping sessions; admins can override with explicit confirmation. +- `materials_status = uploaded` and `speaker_checkin_confirmed = true` required before speaker join. + +### 7. Testing +- Unit tests for session creation, assignment availability checks, and materials state transitions. +- Integration tests covering event session CRUD with nested speaker payloads. +- UI tests verifying admin conflict warnings, speaker upload workflow, and attendee session rendering. + + diff --git a/ems-client/app/dashboard/admin/events/[id]/feedback/page.tsx b/ems-client/app/dashboard/admin/events/[id]/feedback/page.tsx new file mode 100644 index 0000000..275bd47 --- /dev/null +++ b/ems-client/app/dashboard/admin/events/[id]/feedback/page.tsx @@ -0,0 +1,481 @@ +'use client'; + +import { useAuth } from "@/lib/auth-context"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Label } from "@/components/ui/label"; +import { + ArrowLeft, + Settings, + Trash2, + Save, + X, + MessageSquare, + BarChart3, + Eye, + Star, + User, + Calendar +} from "lucide-react"; +import { useRouter, useParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import { useLogger } from "@/lib/logger/LoggerProvider"; +import { feedbackAPI, FeedbackFormResponse, FeedbackFormStatus, FeedbackSubmissionResponse } from "@/lib/api/feedback.api"; +import { eventAPI } from "@/lib/api/event.api"; +import { withAdminAuth } from "@/components/hoc/withAuth"; + +const COMPONENT_NAME = 'FeedbackManagementPage'; + +const statusColors = { + DRAFT: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + PUBLISHED: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + CLOSED: 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200', +}; + +function FeedbackManagementPage() { + const { user } = useAuth(); + const router = useRouter(); + const params = useParams(); + const logger = useLogger(); + const eventId = params.id as string; + + const [event, setEvent] = useState(null); + const [feedbackForm, setFeedbackForm] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [saving, setSaving] = useState(false); + const [deleting, setDeleting] = useState(false); + + // Form state + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [status, setStatus] = useState('DRAFT'); + + // Responses modal state + const [showResponsesModal, setShowResponsesModal] = useState(false); + const [responses, setResponses] = useState([]); + const [loadingResponses, setLoadingResponses] = useState(false); + + useEffect(() => { + loadData(); + }, [eventId]); + + const loadData = async () => { + try { + setLoading(true); + setError(null); + + // Load event + const eventResponse = await eventAPI.getEventById(eventId); + if (eventResponse.success) { + setEvent(eventResponse.data); + } + + // Load feedback form + const form = await feedbackAPI.getFeedbackFormByEventId(eventId); + if (form) { + setFeedbackForm(form); + setTitle(form.title); + setDescription(form.description || ''); + setStatus(form.status); + } else { + setError('Feedback form not found for this event'); + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to load data'; + setError(errorMessage); + logger.error(COMPONENT_NAME, 'Failed to load data', err instanceof Error ? err : new Error(String(err))); + } finally { + setLoading(false); + } + }; + + const handleSave = async () => { + if (!feedbackForm) return; + + try { + setSaving(true); + await feedbackAPI.updateFeedbackForm(feedbackForm.id, { + title, + description: description || undefined, + status, + }); + + logger.info(COMPONENT_NAME, 'Feedback form updated successfully'); + await loadData(); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to update feedback form'; + setError(errorMessage); + logger.error(COMPONENT_NAME, 'Failed to update feedback form', err instanceof Error ? err : new Error(String(err))); + } finally { + setSaving(false); + } + }; + + const handleClose = async () => { + if (!feedbackForm) return; + + try { + setSaving(true); + await feedbackAPI.closeFeedbackForm(feedbackForm.id); + logger.info(COMPONENT_NAME, 'Feedback form closed successfully'); + await loadData(); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to close feedback form'; + setError(errorMessage); + logger.error(COMPONENT_NAME, 'Failed to close feedback form', err instanceof Error ? err : new Error(String(err))); + } finally { + setSaving(false); + } + }; + + const handleDelete = async () => { + if (!feedbackForm) return; + if (!confirm('Are you sure you want to delete this feedback form? This will also delete all feedback responses.')) { + return; + } + + try { + setDeleting(true); + await feedbackAPI.deleteFeedbackForm(feedbackForm.id); + logger.info(COMPONENT_NAME, 'Feedback form deleted successfully'); + router.push(`/dashboard/admin/events/${eventId}`); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to delete feedback form'; + setError(errorMessage); + logger.error(COMPONENT_NAME, 'Failed to delete feedback form', err instanceof Error ? err : new Error(String(err))); + } finally { + setDeleting(false); + } + }; + + const handleViewResponses = async () => { + try { + setLoadingResponses(true); + setShowResponsesModal(true); + + const result = await feedbackAPI.getEventFeedbackSubmissions(eventId, 1, 100); + setResponses(result.submissions); + + logger.info(COMPONENT_NAME, 'Feedback responses loaded', { count: result.submissions.length }); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to load responses'; + setError(errorMessage); + logger.error(COMPONENT_NAME, 'Failed to load responses', err instanceof Error ? err : new Error(String(err))); + } finally { + setLoadingResponses(false); + } + }; + + const renderStars = (rating: number) => { + return Array.from({ length: 5 }, (_, i) => ( + + )); + }; + + if (loading) { + return ( +
+
+
+

Loading feedback form...

+
+
+ ); + } + + if (error && !feedbackForm) { + return ( +
+ + + +

+ {error} +

+ +
+
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+
+
+ +

+ Feedback Form Management +

+
+
+
+
+ + {/* Main Content */} +
+ {error && ( + + +

{error}

+
+
+ )} + + {event && ( + + + Event: {event.name} + + + )} + + {feedbackForm && ( + + +
+
+ Feedback Form + + {feedbackForm.status} + +
+
+
+ + {feedbackForm.responseCount} + responses +
+ {feedbackForm.averageRating && ( +
+ + + {feedbackForm.averageRating.toFixed(1)} + +
+ )} + {feedbackForm.responseCount > 0 && ( + + )} +
+
+
+ +
+
+ + setTitle(e.target.value)} + placeholder="Feedback form title" + /> +
+ +
+ +