Files
PROJECT-CONTORL/openspec/changes/archive/2025-12-28-add-task-management/design.md
beabigegg daca7798e3 feat: implement task management module
Backend (FastAPI):
- Database migration for spaces, projects, task_statuses, tasks tables
- SQLAlchemy models with relationships
- Pydantic schemas for CRUD operations
- Spaces API: CRUD with soft delete
- Projects API: CRUD with auto-created default statuses
- Tasks API: CRUD, status change, assign, subtask support
- Permission middleware with Security Level filtering
- Subtask depth limit (max 2 levels)

Frontend (React + Vite):
- Layout component with navigation
- Spaces list page
- Projects list page
- Tasks list page with status management

Fixes:
- auth_client.py: use 'username' field for external API
- config.py: extend JWT expiry to 7 days
- auth/router.py: sync Redis session with JWT expiry

Tests: 36 passed (unit + integration)
E2E: All APIs verified with real authentication

OpenSpec: add-task-management archived

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 00:31:34 +08:00

232 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Design: add-task-management
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────┐
│ Frontend │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Spaces │ │ Projects │ │ Tasks │ │ Task Detail │ │
│ │ List │ │ List │ │ ListView │ │ Form │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
└───────┼─────────────┼─────────────┼────────────────┼────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ API Layer │
│ ┌──────────────┐ ┌────────────────┐ ┌─────────────────┐ │
│ │ /api/spaces │ │ /api/projects │ │ /api/tasks │ │
│ │ router │ │ router │ │ router │ │
│ └──────┬───────┘ └───────┬────────┘ └────────┬────────┘ │
└─────────┼──────────────────┼────────────────────┼───────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Middleware Layer │
│ ┌─────────────────┐ ┌─────────────────────────────────┐ │
│ │ Auth Middleware│ │ Permission Middleware │ │
│ │ (JWT驗證) │ │ (RBAC + Security Level) │ │
│ └────────┬────────┘ └────────────────┬────────────────┘ │
└───────────┼────────────────────────────┼────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Data Layer │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ pjctrl_ │ │ pjctrl_ │ │ pjctrl_ │ │
│ │ spaces │ │ projects │ │ tasks │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Data Model Details
### pjctrl_spaces
空間是最上層的組織單位,用於將相關專案分組。
```sql
CREATE TABLE pjctrl_spaces (
id CHAR(36) PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
owner_id CHAR(36) NOT NULL,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (owner_id) REFERENCES pjctrl_users(id)
);
CREATE INDEX idx_spaces_owner ON pjctrl_spaces(owner_id);
CREATE INDEX idx_spaces_active ON pjctrl_spaces(is_active);
```
### pjctrl_projects
專案屬於某個空間,具有預算、時程和安全等級設定。
```sql
CREATE TABLE pjctrl_projects (
id CHAR(36) PRIMARY KEY,
space_id CHAR(36) NOT NULL,
title VARCHAR(200) NOT NULL,
description TEXT,
owner_id CHAR(36) NOT NULL,
budget DECIMAL(15, 2),
start_date DATE,
end_date DATE,
security_level ENUM('public', 'department', 'confidential') DEFAULT 'department',
status VARCHAR(50) DEFAULT 'active',
department_id CHAR(36),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (space_id) REFERENCES pjctrl_spaces(id) ON DELETE CASCADE,
FOREIGN KEY (owner_id) REFERENCES pjctrl_users(id),
FOREIGN KEY (department_id) REFERENCES pjctrl_departments(id)
);
CREATE INDEX idx_projects_space ON pjctrl_projects(space_id);
CREATE INDEX idx_projects_owner ON pjctrl_projects(owner_id);
CREATE INDEX idx_projects_department ON pjctrl_projects(department_id);
CREATE INDEX idx_projects_security ON pjctrl_projects(security_level);
```
### pjctrl_task_statuses
定義專案內可用的任務狀態。
```sql
CREATE TABLE pjctrl_task_statuses (
id CHAR(36) PRIMARY KEY,
project_id CHAR(36) NOT NULL,
name VARCHAR(50) NOT NULL,
color VARCHAR(7) DEFAULT '#808080',
position INT DEFAULT 0,
is_done BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES pjctrl_projects(id) ON DELETE CASCADE,
UNIQUE KEY uk_status_name (project_id, name)
);
CREATE INDEX idx_statuses_project ON pjctrl_task_statuses(project_id);
```
### pjctrl_tasks
任務是核心實體,支援父子任務關係。
```sql
CREATE TABLE pjctrl_tasks (
id CHAR(36) PRIMARY KEY,
project_id CHAR(36) NOT NULL,
parent_task_id CHAR(36),
title VARCHAR(500) NOT NULL,
description TEXT,
assignee_id CHAR(36),
status_id CHAR(36),
priority ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium',
original_estimate DECIMAL(8, 2),
time_spent DECIMAL(8, 2) DEFAULT 0,
blocker_flag BOOLEAN DEFAULT false,
due_date DATETIME,
position INT DEFAULT 0,
created_by CHAR(36) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES pjctrl_projects(id) ON DELETE CASCADE,
FOREIGN KEY (parent_task_id) REFERENCES pjctrl_tasks(id) ON DELETE CASCADE,
FOREIGN KEY (assignee_id) REFERENCES pjctrl_users(id),
FOREIGN KEY (status_id) REFERENCES pjctrl_task_statuses(id),
FOREIGN KEY (created_by) REFERENCES pjctrl_users(id)
);
CREATE INDEX idx_tasks_project ON pjctrl_tasks(project_id);
CREATE INDEX idx_tasks_parent ON pjctrl_tasks(parent_task_id);
CREATE INDEX idx_tasks_assignee ON pjctrl_tasks(assignee_id);
CREATE INDEX idx_tasks_status ON pjctrl_tasks(status_id);
CREATE INDEX idx_tasks_due ON pjctrl_tasks(due_date);
CREATE INDEX idx_tasks_blocker ON pjctrl_tasks(blocker_flag);
```
## Permission Model
### Security Level 行為
| Security Level | 可見範圍 |
|----------------|----------|
| public | 所有登入使用者 |
| department | 同部門使用者 + 專案成員 |
| confidential | 僅專案成員 |
### 權限檢查流程
```python
def check_project_access(user, project):
# 系統管理員可存取所有
if user.is_system_admin:
return True
# 專案擁有者
if project.owner_id == user.id:
return True
# 依 security_level 判斷
if project.security_level == 'public':
return True
elif project.security_level == 'department':
return user.department_id == project.department_id
else: # confidential
return is_project_member(user, project)
```
## API Design
### Spaces API
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/spaces | 列出使用者可見的空間 |
| POST | /api/spaces | 建立新空間 |
| GET | /api/spaces/{id} | 取得空間詳情 |
| PATCH | /api/spaces/{id} | 更新空間 |
| DELETE | /api/spaces/{id} | 刪除空間 (軟刪除) |
### Projects API
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/spaces/{space_id}/projects | 列出空間內的專案 |
| POST | /api/spaces/{space_id}/projects | 建立新專案 |
| GET | /api/projects/{id} | 取得專案詳情 |
| PATCH | /api/projects/{id} | 更新專案 |
| DELETE | /api/projects/{id} | 刪除專案 |
### Tasks API
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | /api/projects/{project_id}/tasks | 列出專案內的任務 |
| POST | /api/projects/{project_id}/tasks | 建立新任務 |
| GET | /api/tasks/{id} | 取得任務詳情 |
| PATCH | /api/tasks/{id} | 更新任務 |
| DELETE | /api/tasks/{id} | 刪除任務 |
| PATCH | /api/tasks/{id}/status | 變更任務狀態 |
| PATCH | /api/tasks/{id}/assign | 指派任務 |
## Default Task Statuses
每個新專案自動建立以下預設狀態:
| Name | Color | is_done |
|------|-------|---------|
| To Do | #808080 | false |
| In Progress | #0066cc | false |
| Blocked | #cc0000 | false |
| Done | #00cc66 | true |
## Trade-offs & Decisions
1. **子任務深度限制**: 限制為 2 層 (Task → Sub-task),避免過度巢狀造成的複雜度
2. **軟刪除 vs 硬刪除**: Space 使用軟刪除 (is_active)Tasks 使用級聯硬刪除
3. **狀態定義層級**: 狀態定義在 Project 層級,而非全域,提供專案彈性