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>
585 lines
15 KiB
Markdown
585 lines
15 KiB
Markdown
# 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 與稽核日誌預留設計空間
|