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

10 KiB
Raw Blame History

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

空間是最上層的組織單位,用於將相關專案分組。

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

專案屬於某個空間,具有預算、時程和安全等級設定。

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

定義專案內可用的任務狀態。

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

任務是核心實體,支援父子任務關係。

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 僅專案成員

權限檢查流程

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 層級,而非全域,提供專案彈性