feat: enhance weekly report and realtime notifications

Weekly Report (fix-weekly-report):
- Remove 5-task limit, show all tasks per category
- Add blocked tasks with blocker_reason and blocked_since
- Add next week tasks (due in coming week)
- Add assignee_name, completed_at, days_overdue to task details
- Frontend collapsible sections for each task category
- 8 new tests for enhanced report content

Realtime Notifications (fix-realtime-notifications):
- SQLAlchemy event-based notification publishing
- Redis Pub/Sub for multi-process broadcast
- Fix soft rollback handler stacking issue
- Fix ping scheduling drift (send immediately when interval expires)
- Frontend NotificationContext with WebSocket reconnection

Spec Fixes:
- Add missing ## Purpose sections to 5 specs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beabigegg
2025-12-30 20:52:08 +08:00
parent 10db2c9d1f
commit 64874d5425
25 changed files with 1034 additions and 140 deletions

View File

@@ -0,0 +1,134 @@
## Context
collaboration spec 要求即時通知透過 WebSocket 推播,但現行 NotificationService 僅寫入資料庫,未實作即時推送。
## Goals / Non-Goals
**Goals:**
- 建立 WebSocket 連線管理基礎設施
- 通知建立時透過 Redis Pub/Sub 廣播
- 使用者連線時補送未讀通知
- 前端即時接收並更新通知
**Non-Goals:**
- 不實作通知偏好設定(靜音/訂閱)
- 不實作 Push Notification (PWA/Mobile)
- 不實作通知分組或摺疊
## Decisions
### 1. WebSocket 連線管理
**Decision:** 使用 in-memory dict + Redis Pub/Sub
**Rationale:**
- 單 process 內使用 dict 維護 WebSocket 連線
- 跨 process 透過 Redis Pub/Sub 廣播
- 簡單且符合現有架構
```python
class ConnectionManager:
def __init__(self):
self.active_connections: Dict[str, List[WebSocket]] = {}
async def connect(self, user_id: str, websocket: WebSocket):
await websocket.accept()
if user_id not in self.active_connections:
self.active_connections[user_id] = []
self.active_connections[user_id].append(websocket)
async def disconnect(self, user_id: str, websocket: WebSocket):
self.active_connections[user_id].remove(websocket)
if not self.active_connections[user_id]:
del self.active_connections[user_id]
async def send_to_user(self, user_id: str, message: dict):
if user_id in self.active_connections:
for connection in self.active_connections[user_id]:
await connection.send_json(message)
```
### 2. Redis Pub/Sub 架構
**Decision:** 使用 user-specific channel
**Rationale:**
- Channel 命名: `notifications:{user_id}`
- 避免 broadcast 給不相關的 worker
- 減少訊息處理量
```python
async def publish_notification(user_id: str, notification: dict):
channel = f"notifications:{user_id}"
await redis.publish(channel, json.dumps(notification))
async def subscribe_notifications(user_id: str):
pubsub = redis.pubsub()
await pubsub.subscribe(f"notifications:{user_id}")
return pubsub
```
### 3. 連線時補送未讀
**Decision:** 連線建立後立即查詢並推送
**Rationale:**
- 確保使用者不漏接通知
- 簡化前端狀態同步邏輯
```python
@router.websocket("/ws/notifications")
async def websocket_endpoint(websocket: WebSocket, token: str = Query(...)):
user = await verify_ws_token(token)
await manager.connect(user.id, websocket)
# 連線時補送未讀
unread = get_unread_notifications(db, user.id)
await websocket.send_json({
"type": "unread_sync",
"notifications": [n.dict() for n in unread]
})
# 開始監聽
await listen_for_notifications(user.id, websocket)
```
### 4. 訊息格式
**Decision:** 統一 JSON 格式
```json
{
"type": "notification",
"data": {
"id": "uuid",
"type": "mention|assignment|blocker|...",
"title": "...",
"message": "...",
"reference_type": "task|comment",
"reference_id": "uuid",
"created_at": "ISO8601"
}
}
```
## API Changes
```
# 新增 WebSocket endpoint
WS /ws/notifications?token={jwt_token}
# 訊息類型
-> {"type": "unread_sync", "notifications": [...]} # 連線時
-> {"type": "notification", "data": {...}} # 新通知
-> {"type": "mark_read", "notification_id": "..."} # 已讀確認
<- {"type": "ping"} # 心跳
```
## Risks / Trade-offs
| Risk | Mitigation |
|------|------------|
| WebSocket 連線數量大 | 使用心跳偵測清理斷線 |
| Redis Pub/Sub 可靠性 | Pub/Sub 為 fire-and-forget已有 DB 紀錄作為 fallback |
| Token 驗證 in query | WebSocket 標準限制token 有過期機制 |

View File

