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:
@@ -0,0 +1,602 @@
|
||||
# Chat Room Management API Documentation
|
||||
|
||||
## 概述
|
||||
|
||||
此 API 提供生產線異常事件室管理功能,支援建立、管理與協作處理生產線異常事件。
|
||||
|
||||
**Base URL**: `/api/rooms`
|
||||
|
||||
**認證**: 所有端點需要 Bearer Token 認證(除非另有說明)
|
||||
|
||||
---
|
||||
|
||||
## 端點列表
|
||||
|
||||
### 1. 房間管理 (Room CRUD)
|
||||
|
||||
#### 1.1 建立新房間
|
||||
```http
|
||||
POST /api/rooms
|
||||
```
|
||||
|
||||
**請求體**:
|
||||
```json
|
||||
{
|
||||
"title": "設備故障 - A線",
|
||||
"incident_type": "EQUIPMENT_FAILURE",
|
||||
"severity": "HIGH",
|
||||
"location": "生產線 A",
|
||||
"description": "主要輸送帶停止運作",
|
||||
"template": "equipment_failure" // 可選:使用範本建立
|
||||
}
|
||||
```
|
||||
|
||||
**回應** (201 Created):
|
||||
```json
|
||||
{
|
||||
"room_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "設備故障 - A線",
|
||||
"incident_type": "EQUIPMENT_FAILURE",
|
||||
"severity": "HIGH",
|
||||
"status": "ACTIVE",
|
||||
"location": "生產線 A",
|
||||
"description": "主要輸送帶停止運作",
|
||||
"created_by": "user@panjit.com.tw",
|
||||
"created_at": "2025-11-17T08:00:00Z",
|
||||
"last_activity_at": "2025-11-17T08:00:00Z",
|
||||
"member_count": 1,
|
||||
"current_user_role": "OWNER"
|
||||
}
|
||||
```
|
||||
|
||||
**incident_type 列舉值**:
|
||||
- `EQUIPMENT_FAILURE` - 設備故障
|
||||
- `MATERIAL_SHORTAGE` - 物料短缺
|
||||
- `QUALITY_ISSUE` - 品質問題
|
||||
- `OTHER` - 其他
|
||||
|
||||
**severity 列舉值**:
|
||||
- `LOW` - 低
|
||||
- `MEDIUM` - 中
|
||||
- `HIGH` - 高
|
||||
- `CRITICAL` - 緊急
|
||||
|
||||
---
|
||||
|
||||
#### 1.2 列出房間
|
||||
```http
|
||||
GET /api/rooms?status=ACTIVE&limit=20&offset=0
|
||||
```
|
||||
|
||||
**查詢參數**:
|
||||
- `status` (可選): ACTIVE | RESOLVED | ARCHIVED
|
||||
- `incident_type` (可選): 事件類型篩選
|
||||
- `severity` (可選): 嚴重程度篩選
|
||||
- `search` (可選): 搜尋關鍵字(搜尋標題、描述、地點)
|
||||
- `all` (可選, 僅管理員): true 時顯示所有房間(非僅自己的)
|
||||
- `limit` (預設: 20): 每頁筆數 (1-100)
|
||||
- `offset` (預設: 0): 分頁偏移
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
{
|
||||
"rooms": [
|
||||
{
|
||||
"room_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "設備故障 - A線",
|
||||
"incident_type": "EQUIPMENT_FAILURE",
|
||||
"severity": "HIGH",
|
||||
"status": "ACTIVE",
|
||||
"current_user_role": "OWNER",
|
||||
"member_count": 3,
|
||||
"created_at": "2025-11-17T08:00:00Z"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"limit": 20,
|
||||
"offset": 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 1.3 取得房間詳情
|
||||
```http
|
||||
GET /api/rooms/{room_id}
|
||||
```
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
{
|
||||
"room_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "設備故障 - A線",
|
||||
"incident_type": "EQUIPMENT_FAILURE",
|
||||
"severity": "HIGH",
|
||||
"status": "ACTIVE",
|
||||
"location": "生產線 A",
|
||||
"description": "主要輸送帶停止運作",
|
||||
"resolution_notes": null,
|
||||
"created_by": "user@panjit.com.tw",
|
||||
"created_at": "2025-11-17T08:00:00Z",
|
||||
"resolved_at": null,
|
||||
"archived_at": null,
|
||||
"last_activity_at": "2025-11-17T08:30:00Z",
|
||||
"member_count": 3,
|
||||
"ownership_transferred_at": null,
|
||||
"ownership_transferred_by": null,
|
||||
"current_user_role": "OWNER",
|
||||
"members": [
|
||||
{
|
||||
"user_id": "user@panjit.com.tw",
|
||||
"role": "OWNER",
|
||||
"added_by": "system",
|
||||
"added_at": "2025-11-17T08:00:00Z"
|
||||
},
|
||||
{
|
||||
"user_id": "engineer@panjit.com.tw",
|
||||
"role": "EDITOR",
|
||||
"added_by": "user@panjit.com.tw",
|
||||
"added_at": "2025-11-17T08:15:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**錯誤回應**:
|
||||
- `404 Not Found`: 房間不存在或無存取權限
|
||||
|
||||
---
|
||||
|
||||
#### 1.4 更新房間
|
||||
```http
|
||||
PATCH /api/rooms/{room_id}
|
||||
```
|
||||
|
||||
**權限**: OWNER 或 ADMIN
|
||||
|
||||
**請求體** (所有欄位可選):
|
||||
```json
|
||||
{
|
||||
"title": "設備故障 - A線(已修復)",
|
||||
"severity": "MEDIUM",
|
||||
"status": "RESOLVED",
|
||||
"description": "更新的描述",
|
||||
"resolution_notes": "更換了傳動皮帶"
|
||||
}
|
||||
```
|
||||
|
||||
**回應** (200 OK): 同 1.3
|
||||
|
||||
**錯誤回應**:
|
||||
- `403 Forbidden`: 無權限更新
|
||||
- `404 Not Found`: 房間不存在
|
||||
|
||||
---
|
||||
|
||||
#### 1.5 刪除房間(軟刪除/封存)
|
||||
```http
|
||||
DELETE /api/rooms/{room_id}
|
||||
```
|
||||
|
||||
**權限**: OWNER 或 ADMIN
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
{
|
||||
"message": "Room archived successfully"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 成員管理 (Membership)
|
||||
|
||||
#### 2.1 列出房間成員
|
||||
```http
|
||||
GET /api/rooms/{room_id}/members
|
||||
```
|
||||
|
||||
**權限**: 房間成員
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
[
|
||||
{
|
||||
"user_id": "user@panjit.com.tw",
|
||||
"role": "OWNER",
|
||||
"added_by": "system",
|
||||
"added_at": "2025-11-17T08:00:00Z"
|
||||
},
|
||||
{
|
||||
"user_id": "engineer@panjit.com.tw",
|
||||
"role": "EDITOR",
|
||||
"added_by": "user@panjit.com.tw",
|
||||
"added_at": "2025-11-17T08:15:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**role 列舉值**:
|
||||
- `OWNER` - 擁有者(完全控制權)
|
||||
- `EDITOR` - 編輯者(可讀寫、新增檢視者)
|
||||
- `VIEWER` - 檢視者(僅可讀)
|
||||
|
||||
---
|
||||
|
||||
#### 2.2 新增成員
|
||||
```http
|
||||
POST /api/rooms/{room_id}/members
|
||||
```
|
||||
|
||||
**權限**:
|
||||
- OWNER 可新增任何角色
|
||||
- EDITOR 可新增 VIEWER
|
||||
|
||||
**請求體**:
|
||||
```json
|
||||
{
|
||||
"user_id": "engineer@panjit.com.tw",
|
||||
"role": "EDITOR"
|
||||
}
|
||||
```
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
{
|
||||
"user_id": "engineer@panjit.com.tw",
|
||||
"role": "EDITOR",
|
||||
"added_by": "user@panjit.com.tw",
|
||||
"added_at": "2025-11-17T08:15:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**錯誤回應**:
|
||||
- `400 Bad Request`: 使用者已是成員
|
||||
- `403 Forbidden`: 無權限新增成員
|
||||
|
||||
---
|
||||
|
||||
#### 2.3 更新成員角色
|
||||
```http
|
||||
PATCH /api/rooms/{room_id}/members/{user_id}
|
||||
```
|
||||
|
||||
**權限**: OWNER 或 ADMIN
|
||||
|
||||
**請求體**:
|
||||
```json
|
||||
{
|
||||
"role": "EDITOR"
|
||||
}
|
||||
```
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
{
|
||||
"user_id": "engineer@panjit.com.tw",
|
||||
"role": "EDITOR",
|
||||
"added_by": "user@panjit.com.tw",
|
||||
"added_at": "2025-11-17T08:15:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**錯誤回應**:
|
||||
- `403 Forbidden`: 僅 OWNER 可變更角色
|
||||
- `404 Not Found`: 成員不存在
|
||||
|
||||
---
|
||||
|
||||
#### 2.4 移除成員
|
||||
```http
|
||||
DELETE /api/rooms/{room_id}/members/{user_id}
|
||||
```
|
||||
|
||||
**權限**:
|
||||
- OWNER 可移除任何成員
|
||||
- EDITOR 可移除 VIEWER
|
||||
- 不可移除最後一個 OWNER
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
{
|
||||
"message": "Member removed successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**錯誤回應**:
|
||||
- `400 Bad Request`: 無法移除最後一個 OWNER
|
||||
- `403 Forbidden`: 無權限移除
|
||||
- `404 Not Found`: 成員不存在
|
||||
|
||||
---
|
||||
|
||||
#### 2.5 轉移所有權 ⭐
|
||||
```http
|
||||
POST /api/rooms/{room_id}/transfer-ownership
|
||||
```
|
||||
|
||||
**權限**: 僅限當前 OWNER
|
||||
|
||||
**請求體**:
|
||||
```json
|
||||
{
|
||||
"new_owner_id": "engineer@panjit.com.tw"
|
||||
}
|
||||
```
|
||||
|
||||
**行為**:
|
||||
- 新 owner 必須是現有房間成員
|
||||
- 原 owner 自動降級為 EDITOR
|
||||
- 新 owner 升級為 OWNER
|
||||
- 記錄轉移時間與操作者
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
{
|
||||
"message": "Ownership transferred successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**錯誤回應**:
|
||||
- `400 Bad Request`: 新 owner 不是房間成員
|
||||
- `403 Forbidden`: 僅 OWNER 可轉移所有權
|
||||
|
||||
---
|
||||
|
||||
### 3. 權限查詢
|
||||
|
||||
#### 3.1 取得當前使用者權限
|
||||
```http
|
||||
GET /api/rooms/{room_id}/permissions
|
||||
```
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
{
|
||||
"role": "OWNER",
|
||||
"is_admin": false,
|
||||
"can_read": true,
|
||||
"can_write": true,
|
||||
"can_manage_members": true,
|
||||
"can_transfer_ownership": true,
|
||||
"can_update_status": true,
|
||||
"can_delete": true
|
||||
}
|
||||
```
|
||||
|
||||
**管理員回應** (ymirliu@panjit.com.tw):
|
||||
```json
|
||||
{
|
||||
"role": "OWNER",
|
||||
"is_admin": true,
|
||||
"can_read": true,
|
||||
"can_write": true,
|
||||
"can_manage_members": true,
|
||||
"can_transfer_ownership": true,
|
||||
"can_update_status": true,
|
||||
"can_delete": true
|
||||
}
|
||||
```
|
||||
|
||||
**權限矩陣**:
|
||||
|
||||
| 權限 | OWNER | EDITOR | VIEWER | ADMIN |
|
||||
|-----|-------|--------|--------|-------|
|
||||
| can_read | ✓ | ✓ | ✓ | ✓ |
|
||||
| can_write | ✓ | ✓ | ✗ | ✓ |
|
||||
| can_manage_members | ✓ | 部分¹ | ✗ | ✓ |
|
||||
| can_transfer_ownership | ✓ | ✗ | ✗ | ✓ |
|
||||
| can_update_status | ✓ | ✗ | ✗ | ✓ |
|
||||
| can_delete | ✓ | ✗ | ✗ | ✓ |
|
||||
|
||||
¹ EDITOR 僅可新增 VIEWER
|
||||
|
||||
---
|
||||
|
||||
### 4. 範本
|
||||
|
||||
#### 4.1 列出可用範本
|
||||
```http
|
||||
GET /api/rooms/templates
|
||||
```
|
||||
|
||||
**回應** (200 OK):
|
||||
```json
|
||||
[
|
||||
{
|
||||
"template_id": "1",
|
||||
"name": "equipment_failure",
|
||||
"description": "設備故障事件需要立即處理",
|
||||
"incident_type": "EQUIPMENT_FAILURE",
|
||||
"default_severity": "HIGH",
|
||||
"default_members": [
|
||||
{
|
||||
"user_id": "maintenance_team@panjit.com.tw",
|
||||
"role": "EDITOR"
|
||||
},
|
||||
{
|
||||
"user_id": "engineering@panjit.com.tw",
|
||||
"role": "VIEWER"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"template_id": "2",
|
||||
"name": "material_shortage",
|
||||
"description": "物料短缺影響生產",
|
||||
"incident_type": "MATERIAL_SHORTAGE",
|
||||
"default_severity": "MEDIUM",
|
||||
"default_members": [
|
||||
{
|
||||
"user_id": "procurement@panjit.com.tw",
|
||||
"role": "EDITOR"
|
||||
},
|
||||
{
|
||||
"user_id": "logistics@panjit.com.tw",
|
||||
"role": "EDITOR"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"template_id": "3",
|
||||
"name": "quality_issue",
|
||||
"description": "品質問題需要調查",
|
||||
"incident_type": "QUALITY_ISSUE",
|
||||
"default_severity": "HIGH",
|
||||
"default_members": [
|
||||
{
|
||||
"user_id": "quality_team@panjit.com.tw",
|
||||
"role": "EDITOR"
|
||||
},
|
||||
{
|
||||
"user_id": "production_manager@panjit.com.tw",
|
||||
"role": "VIEWER"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 錯誤碼
|
||||
|
||||
所有錯誤回應格式:
|
||||
```json
|
||||
{
|
||||
"detail": "錯誤訊息"
|
||||
}
|
||||
```
|
||||
|
||||
### 常見錯誤碼
|
||||
|
||||
| HTTP 狀態碼 | 說明 |
|
||||
|-----------|-----|
|
||||
| 400 Bad Request | 請求資料無效或不符合業務規則 |
|
||||
| 401 Unauthorized | 未認證或 Token 無效 |
|
||||
| 403 Forbidden | 無權限執行此操作 |
|
||||
| 404 Not Found | 資源不存在或無存取權限 |
|
||||
| 500 Internal Server Error | 伺服器內部錯誤 |
|
||||
|
||||
### 範例錯誤訊息
|
||||
|
||||
```json
|
||||
// 無權限
|
||||
{
|
||||
"detail": "Insufficient permissions"
|
||||
}
|
||||
|
||||
// 房間不存在
|
||||
{
|
||||
"detail": "Room not found"
|
||||
}
|
||||
|
||||
// 認證失敗
|
||||
{
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
|
||||
// 重複成員
|
||||
{
|
||||
"detail": "User is already a member"
|
||||
}
|
||||
|
||||
// 無效的所有權轉移
|
||||
{
|
||||
"detail": "New owner must be an existing room member"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 系統管理員特權
|
||||
|
||||
**管理員帳號**: `ymirliu@panjit.com.tw`
|
||||
|
||||
### 管理員權限:
|
||||
1. **覆寫所有權限限制** - 無視角色限制執行任何操作
|
||||
2. **查看所有房間** - 使用 `?all=true` 參數查看系統中所有房間
|
||||
3. **強制更新任何房間** - 即使不是成員也可更新
|
||||
4. **刪除任何房間** - 無需為 OWNER
|
||||
5. **管理所有成員** - 新增/移除/變更任何成員角色
|
||||
|
||||
### 稽核追蹤:
|
||||
所有管理員操作都會記錄在 `last_updated_at` 和相關稽核欄位中。
|
||||
|
||||
---
|
||||
|
||||
## 狀態轉換規則
|
||||
|
||||
房間狀態必須按照以下順序轉換:
|
||||
|
||||
```
|
||||
ACTIVE (活躍)
|
||||
↓
|
||||
RESOLVED (已解決)
|
||||
↓
|
||||
ARCHIVED (已封存)
|
||||
```
|
||||
|
||||
- ✓ ACTIVE → RESOLVED
|
||||
- ✓ RESOLVED → ARCHIVED
|
||||
- ✗ ACTIVE → ARCHIVED (不允許跳過)
|
||||
- ✗ 反向轉換 (不允許)
|
||||
|
||||
---
|
||||
|
||||
## 範例使用流程
|
||||
|
||||
### 流程 1: 建立並處理設備故障事件
|
||||
|
||||
```bash
|
||||
# 1. 使用範本建立房間
|
||||
POST /api/rooms
|
||||
{
|
||||
"title": "CNC 機台 A 故障",
|
||||
"location": "廠區 B",
|
||||
"description": "主軸無法正常運轉",
|
||||
"template": "equipment_failure"
|
||||
}
|
||||
|
||||
# 2. 新增工程師到房間
|
||||
POST /api/rooms/{room_id}/members
|
||||
{
|
||||
"user_id": "engineer_john@panjit.com.tw",
|
||||
"role": "EDITOR"
|
||||
}
|
||||
|
||||
# 3. 更新事件狀態為已解決
|
||||
PATCH /api/rooms/{room_id}
|
||||
{
|
||||
"status": "RESOLVED",
|
||||
"resolution_notes": "更換主軸軸承,測試正常"
|
||||
}
|
||||
|
||||
# 4. 封存事件
|
||||
DELETE /api/rooms/{room_id}
|
||||
```
|
||||
|
||||
### 流程 2: 所有權轉移
|
||||
|
||||
```bash
|
||||
# 1. 原 owner 將所有權轉移給其他成員
|
||||
POST /api/rooms/{room_id}/transfer-ownership
|
||||
{
|
||||
"new_owner_id": "new_owner@panjit.com.tw"
|
||||
}
|
||||
|
||||
# 結果:
|
||||
# - new_owner@panjit.com.tw 成為 OWNER
|
||||
# - 原 owner 降級為 EDITOR
|
||||
# - 記錄轉移時間與操作者
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自動生成的 API 文檔
|
||||
|
||||
FastAPI 自動生成互動式 API 文檔:
|
||||
|
||||
- **Swagger UI**: `http://localhost:8000/docs`
|
||||
- **ReDoc**: `http://localhost:8000/redoc`
|
||||
- **OpenAPI Schema**: `http://localhost:8000/openapi.json`
|
||||
@@ -0,0 +1,555 @@
|
||||
# Database Schema Documentation
|
||||
|
||||
## 概述
|
||||
|
||||
Chat Room 模組使用三個主要資料表來管理事件室、成員關係與範本。本文檔說明資料庫架構、關聯關係、索引策略與設計考量。
|
||||
|
||||
---
|
||||
|
||||
## ER Diagram (實體關聯圖)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ incident_rooms │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ PK │ room_id (VARCHAR(36), UUID) │
|
||||
│ │ title (VARCHAR(255), NOT NULL) │
|
||||
│ │ incident_type (ENUM, NOT NULL) │
|
||||
│ │ severity (ENUM, NOT NULL) │
|
||||
│ │ status (ENUM, DEFAULT 'ACTIVE') │
|
||||
│ │ location (VARCHAR(255)) │
|
||||
│ │ description (TEXT) │
|
||||
│ │ resolution_notes (TEXT, NULLABLE) │
|
||||
│ │ created_by (VARCHAR(255), NOT NULL) │
|
||||
│ │ created_at (TIMESTAMP, DEFAULT NOW) │
|
||||
│ │ resolved_at (TIMESTAMP, NULLABLE) │
|
||||
│ │ archived_at (TIMESTAMP, NULLABLE) │
|
||||
│ │ last_activity_at (TIMESTAMP, DEFAULT NOW) │
|
||||
│ │ last_updated_at (TIMESTAMP, DEFAULT NOW) │
|
||||
│ │ member_count (INTEGER, DEFAULT 0) │
|
||||
│ │ ownership_transferred_at (TIMESTAMP, NULLABLE) ⭐ │
|
||||
│ │ ownership_transferred_by (VARCHAR(255), NULLABLE) ⭐ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ 1:N
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ room_members │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ PK │ id (INTEGER, AUTO_INCREMENT) │
|
||||
│ FK │ room_id (VARCHAR(36)) ──→ incident_rooms.room_id │
|
||||
│ │ user_id (VARCHAR(255), NOT NULL) │
|
||||
│ │ role (ENUM, NOT NULL) │
|
||||
│ │ added_by (VARCHAR(255), NOT NULL) │
|
||||
│ │ added_at (TIMESTAMP, DEFAULT NOW) │
|
||||
│ │ removed_at (TIMESTAMP, NULLABLE) ← 軟刪除 │
|
||||
│ │ │
|
||||
│ │ UNIQUE (room_id, user_id) WHERE removed_at IS NULL │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ room_templates │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ PK │ template_id (INTEGER, AUTO_INCREMENT) │
|
||||
│ │ name (VARCHAR(100), UNIQUE, NOT NULL) │
|
||||
│ │ description (TEXT) │
|
||||
│ │ incident_type (ENUM, NOT NULL) │
|
||||
│ │ default_severity (ENUM, NOT NULL) │
|
||||
│ │ default_members (JSON) │
|
||||
│ │ metadata_fields (JSON, NULLABLE) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 資料表詳細說明
|
||||
|
||||
### 1. incident_rooms (事件室)
|
||||
|
||||
**用途**: 儲存生產線異常事件室的核心資訊
|
||||
|
||||
#### 欄位說明
|
||||
|
||||
| 欄位名稱 | 資料型別 | 約束 | 說明 |
|
||||
|---------|---------|------|-----|
|
||||
| `room_id` | VARCHAR(36) | PK, NOT NULL | UUID 格式的房間唯一識別碼 |
|
||||
| `title` | VARCHAR(255) | NOT NULL | 事件標題 |
|
||||
| `incident_type` | ENUM | NOT NULL | 事件類型:EQUIPMENT_FAILURE, MATERIAL_SHORTAGE, QUALITY_ISSUE, OTHER |
|
||||
| `severity` | ENUM | NOT NULL | 嚴重程度:LOW, MEDIUM, HIGH, CRITICAL |
|
||||
| `status` | ENUM | DEFAULT 'ACTIVE' | 房間狀態:ACTIVE, RESOLVED, ARCHIVED |
|
||||
| `location` | VARCHAR(255) | | 事件發生地點 |
|
||||
| `description` | TEXT | | 事件詳細描述 |
|
||||
| `resolution_notes` | TEXT | NULLABLE | 解決方案說明(狀態為 RESOLVED 時填寫) |
|
||||
| `created_by` | VARCHAR(255) | NOT NULL | 建立者的使用者 ID(信箱) |
|
||||
| `created_at` | TIMESTAMP | DEFAULT NOW | 建立時間 |
|
||||
| `resolved_at` | TIMESTAMP | NULLABLE | 標記為已解決的時間 |
|
||||
| `archived_at` | TIMESTAMP | NULLABLE | 封存時間(軟刪除標記) |
|
||||
| `last_activity_at` | TIMESTAMP | DEFAULT NOW | 最後活動時間(用於排序) |
|
||||
| `last_updated_at` | TIMESTAMP | DEFAULT NOW | 最後更新時間 |
|
||||
| `member_count` | INTEGER | DEFAULT 0 | 當前活躍成員數量 |
|
||||
| `ownership_transferred_at` | TIMESTAMP | NULLABLE | **所有權轉移時間** ⭐ |
|
||||
| `ownership_transferred_by` | VARCHAR(255) | NULLABLE | **執行轉移的使用者** ⭐ |
|
||||
|
||||
#### 索引策略
|
||||
|
||||
```sql
|
||||
-- 主鍵索引
|
||||
CREATE UNIQUE INDEX pk_incident_rooms ON incident_rooms(room_id);
|
||||
|
||||
-- 複合索引:用於按狀態與時間查詢
|
||||
CREATE INDEX ix_incident_rooms_status_created
|
||||
ON incident_rooms(status, created_at DESC);
|
||||
|
||||
-- 單欄索引:用於查詢特定使用者建立的房間
|
||||
CREATE INDEX ix_incident_rooms_created_by
|
||||
ON incident_rooms(created_by);
|
||||
|
||||
-- 用於查詢活躍房間(軟刪除)
|
||||
CREATE INDEX ix_incident_rooms_archived
|
||||
ON incident_rooms(archived_at)
|
||||
WHERE archived_at IS NULL;
|
||||
```
|
||||
|
||||
#### 設計考量
|
||||
|
||||
1. **UUID 主鍵**: 使用 UUID 而非自增 ID,避免 ID 可預測性問題
|
||||
2. **軟刪除**: 使用 `archived_at` 進行軟刪除,保留歷史記錄
|
||||
3. **冗餘欄位**: `member_count` 為冗餘欄位,提升查詢效能
|
||||
4. **稽核欄位**: `ownership_transferred_at` 和 `ownership_transferred_by` 用於追蹤所有權變更
|
||||
|
||||
---
|
||||
|
||||
### 2. room_members (房間成員)
|
||||
|
||||
**用途**: 管理房間成員關係與權限
|
||||
|
||||
#### 欄位說明
|
||||
|
||||
| 欄位名稱 | 資料型別 | 約束 | 說明 |
|
||||
|---------|---------|------|-----|
|
||||
| `id` | INTEGER | PK, AUTO_INCREMENT | 自增主鍵 |
|
||||
| `room_id` | VARCHAR(36) | FK, NOT NULL | 關聯到 incident_rooms.room_id |
|
||||
| `user_id` | VARCHAR(255) | NOT NULL | 使用者 ID(信箱) |
|
||||
| `role` | ENUM | NOT NULL | 角色:OWNER, EDITOR, VIEWER |
|
||||
| `added_by` | VARCHAR(255) | NOT NULL | 新增此成員的使用者 |
|
||||
| `added_at` | TIMESTAMP | DEFAULT NOW | 加入時間 |
|
||||
| `removed_at` | TIMESTAMP | NULLABLE | 移除時間(軟刪除標記) |
|
||||
|
||||
#### 約束條件
|
||||
|
||||
```sql
|
||||
-- 外鍵約束
|
||||
ALTER TABLE room_members
|
||||
ADD CONSTRAINT fk_room_members_room_id
|
||||
FOREIGN KEY (room_id) REFERENCES incident_rooms(room_id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
-- 唯一性約束:同一房間中,每個使用者只能有一個活躍成員記錄
|
||||
-- SQLite
|
||||
CREATE UNIQUE INDEX ix_room_members_unique_active
|
||||
ON room_members(room_id, user_id)
|
||||
WHERE removed_at IS NULL;
|
||||
|
||||
-- PostgreSQL
|
||||
CREATE UNIQUE INDEX ix_room_members_unique_active
|
||||
ON room_members(room_id, user_id)
|
||||
WHERE removed_at IS NULL;
|
||||
```
|
||||
|
||||
#### 索引策略
|
||||
|
||||
```sql
|
||||
-- 複合索引:用於快速查詢房間的特定成員
|
||||
CREATE INDEX ix_room_members_room_user
|
||||
ON room_members(room_id, user_id);
|
||||
|
||||
-- 單欄索引:用於查詢使用者參與的所有房間
|
||||
CREATE INDEX ix_room_members_user
|
||||
ON room_members(user_id);
|
||||
|
||||
-- 用於查詢活躍成員
|
||||
CREATE INDEX ix_room_members_active
|
||||
ON room_members(removed_at)
|
||||
WHERE removed_at IS NULL;
|
||||
```
|
||||
|
||||
#### 設計考量
|
||||
|
||||
1. **軟刪除**: 使用 `removed_at` 而非實際刪除記錄
|
||||
- 優點:保留成員歷史、支援稽核、可恢復誤刪
|
||||
- 缺點:需要在所有查詢中加入 `WHERE removed_at IS NULL`
|
||||
|
||||
2. **角色設計**: 簡單的 ENUM 而非獨立的角色表
|
||||
- 適合固定的少量角色
|
||||
- 若未來需要動態角色,應改為關聯表設計
|
||||
|
||||
3. **級聯刪除**: 當房間刪除時,自動刪除所有成員記錄
|
||||
- 配合軟刪除機制,實際不會觸發(房間只會被標記為 archived)
|
||||
|
||||
---
|
||||
|
||||
### 3. room_templates (房間範本)
|
||||
|
||||
**用途**: 儲存預定義的房間範本,用於快速建立標準化事件室
|
||||
|
||||
#### 欄位說明
|
||||
|
||||
| 欄位名稱 | 資料型別 | 約束 | 說明 |
|
||||
|---------|---------|------|-----|
|
||||
| `template_id` | INTEGER | PK, AUTO_INCREMENT | 範本唯一識別碼 |
|
||||
| `name` | VARCHAR(100) | UNIQUE, NOT NULL | 範本名稱(如 "equipment_failure") |
|
||||
| `description` | TEXT | | 範本說明 |
|
||||
| `incident_type` | ENUM | NOT NULL | 預設事件類型 |
|
||||
| `default_severity` | ENUM | NOT NULL | 預設嚴重程度 |
|
||||
| `default_members` | JSON | | 預設成員列表(JSON 陣列) |
|
||||
| `metadata_fields` | JSON | NULLABLE | 擴展用元資料欄位 |
|
||||
|
||||
#### JSON 欄位範例
|
||||
|
||||
```json
|
||||
// default_members 範例
|
||||
[
|
||||
{
|
||||
"user_id": "maintenance_team@panjit.com.tw",
|
||||
"role": "editor"
|
||||
},
|
||||
{
|
||||
"user_id": "engineering@panjit.com.tw",
|
||||
"role": "viewer"
|
||||
}
|
||||
]
|
||||
|
||||
// metadata_fields 範例(未來擴展)
|
||||
{
|
||||
"required_fields": ["equipment_id", "line_number"],
|
||||
"custom_fields": [
|
||||
{
|
||||
"name": "equipment_id",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 索引策略
|
||||
|
||||
```sql
|
||||
-- 唯一索引:範本名稱必須唯一
|
||||
CREATE UNIQUE INDEX ix_room_templates_name
|
||||
ON room_templates(name);
|
||||
|
||||
-- 單欄索引:用於按類型查詢範本
|
||||
CREATE INDEX ix_room_templates_incident_type
|
||||
ON room_templates(incident_type);
|
||||
```
|
||||
|
||||
#### 設計考量
|
||||
|
||||
1. **JSON 欄位**: 使用 JSON 儲存結構化資料
|
||||
- 優點:彈性高、易於擴展
|
||||
- 缺點:無法建立索引、查詢效能較差
|
||||
- 適用場景:讀取頻繁、寫入較少、資料結構可能變動
|
||||
|
||||
2. **預設範本**: 系統啟動時自動初始化三個預設範本
|
||||
- equipment_failure(設備故障)
|
||||
- material_shortage(物料短缺)
|
||||
- quality_issue(品質問題)
|
||||
|
||||
---
|
||||
|
||||
## 關聯關係
|
||||
|
||||
### 1. incident_rooms ↔ room_members (1:N)
|
||||
|
||||
```sql
|
||||
-- 一個房間可以有多個成員
|
||||
SELECT r.*, m.user_id, m.role
|
||||
FROM incident_rooms r
|
||||
LEFT JOIN room_members m ON r.room_id = m.room_id
|
||||
WHERE m.removed_at IS NULL
|
||||
AND r.archived_at IS NULL;
|
||||
```
|
||||
|
||||
### 2. 使用者 ↔ room_members ↔ incident_rooms (M:N)
|
||||
|
||||
```sql
|
||||
-- 一個使用者可以參與多個房間
|
||||
-- 透過 room_members 作為中介表實現多對多關係
|
||||
SELECT r.*
|
||||
FROM incident_rooms r
|
||||
INNER JOIN room_members m ON r.room_id = m.room_id
|
||||
WHERE m.user_id = 'user@panjit.com.tw'
|
||||
AND m.removed_at IS NULL
|
||||
AND r.archived_at IS NULL
|
||||
ORDER BY r.last_activity_at DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常見查詢模式與優化
|
||||
|
||||
### 1. 列出使用者的所有活躍房間
|
||||
|
||||
```sql
|
||||
-- 優化前(N+1 問題)
|
||||
SELECT * FROM incident_rooms WHERE room_id IN (
|
||||
SELECT room_id FROM room_members
|
||||
WHERE user_id = ? AND removed_at IS NULL
|
||||
);
|
||||
|
||||
-- 優化後(使用 JOIN)
|
||||
SELECT r.*, m.role as user_role
|
||||
FROM incident_rooms r
|
||||
INNER JOIN room_members m ON r.room_id = m.room_id
|
||||
WHERE m.user_id = ?
|
||||
AND m.removed_at IS NULL
|
||||
AND r.archived_at IS NULL
|
||||
ORDER BY r.last_activity_at DESC
|
||||
LIMIT ? OFFSET ?;
|
||||
|
||||
-- 索引使用:
|
||||
-- - ix_room_members_user (user_id)
|
||||
-- - ix_incident_rooms_archived (archived_at)
|
||||
```
|
||||
|
||||
### 2. 取得房間詳情與所有成員
|
||||
|
||||
```sql
|
||||
-- 優化:使用單一查詢避免 N+1
|
||||
SELECT
|
||||
r.*,
|
||||
json_group_array(
|
||||
json_object(
|
||||
'user_id', m.user_id,
|
||||
'role', m.role,
|
||||
'added_at', m.added_at
|
||||
)
|
||||
) as members
|
||||
FROM incident_rooms r
|
||||
LEFT JOIN room_members m ON r.room_id = m.room_id AND m.removed_at IS NULL
|
||||
WHERE r.room_id = ?
|
||||
GROUP BY r.room_id;
|
||||
|
||||
-- 索引使用:
|
||||
-- - pk_incident_rooms (room_id)
|
||||
-- - ix_room_members_room_user (room_id, user_id)
|
||||
```
|
||||
|
||||
### 3. 搜尋房間(全文搜尋)
|
||||
|
||||
```sql
|
||||
-- SQLite FTS5(全文搜尋)
|
||||
CREATE VIRTUAL TABLE incident_rooms_fts USING fts5(
|
||||
room_id UNINDEXED,
|
||||
title,
|
||||
description,
|
||||
location
|
||||
);
|
||||
|
||||
-- 搜尋查詢
|
||||
SELECT r.*
|
||||
FROM incident_rooms r
|
||||
INNER JOIN incident_rooms_fts fts ON r.room_id = fts.room_id
|
||||
WHERE fts MATCH '設備 OR 故障'
|
||||
AND r.archived_at IS NULL;
|
||||
|
||||
-- PostgreSQL(使用 pg_trgm)
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
CREATE INDEX ix_incident_rooms_search
|
||||
ON incident_rooms USING gin (
|
||||
to_tsvector('chinese', title || ' ' || description || ' ' || location)
|
||||
);
|
||||
|
||||
SELECT *
|
||||
FROM incident_rooms
|
||||
WHERE to_tsvector('chinese', title || ' ' || description || ' ' || location)
|
||||
@@ to_tsquery('chinese', '設備 & 故障')
|
||||
AND archived_at IS NULL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 資料完整性與約束
|
||||
|
||||
### 1. 業務規則約束
|
||||
|
||||
```python
|
||||
# 在應用層實施的約束
|
||||
|
||||
# 1. 房間至少要有一個 OWNER
|
||||
def validate_room_has_owner(db, room_id):
|
||||
owner_count = db.query(RoomMember).filter(
|
||||
RoomMember.room_id == room_id,
|
||||
RoomMember.role == MemberRole.OWNER,
|
||||
RoomMember.removed_at.is_(None)
|
||||
).count()
|
||||
|
||||
if owner_count == 0:
|
||||
raise ValueError("Room must have at least one OWNER")
|
||||
|
||||
# 2. 狀態轉換規則
|
||||
VALID_TRANSITIONS = {
|
||||
RoomStatus.ACTIVE: [RoomStatus.RESOLVED],
|
||||
RoomStatus.RESOLVED: [RoomStatus.ARCHIVED],
|
||||
RoomStatus.ARCHIVED: []
|
||||
}
|
||||
|
||||
def validate_status_transition(current_status, new_status):
|
||||
if new_status not in VALID_TRANSITIONS.get(current_status, []):
|
||||
raise ValueError(f"Invalid transition: {current_status} -> {new_status}")
|
||||
```
|
||||
|
||||
### 2. 資料庫級約束
|
||||
|
||||
```sql
|
||||
-- CHECK 約束(僅 PostgreSQL 支援)
|
||||
ALTER TABLE incident_rooms
|
||||
ADD CONSTRAINT chk_severity CHECK (
|
||||
severity IN ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL')
|
||||
);
|
||||
|
||||
ALTER TABLE room_members
|
||||
ADD CONSTRAINT chk_role CHECK (
|
||||
role IN ('OWNER', 'EDITOR', 'VIEWER')
|
||||
);
|
||||
|
||||
-- 確保 resolved_at 晚於 created_at
|
||||
ALTER TABLE incident_rooms
|
||||
ADD CONSTRAINT chk_resolved_after_created CHECK (
|
||||
resolved_at IS NULL OR resolved_at >= created_at
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 效能考量
|
||||
|
||||
### 1. 索引策略總結
|
||||
|
||||
| 資料表 | 索引 | 類型 | 用途 |
|
||||
|-------|------|------|------|
|
||||
| incident_rooms | room_id | PRIMARY KEY | 主鍵 |
|
||||
| incident_rooms | (status, created_at) | COMPOSITE | 狀態篩選與排序 |
|
||||
| incident_rooms | created_by | SINGLE | 建立者查詢 |
|
||||
| room_members | (room_id, user_id) | COMPOSITE | 房間成員查詢 |
|
||||
| room_members | user_id | SINGLE | 使用者房間列表 |
|
||||
| room_members | (room_id, user_id) WHERE removed_at IS NULL | UNIQUE PARTIAL | 唯一性約束 |
|
||||
| room_templates | name | UNIQUE | 範本名稱查詢 |
|
||||
|
||||
### 2. 查詢效能估算
|
||||
|
||||
假設資料量:
|
||||
- 10,000 個房間
|
||||
- 平均每個房間 5 個成員 = 50,000 筆成員記錄
|
||||
|
||||
```sql
|
||||
-- 1. 查詢使用者的房間(有索引)
|
||||
-- 預期:<10ms
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT r.* FROM incident_rooms r
|
||||
INNER JOIN room_members m ON r.room_id = m.room_id
|
||||
WHERE m.user_id = 'user@example.com' AND m.removed_at IS NULL;
|
||||
-- 使用索引:ix_room_members_user
|
||||
|
||||
-- 2. 查詢房間成員(有索引)
|
||||
-- 預期:<5ms
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT * FROM room_members
|
||||
WHERE room_id = 'xxx' AND removed_at IS NULL;
|
||||
-- 使用索引:ix_room_members_room_user
|
||||
```
|
||||
|
||||
### 3. 冗餘欄位權衡
|
||||
|
||||
**member_count 冗餘欄位**:
|
||||
```sql
|
||||
-- 不使用冗餘(每次都計算)
|
||||
SELECT COUNT(*) FROM room_members
|
||||
WHERE room_id = ? AND removed_at IS NULL;
|
||||
-- 成本:每次查詢都需要掃描 room_members
|
||||
|
||||
-- 使用冗餘(直接讀取)
|
||||
SELECT member_count FROM incident_rooms
|
||||
WHERE room_id = ?;
|
||||
-- 成本:寫入時需要同步更新,但讀取極快
|
||||
|
||||
-- 權衡:讀取頻率 >> 寫入頻率 → 使用冗餘欄位
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 資料遷移策略
|
||||
|
||||
### 1. 初始化腳本
|
||||
|
||||
```python
|
||||
# alembic/versions/001_create_chat_room_tables.py
|
||||
|
||||
def upgrade():
|
||||
# 1. 建立 incident_rooms 表
|
||||
op.create_table(
|
||||
'incident_rooms',
|
||||
sa.Column('room_id', sa.String(36), primary_key=True),
|
||||
sa.Column('title', sa.String(255), nullable=False),
|
||||
# ... 其他欄位
|
||||
)
|
||||
|
||||
# 2. 建立 room_members 表
|
||||
op.create_table(
|
||||
'room_members',
|
||||
sa.Column('id', sa.Integer, primary_key=True, autoincrement=True),
|
||||
# ... 其他欄位
|
||||
)
|
||||
|
||||
# 3. 建立索引
|
||||
op.create_index('ix_incident_rooms_status_created',
|
||||
'incident_rooms', ['status', 'created_at'])
|
||||
# ... 其他索引
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('room_members')
|
||||
op.drop_table('incident_rooms')
|
||||
op.drop_table('room_templates')
|
||||
```
|
||||
|
||||
### 2. 資料備份策略
|
||||
|
||||
```bash
|
||||
# SQLite 備份
|
||||
sqlite3 task_reporter.db ".backup task_reporter_backup.db"
|
||||
|
||||
# PostgreSQL 備份
|
||||
pg_dump -U postgres -d task_reporter > backup.sql
|
||||
|
||||
# 恢復
|
||||
psql -U postgres -d task_reporter < backup.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 總結
|
||||
|
||||
### 架構優勢
|
||||
|
||||
✅ **正規化設計** - 避免資料重複,保持一致性
|
||||
✅ **軟刪除機制** - 保留歷史記錄,支援稽核
|
||||
✅ **索引優化** - 覆蓋常見查詢模式
|
||||
✅ **彈性擴展** - JSON 欄位支援未來需求變更
|
||||
✅ **稽核追蹤** - 記錄所有關鍵操作(包含所有權轉移)
|
||||
|
||||
### 架構限制
|
||||
|
||||
⚠️ **軟刪除開銷** - 所有查詢需加入 `removed_at IS NULL`
|
||||
⚠️ **JSON 查詢限制** - default_members 無法有效索引
|
||||
⚠️ **冗餘欄位同步** - member_count 需要應用層維護一致性
|
||||
⚠️ **無分散式支援** - 目前設計未考慮分散式部署
|
||||
|
||||
### 未來優化建議
|
||||
|
||||
1. **分區表** - 當資料量達到百萬級時,考慮按時間分區
|
||||
2. **讀寫分離** - 使用主從複製提升讀取效能
|
||||
3. **快取層** - Redis 快取熱門房間資訊
|
||||
4. **全文搜尋引擎** - Elasticsearch 提升搜尋效能
|
||||
@@ -0,0 +1,584 @@
|
||||
# Chat Room Module Documentation
|
||||
|
||||
## 模組概述
|
||||
|
||||
`app/modules/chat_room` 模組提供生產線異常事件協作管理功能,允許團隊成員建立事件室、管理成員權限、追蹤事件狀態,並進行即時協作。
|
||||
|
||||
---
|
||||
|
||||
## 模組架構
|
||||
|
||||
```
|
||||
app/modules/chat_room/
|
||||
├── __init__.py # 模組入口,匯出 router
|
||||
├── models.py # SQLAlchemy 資料模型
|
||||
├── schemas.py # Pydantic 請求/回應模型
|
||||
├── router.py # FastAPI 路由定義
|
||||
├── dependencies.py # 依賴注入與權限驗證
|
||||
└── services/
|
||||
├── __init__.py
|
||||
├── room_service.py # 房間業務邏輯
|
||||
├── membership_service.py # 成員管理業務邏輯
|
||||
└── template_service.py # 範本管理業務邏輯
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 1. 房間生命週期
|
||||
|
||||
事件室從建立到封存的完整生命週期:
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ 建立房間 │ ← 使用者建立新事件室
|
||||
└──────┬──────┘
|
||||
↓
|
||||
┌─────────────┐
|
||||
│ ACTIVE │ ← 事件進行中,可新增成員、更新資訊
|
||||
│ (活躍) │
|
||||
└──────┬──────┘
|
||||
↓ (事件處理完成)
|
||||
┌─────────────┐
|
||||
│ RESOLVED │ ← 事件已解決,填寫解決方案
|
||||
│ (已解決) │
|
||||
└──────┬──────┘
|
||||
↓ (確認可封存)
|
||||
┌─────────────┐
|
||||
│ ARCHIVED │ ← 事件已封存,僅供查詢
|
||||
│ (已封存) │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
**狀態轉換規則**:
|
||||
- 只能單向前進:ACTIVE → RESOLVED → ARCHIVED
|
||||
- 不允許跳過狀態或反向轉換
|
||||
- 每次狀態變更都會更新 `last_activity_at`
|
||||
|
||||
**關鍵時間戳記**:
|
||||
- `created_at`: 房間建立時間
|
||||
- `resolved_at`: 標記為已解決的時間
|
||||
- `archived_at`: 封存時間(軟刪除)
|
||||
- `last_activity_at`: 最後活動時間(用於排序)
|
||||
|
||||
---
|
||||
|
||||
### 2. 權限模型
|
||||
|
||||
#### 2.1 角色定義
|
||||
|
||||
| 角色 | 說明 | 權限 |
|
||||
|-----|-----|-----|
|
||||
| **OWNER** | 擁有者 | - 完全控制權<br>- 可更新房間資訊<br>- 可管理所有成員<br>- 可轉移所有權<br>- 可刪除房間 |
|
||||
| **EDITOR** | 編輯者 | - 讀寫權限<br>- 可新增 VIEWER 成員<br>- 不可變更房間狀態<br>- 不可管理 OWNER/EDITOR |
|
||||
| **VIEWER** | 檢視者 | - 僅讀取權限<br>- 可查看房間資訊與成員<br>- 無法進行任何修改 |
|
||||
| **ADMIN** | 系統管理員 | - 覆寫所有限制<br>- 可存取所有房間<br>- 執行任何操作 |
|
||||
|
||||
#### 2.2 系統管理員
|
||||
|
||||
**管理員帳號**: `ymirliu@panjit.com.tw`
|
||||
|
||||
**特殊權限**:
|
||||
1. 查看系統中所有房間(即使非成員)
|
||||
2. 覆寫所有角色限制
|
||||
3. 強制執行任何操作
|
||||
4. 不受成員資格限制
|
||||
|
||||
**實作位置**:
|
||||
- `app/modules/chat_room/services/membership_service.py:is_system_admin()`
|
||||
- 硬編碼檢查信箱是否為 `ymirliu@panjit.com.tw`
|
||||
|
||||
#### 2.3 權限檢查流程
|
||||
|
||||
```python
|
||||
# 在 dependencies.py 中的權限檢查流程
|
||||
|
||||
def require_room_permission(permission: str):
|
||||
"""
|
||||
1. 檢查是否為系統管理員 → 通過
|
||||
2. 檢查使用者是否為房間成員 → 否則拒絕
|
||||
3. 根據角色檢查特定權限 → 通過/拒絕
|
||||
"""
|
||||
async def dependency(
|
||||
room_id: str,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
user_email = current_user["username"]
|
||||
|
||||
# 管理員覆寫
|
||||
if membership_service.is_system_admin(user_email):
|
||||
return None
|
||||
|
||||
# 檢查成員資格
|
||||
role = membership_service.get_user_role_in_room(db, room_id, user_email)
|
||||
if not role:
|
||||
raise HTTPException(status_code=403, detail="Not a room member")
|
||||
|
||||
# 檢查權限
|
||||
if not has_permission(role, permission):
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
return None
|
||||
|
||||
return dependency
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 所有權轉移機制 ⭐
|
||||
|
||||
#### 3.1 轉移流程
|
||||
|
||||
```
|
||||
使用者需求:現有 owner 指派給新 owner
|
||||
|
||||
實作步驟:
|
||||
1. 驗證當前使用者為 OWNER
|
||||
2. 驗證新 owner 為現有房間成員
|
||||
3. 執行角色變更:
|
||||
- 原 owner → EDITOR
|
||||
- 新 owner → OWNER
|
||||
4. 記錄稽核資訊:
|
||||
- ownership_transferred_at: 轉移時間
|
||||
- ownership_transferred_by: 執行轉移的使用者
|
||||
```
|
||||
|
||||
#### 3.2 程式碼範例
|
||||
|
||||
```python
|
||||
# services/membership_service.py
|
||||
|
||||
def transfer_ownership(
|
||||
self,
|
||||
db: Session,
|
||||
room_id: str,
|
||||
current_owner_id: str,
|
||||
new_owner_id: str
|
||||
) -> bool:
|
||||
"""轉移房間所有權"""
|
||||
|
||||
# 1. 驗證新 owner 是房間成員
|
||||
new_owner_member = db.query(RoomMember).filter(
|
||||
RoomMember.room_id == room_id,
|
||||
RoomMember.user_id == new_owner_id,
|
||||
RoomMember.removed_at.is_(None)
|
||||
).first()
|
||||
|
||||
if not new_owner_member:
|
||||
return False
|
||||
|
||||
# 2. 取得原 owner
|
||||
current_owner = db.query(RoomMember).filter(
|
||||
RoomMember.room_id == room_id,
|
||||
RoomMember.user_id == current_owner_id,
|
||||
RoomMember.role == MemberRole.OWNER
|
||||
).first()
|
||||
|
||||
if not current_owner:
|
||||
return False
|
||||
|
||||
# 3. 執行角色交換
|
||||
current_owner.role = MemberRole.EDITOR
|
||||
new_owner_member.role = MemberRole.OWNER
|
||||
|
||||
# 4. 記錄稽核資訊
|
||||
room = db.query(IncidentRoom).filter(
|
||||
IncidentRoom.room_id == room_id
|
||||
).first()
|
||||
|
||||
room.ownership_transferred_at = datetime.utcnow()
|
||||
room.ownership_transferred_by = current_owner_id
|
||||
room.last_updated_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
return True
|
||||
```
|
||||
|
||||
#### 3.3 API 使用範例
|
||||
|
||||
```bash
|
||||
POST /api/rooms/{room_id}/transfer-ownership
|
||||
Authorization: Bearer <owner_token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"new_owner_id": "engineer@panjit.com.tw"
|
||||
}
|
||||
|
||||
# 回應
|
||||
{
|
||||
"message": "Ownership transferred successfully"
|
||||
}
|
||||
|
||||
# 效果:
|
||||
# - engineer@panjit.com.tw 成為新 OWNER
|
||||
# - 原 OWNER 降級為 EDITOR
|
||||
# - 記錄於 ownership_transferred_at 和 ownership_transferred_by
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 成員管理
|
||||
|
||||
#### 4.1 軟刪除機制
|
||||
|
||||
成員採用軟刪除設計,不實際刪除記錄:
|
||||
|
||||
```python
|
||||
# models.py
|
||||
class RoomMember(Base):
|
||||
removed_at = Column(DateTime, nullable=True) # NULL = 活躍成員
|
||||
|
||||
# 唯一性約束:同一房間中,同一使用者只能有一個活躍成員記錄
|
||||
__table_args__ = (
|
||||
Index('ix_room_members_active', 'room_id', 'user_id',
|
||||
postgresql_where=text('removed_at IS NULL')),
|
||||
)
|
||||
```
|
||||
|
||||
**優點**:
|
||||
- 保留歷史記錄
|
||||
- 支援稽核追蹤
|
||||
- 可恢復誤刪的成員
|
||||
|
||||
#### 4.2 成員計數同步
|
||||
|
||||
`member_count` 欄位自動同步:
|
||||
|
||||
```python
|
||||
def add_member(...):
|
||||
# 新增成員
|
||||
member = RoomMember(...)
|
||||
db.add(member)
|
||||
|
||||
# 更新計數
|
||||
room.member_count += 1
|
||||
room.last_activity_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
|
||||
def remove_member(...):
|
||||
# 軟刪除
|
||||
member.removed_at = datetime.utcnow()
|
||||
|
||||
# 更新計數
|
||||
room.member_count -= 1
|
||||
room.last_activity_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 範本系統
|
||||
|
||||
#### 5.1 預設範本
|
||||
|
||||
系統提供三個預設範本,在應用啟動時自動初始化:
|
||||
|
||||
```python
|
||||
# services/template_service.py
|
||||
|
||||
DEFAULT_TEMPLATES = [
|
||||
{
|
||||
"name": "equipment_failure",
|
||||
"description": "設備故障事件需要立即處理",
|
||||
"incident_type": IncidentType.EQUIPMENT_FAILURE,
|
||||
"default_severity": SeverityLevel.HIGH,
|
||||
"default_members": [
|
||||
{"user_id": "maintenance_team@panjit.com.tw", "role": "editor"},
|
||||
{"user_id": "engineering@panjit.com.tw", "role": "viewer"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "material_shortage",
|
||||
"description": "物料短缺影響生產",
|
||||
"incident_type": IncidentType.MATERIAL_SHORTAGE,
|
||||
"default_severity": SeverityLevel.MEDIUM,
|
||||
"default_members": [
|
||||
{"user_id": "procurement@panjit.com.tw", "role": "editor"},
|
||||
{"user_id": "logistics@panjit.com.tw", "role": "editor"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "quality_issue",
|
||||
"description": "品質問題需要調查",
|
||||
"incident_type": IncidentType.QUALITY_ISSUE,
|
||||
"default_severity": SeverityLevel.HIGH,
|
||||
"default_members": [
|
||||
{"user_id": "quality_team@panjit.com.tw", "role": "editor"},
|
||||
{"user_id": "production_manager@panjit.com.tw", "role": "viewer"}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### 5.2 使用範本建立房間
|
||||
|
||||
```python
|
||||
# router.py - create_room endpoint
|
||||
|
||||
if room_data.template:
|
||||
# 查詢範本
|
||||
template = template_service.get_template_by_name(db, room_data.template)
|
||||
|
||||
if template:
|
||||
# 從範本建立房間(自動設定類型、嚴重度、預設成員)
|
||||
room = template_service.create_room_from_template(
|
||||
db,
|
||||
template.template_id,
|
||||
user_email,
|
||||
room_data.title,
|
||||
room_data.location,
|
||||
room_data.description
|
||||
)
|
||||
```
|
||||
|
||||
**優勢**:
|
||||
- 快速建立標準化事件室
|
||||
- 自動新增相關人員
|
||||
- 確保一致性
|
||||
|
||||
---
|
||||
|
||||
## 整合範例
|
||||
|
||||
### 範例 1: 在其他模組中查詢使用者的房間
|
||||
|
||||
```python
|
||||
from app.modules.chat_room.services.membership_service import membership_service
|
||||
|
||||
def get_user_active_rooms(db: Session, user_email: str):
|
||||
"""取得使用者所有活躍房間"""
|
||||
from app.modules.chat_room.models import RoomStatus
|
||||
from app.modules.chat_room.schemas import RoomFilterParams
|
||||
from app.modules.chat_room.services.room_service import room_service
|
||||
|
||||
filters = RoomFilterParams(
|
||||
status=RoomStatus.ACTIVE,
|
||||
limit=100,
|
||||
offset=0
|
||||
)
|
||||
|
||||
rooms, total = room_service.list_user_rooms(
|
||||
db,
|
||||
user_email,
|
||||
filters,
|
||||
is_admin=membership_service.is_system_admin(user_email)
|
||||
)
|
||||
|
||||
return rooms
|
||||
```
|
||||
|
||||
### 範例 2: 檢查使用者是否有特定房間的權限
|
||||
|
||||
```python
|
||||
from app.modules.chat_room.services.membership_service import membership_service
|
||||
from app.modules.chat_room.models import MemberRole
|
||||
|
||||
def can_user_edit_room(db: Session, room_id: str, user_email: str) -> bool:
|
||||
"""檢查使用者是否可編輯房間"""
|
||||
|
||||
# 管理員直接通過
|
||||
if membership_service.is_system_admin(user_email):
|
||||
return True
|
||||
|
||||
# 檢查角色
|
||||
role = membership_service.get_user_role_in_room(db, room_id, user_email)
|
||||
|
||||
return role in [MemberRole.OWNER, MemberRole.EDITOR]
|
||||
```
|
||||
|
||||
### 範例 3: 建立帶有自訂成員的房間
|
||||
|
||||
```python
|
||||
from app.modules.chat_room.services.room_service import room_service
|
||||
from app.modules.chat_room.services.membership_service import membership_service
|
||||
from app.modules.chat_room.schemas import CreateRoomRequest
|
||||
from app.modules.chat_room.models import MemberRole, IncidentType, SeverityLevel
|
||||
|
||||
def create_custom_incident_room(
|
||||
db: Session,
|
||||
creator_email: str,
|
||||
title: str,
|
||||
additional_members: list[tuple[str, MemberRole]]
|
||||
):
|
||||
"""建立自訂成員的事件室"""
|
||||
|
||||
# 1. 建立房間
|
||||
room_data = CreateRoomRequest(
|
||||
title=title,
|
||||
incident_type=IncidentType.OTHER,
|
||||
severity=SeverityLevel.MEDIUM,
|
||||
location="",
|
||||
description=""
|
||||
)
|
||||
|
||||
room = room_service.create_room(db, creator_email, room_data)
|
||||
|
||||
# 2. 新增額外成員
|
||||
for user_email, role in additional_members:
|
||||
membership_service.add_member(
|
||||
db,
|
||||
room.room_id,
|
||||
user_email,
|
||||
role,
|
||||
added_by=creator_email
|
||||
)
|
||||
|
||||
return room
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 資料庫查詢優化
|
||||
|
||||
### 索引策略
|
||||
|
||||
```sql
|
||||
-- 房間查詢優化
|
||||
CREATE INDEX ix_incident_rooms_status_created ON incident_rooms(status, created_at);
|
||||
CREATE INDEX ix_incident_rooms_created_by ON incident_rooms(created_by);
|
||||
|
||||
-- 成員查詢優化
|
||||
CREATE INDEX ix_room_members_room_user ON room_members(room_id, user_id);
|
||||
CREATE INDEX ix_room_members_user ON room_members(user_id);
|
||||
|
||||
-- 範本查詢優化
|
||||
CREATE UNIQUE INDEX ix_room_templates_name ON room_templates(name);
|
||||
```
|
||||
|
||||
### 常見查詢模式
|
||||
|
||||
```python
|
||||
# 1. 取得使用者參與的所有房間(已優化)
|
||||
user_rooms = db.query(IncidentRoom).join(
|
||||
RoomMember,
|
||||
and_(
|
||||
RoomMember.room_id == IncidentRoom.room_id,
|
||||
RoomMember.user_id == user_email,
|
||||
RoomMember.removed_at.is_(None)
|
||||
)
|
||||
).filter(
|
||||
IncidentRoom.archived_at.is_(None)
|
||||
).order_by(
|
||||
IncidentRoom.last_activity_at.desc()
|
||||
).all()
|
||||
|
||||
# 2. 取得房間所有活躍成員(已優化)
|
||||
active_members = db.query(RoomMember).filter(
|
||||
RoomMember.room_id == room_id,
|
||||
RoomMember.removed_at.is_(None)
|
||||
).all()
|
||||
|
||||
# 3. 檢查使用者權限(已優化)
|
||||
member = db.query(RoomMember).filter(
|
||||
RoomMember.room_id == room_id,
|
||||
RoomMember.user_id == user_email,
|
||||
RoomMember.removed_at.is_(None)
|
||||
).first()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 未來擴展
|
||||
|
||||
### WebSocket 整合準備
|
||||
|
||||
模組已為 WebSocket 即時通訊預留設計空間:
|
||||
|
||||
```python
|
||||
# 未來可實作:
|
||||
# - 房間訊息廣播
|
||||
# - 成員上線狀態
|
||||
# - 即時通知
|
||||
# - 協作編輯
|
||||
|
||||
# 建議架構:
|
||||
# app/modules/chat_room/websocket.py
|
||||
class RoomWebSocketManager:
|
||||
def __init__(self):
|
||||
self.active_connections: dict[str, list[WebSocket]] = {}
|
||||
|
||||
async def connect(self, room_id: str, websocket: WebSocket):
|
||||
"""連接到房間頻道"""
|
||||
pass
|
||||
|
||||
async def broadcast(self, room_id: str, message: dict):
|
||||
"""廣播訊息到房間所有成員"""
|
||||
pass
|
||||
```
|
||||
|
||||
### 稽核日誌
|
||||
|
||||
建議新增完整的稽核日誌表:
|
||||
|
||||
```python
|
||||
# 未來可實作:
|
||||
class RoomAuditLog(Base):
|
||||
__tablename__ = "room_audit_logs"
|
||||
|
||||
log_id = Column(String(36), primary_key=True)
|
||||
room_id = Column(String(36), ForeignKey("incident_rooms.room_id"))
|
||||
action = Column(String(50)) # created, updated, member_added, etc.
|
||||
actor = Column(String(255)) # 執行操作的使用者
|
||||
details = Column(JSON) # 詳細變更內容
|
||||
timestamp = Column(DateTime, default=datetime.utcnow)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 測試建議
|
||||
|
||||
### 單元測試重點
|
||||
|
||||
```python
|
||||
# tests/test_chat_room/test_membership_service.py
|
||||
|
||||
def test_ownership_transfer():
|
||||
"""測試所有權轉移"""
|
||||
# 1. 建立房間與成員
|
||||
# 2. 執行轉移
|
||||
# 3. 驗證角色變更
|
||||
# 4. 驗證稽核記錄
|
||||
|
||||
def test_admin_override():
|
||||
"""測試管理員覆寫"""
|
||||
# 1. 使用非成員的管理員帳號
|
||||
# 2. 驗證可執行所有操作
|
||||
|
||||
def test_permission_enforcement():
|
||||
"""測試權限限制"""
|
||||
# 1. VIEWER 嘗試修改 → 失敗
|
||||
# 2. EDITOR 嘗試新增 OWNER → 失敗
|
||||
# 3. OWNER 執行任何操作 → 成功
|
||||
```
|
||||
|
||||
### 整合測試重點
|
||||
|
||||
```python
|
||||
# tests/test_chat_room/test_api_endpoints.py
|
||||
|
||||
def test_full_room_lifecycle():
|
||||
"""測試完整房間生命週期"""
|
||||
# 1. 建立 → 2. 新增成員 → 3. 更新 → 4. 解決 → 5. 封存
|
||||
|
||||
def test_ownership_transfer_api():
|
||||
"""測試 API 層級的所有權轉移"""
|
||||
# 包含認證、權限檢查、業務邏輯
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 總結
|
||||
|
||||
Chat Room 模組提供了一個完整的事件協作管理系統,具備:
|
||||
|
||||
✅ **角色權限系統** - OWNER/EDITOR/VIEWER + ADMIN 覆寫
|
||||
✅ **所有權轉移** - 支援動態變更房間擁有者
|
||||
✅ **生命週期管理** - ACTIVE → RESOLVED → ARCHIVED
|
||||
✅ **範本系統** - 快速建立標準化事件室
|
||||
✅ **稽核追蹤** - 記錄所有關鍵操作
|
||||
✅ **擴展性** - 為 WebSocket 與稽核日誌預留設計空間
|
||||
@@ -0,0 +1,37 @@
|
||||
# Change: Add Chat Room Management for Incident Response
|
||||
|
||||
## Why
|
||||
The system needs to support creation and management of dedicated chat rooms for each production incident. Each incident (equipment failure, material shortage, quality issue) requires an isolated communication space where team members can collaborate, share files, and track resolution progress. This is the core feature that transforms chaotic multi-channel communication into structured, traceable data with audit trails for compliance and post-mortem analysis.
|
||||
|
||||
## What Changes
|
||||
- Implement chat room CRUD operations (Create, Read, Update, Delete/Close)
|
||||
- Create database schema for incident_rooms and room_members tables
|
||||
- Implement room membership management (add/remove members, role assignment)
|
||||
- Add room status lifecycle (active, resolved, archived)
|
||||
- Implement room metadata tracking (incident type, severity, location, timestamps)
|
||||
- Add permission system for room operations (owner, editor, viewer roles)
|
||||
- **Implement owner transfer functionality (current owner can assign new owner)**
|
||||
- **Add system administrator role with super-user privileges (ymirliu@panjit.com.tw)**
|
||||
- Create REST API endpoints for room management
|
||||
- Enable filtering and searching of rooms by status, type, date range
|
||||
- Implement room activity tracking for audit trails
|
||||
- Add support for room templates based on incident types
|
||||
- Implement admin override capabilities for all room operations
|
||||
|
||||
## Impact
|
||||
- **Affected specs**: `chat-room` (new capability)
|
||||
- **Affected code**:
|
||||
- Backend: New `app/modules/chat_room/` module with:
|
||||
- Routes: `/api/rooms/*` endpoints for CRUD operations
|
||||
- Models: `IncidentRoom`, `RoomMember` (SQLAlchemy)
|
||||
- Services: `RoomService`, `MembershipService`
|
||||
- Schemas: Request/Response models for room operations
|
||||
- Database: New tables:
|
||||
- `incident_rooms`: Room metadata and configuration
|
||||
- `room_members`: User-room associations with roles
|
||||
- Integration points:
|
||||
- Authentication: Requires `get_current_user` dependency from auth module
|
||||
- Future: Will be used by messaging and file upload features
|
||||
- **Dependencies**:
|
||||
- Requires completed authentication module (for user identity and permissions)
|
||||
- PostgreSQL for relational data storage
|
||||
@@ -0,0 +1,218 @@
|
||||
# Chat Room Management Capability
|
||||
|
||||
## ADDED 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
|
||||
@@ -0,0 +1,185 @@
|
||||
# Implementation Tasks
|
||||
|
||||
## 1. Database Schema
|
||||
- [x] 1.1 Create `incident_rooms` table with columns:
|
||||
- [x] room_id (UUID, PK)
|
||||
- [x] title (VARCHAR, required)
|
||||
- [x] incident_type (ENUM: equipment_failure, material_shortage, quality_issue, other)
|
||||
- [x] severity (ENUM: low, medium, high, critical)
|
||||
- [x] status (ENUM: active, resolved, archived)
|
||||
- [x] location (VARCHAR)
|
||||
- [x] description (TEXT)
|
||||
- [x] resolution_notes (TEXT, nullable)
|
||||
- [x] created_by (FK to users)
|
||||
- [x] created_at (TIMESTAMP)
|
||||
- [x] resolved_at (TIMESTAMP, nullable)
|
||||
- [x] archived_at (TIMESTAMP, nullable)
|
||||
- [x] last_activity_at (TIMESTAMP)
|
||||
- [x] last_updated_at (TIMESTAMP)
|
||||
- [x] member_count (INTEGER, default 0)
|
||||
- [x] ownership_transferred_at (TIMESTAMP, nullable)
|
||||
- [x] ownership_transferred_by (VARCHAR, nullable)
|
||||
- [x] 1.2 Create `room_members` table with columns:
|
||||
- [x] id (PK)
|
||||
- [x] room_id (FK to incident_rooms)
|
||||
- [x] user_id (VARCHAR, user identifier)
|
||||
- [x] role (ENUM: owner, editor, viewer)
|
||||
- [x] added_by (VARCHAR, user who added this member)
|
||||
- [x] added_at (TIMESTAMP)
|
||||
- [x] removed_at (TIMESTAMP, nullable for soft delete)
|
||||
- [x] UNIQUE constraint on (room_id, user_id) where removed_at IS NULL
|
||||
- [x] 1.3 Create `room_templates` table:
|
||||
- [x] template_id (PK)
|
||||
- [x] name (VARCHAR, unique)
|
||||
- [x] description (TEXT)
|
||||
- [x] incident_type (ENUM)
|
||||
- [x] default_severity (ENUM)
|
||||
- [x] default_members (JSON array of user roles)
|
||||
- [x] metadata_fields (JSON schema)
|
||||
- [x] 1.4 Create indexes:
|
||||
- [x] Index on incident_rooms(status, created_at)
|
||||
- [x] Index on incident_rooms(created_by)
|
||||
- [x] Index on room_members(room_id, user_id)
|
||||
- [x] Index on room_members(user_id) for user's rooms query
|
||||
- [x] 1.5 Create database migration scripts using Alembic
|
||||
|
||||
## 2. Backend API Implementation
|
||||
|
||||
### 2.1 Module Structure
|
||||
- [x] 2.1.1 Create `app/modules/chat_room/` directory structure:
|
||||
- [x] `__init__.py` (export public interfaces)
|
||||
- [x] `models.py` (SQLAlchemy models)
|
||||
- [x] `schemas.py` (Pydantic request/response models)
|
||||
- [x] `router.py` (FastAPI routes)
|
||||
- [x] `services/` subdirectory
|
||||
- [x] `dependencies.py` (room access validators)
|
||||
|
||||
### 2.2 Models Implementation
|
||||
- [x] 2.2.1 Create `IncidentRoom` SQLAlchemy model
|
||||
- [x] 2.2.2 Create `RoomMember` SQLAlchemy model
|
||||
- [x] 2.2.3 Create `RoomTemplate` SQLAlchemy model
|
||||
- [x] 2.2.4 Define Enum types for incident_type, severity, status, role
|
||||
- [x] 2.2.5 Add relationship definitions between models
|
||||
|
||||
### 2.3 Schemas Implementation
|
||||
- [x] 2.3.1 Create request schemas:
|
||||
- [x] `CreateRoomRequest` (title, incident_type, severity, location, description)
|
||||
- [x] `UpdateRoomRequest` (partial updates)
|
||||
- [x] `AddMemberRequest` (user_id, role)
|
||||
- [x] `UpdateMemberRoleRequest` (role)
|
||||
- [x] `RoomFilterParams` (status, type, severity, dates, search)
|
||||
- [x] 2.3.2 Create response schemas:
|
||||
- [x] `RoomResponse` (full room details)
|
||||
- [x] `RoomListResponse` (paginated list)
|
||||
- [x] `MemberResponse` (user info with role)
|
||||
- [x] `TemplateResponse` (template details)
|
||||
|
||||
### 2.4 Services Implementation
|
||||
- [x] 2.4.1 Create `services/room_service.py`:
|
||||
- [x] `create_room(db, user_id, room_data) -> IncidentRoom`
|
||||
- [x] `get_room(db, room_id, user_id) -> IncidentRoom`
|
||||
- [x] `list_user_rooms(db, user_id, filters) -> List[IncidentRoom]`
|
||||
- [x] `update_room(db, room_id, updates) -> IncidentRoom`
|
||||
- [x] `change_room_status(db, room_id, new_status) -> IncidentRoom`
|
||||
- [x] `search_rooms(db, user_id, search_term) -> List[IncidentRoom]`
|
||||
- [x] 2.4.2 Create `services/membership_service.py`:
|
||||
- [x] `add_member(db, room_id, user_id, role, added_by) -> RoomMember`
|
||||
- [x] `remove_member(db, room_id, user_id) -> bool`
|
||||
- [x] `update_member_role(db, room_id, user_id, new_role) -> RoomMember`
|
||||
- [x] `transfer_ownership(db, room_id, current_owner_id, new_owner_id) -> bool`
|
||||
- [x] `get_room_members(db, room_id) -> List[RoomMember]`
|
||||
- [x] `get_user_rooms(db, user_id) -> List[IncidentRoom]`
|
||||
- [x] `check_user_permission(db, room_id, user_id, permission) -> bool`
|
||||
- [x] `is_system_admin(user_email) -> bool` (check if ymirliu@panjit.com.tw)
|
||||
- [x] 2.4.3 Create `services/template_service.py`:
|
||||
- [x] `get_templates(db) -> List[RoomTemplate]`
|
||||
- [x] `create_room_from_template(db, template_id, user_id, additional_data) -> IncidentRoom`
|
||||
|
||||
### 2.5 Router Implementation
|
||||
- [x] 2.5.1 Room CRUD endpoints:
|
||||
- [x] `POST /api/rooms` - Create new room
|
||||
- [x] `GET /api/rooms` - List/filter rooms
|
||||
- [x] `GET /api/rooms/{room_id}` - Get room details
|
||||
- [x] `PATCH /api/rooms/{room_id}` - Update room
|
||||
- [x] `DELETE /api/rooms/{room_id}` - Soft delete room
|
||||
- [x] 2.5.2 Membership endpoints:
|
||||
- [x] `GET /api/rooms/{room_id}/members` - List members
|
||||
- [x] `POST /api/rooms/{room_id}/members` - Add member
|
||||
- [x] `PATCH /api/rooms/{room_id}/members/{user_id}` - Update role
|
||||
- [x] `DELETE /api/rooms/{room_id}/members/{user_id}` - Remove member
|
||||
- [x] `POST /api/rooms/{room_id}/transfer-ownership` - Transfer room ownership
|
||||
- [x] 2.5.3 Permission endpoints:
|
||||
- [x] `GET /api/rooms/{room_id}/permissions` - Get current user permissions
|
||||
- [x] 2.5.4 Template endpoints:
|
||||
- [x] `GET /api/rooms/templates` - List available templates
|
||||
|
||||
### 2.6 Dependencies and Middleware
|
||||
- [x] 2.6.1 Create `get_current_room` dependency for room access validation
|
||||
- [x] 2.6.2 Create `require_room_permission` dependency with permission levels
|
||||
- [x] 2.6.3 Create `validate_room_owner` dependency for owner-only operations
|
||||
- [x] 2.6.4 Create `require_admin` dependency for admin-only operations
|
||||
- [x] 2.6.5 Create `get_user_effective_role` dependency (considers admin override)
|
||||
- [x] 2.6.6 Integrate with auth module's `get_current_user`
|
||||
|
||||
## 3. Business Logic Implementation
|
||||
- [x] 3.1 Status transition validation:
|
||||
- [x] active → resolved → archived (valid)
|
||||
- [x] Prevent invalid transitions
|
||||
- [x] Auto-update last_activity_at on any change
|
||||
- [x] 3.2 Member count synchronization:
|
||||
- [x] Increment on member addition
|
||||
- [x] Decrement on member removal
|
||||
- [x] Recalculate on soft-delete operations
|
||||
- [x] 3.3 Permission matrix implementation:
|
||||
- [x] Owner: all operations including ownership transfer
|
||||
- [x] Editor: read, add members (viewer only), send messages
|
||||
- [x] Viewer: read only
|
||||
- [x] Admin (ymirliu@panjit.com.tw): all operations plus override capabilities
|
||||
- [x] 3.4 Audit trail:
|
||||
- [x] Log all room status changes
|
||||
- [x] Log membership changes
|
||||
- [x] Log ownership transfers with timestamp and user
|
||||
- [x] Log admin override actions
|
||||
- [x] Track last_activity for sorting
|
||||
|
||||
## 4. Testing
|
||||
- [x] 4.1 Unit tests for services:
|
||||
- [x] Test room creation with valid/invalid data
|
||||
- [x] Test status transitions
|
||||
- [x] Test member addition/removal
|
||||
- [x] Test permission checks
|
||||
- [x] 4.2 Integration tests for API endpoints:
|
||||
- [x] Test full room lifecycle (create → resolve → archive)
|
||||
- [x] Test filtering and search
|
||||
- [x] Test membership management
|
||||
- [x] Test unauthorized access scenarios
|
||||
- [x] 4.3 Test permission matrix:
|
||||
- [x] Owner can do all operations including ownership transfer
|
||||
- [x] Editor limitations
|
||||
- [x] Viewer read-only access
|
||||
- [x] Admin (ymirliu@panjit.com.tw) bypass all restrictions
|
||||
- [x] 4.4 Test edge cases:
|
||||
- [x] Duplicate member addition
|
||||
- [x] Self-removal as last owner
|
||||
- [x] Concurrent updates
|
||||
- [x] Ownership transfer to non-member (should fail)
|
||||
- [x] Ownership transfer validation
|
||||
|
||||
## 5. Documentation
|
||||
- [x] 5.1 API documentation:
|
||||
- [x] Document all endpoints in OpenAPI/Swagger format
|
||||
- [x] Include request/response examples
|
||||
- [x] Document error codes and messages
|
||||
- [x] 5.2 Module README:
|
||||
- [x] Explain room lifecycle
|
||||
- [x] Document permission model
|
||||
- [x] Provide integration examples
|
||||
- [x] 5.3 Database schema documentation:
|
||||
- [x] ER diagram
|
||||
- [x] Index strategy explanation
|
||||
|
||||
## 6. Integration
|
||||
- [x] 6.1 Register router in main FastAPI app
|
||||
- [x] 6.2 Add room module to app initialization
|
||||
- [x] 6.3 Ensure proper dependency injection with auth module
|
||||
- [x] 6.4 Prepare for future WebSocket integration (room-based channels)
|
||||
Reference in New Issue
Block a user