feat: Initial commit - Task Reporter incident response system
Complete implementation of the production line incident response system (生產線異常即時反應系統) including: Backend (FastAPI): - User authentication with AD integration and session management - Chat room management (create, list, update, members, roles) - Real-time messaging via WebSocket (typing indicators, reactions) - File storage with MinIO (upload, download, image preview) Frontend (React + Vite): - Authentication flow with token management - Room list with filtering, search, and pagination - Real-time chat interface with WebSocket - File upload with drag-and-drop and image preview - Member management and room settings - Breadcrumb navigation - 53 unit tests (Vitest) Specifications: - authentication: AD auth, sessions, JWT tokens - chat-room: rooms, members, templates - realtime-messaging: WebSocket, messages, reactions - file-storage: MinIO integration, file management - frontend-core: React SPA structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
126
openspec/specs/authentication/spec.md
Normal file
126
openspec/specs/authentication/spec.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# authentication Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change add-user-authentication. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: User Login with Dual-Token Session Management
|
||||
The system SHALL authenticate users by forwarding credentials to the Panjit AD authentication API (https://pj-auth-api.vercel.app/api/auth/login). Upon successful AD authentication, the system SHALL generate its own internal session token (separate from the AD token), encrypt the user's password using AES-256 encryption (for auto-refresh capability), and store all session data in the database. The internal token is returned to the client for subsequent requests.
|
||||
|
||||
#### Scenario: Successful login with valid credentials
|
||||
- **WHEN** a user submits username "ymirliu@panjit.com.tw" and password "4RFV5tgb6yhn" to `POST /api/auth/login`
|
||||
- **THEN** the system SHALL forward the credentials to the AD API
|
||||
- **AND** receive a 200 response with `token` and `username` fields
|
||||
- **AND** encrypt the password using Fernet symmetric encryption (AES-256)
|
||||
- **AND** generate a unique internal session token (UUID4)
|
||||
- **AND** estimate or record the AD token expiry time (e.g., current_time + 1 hour)
|
||||
- **AND** create a session record in the `user_sessions` table with: username, display_name (from AD), internal_token, ad_token, encrypted_password, ad_token_expires_at, refresh_attempt_count (default 0), last_activity (current time), created_at
|
||||
- **AND** return status 200 with JSON body `{"token": "<internal_token>", "display_name": "<username>"}`
|
||||
|
||||
#### Scenario: Password stored securely with encryption
|
||||
- **WHEN** a password is stored in the user_sessions table
|
||||
- **THEN** the system SHALL encrypt it using the Fernet encryption key from environment variable FERNET_KEY
|
||||
- **AND** the encrypted_password field SHALL contain ciphertext that differs from the plaintext
|
||||
- **AND** decrypting the ciphertext SHALL reproduce the original password exactly
|
||||
|
||||
#### Scenario: Failed login with invalid credentials
|
||||
- **WHEN** a user submits incorrect username or password to `POST /api/auth/login`
|
||||
- **THEN** the system SHALL forward the credentials to the AD API
|
||||
- **AND** receive a non-200 response (e.g., 401 Unauthorized)
|
||||
- **AND** return status 401 to the client with error message `{"error": "Invalid credentials"}`
|
||||
- **AND** NOT create any session record in the database
|
||||
|
||||
#### Scenario: AD API service unavailable
|
||||
- **WHEN** a user attempts to login but the AD API (https://pj-auth-api.vercel.app) is unreachable
|
||||
- **THEN** the system SHALL return status 503 with error message `{"error": "Authentication service unavailable"}`
|
||||
|
||||
### Requirement: User Logout
|
||||
The system SHALL provide a logout endpoint that deletes the user's session record from the database.
|
||||
|
||||
#### Scenario: Successful logout with valid internal token
|
||||
- **WHEN** an authenticated user sends `POST /api/auth/logout` with header `Authorization: Bearer <valid_internal_token>`
|
||||
- **THEN** the system SHALL delete the session record from the `user_sessions` table
|
||||
- **AND** return status 200 with `{"message": "Logout successful"}`
|
||||
|
||||
#### Scenario: Logout without authentication token
|
||||
- **WHEN** a user sends `POST /api/auth/logout` without the Authorization header
|
||||
- **THEN** the system SHALL return status 401 with `{"error": "No authentication token provided"}`
|
||||
|
||||
### Requirement: Automatic AD Token Refresh with Retry Limit
|
||||
The system SHALL automatically refresh the AD token before it expires (within 5 minutes of expiry) when the user makes any API request, using the encrypted password stored in the database. The system SHALL limit auto-refresh attempts to a maximum of 3 consecutive failures, after which the session is forcibly terminated (to handle cases where the AD password has been changed).
|
||||
|
||||
#### Scenario: Auto-refresh AD token on protected route access
|
||||
- **WHEN** an authenticated user accesses a protected endpoint with a valid internal_token
|
||||
- **AND** the stored ad_token will expire in less than 5 minutes (ad_token_expires_at - now < 5 minutes)
|
||||
- **AND** refresh_attempt_count is less than 3
|
||||
- **THEN** the system SHALL decrypt the encrypted_password from the database
|
||||
- **AND** re-authenticate with the AD API using the decrypted password
|
||||
- **AND** if authentication succeeds: update ad_token, ad_token_expires_at, reset refresh_attempt_count to 0
|
||||
- **AND** update the last_activity timestamp
|
||||
- **AND** allow the request to proceed normally
|
||||
|
||||
#### Scenario: No refresh needed for fresh AD token
|
||||
- **WHEN** an authenticated user accesses a protected endpoint with a valid internal_token
|
||||
- **AND** the stored ad_token will not expire within 5 minutes
|
||||
- **THEN** the system SHALL NOT call the AD API
|
||||
- **AND** update only the last_activity timestamp
|
||||
- **AND** allow the request to proceed
|
||||
|
||||
#### Scenario: Auto-refresh fails but retry limit not reached
|
||||
- **WHEN** an authenticated user accesses a protected endpoint triggering auto-refresh
|
||||
- **AND** the AD API returns 401 (password invalid or changed)
|
||||
- **AND** refresh_attempt_count is 0, 1, or 2
|
||||
- **THEN** the system SHALL increment refresh_attempt_count by 1
|
||||
- **AND** log the failed refresh attempt with timestamp for audit
|
||||
- **AND** return status 401 with `{"error": "Token refresh failed. Please try again or re-login if issue persists."}`
|
||||
- **AND** keep the session record in the database
|
||||
|
||||
#### Scenario: Auto-refresh fails 3 consecutive times - force logout
|
||||
- **WHEN** an authenticated user accesses a protected endpoint
|
||||
- **AND** refresh_attempt_count is already 2
|
||||
- **AND** the AD API returns 401 on the 3rd refresh attempt
|
||||
- **THEN** the system SHALL increment refresh_attempt_count to 3
|
||||
- **AND** delete the session record from the database
|
||||
- **AND** log the forced logout event with reason "Password may have been changed in AD"
|
||||
- **AND** return status 401 with `{"error": "Session terminated. Your password may have been changed. Please login again."}`
|
||||
|
||||
#### Scenario: Session blocked due to previous 3 failed refresh attempts
|
||||
- **WHEN** an authenticated user accesses a protected endpoint
|
||||
- **AND** refresh_attempt_count is already 3 (from previous failed refreshes)
|
||||
- **THEN** the system SHALL delete the session record immediately
|
||||
- **AND** return status 401 with `{"error": "Session expired due to authentication failures. Please login again."}`
|
||||
|
||||
### Requirement: 3-Day Inactivity Timeout
|
||||
The system SHALL automatically invalidate user sessions that have been inactive for more than 3 days (72 hours). Inactivity is measured by the last_activity timestamp, which is updated on every API request.
|
||||
|
||||
#### Scenario: Reject request from inactive session
|
||||
- **WHEN** a user accesses a protected endpoint with an internal_token
|
||||
- **AND** the last_activity timestamp is more than 3 days (72 hours) in the past
|
||||
- **THEN** the system SHALL delete the session record from the database
|
||||
- **AND** return status 401 with `{"error": "Session expired due to inactivity. Please login again."}`
|
||||
|
||||
#### Scenario: Active user maintains session across multiple days
|
||||
- **WHEN** a user logs in on Day 1
|
||||
- **AND** makes at least one API request every 2 days (Day 2, Day 4, Day 6, etc.)
|
||||
- **THEN** the system SHALL keep the session active indefinitely
|
||||
- **AND** update last_activity on each request
|
||||
- **AND** auto-refresh the AD token as needed
|
||||
|
||||
### Requirement: Token-Based Authentication for Protected Routes
|
||||
The system SHALL validate internal session tokens for protected API endpoints by checking the `Authorization: Bearer <internal_token>` header against the user_sessions table, enforcing inactivity timeout and auto-refreshing AD tokens when necessary.
|
||||
|
||||
#### Scenario: Access protected endpoint with valid active session
|
||||
- **WHEN** a request includes header `Authorization: Bearer <valid_internal_token>`
|
||||
- **AND** the session exists in user_sessions table
|
||||
- **AND** last_activity is within 3 days
|
||||
- **THEN** the system SHALL update last_activity to current time
|
||||
- **AND** check and refresh AD token if needed (per auto-refresh requirement)
|
||||
- **AND** allow the request to proceed with user identity available
|
||||
|
||||
#### Scenario: Access protected endpoint with invalid internal token
|
||||
- **WHEN** a request includes an internal_token that does not exist in the user_sessions table
|
||||
- **THEN** the system SHALL return status 401 with `{"error": "Invalid or expired token"}`
|
||||
|
||||
#### Scenario: Access protected endpoint without token
|
||||
- **WHEN** a request to a protected endpoint omits the Authorization header
|
||||
- **THEN** the system SHALL return status 401 with `{"error": "Authentication required"}`
|
||||
|
||||
220
openspec/specs/chat-room/spec.md
Normal file
220
openspec/specs/chat-room/spec.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# chat-room Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change add-chat-room-management. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: System Administrator Role
|
||||
The system SHALL recognize ymirliu@panjit.com.tw as a system administrator with super-user privileges across all chat rooms, including the ability to override access controls, manage any room, and perform administrative operations.
|
||||
|
||||
#### Scenario: Identify system administrator
|
||||
- **WHEN** a user with email "ymirliu@panjit.com.tw" authenticates
|
||||
- **THEN** the system SHALL flag this user as a system administrator
|
||||
- **AND** grant elevated privileges for all room operations
|
||||
- **AND** log all admin actions for audit purposes
|
||||
|
||||
#### Scenario: Admin bypass room restrictions
|
||||
- **WHEN** a system administrator attempts any room operation
|
||||
- **THEN** the system SHALL allow the operation regardless of:
|
||||
- Room membership status
|
||||
- Room status (active, resolved, archived)
|
||||
- Normal role restrictions
|
||||
- **AND** record the admin override in audit log
|
||||
|
||||
### Requirement: Create Incident Room
|
||||
The system SHALL allow authenticated users to create a new incident room with metadata including title, incident type, severity level, location, and description. Each room SHALL be assigned a unique identifier and timestamp upon creation.
|
||||
|
||||
#### Scenario: Create room for equipment failure incident
|
||||
- **WHEN** an authenticated user sends `POST /api/rooms` with body:
|
||||
```json
|
||||
{
|
||||
"title": "Line 3 Conveyor Belt Stopped",
|
||||
"incident_type": "equipment_failure",
|
||||
"severity": "high",
|
||||
"location": "Building A, Line 3",
|
||||
"description": "Conveyor belt motor overheating, production halted"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL create a new incident_rooms record with:
|
||||
- Unique room_id (UUID)
|
||||
- Provided metadata fields
|
||||
- Status set to "active"
|
||||
- created_by set to current user's ID
|
||||
- created_at timestamp
|
||||
- **AND** automatically add the creator as a room member with "owner" role
|
||||
- **AND** return status 201 with the room details including room_id
|
||||
|
||||
#### Scenario: Create room with missing required fields
|
||||
- **WHEN** a user attempts to create a room without required fields (title, incident_type)
|
||||
- **THEN** the system SHALL return status 400 with validation error details
|
||||
|
||||
#### Scenario: Create room without authentication
|
||||
- **WHEN** an unauthenticated request is sent to `POST /api/rooms`
|
||||
- **THEN** the system SHALL return status 401 with "Authentication required"
|
||||
|
||||
### Requirement: List and Filter Incident Rooms
|
||||
The system SHALL provide endpoints to list incident rooms with filtering capabilities by status, incident type, severity, date range, and user membership.
|
||||
|
||||
#### Scenario: List all active rooms for current user
|
||||
- **WHEN** an authenticated user sends `GET /api/rooms?status=active`
|
||||
- **THEN** the system SHALL return all active rooms where the user is a member
|
||||
- **AND** include room metadata (title, type, severity, member count, last activity)
|
||||
- **AND** sort by last_activity_at descending (most recent first)
|
||||
|
||||
#### Scenario: Filter rooms by incident type and date range
|
||||
- **WHEN** a user sends `GET /api/rooms?incident_type=quality_issue&created_after=2025-01-01&created_before=2025-01-31`
|
||||
- **THEN** the system SHALL return rooms matching ALL filter criteria
|
||||
- **AND** only include rooms where the user is a member
|
||||
|
||||
#### Scenario: Search rooms by title or description
|
||||
- **WHEN** a user sends `GET /api/rooms?search=conveyor`
|
||||
- **THEN** the system SHALL return rooms where title OR description contains "conveyor" (case-insensitive)
|
||||
- **AND** highlight matching terms in the response
|
||||
|
||||
### Requirement: Manage Room Membership
|
||||
The system SHALL allow room owners and members with appropriate permissions to add or remove members and assign roles (owner, editor, viewer). Room owners SHALL be able to transfer ownership to another member. System administrators SHALL have override capabilities for all membership operations.
|
||||
|
||||
#### Scenario: Add member to existing room
|
||||
- **WHEN** a room owner sends `POST /api/rooms/{room_id}/members` with:
|
||||
```json
|
||||
{
|
||||
"user_id": "user123",
|
||||
"role": "editor"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL create a room_members record with specified role
|
||||
- **AND** update room's member_count
|
||||
- **AND** record added_by and added_at timestamp
|
||||
- **AND** return status 200 with updated member list
|
||||
|
||||
#### Scenario: Remove member from room
|
||||
- **WHEN** a room owner sends `DELETE /api/rooms/{room_id}/members/{user_id}`
|
||||
- **THEN** the system SHALL soft-delete the membership (set removed_at timestamp)
|
||||
- **AND** update room's member_count
|
||||
- **AND** return status 200
|
||||
|
||||
#### Scenario: Non-owner attempts to add member
|
||||
- **WHEN** a room member without owner role attempts to add another member
|
||||
- **THEN** the system SHALL return status 403 with "Insufficient permissions"
|
||||
- **UNLESS** the user is a system administrator, in which case the operation succeeds
|
||||
|
||||
#### Scenario: Change member role
|
||||
- **WHEN** a room owner sends `PATCH /api/rooms/{room_id}/members/{user_id}` with:
|
||||
```json
|
||||
{
|
||||
"role": "viewer"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL update the member's role
|
||||
- **AND** record the change in audit log
|
||||
|
||||
#### Scenario: Transfer room ownership
|
||||
- **WHEN** a room owner sends `POST /api/rooms/{room_id}/transfer-ownership` with:
|
||||
```json
|
||||
{
|
||||
"new_owner_id": "user456"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL verify the new owner is an existing room member
|
||||
- **AND** update the new owner's role to "owner"
|
||||
- **AND** change the previous owner's role to "editor"
|
||||
- **AND** record the ownership transfer in audit log with timestamp
|
||||
- **AND** return status 200 with updated member list
|
||||
|
||||
#### Scenario: Admin override - Add member to any room
|
||||
- **WHEN** a system administrator (ymirliu@panjit.com.tw) adds a member to any room
|
||||
- **THEN** the system SHALL allow the operation regardless of room membership
|
||||
- **AND** record the admin action in audit log
|
||||
|
||||
### Requirement: Update Room Status and Metadata
|
||||
The system SHALL allow room owners to update room metadata and transition room status through its lifecycle (active → resolved → archived).
|
||||
|
||||
#### Scenario: Mark room as resolved
|
||||
- **WHEN** a room owner sends `PATCH /api/rooms/{room_id}` with:
|
||||
```json
|
||||
{
|
||||
"status": "resolved",
|
||||
"resolution_notes": "Replaced motor, production resumed"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL update status to "resolved"
|
||||
- **AND** set resolved_at timestamp
|
||||
- **AND** store resolution_notes
|
||||
- **AND** keep room accessible in read-only mode for members
|
||||
|
||||
#### Scenario: Update room metadata
|
||||
- **WHEN** a room owner sends `PATCH /api/rooms/{room_id}` with:
|
||||
```json
|
||||
{
|
||||
"severity": "critical",
|
||||
"description": "Updated: Fire hazard detected"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL update only the provided fields
|
||||
- **AND** record the update in room_activity log
|
||||
- **AND** set last_updated_at timestamp
|
||||
|
||||
#### Scenario: Archive resolved room
|
||||
- **WHEN** a room owner sends `PATCH /api/rooms/{room_id}` with status "archived"
|
||||
- **AND** the room is already in "resolved" status
|
||||
- **THEN** the system SHALL update status to "archived"
|
||||
- **AND** set archived_at timestamp
|
||||
- **AND** make room read-only for all members
|
||||
|
||||
#### Scenario: Invalid status transition
|
||||
- **WHEN** attempting to change status from "active" directly to "archived"
|
||||
- **THEN** the system SHALL return status 400 with "Invalid status transition"
|
||||
|
||||
### Requirement: Room Access Control
|
||||
The system SHALL enforce role-based access control for all room operations based on user membership and assigned roles. System administrators SHALL have full access to all rooms regardless of membership status.
|
||||
|
||||
#### Scenario: Access room details as member
|
||||
- **WHEN** a room member sends `GET /api/rooms/{room_id}`
|
||||
- **THEN** the system SHALL return full room details including:
|
||||
- Room metadata
|
||||
- Member list with roles
|
||||
- Activity summary
|
||||
- Current user's role and permissions
|
||||
|
||||
#### Scenario: Access room without membership
|
||||
- **WHEN** a non-member sends `GET /api/rooms/{room_id}`
|
||||
- **THEN** the system SHALL return status 403 with "Not a member of this room"
|
||||
- **UNLESS** the user is a system administrator, in which case full access is granted
|
||||
|
||||
#### Scenario: List user's permissions in room
|
||||
- **WHEN** a member sends `GET /api/rooms/{room_id}/permissions`
|
||||
- **THEN** the system SHALL return their role and specific permissions:
|
||||
- Owner: all operations including ownership transfer
|
||||
- Editor: read, write messages, upload files
|
||||
- Viewer: read only
|
||||
- Admin: all operations plus system override capabilities
|
||||
|
||||
#### Scenario: Admin access to all rooms
|
||||
- **WHEN** a system administrator (ymirliu@panjit.com.tw) sends `GET /api/rooms?all=true`
|
||||
- **THEN** the system SHALL return ALL rooms in the system, not just rooms where admin is a member
|
||||
- **AND** include an "is_admin_view" flag in the response
|
||||
|
||||
### Requirement: Room Templates
|
||||
The system SHALL support predefined room templates for common incident types to streamline room creation with preset metadata and initial members.
|
||||
|
||||
#### Scenario: Create room from template
|
||||
- **WHEN** a user sends `POST /api/rooms` with:
|
||||
```json
|
||||
{
|
||||
"template": "equipment_failure",
|
||||
"title": "Molding Machine #5 Down",
|
||||
"location": "Building B"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL create room with:
|
||||
- Preset incident_type from template
|
||||
- Default severity level from template
|
||||
- Auto-assigned team members based on template configuration
|
||||
- Template-specific metadata fields
|
||||
|
||||
#### Scenario: List available templates
|
||||
- **WHEN** a user sends `GET /api/room-templates`
|
||||
- **THEN** the system SHALL return available templates with:
|
||||
- Template name and description
|
||||
- Default values for each template
|
||||
- Required additional fields
|
||||
|
||||
266
openspec/specs/file-storage/spec.md
Normal file
266
openspec/specs/file-storage/spec.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# file-storage Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change add-file-upload-minio. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: File Upload with Validation
|
||||
The system SHALL accept multipart file uploads to incident rooms, validate file type and size, and persist files to MinIO object storage with metadata tracking in PostgreSQL.
|
||||
|
||||
#### Scenario: Upload image to incident room
|
||||
- **WHEN** a user with OWNER or EDITOR role uploads an image file via `POST /api/rooms/{room_id}/files`
|
||||
```http
|
||||
POST /api/rooms/room-123/files
|
||||
Content-Type: multipart/form-data
|
||||
Authorization: Bearer {jwt_token}
|
||||
|
||||
file: [binary data of defect.jpg, 2.5MB]
|
||||
description: "Defect found on product batch A-45"
|
||||
```
|
||||
- **THEN** the system SHALL:
|
||||
- Validate JWT token and extract user_id
|
||||
- Verify user is member of room-123 with OWNER or EDITOR role
|
||||
- Validate file MIME type is image/jpeg, image/png, or image/gif
|
||||
- Validate file size ≤ 10MB
|
||||
- Generate unique file_id (UUID)
|
||||
- Upload file to MinIO bucket `task-reporter-files` at path `room-123/images/{file_id}.jpg`
|
||||
- Create database record in `room_files` table
|
||||
- Return file metadata with presigned download URL (1-hour expiry)
|
||||
|
||||
#### Scenario: Reject oversized file upload
|
||||
- **WHEN** a user attempts to upload a 15MB PDF file
|
||||
- **THEN** the system SHALL:
|
||||
- Detect file size exceeds 20MB limit for documents
|
||||
- Return 413 Payload Too Large error
|
||||
- Include error message: "File size exceeds limit: 15MB > 20MB"
|
||||
- NOT upload file to MinIO
|
||||
- NOT create database record
|
||||
|
||||
#### Scenario: Reject unauthorized file type
|
||||
- **WHEN** a user attempts to upload an executable file (e.g., .exe, .sh, .bat)
|
||||
- **THEN** the system SHALL:
|
||||
- Detect MIME type not in whitelist
|
||||
- Return 400 Bad Request error
|
||||
- Include error message: "File type not allowed: application/x-msdownload"
|
||||
- NOT upload file to MinIO
|
||||
- NOT create database record
|
||||
|
||||
#### Scenario: Upload log file to incident room
|
||||
- **WHEN** an engineer uploads a machine log file
|
||||
```http
|
||||
POST /api/rooms/room-456/files
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
file: [machine_error.log, 1.2MB]
|
||||
description: "Equipment error log from 2025-11-17"
|
||||
```
|
||||
- **THEN** the system SHALL:
|
||||
- Validate MIME type is text/plain
|
||||
- Upload to MinIO at `room-456/logs/{file_id}.log`
|
||||
- Store metadata with file_type='log'
|
||||
- Return success response with file_id
|
||||
|
||||
### Requirement: File Download with Access Control
|
||||
The system SHALL generate time-limited presigned download URLs for files, enforcing room membership-based access control.
|
||||
|
||||
#### Scenario: Download file with valid membership
|
||||
- **WHEN** a user who is member of room-123 requests `GET /api/rooms/room-123/files/{file_id}`
|
||||
- **THEN** the system SHALL:
|
||||
- Validate user is member of room-123
|
||||
- Retrieve file metadata from database
|
||||
- Generate MinIO presigned URL with 1-hour expiry
|
||||
- Return JSON response:
|
||||
```json
|
||||
{
|
||||
"file_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"filename": "defect.jpg",
|
||||
"file_type": "image",
|
||||
"file_size": 2621440,
|
||||
"download_url": "http://localhost:9000/task-reporter-files/room-123/images/...?X-Amz-Expires=3600",
|
||||
"uploaded_at": "2025-11-17T10:30:00Z",
|
||||
"uploader_id": "operator@panjit.com.tw"
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: Reject download for non-member
|
||||
- **WHEN** a user who is NOT member of room-123 requests file from that room
|
||||
- **THEN** the system SHALL:
|
||||
- Validate room membership
|
||||
- Return 403 Forbidden error
|
||||
- Include error message: "You are not a member of this room"
|
||||
- NOT generate presigned URL
|
||||
- Log unauthorized access attempt
|
||||
|
||||
#### Scenario: Download deleted file
|
||||
- **WHEN** a user requests a file where `deleted_at` is NOT NULL
|
||||
- **THEN** the system SHALL:
|
||||
- Return 404 Not Found error
|
||||
- Include error message: "File has been deleted"
|
||||
- NOT generate presigned URL
|
||||
|
||||
### Requirement: File Metadata Management
|
||||
The system SHALL maintain comprehensive metadata for all uploaded files, support file listing with pagination, and implement soft delete for audit trail preservation.
|
||||
|
||||
#### Scenario: List files in incident room
|
||||
- **WHEN** a user requests `GET /api/rooms/room-123/files?limit=20&offset=0`
|
||||
- **THEN** the system SHALL:
|
||||
- Validate user is room member
|
||||
- Query `room_files` table filtered by room_id
|
||||
- Exclude soft-deleted files (deleted_at IS NULL)
|
||||
- Order by uploaded_at DESC
|
||||
- Return paginated response:
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"file_id": "...",
|
||||
"filename": "defect.jpg",
|
||||
"file_type": "image",
|
||||
"file_size": 2621440,
|
||||
"uploaded_at": "2025-11-17T10:30:00Z",
|
||||
"uploader_id": "operator@panjit.com.tw",
|
||||
"description": "Defect found on product"
|
||||
}
|
||||
],
|
||||
"total": 45,
|
||||
"limit": 20,
|
||||
"offset": 0,
|
||||
"has_more": true
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: Filter files by type
|
||||
- **WHEN** a user requests `GET /api/rooms/room-123/files?file_type=image`
|
||||
- **THEN** the system SHALL:
|
||||
- Filter results where file_type='image'
|
||||
- Return only image files
|
||||
- Include pagination metadata
|
||||
|
||||
#### Scenario: Soft delete file
|
||||
- **WHEN** a file uploader or room OWNER requests `DELETE /api/rooms/room-123/files/{file_id}`
|
||||
- **THEN** the system SHALL:
|
||||
- Validate user is uploader OR room OWNER
|
||||
- Set `deleted_at = NOW()` in database
|
||||
- NOT delete file from MinIO (preserve for audit)
|
||||
- Return 204 No Content
|
||||
- Broadcast file deletion event via WebSocket
|
||||
|
||||
#### Scenario: Prevent deletion by non-owner
|
||||
- **WHEN** a user who is neither uploader nor room OWNER attempts DELETE
|
||||
- **THEN** the system SHALL:
|
||||
- Return 403 Forbidden error
|
||||
- Include error message: "Only file uploader or room owner can delete files"
|
||||
- NOT modify database record
|
||||
|
||||
### Requirement: MinIO Integration and Connection Management
|
||||
The system SHALL maintain persistent connection pool to MinIO server, handle connection failures gracefully, and support bucket initialization.
|
||||
|
||||
#### Scenario: Initialize MinIO connection on startup
|
||||
- **WHEN** the FastAPI application starts
|
||||
- **THEN** the system SHALL:
|
||||
- Read MinIO configuration from environment variables (MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY)
|
||||
- Initialize Minio client instance
|
||||
- Check if bucket `task-reporter-files` exists
|
||||
- Create bucket if not exists with appropriate permissions
|
||||
- Log successful connection: "MinIO connected: localhost:9000"
|
||||
|
||||
#### Scenario: Handle MinIO connection failure
|
||||
- **WHEN** MinIO service is unreachable during file upload
|
||||
- **THEN** the system SHALL:
|
||||
- Catch connection exception
|
||||
- Return 503 Service Unavailable error
|
||||
- Include error message: "File storage service temporarily unavailable"
|
||||
- Log error with stack trace
|
||||
- NOT create database record
|
||||
|
||||
#### Scenario: Retry failed MinIO upload
|
||||
- **WHEN** MinIO upload fails with transient error (e.g., timeout)
|
||||
- **THEN** the system SHALL:
|
||||
- Retry upload up to 3 times with exponential backoff
|
||||
- On success after retry, proceed normally
|
||||
- On failure after 3 retries, return 500 Internal Server Error
|
||||
- Log retry attempts for debugging
|
||||
|
||||
### Requirement: Realtime File Upload Notifications
|
||||
The system SHALL broadcast file upload events to all room members via WebSocket, enabling instant file availability notifications.
|
||||
|
||||
#### Scenario: Broadcast file upload to room members
|
||||
- **WHEN** a file upload completes successfully
|
||||
- **THEN** the system SHALL:
|
||||
- Retrieve all active WebSocket connections for the room
|
||||
- Broadcast message to all connected members:
|
||||
```json
|
||||
{
|
||||
"type": "file_uploaded",
|
||||
"room_id": "room-123",
|
||||
"file": {
|
||||
"file_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"filename": "defect.jpg",
|
||||
"file_type": "image",
|
||||
"file_size": 2621440,
|
||||
"uploader_id": "operator@panjit.com.tw",
|
||||
"uploaded_at": "2025-11-17T10:30:00Z",
|
||||
"thumbnail_url": "http://localhost:9000/task-reporter-files/room-123/images/..."
|
||||
},
|
||||
"timestamp": "2025-11-17T10:30:01Z"
|
||||
}
|
||||
```
|
||||
- Connected clients SHALL update UI to display new file
|
||||
|
||||
#### Scenario: Send file upload acknowledgment to uploader
|
||||
- **WHEN** file upload completes
|
||||
- **THEN** the system SHALL:
|
||||
- Send personal WebSocket message to uploader:
|
||||
```json
|
||||
{
|
||||
"type": "file_upload_ack",
|
||||
"file_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"status": "success",
|
||||
"download_url": "http://localhost:9000/...",
|
||||
"timestamp": "2025-11-17T10:30:01Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: Broadcast file deletion event
|
||||
- **WHEN** a file is soft-deleted
|
||||
- **THEN** the system SHALL:
|
||||
- Broadcast to all room members:
|
||||
```json
|
||||
{
|
||||
"type": "file_deleted",
|
||||
"room_id": "room-123",
|
||||
"file_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"deleted_by": "supervisor@panjit.com.tw",
|
||||
"timestamp": "2025-11-17T11:00:00Z"
|
||||
}
|
||||
```
|
||||
- Connected clients SHALL remove file from UI
|
||||
|
||||
### Requirement: File Type Detection and Security
|
||||
The system SHALL validate file types using MIME type detection (not just file extension), prevent malicious file uploads, and enforce strict content-type validation.
|
||||
|
||||
#### Scenario: Detect real MIME type regardless of extension
|
||||
- **WHEN** a user uploads a file named "image.jpg" but actual content is PDF
|
||||
- **THEN** the system SHALL:
|
||||
- Use `python-magic` to detect actual MIME type (application/pdf)
|
||||
- Reject upload with error: "File content does not match extension"
|
||||
- Log potential security violation
|
||||
- Return 400 Bad Request
|
||||
|
||||
#### Scenario: Allow only whitelisted file types
|
||||
- **WHEN** validating uploaded file
|
||||
- **THEN** the system SHALL:
|
||||
- Check MIME type against whitelist:
|
||||
- Images: image/jpeg, image/png, image/gif
|
||||
- Documents: application/pdf
|
||||
- Logs: text/plain, text/csv
|
||||
- Reject any MIME type not in whitelist
|
||||
- Return 400 Bad Request with specific error
|
||||
|
||||
#### Scenario: Prevent script file uploads
|
||||
- **WHEN** a user attempts to upload .js, .sh, .bat, .exe, or other executable
|
||||
- **THEN** the system SHALL:
|
||||
- Detect script/executable MIME type (application/x-sh, application/javascript, etc.)
|
||||
- Return 400 Bad Request error: "Executable files are not allowed"
|
||||
- NOT upload to MinIO
|
||||
- Log security event
|
||||
|
||||
272
openspec/specs/frontend-core/spec.md
Normal file
272
openspec/specs/frontend-core/spec.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# frontend-core Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change add-react-frontend. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: User Authentication Interface
|
||||
The frontend SHALL provide a login interface that authenticates users against the backend API and maintains session state.
|
||||
|
||||
#### Scenario: Successful login
|
||||
- **WHEN** a user enters valid AD credentials and submits the login form
|
||||
- **THEN** the system SHALL:
|
||||
- Send POST request to `/api/auth/login`
|
||||
- Store the returned token in localStorage
|
||||
- Redirect user to the room list page
|
||||
- Display the user's display name in the navigation
|
||||
|
||||
#### Scenario: Failed login with invalid credentials
|
||||
- **WHEN** a user enters invalid credentials
|
||||
- **THEN** the system SHALL:
|
||||
- Display error message "Invalid credentials"
|
||||
- Keep user on login page
|
||||
- Clear the password field
|
||||
|
||||
#### Scenario: Session persistence across page refresh
|
||||
- **WHEN** a user refreshes the page while logged in
|
||||
- **THEN** the system SHALL:
|
||||
- Retrieve token from localStorage
|
||||
- Validate session is still active
|
||||
- Restore user session without requiring re-login
|
||||
|
||||
#### Scenario: Logout
|
||||
- **WHEN** a logged-in user clicks the logout button
|
||||
- **THEN** the system SHALL:
|
||||
- Send POST request to `/api/auth/logout`
|
||||
- Clear token from localStorage
|
||||
- Redirect to login page
|
||||
|
||||
### Requirement: Incident Room List
|
||||
The frontend SHALL display a filterable, searchable list of incident rooms accessible to the current user.
|
||||
|
||||
#### Scenario: Display room list
|
||||
- **WHEN** a logged-in user navigates to the room list page
|
||||
- **THEN** the system SHALL:
|
||||
- Fetch rooms from `GET /api/rooms`
|
||||
- Display rooms as cards with title, status, severity, and timestamp
|
||||
- Show the user's role in each room
|
||||
- Order by last activity (most recent first)
|
||||
|
||||
#### Scenario: Filter rooms by status
|
||||
- **WHEN** a user selects a status filter (Active, Resolved, Archived)
|
||||
- **THEN** the system SHALL:
|
||||
- Update the room list to show only rooms matching the filter
|
||||
- Preserve other active filters
|
||||
|
||||
#### Scenario: Search rooms
|
||||
- **WHEN** a user enters text in the search box
|
||||
- **THEN** the system SHALL:
|
||||
- Filter rooms by title and description containing the search text
|
||||
- Update results in real-time (debounced)
|
||||
|
||||
#### Scenario: Create new room
|
||||
- **WHEN** a user clicks "New Room" and fills the creation form
|
||||
- **THEN** the system SHALL:
|
||||
- Display template selection if templates exist
|
||||
- Submit room creation to `POST /api/rooms`
|
||||
- Navigate to the new room on success
|
||||
- Show error message on failure
|
||||
|
||||
### Requirement: Incident Room Detail View
|
||||
The frontend SHALL display complete incident room information including metadata, members, and provide management controls for authorized users.
|
||||
|
||||
#### Scenario: View room details
|
||||
- **WHEN** a user navigates to a room detail page
|
||||
- **THEN** the system SHALL:
|
||||
- Fetch room details from `GET /api/rooms/{room_id}`
|
||||
- Display room title, status, severity, location, description
|
||||
- Show member list with roles
|
||||
- Display created timestamp and last activity
|
||||
|
||||
#### Scenario: Update room metadata (Owner/Editor)
|
||||
- **WHEN** an owner or editor updates room fields (title, severity, description)
|
||||
- **THEN** the system SHALL:
|
||||
- Submit changes to `PATCH /api/rooms/{room_id}`
|
||||
- Update the UI optimistically
|
||||
- Revert on failure with error message
|
||||
|
||||
#### Scenario: Change room status (Owner only)
|
||||
- **WHEN** a room owner changes status to Resolved or Archived
|
||||
- **THEN** the system SHALL:
|
||||
- Prompt for confirmation
|
||||
- Submit status change to backend
|
||||
- Update room header to reflect new status
|
||||
- Broadcast status change to connected members
|
||||
|
||||
#### Scenario: View room with insufficient permissions
|
||||
- **WHEN** a user tries to access a room they are not a member of
|
||||
- **THEN** the system SHALL:
|
||||
- Display "Access Denied" message
|
||||
- Provide option to request access or return to room list
|
||||
|
||||
### Requirement: Real-time Chat Interface
|
||||
The frontend SHALL provide a real-time chat interface using WebSocket connections for instant message delivery.
|
||||
|
||||
#### Scenario: Connect to room chat
|
||||
- **WHEN** a user opens a room detail page
|
||||
- **THEN** the system SHALL:
|
||||
- Establish WebSocket connection to `/api/ws/{room_id}?token={token}`
|
||||
- Load message history from REST API
|
||||
- Display connection status indicator
|
||||
- Subscribe to real-time message updates
|
||||
|
||||
#### Scenario: Send text message
|
||||
- **WHEN** a user types a message and presses Enter or clicks Send
|
||||
- **THEN** the system SHALL:
|
||||
- Send message via WebSocket
|
||||
- Display message immediately (optimistic update)
|
||||
- Show delivery confirmation when acknowledged
|
||||
- Show error indicator if delivery fails
|
||||
|
||||
#### Scenario: Receive message from other user
|
||||
- **WHEN** another user sends a message to the room
|
||||
- **THEN** the system SHALL:
|
||||
- Display the new message in the chat
|
||||
- Scroll to the new message if user is at bottom
|
||||
- Show notification badge if user has scrolled up
|
||||
|
||||
#### Scenario: Display typing indicator
|
||||
- **WHEN** another user is typing
|
||||
- **THEN** the system SHALL:
|
||||
- Show "{username} is typing..." indicator
|
||||
- Hide indicator after 3 seconds of inactivity
|
||||
|
||||
#### Scenario: Edit own message
|
||||
- **WHEN** a user edits their own message within edit window
|
||||
- **THEN** the system SHALL:
|
||||
- Display edit input pre-filled with original content
|
||||
- Submit edit via WebSocket
|
||||
- Update message with "edited" indicator
|
||||
|
||||
#### Scenario: Delete own message
|
||||
- **WHEN** a user deletes their own message
|
||||
- **THEN** the system SHALL:
|
||||
- Prompt for confirmation
|
||||
- Submit delete via WebSocket
|
||||
- Remove or mark message as deleted in UI
|
||||
|
||||
#### Scenario: WebSocket reconnection
|
||||
- **WHEN** WebSocket connection is lost
|
||||
- **THEN** the system SHALL:
|
||||
- Display "Reconnecting..." indicator
|
||||
- Attempt reconnection with exponential backoff
|
||||
- Restore connection and sync missed messages
|
||||
- Show error after max retry attempts
|
||||
|
||||
### Requirement: File Upload and Management Interface
|
||||
The frontend SHALL provide file upload capabilities with progress indication, preview for images, and file management controls.
|
||||
|
||||
#### Scenario: Upload file via button
|
||||
- **WHEN** a user clicks the upload button and selects a file
|
||||
- **THEN** the system SHALL:
|
||||
- Validate file type and size client-side
|
||||
- Display upload progress bar
|
||||
- Upload to `POST /api/rooms/{room_id}/files`
|
||||
- Show file in chat/file list on success
|
||||
- Display error message on failure
|
||||
|
||||
#### Scenario: Upload file via drag and drop
|
||||
- **WHEN** a user drags a file into the chat area
|
||||
- **THEN** the system SHALL:
|
||||
- Show drop zone indicator
|
||||
- Accept the file on drop
|
||||
- Proceed with upload as button upload
|
||||
|
||||
#### Scenario: Preview image file
|
||||
- **WHEN** a user clicks on an uploaded image
|
||||
- **THEN** the system SHALL:
|
||||
- Open image in a modal preview
|
||||
- Allow zoom and pan
|
||||
- Provide download button
|
||||
|
||||
#### Scenario: Download file
|
||||
- **WHEN** a user clicks download on a file
|
||||
- **THEN** the system SHALL:
|
||||
- Fetch presigned URL from `GET /api/rooms/{room_id}/files/{file_id}`
|
||||
- Trigger browser download with original filename
|
||||
|
||||
#### Scenario: Delete file (uploader or owner)
|
||||
- **WHEN** file uploader or room owner clicks delete on a file
|
||||
- **THEN** the system SHALL:
|
||||
- Prompt for confirmation
|
||||
- Submit delete to `DELETE /api/rooms/{room_id}/files/{file_id}`
|
||||
- Remove file from UI
|
||||
|
||||
#### Scenario: View file list
|
||||
- **WHEN** a user opens the files panel in a room
|
||||
- **THEN** the system SHALL:
|
||||
- Fetch files from `GET /api/rooms/{room_id}/files`
|
||||
- Display files with thumbnails for images
|
||||
- Show filename, size, uploader, and timestamp
|
||||
- Support filtering by file type
|
||||
|
||||
### Requirement: Member Management Interface
|
||||
The frontend SHALL provide member management controls for room owners and editors with appropriate role-based access.
|
||||
|
||||
#### Scenario: View member list
|
||||
- **WHEN** a user views a room
|
||||
- **THEN** the system SHALL:
|
||||
- Display all active members
|
||||
- Show each member's role (Owner, Editor, Viewer)
|
||||
- Indicate online status for connected members
|
||||
|
||||
#### Scenario: Add member (Owner/Editor)
|
||||
- **WHEN** an owner or editor adds a new member
|
||||
- **THEN** the system SHALL:
|
||||
- Display member search/input field
|
||||
- Allow role selection
|
||||
- Submit to `POST /api/rooms/{room_id}/members`
|
||||
- Update member list on success
|
||||
|
||||
#### Scenario: Change member role (Owner only)
|
||||
- **WHEN** an owner changes another member's role
|
||||
- **THEN** the system SHALL:
|
||||
- Display role selector
|
||||
- Submit to `PATCH /api/rooms/{room_id}/members/{user_id}`
|
||||
- Update member display on success
|
||||
|
||||
#### Scenario: Remove member (Owner/Editor)
|
||||
- **WHEN** an owner or editor removes a member
|
||||
- **THEN** the system SHALL:
|
||||
- Prompt for confirmation
|
||||
- Submit to `DELETE /api/rooms/{room_id}/members/{user_id}`
|
||||
- Remove member from list
|
||||
|
||||
#### Scenario: Transfer ownership (Owner only)
|
||||
- **WHEN** a room owner transfers ownership to another member
|
||||
- **THEN** the system SHALL:
|
||||
- Prompt for confirmation with warning
|
||||
- Submit to `POST /api/rooms/{room_id}/transfer-ownership`
|
||||
- Update roles in UI (current owner becomes Editor)
|
||||
|
||||
### Requirement: Responsive Layout and Navigation
|
||||
The frontend SHALL provide a responsive layout that works on desktop and tablet devices with intuitive navigation.
|
||||
|
||||
#### Scenario: Desktop layout
|
||||
- **WHEN** viewed on desktop (>1024px)
|
||||
- **THEN** the system SHALL:
|
||||
- Display sidebar navigation
|
||||
- Show room list and detail side-by-side when applicable
|
||||
- Display member panel as sidebar
|
||||
|
||||
#### Scenario: Tablet layout
|
||||
- **WHEN** viewed on tablet (768px-1024px)
|
||||
- **THEN** the system SHALL:
|
||||
- Collapse sidebar to icons or hamburger menu
|
||||
- Use full width for content areas
|
||||
- Stack panels vertically
|
||||
|
||||
#### Scenario: Navigation between pages
|
||||
- **WHEN** a user navigates using browser back/forward
|
||||
- **THEN** the system SHALL:
|
||||
- Maintain correct route state
|
||||
- Preserve scroll position where applicable
|
||||
- Not lose unsent message drafts
|
||||
|
||||
#### Scenario: Error handling
|
||||
- **WHEN** an API request fails
|
||||
- **THEN** the system SHALL:
|
||||
- Display user-friendly error message
|
||||
- Provide retry option where applicable
|
||||
- Log error details for debugging
|
||||
- Not crash or show blank screen
|
||||
|
||||
194
openspec/specs/realtime-messaging/spec.md
Normal file
194
openspec/specs/realtime-messaging/spec.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# realtime-messaging Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change add-realtime-messaging. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: WebSocket Connection Management
|
||||
The system SHALL provide WebSocket endpoints for establishing persistent bidirectional connections between clients and server, with automatic reconnection handling and connection state management.
|
||||
|
||||
#### Scenario: Establish WebSocket connection
|
||||
- **WHEN** an authenticated user connects to `ws://localhost:8000/ws/{room_id}`
|
||||
- **THEN** the system SHALL validate user's room membership
|
||||
- **AND** establish a WebSocket connection
|
||||
- **AND** add the connection to the room's active connections pool
|
||||
- **AND** broadcast a "user joined" event to other room members
|
||||
|
||||
#### Scenario: Handle connection authentication
|
||||
- **WHEN** a WebSocket connection request is made without valid authentication token
|
||||
- **THEN** the system SHALL reject the connection with status 401
|
||||
- **AND** close the WebSocket immediately
|
||||
|
||||
#### Scenario: Automatic reconnection
|
||||
- **WHEN** a WebSocket connection is dropped unexpectedly
|
||||
- **THEN** the client SHALL attempt to reconnect automatically with exponential backoff
|
||||
- **AND** resume from the last received message sequence number
|
||||
- **AND** request any missed messages during disconnection
|
||||
|
||||
### Requirement: Real-time Message Broadcasting
|
||||
The system SHALL broadcast messages to all active room members in real-time, ensuring message ordering and delivery acknowledgment.
|
||||
|
||||
#### Scenario: Send text message
|
||||
- **WHEN** a room member sends a message via WebSocket:
|
||||
```json
|
||||
{
|
||||
"type": "message",
|
||||
"content": "Equipment temperature rising to 85°C",
|
||||
"message_type": "text"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL:
|
||||
- Validate user has write permission (OWNER or EDITOR role)
|
||||
- Assign a unique message_id and timestamp
|
||||
- Store the message in database
|
||||
- Broadcast to all active WebSocket connections in the room
|
||||
- Return acknowledgment to sender with message_id
|
||||
|
||||
#### Scenario: Send system notification
|
||||
- **WHEN** a system event occurs (user joined, room status changed, etc.)
|
||||
- **THEN** the system SHALL broadcast a system message:
|
||||
```json
|
||||
{
|
||||
"type": "system",
|
||||
"event": "user_joined",
|
||||
"user_id": "john.doe@panjit.com.tw",
|
||||
"timestamp": "2025-11-17T10:00:00Z"
|
||||
}
|
||||
```
|
||||
- **AND** all connected clients SHALL display the notification
|
||||
|
||||
#### Scenario: Handle message ordering
|
||||
- **WHEN** multiple messages are sent simultaneously
|
||||
- **THEN** the system SHALL ensure FIFO ordering using message sequence numbers
|
||||
- **AND** clients SHALL display messages in the correct order
|
||||
|
||||
### Requirement: Message Persistence and History
|
||||
The system SHALL persist all messages to database for audit trail, report generation, and history retrieval.
|
||||
|
||||
#### Scenario: Store message in database
|
||||
- **WHEN** a message is sent through WebSocket
|
||||
- **THEN** the system SHALL create a database record with:
|
||||
- message_id (UUID)
|
||||
- room_id (FK to incident_rooms)
|
||||
- sender_id (user email)
|
||||
- content (text or JSON for structured messages)
|
||||
- message_type (text, image_ref, file_ref, system)
|
||||
- created_at timestamp
|
||||
- edited_at (nullable for message edits)
|
||||
- deleted_at (nullable for soft delete)
|
||||
- sequence_number (for ordering)
|
||||
|
||||
#### Scenario: Retrieve message history
|
||||
- **WHEN** a user joins a room or reconnects
|
||||
- **THEN** the system SHALL load recent messages via `GET /api/rooms/{room_id}/messages?limit=50&before={timestamp}`
|
||||
- **AND** return messages in reverse chronological order
|
||||
- **AND** include pagination metadata for loading more history
|
||||
|
||||
#### Scenario: Search messages
|
||||
- **WHEN** a user searches for messages containing specific keywords
|
||||
- **THEN** the system SHALL query the database with full-text search
|
||||
- **AND** return matching messages with highlighted search terms
|
||||
- **AND** maintain user's access control (only rooms they're members of)
|
||||
|
||||
### Requirement: Message Types and Formatting
|
||||
The system SHALL support various message types including text, image references, file references, and structured data for production incidents.
|
||||
|
||||
#### Scenario: Text message with mentions
|
||||
- **WHEN** a user sends a message with @mentions
|
||||
```json
|
||||
{
|
||||
"content": "@maintenance_team Please check Line 3 immediately",
|
||||
"mentions": ["maintenance_team@panjit.com.tw"]
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL parse and store mentions
|
||||
- **AND** potentially trigger notifications to mentioned users
|
||||
|
||||
#### Scenario: Image reference message
|
||||
- **WHEN** a user uploads an image and sends reference
|
||||
```json
|
||||
{
|
||||
"type": "message",
|
||||
"message_type": "image_ref",
|
||||
"content": "Defect found on product",
|
||||
"file_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"file_url": "http://localhost:9000/bucket/room-123/image.jpg"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL store the file reference
|
||||
- **AND** clients SHALL display image preview inline
|
||||
|
||||
#### Scenario: Structured incident data
|
||||
- **WHEN** reporting specific incident metrics
|
||||
```json
|
||||
{
|
||||
"type": "message",
|
||||
"message_type": "incident_data",
|
||||
"content": {
|
||||
"temperature": 85,
|
||||
"pressure": 120,
|
||||
"production_rate": 450,
|
||||
"timestamp": "2025-11-17T10:15:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL store structured data as JSON
|
||||
- **AND** enable querying/filtering by specific fields later
|
||||
|
||||
### Requirement: Connection State Management
|
||||
The system SHALL track online presence and typing indicators for better collaboration experience.
|
||||
|
||||
#### Scenario: Track online users
|
||||
- **WHEN** users connect/disconnect from a room
|
||||
- **THEN** the system SHALL maintain a list of online users
|
||||
- **AND** broadcast presence updates to all room members
|
||||
- **AND** display online status indicators in UI
|
||||
|
||||
#### Scenario: Typing indicators
|
||||
- **WHEN** a user starts typing a message
|
||||
- **THEN** the client SHALL send a "typing" event via WebSocket
|
||||
- **AND** the system SHALL broadcast to other room members
|
||||
- **AND** automatically clear typing status after 3 seconds of inactivity
|
||||
|
||||
#### Scenario: Connection health monitoring
|
||||
- **WHEN** a WebSocket connection is established
|
||||
- **THEN** the system SHALL send ping frames every 30 seconds
|
||||
- **AND** expect pong responses within 10 seconds
|
||||
- **AND** terminate connection if no response received
|
||||
|
||||
### Requirement: Message Operations
|
||||
The system SHALL support message editing and deletion with proper audit trail and permissions.
|
||||
|
||||
#### Scenario: Edit own message
|
||||
- **WHEN** a user edits their own message within 15 minutes
|
||||
```json
|
||||
{
|
||||
"type": "edit_message",
|
||||
"message_id": "msg-123",
|
||||
"content": "Updated: Equipment temperature stabilized at 75°C"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL update the message content
|
||||
- **AND** set edited_at timestamp
|
||||
- **AND** broadcast the edit to all connected clients
|
||||
- **AND** preserve original message in audit log
|
||||
|
||||
#### Scenario: Delete message
|
||||
- **WHEN** a user deletes their own message or admin deletes any message
|
||||
- **THEN** the system SHALL perform soft delete (set deleted_at)
|
||||
- **AND** broadcast deletion event to all clients
|
||||
- **AND** clients SHALL show "message deleted" placeholder
|
||||
- **AND** preserve message in database for audit
|
||||
|
||||
#### Scenario: React to message
|
||||
- **WHEN** a user adds a reaction emoji to a message
|
||||
```json
|
||||
{
|
||||
"type": "add_reaction",
|
||||
"message_id": "msg-123",
|
||||
"emoji": "👍"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL store the reaction
|
||||
- **AND** broadcast to all connected clients
|
||||
- **AND** aggregate reaction counts for display
|
||||
|
||||
Reference in New Issue
Block a user