@@ -0,0 +1,50 @@
# Change: Fix Real-time Notifications Alignment
## Why
現行實作與 collaboration spec 的 Real-time Notifications requirement 有以下差距:
1. 通知僅寫入資料庫,未透過 WebSocket 即時推播
2. 未使用 Redis Pub/Sub 處理多 process 推播
3. 使用者連線時未補送未讀通知
## What Changes
- **WebSocket Manager** - 建立 WebSocket 連線管理模組
- **Redis Pub/Sub** - 整合 Redis 處理跨 process 通知推播
- **NotificationService** - 新增即時推播呼叫
- **API** - 新增 `/ws/notifications` WebSocket endpoint
- **Frontend** - 整合 WebSocket 接收即時通知
## Impact
- Affected specs: `collaboration`
- Affected code:
- `backend/app/core/websocket.py` - 新增 WebSocket 管理
- `backend/app/core/redis_pubsub.py` - 新增 Redis Pub/Sub 服務
- `backend/app/services/notification_service.py` - 加入即時推播
- `backend/app/api/notifications/router.py` - 新增 WebSocket endpoint
- `frontend/src/services/websocket.ts` - 新增 WebSocket client
- `frontend/src/contexts/NotificationContext.tsx` - 整合即時通知
## Implementation Phases
### Phase 1: WebSocket Infrastructure
- WebSocket 連線管理器
- 使用者連線/斷線處理
- 連線時補送未讀通知
### Phase 2: Redis Pub/Sub Integration
- Redis Pub/Sub 服務封裝
- 多 process 通知廣播
- 訊息序列化/反序列化
### Phase 3: Service Integration
- NotificationService 加入推播
- 前端 WebSocket client
- 未讀數量即時更新
## Dependencies
- collaboration (已完成)
- Redis 已在 user-auth 中使用
## Technical Decisions
- 使用 FastAPI WebSocket 原生支援
- Redis Pub/Sub 處理多 worker 同步
- 使用者以 user_id 為 channel key

View File

@@ -0,0 +1,36 @@
## MODIFIED Requirements
### Requirement: Real-time Notifications
系統 SHALL 透過 WebSocket 與 Redis Pub/Sub 推播即時通知。
#### Scenario: 即時通知推播
- **GIVEN** 發生需要通知的事件(如:被指派任務、被 @提及、阻礙標記)
- **WHEN** NotificationService.create_notification() 執行
- **THEN** 系統透過 Redis Pub/Sub 發布通知至 `notifications:{user_id}` channel
- **AND** 訂閱該 channel 的 WebSocket 連線接收訊息
- **AND** ConnectionManager 推送通知給使用者的 WebSocket
#### Scenario: 連線時補送未讀
- **GIVEN** 使用者建立 WebSocket 連線
- **WHEN** 連線驗證成功
- **THEN** 系統查詢該使用者的未讀通知 (is_read = false)
- **AND** 透過 unread_sync 訊息一次推送所有未讀通知
- **AND** 開始訂閱 Redis channel 接收新通知
#### Scenario: 心跳偵測
- **GIVEN** 使用者已建立 WebSocket 連線
- **WHEN** 連線超過心跳間隔無回應
- **THEN** 系統將連線標記為斷線並從 ConnectionManager 移除
## MODIFIED Technical Notes
- 使用 Redis Pub/Sub 處理即時通知推播
- WebSocket 連線管理:
- ConnectionManager 維護 user_id → WebSocket[] 映射
- 心跳偵測清理斷線連線
- Token 驗證透過 query parameter
- 通知推播流程:
1. NotificationService.create_notification() 建立通知
2. 呼叫 redis_pubsub.publish_notification() 發布
3. 訂閱該 user channel 的 worker 收到訊息
4. ConnectionManager.send_to_user() 推送給連線的 WebSocket

View File

@@ -0,0 +1,56 @@
## Phase 1: WebSocket Infrastructure
### 1.1 Connection Manager
- [ ] 1.1.1 建立 backend/app/core/websocket.py
- [ ] 1.1.2 實作 ConnectionManager class
- [ ] 1.1.3 實作 connect/disconnect/send_to_user 方法
- [ ] 1.1.4 加入心跳偵測機制
### 1.2 WebSocket Endpoint
- [ ] 1.2.1 新增 WS /ws/notifications endpoint
- [ ] 1.2.2 實作 WebSocket token 驗證
- [ ] 1.2.3 連線時查詢並推送未讀通知
- [ ] 1.2.4 處理 WebSocket 異常與斷線
### 1.3 Testing - Phase 1
- [ ] 1.3.1 WebSocket 連線測試
- [ ] 1.3.2 未讀通知補送測試
- [ ] 1.3.3 斷線處理測試
## Phase 2: Redis Pub/Sub Integration
### 2.1 Redis Pub/Sub Service
- [ ] 2.1.1 建立 backend/app/core/redis_pubsub.py
- [ ] 2.1.2 實作 publish_notification 函數
- [ ] 2.1.3 實作 subscribe_user_channel 函數
- [ ] 2.1.4 訊息 JSON 序列化處理
### 2.2 Cross-Process Broadcasting
- [ ] 2.2.1 WebSocket endpoint 訂閱 user channel
- [ ] 2.2.2 收到 Redis 訊息時推送給連線
- [ ] 2.2.3 處理訂閱錯誤與重連
### 2.3 Testing - Phase 2
- [ ] 2.3.1 Redis Pub/Sub 單元測試
- [ ] 2.3.2 跨 process 通知測試(手動驗證)
## Phase 3: Service Integration
### 3.1 NotificationService 整合
- [ ] 3.1.1 create_notification 後呼叫 publish_notification
- [ ] 3.1.2 確保所有通知類型都即時推播
- [ ] 3.1.3 處理 Redis 連線失敗 gracefully
### 3.2 Frontend WebSocket Client
- [ ] 3.2.1 建立 frontend/src/services/websocket.ts
- [ ] 3.2.2 實作 WebSocket 連線與重連邏輯
- [ ] 3.2.3 訊息處理與分發
### 3.3 NotificationContext 整合
- [ ] 3.3.1 修改 NotificationContext 使用 WebSocket
- [ ] 3.3.2 收到通知時更新未讀數量
- [ ] 3.3.3 收到 unread_sync 時同步狀態
### 3.4 Testing - Phase 3
- [ ] 3.4.1 完整即時通知流程測試
- [ ] 3.4.2 前端 WebSocket 整合測試