feat: implement user authentication module

- Backend (FastAPI):
  - External API authentication (pj-auth-api.vercel.app)
  - JWT token validation with Redis session storage
  - RBAC with department isolation
  - User, Role, Department models with pjctrl_ prefix
  - Alembic migrations with project-specific version table
  - Complete test coverage (13 tests)

- Frontend (React + Vite):
  - AuthContext for state management
  - Login page with error handling
  - Protected route component
  - Dashboard with user info display

- OpenSpec:
  - 7 capability specs defined
  - add-user-auth change archived

🤖 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-28 23:41:37 +08:00
commit 1fda7da2c2
77 changed files with 6562 additions and 0 deletions

456
openspec/AGENTS.md Normal file
View File

@@ -0,0 +1,456 @@
# OpenSpec Instructions
Instructions for AI coding assistants using OpenSpec for spec-driven development.
## TL;DR Quick Checklist
- Search existing work: `openspec spec list --long`, `openspec list` (use `rg` only for full-text search)
- Decide scope: new capability vs modify existing capability
- Pick a unique `change-id`: kebab-case, verb-led (`add-`, `update-`, `remove-`, `refactor-`)
- Scaffold: `proposal.md`, `tasks.md`, `design.md` (only if needed), and delta specs per affected capability
- Write deltas: use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements`; include at least one `#### Scenario:` per requirement
- Validate: `openspec validate [change-id] --strict` and fix issues
- Request approval: Do not start implementation until proposal is approved
## Three-Stage Workflow
### Stage 1: Creating Changes
Create proposal when you need to:
- Add features or functionality
- Make breaking changes (API, schema)
- Change architecture or patterns
- Optimize performance (changes behavior)
- Update security patterns
Triggers (examples):
- "Help me create a change proposal"
- "Help me plan a change"
- "Help me create a proposal"
- "I want to create a spec proposal"
- "I want to create a spec"
Loose matching guidance:
- Contains one of: `proposal`, `change`, `spec`
- With one of: `create`, `plan`, `make`, `start`, `help`
Skip proposal for:
- Bug fixes (restore intended behavior)
- Typos, formatting, comments
- Dependency updates (non-breaking)
- Configuration changes
- Tests for existing behavior
**Workflow**
1. Review `openspec/project.md`, `openspec list`, and `openspec list --specs` to understand current context.
2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, optional `design.md`, and spec deltas under `openspec/changes/<id>/`.
3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement.
4. Run `openspec validate <id> --strict` and resolve any issues before sharing the proposal.
### Stage 2: Implementing Changes
Track these steps as TODOs and complete them one by one.
1. **Read proposal.md** - Understand what's being built
2. **Read design.md** (if exists) - Review technical decisions
3. **Read tasks.md** - Get implementation checklist
4. **Implement tasks sequentially** - Complete in order
5. **Confirm completion** - Ensure every item in `tasks.md` is finished before updating statuses
6. **Update checklist** - After all work is done, set every task to `- [x]` so the list reflects reality
7. **Approval gate** - Do not start implementation until the proposal is reviewed and approved
### Stage 3: Archiving Changes
After deployment, create separate PR to:
- Move `changes/[name]/``changes/archive/YYYY-MM-DD-[name]/`
- Update `specs/` if capabilities changed
- Use `openspec archive <change-id> --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly)
- Run `openspec validate --strict` to confirm the archived change passes checks
## Before Any Task
**Context Checklist:**
- [ ] Read relevant specs in `specs/[capability]/spec.md`
- [ ] Check pending changes in `changes/` for conflicts
- [ ] Read `openspec/project.md` for conventions
- [ ] Run `openspec list` to see active changes
- [ ] Run `openspec list --specs` to see existing capabilities
**Before Creating Specs:**
- Always check if capability already exists
- Prefer modifying existing specs over creating duplicates
- Use `openspec show [spec]` to review current state
- If request is ambiguous, ask 12 clarifying questions before scaffolding
### Search Guidance
- Enumerate specs: `openspec spec list --long` (or `--json` for scripts)
- Enumerate changes: `openspec list` (or `openspec change list --json` - deprecated but available)
- Show details:
- Spec: `openspec show <spec-id> --type spec` (use `--json` for filters)
- Change: `openspec show <change-id> --json --deltas-only`
- Full-text search (use ripgrep): `rg -n "Requirement:|Scenario:" openspec/specs`
## Quick Start
### CLI Commands
```bash
# Essential commands
openspec list # List active changes
openspec list --specs # List specifications
openspec show [item] # Display change or spec
openspec validate [item] # Validate changes or specs
openspec archive <change-id> [--yes|-y] # Archive after deployment (add --yes for non-interactive runs)
# Project management
openspec init [path] # Initialize OpenSpec
openspec update [path] # Update instruction files
# Interactive mode
openspec show # Prompts for selection
openspec validate # Bulk validation mode
# Debugging
openspec show [change] --json --deltas-only
openspec validate [change] --strict
```
### Command Flags
- `--json` - Machine-readable output
- `--type change|spec` - Disambiguate items
- `--strict` - Comprehensive validation
- `--no-interactive` - Disable prompts
- `--skip-specs` - Archive without spec updates
- `--yes`/`-y` - Skip confirmation prompts (non-interactive archive)
## Directory Structure
```
openspec/
├── project.md # Project conventions
├── specs/ # Current truth - what IS built
│ └── [capability]/ # Single focused capability
│ ├── spec.md # Requirements and scenarios
│ └── design.md # Technical patterns
├── changes/ # Proposals - what SHOULD change
│ ├── [change-name]/
│ │ ├── proposal.md # Why, what, impact
│ │ ├── tasks.md # Implementation checklist
│ │ ├── design.md # Technical decisions (optional; see criteria)
│ │ └── specs/ # Delta changes
│ │ └── [capability]/
│ │ └── spec.md # ADDED/MODIFIED/REMOVED
│ └── archive/ # Completed changes
```
## Creating Change Proposals
### Decision Tree
```
New request?
├─ Bug fix restoring spec behavior? → Fix directly
├─ Typo/format/comment? → Fix directly
├─ New feature/capability? → Create proposal
├─ Breaking change? → Create proposal
├─ Architecture change? → Create proposal
└─ Unclear? → Create proposal (safer)
```
### Proposal Structure
1. **Create directory:** `changes/[change-id]/` (kebab-case, verb-led, unique)
2. **Write proposal.md:**
```markdown
# Change: [Brief description of change]
## Why
[1-2 sentences on problem/opportunity]
## What Changes
- [Bullet list of changes]
- [Mark breaking changes with **BREAKING**]
## Impact
- Affected specs: [list capabilities]
- Affected code: [key files/systems]
```
3. **Create spec deltas:** `specs/[capability]/spec.md`
```markdown
## ADDED Requirements
### Requirement: New Feature
The system SHALL provide...
#### Scenario: Success case
- **WHEN** user performs action
- **THEN** expected result
## MODIFIED Requirements
### Requirement: Existing Feature
[Complete modified requirement]
## REMOVED Requirements
### Requirement: Old Feature
**Reason**: [Why removing]
**Migration**: [How to handle]
```
If multiple capabilities are affected, create multiple delta files under `changes/[change-id]/specs/<capability>/spec.md`—one per capability.
4. **Create tasks.md:**
```markdown
## 1. Implementation
- [ ] 1.1 Create database schema
- [ ] 1.2 Implement API endpoint
- [ ] 1.3 Add frontend component
- [ ] 1.4 Write tests
```
5. **Create design.md when needed:**
Create `design.md` if any of the following apply; otherwise omit it:
- Cross-cutting change (multiple services/modules) or a new architectural pattern
- New external dependency or significant data model changes
- Security, performance, or migration complexity
- Ambiguity that benefits from technical decisions before coding
Minimal `design.md` skeleton:
```markdown
## Context
[Background, constraints, stakeholders]
## Goals / Non-Goals
- Goals: [...]
- Non-Goals: [...]
## Decisions
- Decision: [What and why]
- Alternatives considered: [Options + rationale]
## Risks / Trade-offs
- [Risk] → Mitigation
## Migration Plan
[Steps, rollback]
## Open Questions
- [...]
```
## Spec File Format
### Critical: Scenario Formatting
**CORRECT** (use #### headers):
```markdown
#### Scenario: User login success
- **WHEN** valid credentials provided
- **THEN** return JWT token
```
**WRONG** (don't use bullets or bold):
```markdown
- **Scenario: User login** ❌
**Scenario**: User login ❌
### Scenario: User login ❌
```
Every requirement MUST have at least one scenario.
### Requirement Wording
- Use SHALL/MUST for normative requirements (avoid should/may unless intentionally non-normative)
### Delta Operations
- `## ADDED Requirements` - New capabilities
- `## MODIFIED Requirements` - Changed behavior
- `## REMOVED Requirements` - Deprecated features
- `## RENAMED Requirements` - Name changes
Headers matched with `trim(header)` - whitespace ignored.
#### When to use ADDED vs MODIFIED
- ADDED: Introduces a new capability or sub-capability that can stand alone as a requirement. Prefer ADDED when the change is orthogonal (e.g., adding "Slash Command Configuration") rather than altering the semantics of an existing requirement.
- MODIFIED: Changes the behavior, scope, or acceptance criteria of an existing requirement. Always paste the full, updated requirement content (header + all scenarios). The archiver will replace the entire requirement with what you provide here; partial deltas will drop previous details.
- RENAMED: Use when only the name changes. If you also change behavior, use RENAMED (name) plus MODIFIED (content) referencing the new name.
Common pitfall: Using MODIFIED to add a new concern without including the previous text. This causes loss of detail at archive time. If you arent explicitly changing the existing requirement, add a new requirement under ADDED instead.
Authoring a MODIFIED requirement correctly:
1) Locate the existing requirement in `openspec/specs/<capability>/spec.md`.
2) Copy the entire requirement block (from `### Requirement: ...` through its scenarios).
3) Paste it under `## MODIFIED Requirements` and edit to reflect the new behavior.
4) Ensure the header text matches exactly (whitespace-insensitive) and keep at least one `#### Scenario:`.
Example for RENAMED:
```markdown
## RENAMED Requirements
- FROM: `### Requirement: Login`
- TO: `### Requirement: User Authentication`
```
## Troubleshooting
### Common Errors
**"Change must have at least one delta"**
- Check `changes/[name]/specs/` exists with .md files
- Verify files have operation prefixes (## ADDED Requirements)
**"Requirement must have at least one scenario"**
- Check scenarios use `#### Scenario:` format (4 hashtags)
- Don't use bullet points or bold for scenario headers
**Silent scenario parsing failures**
- Exact format required: `#### Scenario: Name`
- Debug with: `openspec show [change] --json --deltas-only`
### Validation Tips
```bash
# Always use strict mode for comprehensive checks
openspec validate [change] --strict
# Debug delta parsing
openspec show [change] --json | jq '.deltas'
# Check specific requirement
openspec show [spec] --json -r 1
```
## Happy Path Script
```bash
# 1) Explore current state
openspec spec list --long
openspec list
# Optional full-text search:
# rg -n "Requirement:|Scenario:" openspec/specs
# rg -n "^#|Requirement:" openspec/changes
# 2) Choose change id and scaffold
CHANGE=add-two-factor-auth
mkdir -p openspec/changes/$CHANGE/{specs/auth}
printf "## Why\n...\n\n## What Changes\n- ...\n\n## Impact\n- ...\n" > openspec/changes/$CHANGE/proposal.md
printf "## 1. Implementation\n- [ ] 1.1 ...\n" > openspec/changes/$CHANGE/tasks.md
# 3) Add deltas (example)
cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF'
## ADDED Requirements
### Requirement: Two-Factor Authentication
Users MUST provide a second factor during login.
#### Scenario: OTP required
- **WHEN** valid credentials are provided
- **THEN** an OTP challenge is required
EOF
# 4) Validate
openspec validate $CHANGE --strict
```
## Multi-Capability Example
```
openspec/changes/add-2fa-notify/
├── proposal.md
├── tasks.md
└── specs/
├── auth/
│ └── spec.md # ADDED: Two-Factor Authentication
└── notifications/
└── spec.md # ADDED: OTP email notification
```
auth/spec.md
```markdown
## ADDED Requirements
### Requirement: Two-Factor Authentication
...
```
notifications/spec.md
```markdown
## ADDED Requirements
### Requirement: OTP Email Notification
...
```
## Best Practices
### Simplicity First
- Default to <100 lines of new code
- Single-file implementations until proven insufficient
- Avoid frameworks without clear justification
- Choose boring, proven patterns
### Complexity Triggers
Only add complexity with:
- Performance data showing current solution too slow
- Concrete scale requirements (>1000 users, >100MB data)
- Multiple proven use cases requiring abstraction
### Clear References
- Use `file.ts:42` format for code locations
- Reference specs as `specs/auth/spec.md`
- Link related changes and PRs
### Capability Naming
- Use verb-noun: `user-auth`, `payment-capture`
- Single purpose per capability
- 10-minute understandability rule
- Split if description needs "AND"
### Change ID Naming
- Use kebab-case, short and descriptive: `add-two-factor-auth`
- Prefer verb-led prefixes: `add-`, `update-`, `remove-`, `refactor-`
- Ensure uniqueness; if taken, append `-2`, `-3`, etc.
## Tool Selection Guide
| Task | Tool | Why |
|------|------|-----|
| Find files by pattern | Glob | Fast pattern matching |
| Search code content | Grep | Optimized regex search |
| Read specific files | Read | Direct file access |
| Explore unknown scope | Task | Multi-step investigation |
## Error Recovery
### Change Conflicts
1. Run `openspec list` to see active changes
2. Check for overlapping specs
3. Coordinate with change owners
4. Consider combining proposals
### Validation Failures
1. Run with `--strict` flag
2. Check JSON output for details
3. Verify spec file format
4. Ensure scenarios properly formatted
### Missing Context
1. Read project.md first
2. Check related specs
3. Review recent archives
4. Ask for clarification
## Quick Reference
### Stage Indicators
- `changes/` - Proposed, not yet built
- `specs/` - Built and deployed
- `archive/` - Completed changes
### File Purposes
- `proposal.md` - Why and what
- `tasks.md` - Implementation steps
- `design.md` - Technical decisions
- `spec.md` - Requirements and behavior
### CLI Essentials
```bash
openspec list # What's in progress?
openspec show [item] # View details
openspec validate --strict # Is it correct?
openspec archive <change-id> [--yes|-y] # Mark complete (add --yes for automation)
```
Remember: Specs are truth. Changes are proposals. Keep them in sync.

View File

@@ -0,0 +1,199 @@
# Design: add-user-auth
## Context
這是專案的第一個模組,需要建立認證與授權的基礎架構。系統使用外部認證 API 進行身份驗證,本地不儲存密碼。認證成功後由本地系統核發 JWT Token 並管理 Session。
### Stakeholders
- 所有系統使用者工程師、主管、PMO
- 系統管理員 (ymirliu@panjit.com.tw)
- 外部認證 API 維護者
### Constraints
- 必須使用外部 API 認證,不可本地繞過
- 資料表必須使用 `pjctrl_` 前綴
- 需支援部門級資料隔離
## Goals / Non-Goals
### Goals
- 實現安全的外部 API 認證整合
- 建立 RBAC 權限控制框架
- 支援部門級資料隔離
- 預設系統管理員帳號
### Non-Goals
- 本地密碼儲存與驗證
- 多因素認證 (MFA) - 由外部 API 處理
- OAuth2 第三方登入
- 使用者自助註冊
## Decisions
### Decision 1: 認證流程架構
**選擇**: Frontend-to-External-API 模式
```
┌─────────┐ ┌─────────────┐ ┌──────────────────────────┐
│ User │────▶│ Frontend │────▶│ pj-auth-api.vercel.app │
└─────────┘ └─────────────┘ └──────────────────────────┘
│ │
│◀────── Auth Token ──────│
┌─────────────┐
│ Backend │
│ - 驗證 Token│
│ - 核發 JWT │
│ - 建立 Session
└─────────────┘
```
**原因**:
- 減少後端介入認證流程的攻擊面
- 外部 API 已處理密碼安全性
- 後端只負責驗證與授權
**替代方案考量**:
- Backend Proxy 模式:增加延遲與複雜度,無明顯優勢
### Decision 2: JWT Token 策略
**選擇**: 短期 Access Token + Redis Session
```python
JWT_PAYLOAD = {
"sub": "user_id",
"email": "user@example.com",
"role": "engineer",
"department_id": "uuid",
"is_system_admin": false,
"exp": "15 minutes from now",
"iat": "now"
}
```
**原因**:
- 短期 Token 減少被盜用風險
- Redis Session 可即時撤銷
- 權限資訊嵌入 Token 減少 DB 查詢
### Decision 3: 權限模型
**選擇**: RBAC + 部門隔離混合模式
```python
# 權限檢查順序
def check_permission(user, resource, action):
# 1. 系統管理員 - 全權通過
if user.is_system_admin:
return True
# 2. 角色權限檢查
if not has_role_permission(user.role, action):
return False
# 3. 部門隔離檢查
if not is_same_department_or_allowed(user, resource):
return False
return True
```
**原因**:
- 系統管理員需無限制存取(用於問題排查)
- RBAC 提供功能層面控制
- 部門隔離提供資料層面控制
### Decision 4: 資料表設計
```sql
-- 使用 UUID 而非 auto-increment
-- 便於分散式環境與資料合併
CREATE TABLE pjctrl_roles (
id CHAR(36) PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
permissions JSON NOT NULL,
is_system_role BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE pjctrl_departments (
id CHAR(36) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
parent_id CHAR(36) REFERENCES pjctrl_departments(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE pjctrl_users (
id CHAR(36) PRIMARY KEY,
email VARCHAR(200) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
department_id CHAR(36) REFERENCES pjctrl_departments(id),
role_id CHAR(36) REFERENCES pjctrl_roles(id),
skills JSON,
capacity DECIMAL(5,2) DEFAULT 40.00,
is_active BOOLEAN DEFAULT TRUE,
is_system_admin BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
## Risks / Trade-offs
| 風險 | 影響 | 緩解措施 |
|------|------|---------|
| 外部 API 不可用 | 所有使用者無法登入 | 顯示友善錯誤訊息、記錄事件、監控告警 |
| JWT Token 洩漏 | 短期內帳號被盜用 | 15 分鐘過期、Redis 可即時撤銷 |
| 部門隔離邏輯錯誤 | 資料外洩 | 完整測試案例、程式碼審查 |
## Migration Plan
1. 執行資料庫 migration 建立資料表
2. 執行 seed data 建立預設角色與管理員
3. 部署後端認證模組
4. 部署前端登入頁面
5. 驗證管理員可登入
**Rollback**:
- 資料庫:執行 down migration
- 程式碼:回復至前一版本
## Environment Configuration
```bash
# Database
MYSQL_HOST=mysql.theaken.com
MYSQL_PORT=33306
MYSQL_USER=A060
MYSQL_PASSWORD=<見密碼管理>
MYSQL_DATABASE=db_A060
# Redis (待設定)
REDIS_HOST=localhost
REDIS_PORT=6379
# JWT
JWT_SECRET_KEY=<生成隨機金鑰>
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=15
# External Auth API
AUTH_API_URL=https://pj-auth-api.vercel.app
```
## Admin Credentials
- **Email**: `ymirliu@panjit.com.tw`
- **Password**: 由外部認證 API 管理(已設定於外部系統)
## Open Questions
1. ~~外部認證 API 的回應格式?~~ (需確認)
2. ~~Session 過期時間?~~ 建議 15 分鐘,可透過環境變數調整
3. ~~是否需要 Refresh Token 機制?~~ 暫不需要,視實際使用情況再評估
4. ~~MySQL 連線資訊?~~ 已提供
5. ~~Admin 帳號密碼?~~ 已提供 (ymirliu@panjit.com.tw)

View File

@@ -0,0 +1,38 @@
# Change: Add User Authentication & Authorization
## Why
系統需要使用者認證與授權機制作為所有功能的基礎。沒有認證系統,無法識別使用者身份、無法實施權限控制、無法追蹤操作記錄。這是整個專案管理系統的第一個必要模組。
## What Changes
- **新增** 外部 API 認證整合 (https://pj-auth-api.vercel.app)
- **新增** JWT Token 驗證與 Session 管理
- **新增** 使用者資料表 (`pjctrl_users`)
- **新增** 部門資料表 (`pjctrl_departments`)
- **新增** 角色資料表 (`pjctrl_roles`)
- **新增** 預設系統管理員帳號 (`ymirliu@panjit.com.tw`)
- **新增** RBAC 權限檢查中間件
- **新增** 部門級資料隔離機制
## Impact
- **Affected specs**: `user-auth` (新增)
- **Affected code**:
- Backend: `app/api/auth/`, `app/models/user.py`, `app/core/security.py`
- Frontend: `src/contexts/AuthContext.tsx`, `src/pages/Login.tsx`
- Database: Migration for `pjctrl_users`, `pjctrl_departments`, `pjctrl_roles`
## Dependencies
- 外部認證 API: https://pj-auth-api.vercel.app (必須可用)
- MySQL 資料庫連線
- Redis (用於 Session 儲存)
## Success Criteria
1. 使用者可透過外部 API 完成登入
2. JWT Token 正確核發與驗證
3. 系統管理員帳號可登入並存取所有資源
4. 非授權使用者無法存取受保護的 API
5. 部門隔離正確運作

View File

@@ -0,0 +1,86 @@
## ADDED Requirements
### Requirement: API-Based Authentication
系統 SHALL 限定使用外部認證 API (https://pj-auth-api.vercel.app) 進行登入認證,不支援其他認證方式。
#### Scenario: API 登入成功
- **GIVEN** 使用者擁有有效的企業帳號
- **WHEN** 使用者透過前端提交憑證
- **THEN** 系統呼叫 https://pj-auth-api.vercel.app 驗證憑證
- **AND** 驗證成功後建立 session 並回傳 JWT token
#### Scenario: API 登入失敗
- **GIVEN** 使用者提供無效的憑證
- **WHEN** 使用者嘗試登入
- **THEN** 認證 API 回傳錯誤
- **AND** 系統拒絕登入並顯示錯誤訊息
- **AND** 記錄失敗的登入嘗試
#### Scenario: 認證 API 無法連線
- **GIVEN** 認證 API 服務無法連線
- **WHEN** 使用者嘗試登入
- **THEN** 系統顯示服務暫時無法使用的訊息
- **AND** 記錄連線失敗事件
### Requirement: System Administrator
系統 SHALL 預設一個系統管理員帳號,擁有所有權限。系統管理員帳號必須存在於外部認證系統,且登入流程仍需透過外部認證 API不允許本地繞過認證。
#### Scenario: 預設管理員帳號
- **GIVEN** 系統初始化完成
- **WHEN** 系統啟動
- **THEN** 存在預設管理員帳號 ymirliu@panjit.com.tw
- **AND** 該帳號擁有 super_admin 角色
- **AND** 該帳號不可被刪除或降級
#### Scenario: 管理員登入流程
- **GIVEN** 管理員帳號 ymirliu@panjit.com.tw 需要登入
- **WHEN** 管理員提交憑證
- **THEN** 系統仍需呼叫 https://pj-auth-api.vercel.app 驗證
- **AND** 不存在任何本地繞過認證的機制
- **AND** 驗證成功後才授予 super_admin 權限
#### Scenario: 管理員全域權限
- **GIVEN** 管理員帳號 ymirliu@panjit.com.tw 已通過 API 認證並登入
- **WHEN** 管理員存取任何資源
- **THEN** 系統允許存取,無視部門隔離限制
### Requirement: Role-Based Access Control
系統 SHALL 支援基於角色的存取控制 (RBAC)。
#### Scenario: 角色權限檢查
- **GIVEN** 使用者被指派特定角色
- **WHEN** 使用者嘗試存取受保護的資源
- **THEN** 系統根據角色權限決定是否允許存取
#### Scenario: 角色指派
- **GIVEN** 管理員擁有使用者管理權限
- **WHEN** 管理員為使用者指派角色
- **THEN** 系統更新使用者的角色設定
- **AND** 新權限立即生效
### Requirement: Department Isolation
系統 SHALL 實施部門級別的資料隔離,確保跨部門資料安全。
#### Scenario: 部門資料隔離
- **GIVEN** 使用者屬於研發部門
- **WHEN** 使用者嘗試存取廠務部門的專案
- **THEN** 系統拒絕存取並顯示無權限訊息
#### Scenario: 跨部門專案存取
- **GIVEN** 專案被設定為跨部門可見
- **WHEN** 不同部門的使用者嘗試存取該專案
- **THEN** 系統根據專案的 Security_Level 設定決定是否允許存取
### Requirement: Session Management
系統 SHALL 管理使用者 session包含過期與登出機制。
#### Scenario: Session 過期
- **GIVEN** 使用者已登入系統
- **WHEN** Session 超過設定的有效期限
- **THEN** 系統自動使 session 失效
- **AND** 使用者需重新登入
#### Scenario: 主動登出
- **GIVEN** 使用者已登入系統
- **WHEN** 使用者執行登出操作
- **THEN** 系統銷毀 session 並清除 token

View File

@@ -0,0 +1,138 @@
# Tasks: add-user-auth
## 1. 專案初始化
- [x] 1.1 建立 Conda 環境與 requirements.txt
- [x] 1.2 初始化 FastAPI 專案結構
- [x] 1.3 設定 MySQL 連線與 SQLAlchemy
- [x] 1.4 設定 Redis 連線
- [x] 1.5 建立環境變數配置 (.env.example)
## 2. 資料庫模型
- [x] 2.1 建立 `pjctrl_roles` 資料表 migration
- [x] 2.2 建立 `pjctrl_departments` 資料表 migration
- [x] 2.3 建立 `pjctrl_users` 資料表 migration
- [x] 2.4 建立 seed data (預設管理員與角色)
- [x] 2.5 驗證 migration 可正確執行與回滾
## 3. 認證模組
- [x] 3.1 實作外部 API 認證 client (`app/services/auth_client.py`)
- [x] 3.2 實作 JWT Token 驗證邏輯
- [x] 3.3 實作登入 API endpoint (`POST /api/auth/login`)
- [x] 3.4 實作登出 API endpoint (`POST /api/auth/logout`)
- [x] 3.5 實作取得當前使用者 API (`GET /api/auth/me`)
- [x] 3.6 處理認證 API 連線失敗情境
## 4. Session 管理
- [x] 4.1 實作 Redis Session 儲存
- [x] 4.2 實作 Session 過期機制
- [x] 4.3 實作 Token 刷新機制 (如需要) - 暫不需要
## 5. 權限控制
- [x] 5.1 實作認證中間件 (`app/middleware/auth.py`)
- [x] 5.2 實作 RBAC 權限檢查裝飾器
- [x] 5.3 實作部門隔離邏輯
- [x] 5.4 實作系統管理員全域權限判斷
## 6. 使用者管理 API
- [x] 6.1 實作使用者列表 API (`GET /api/users`)
- [x] 6.2 實作使用者詳情 API (`GET /api/users/{id}`)
- [x] 6.3 實作角色指派 API (`PATCH /api/users/{id}/role`)
- [x] 6.4 實作部門管理 API (CRUD)
## 7. 測試
- [x] 7.1 撰寫認證模組單元測試
- [x] 7.2 撰寫權限檢查單元測試
- [x] 7.3 撰寫 API 整合測試
- [x] 7.4 測試系統管理員權限
- [x] 7.5 測試部門隔離情境
## 8. 前端 (基礎)
- [x] 8.1 建立 React 專案結構
- [x] 8.2 實作 AuthContext (認證狀態管理)
- [x] 8.3 實作登入頁面
- [x] 8.4 實作 Protected Route 元件
- [x] 8.5 實作登出功能
## Dependencies
```
1.x (專案初始化) → 2.x (資料庫) → 3.x (認證) → 4.x (Session)
5.x (權限) → 6.x (使用者管理)
7.x (測試)
8.x (前端) 可與 3.x-6.x 並行開發
```
## Notes
- 所有資料表使用 `pjctrl_` 前綴
- 認證必須透過外部 API不可有本地繞過
- 系統管理員帳號在 seed data 中建立
## Implementation Summary
完成日期: 2024-01-XX
### 已建立的檔案結構
```
backend/
├── app/
│ ├── api/
│ │ ├── auth/router.py # 認證 API
│ │ ├── users/router.py # 使用者管理 API
│ │ └── departments/router.py # 部門管理 API
│ ├── core/
│ │ ├── config.py # 環境配置
│ │ ├── database.py # 資料庫連線
│ │ ├── redis.py # Redis 連線
│ │ └── security.py # JWT 處理
│ ├── middleware/
│ │ └── auth.py # 認證與權限中間件
│ ├── models/
│ │ ├── user.py # User model
│ │ ├── role.py # Role model
│ │ └── department.py # Department model
│ ├── schemas/ # Pydantic schemas
│ ├── services/
│ │ └── auth_client.py # 外部認證 API client
│ └── main.py # FastAPI 應用程式
├── migrations/
│ └── versions/
│ └── 001_initial_auth_tables.py
├── tests/
│ ├── conftest.py
│ ├── test_auth.py
│ └── test_users.py
├── .env
├── .env.example
├── requirements.txt
└── environment.yml
frontend/
├── src/
│ ├── components/
│ │ └── ProtectedRoute.tsx
│ ├── contexts/
│ │ └── AuthContext.tsx
│ ├── pages/
│ │ ├── Login.tsx
│ │ └── Dashboard.tsx
│ ├── services/
│ │ └── api.ts
│ ├── App.tsx
│ └── main.tsx
├── package.json
├── tsconfig.json
└── vite.config.ts
```

78
openspec/project.md Normal file
View File

@@ -0,0 +1,78 @@
# Project Context
## Purpose
Cross-departmental project management system serving as a Single Source of Truth to:
- Reduce engineering time-reporting burden
- Provide real-time resource load and project progress analysis
- Standardize task tracking across departments
Target users:
- **Engineers**: Simplified reporting, automated reminders, personal task dashboard
- **Unit Managers**: Team workload visibility, resource allocation heatmaps, multi-project health boards
- **Admin/PMO**: Centralized data, automated weekly reports, document version control
## Tech Stack
- **Frontend**: React.js (recommended for complex admin dashboards)
- **Backend**: Python with FastAPI (enables future AI/ML integration for risk prediction)
- **Database**: MySQL (relational structure for complex project hierarchies and dependencies)
- **Cache & Real-time**: Redis (push notifications, task state locking)
- **Real-time Sync**: WebSocket for live collaboration
- **Authentication**: Enterprise Windows AD/LDAP via SSO (https://pj-auth-api.vercel.app)
- **Environment Management**: Conda (for Python dependency isolation)
## Project Conventions
### Code Style
- [To be defined - React/TypeScript conventions for frontend]
- [To be defined - Python/FastAPI conventions for backend]
### Database Naming
- **Table Prefix**: All tables must use `pjctrl_` prefix (e.g., `pjctrl_users`, `pjctrl_projects`, `pjctrl_tasks`)
- This ensures isolation in shared database environments and prevents migration conflicts
### Architecture Patterns
- 3-Tier Architecture (Presentation, Application, Data layers)
- Designed for future extensibility (MES/ERP integration capability)
- Multi-level task hierarchy: Space > Project > Task > Sub-task
### Testing Strategy
- [To be defined]
### Git Workflow
- [To be defined]
## Domain Context
**Semiconductor/Manufacturing Industry**
- Custom fields support domain-specific data: package types, machine numbers, expected yield rates
- Document encryption (AES-256) for sensitive semiconductor drawings
- User watermarking on downloads for IP protection
- Department-level access control (e.g., factory ops cannot view R&D projects)
## Important Constraints
- **Security**: Enterprise AD/LDAP integration with fine-grained permissions
- **Audit Trail**: All changes logged (deadline modifications, file deletions, etc.)
- **Data Sensitivity**: Encrypted storage for sensitive technical documents
- **Real-time Requirements**: WebSocket-based live updates for concurrent editing
- **Database Isolation**: All tables MUST use a designated prefix (e.g., `pjctrl_`) to prevent conflicts during sync/migration with other tables in shared database environments
## External Dependencies
- **SSO Auth API**: https://pj-auth-api.vercel.app (Windows AD integration)
- **On-premise Storage**: NAS for file attachments
- Future: MES/ERP system integration
## Database Connection
- **Host**: mysql.theaken.com
- **Port**: 33306
- **User**: A060
- **Database**: db_A060
- **Table Prefix**: `pjctrl_`
## System Administrator
- **Email**: ymirliu@panjit.com.tw
- **Role**: super_admin (不可刪除或降級)
## Core Data Model (ERD)
- **User**: ID, Name, Department, Role, Skills, Capacity
- **Project**: ID, Title, Owner, Budget, Timeline, Security_Level
- **Task**: ID, Project_ID, Assignee, Priority, Status, Original_Estimate, Time_Spent, Blocker_Flag
- **Attachment**: ID, Task_ID, Version, File_Path

View File

@@ -0,0 +1,159 @@
# Audit Trail
系統級稽核追蹤,記錄所有關鍵變更操作供合規與追溯需求。
## Requirements
### Requirement: Change Logging
系統 SHALL 記錄所有關鍵變更操作,包含誰在何時改了什麼。
#### Scenario: 任務欄位變更記錄
- **GIVEN** 使用者修改任務的任何欄位(如截止日期、狀態、指派者)
- **WHEN** 變更儲存成功
- **THEN** 系統記錄變更前後的值
- **AND** 記錄操作者、時間、IP 位址
#### Scenario: 專案設定變更記錄
- **GIVEN** 管理者修改專案設定
- **WHEN** 設定變更儲存
- **THEN** 系統記錄所有變更的設定項目
- **AND** 記錄操作者與時間
#### Scenario: 權限變更記錄
- **GIVEN** 管理者修改使用者權限或角色
- **WHEN** 權限變更生效
- **THEN** 系統記錄權限變更詳情
- **AND** 標記為高敏感度操作
### Requirement: Delete Operations Tracking
系統 SHALL 追蹤所有刪除操作,支援軟刪除與追溯。
#### Scenario: 任務刪除記錄
- **GIVEN** 使用者刪除任務
- **WHEN** 刪除操作執行
- **THEN** 系統執行軟刪除(標記 is_deleted = true
- **AND** 記錄刪除操作與原因
#### Scenario: 附件刪除記錄
- **GIVEN** 使用者刪除附件
- **WHEN** 刪除操作執行
- **THEN** 系統保留檔案於存檔區
- **AND** 記錄刪除操作詳情
### Requirement: Audit Log Immutability
系統 SHALL 確保稽核日誌不可竄改。
#### Scenario: 日誌寫入
- **GIVEN** 需要記錄稽核事件
- **WHEN** 日誌寫入
- **THEN** 日誌記錄不可被修改或刪除
- **AND** 包含校驗碼確保完整性
#### Scenario: 日誌完整性驗證
- **GIVEN** 稽核人員需要驗證日誌完整性
- **WHEN** 執行完整性檢查
- **THEN** 系統驗證所有日誌記錄的校驗碼
- **AND** 報告任何異常
### Requirement: Audit Query Interface
系統 SHALL 提供稽核查詢介面供授權人員使用。
#### Scenario: 依時間範圍查詢
- **GIVEN** 稽核人員需要查詢特定時間範圍的操作
- **WHEN** 設定時間範圍並執行查詢
- **THEN** 顯示該時間範圍內的所有稽核記錄
#### Scenario: 依操作者查詢
- **GIVEN** 稽核人員需要查詢特定使用者的操作歷史
- **WHEN** 選擇使用者並執行查詢
- **THEN** 顯示該使用者的所有操作記錄
#### Scenario: 依資源查詢
- **GIVEN** 稽核人員需要查詢特定任務或專案的變更歷史
- **WHEN** 選擇資源並執行查詢
- **THEN** 顯示該資源的完整變更歷程
#### Scenario: 稽核報告匯出
- **GIVEN** 稽核人員需要匯出稽核報告
- **WHEN** 選擇匯出格式CSV/PDF
- **THEN** 系統生成報告檔案供下載
### Requirement: Sensitive Operation Alerts
系統 SHALL 對高敏感度操作發送即時警示。
#### Scenario: 權限提升警示
- **GIVEN** 使用者被授予管理員權限
- **WHEN** 權限變更生效
- **THEN** 系統發送警示給安全管理員
#### Scenario: 大量刪除警示
- **GIVEN** 使用者在短時間內刪除大量資料
- **WHEN** 偵測到異常刪除模式
- **THEN** 系統發送警示並暫停操作
#### Scenario: 異常登入警示
- **GIVEN** 偵測到異常登入行為(如異地登入、非工作時間登入)
- **WHEN** 異常行為發生
- **THEN** 系統記錄並發送警示
## Data Model
```
pjctrl_audit_logs
├── id: UUID (PK)
├── event_type: VARCHAR(50)
│ └── 'task.update', 'task.delete', 'project.update', 'user.permission_change', etc.
├── resource_type: VARCHAR(50)
│ └── 'task', 'project', 'user', 'attachment', 'trigger', etc.
├── resource_id: UUID
├── user_id: UUID (FK -> users)
├── action: ENUM('create', 'update', 'delete', 'restore', 'login', 'logout')
├── changes: JSON
│ └── { "field": "due_date", "old_value": "2024-01-15", "new_value": "2024-01-20" }
├── metadata: JSON
│ └── { "ip_address": "192.168.1.100", "user_agent": "...", "session_id": "..." }
├── sensitivity_level: ENUM('low', 'medium', 'high', 'critical')
├── checksum: VARCHAR(64) (SHA-256 of record content)
├── created_at: TIMESTAMP (immutable)
└── INDEX idx_audit_user (user_id, created_at)
└── INDEX idx_audit_resource (resource_type, resource_id, created_at)
└── INDEX idx_audit_time (created_at)
pjctrl_audit_alerts
├── id: UUID (PK)
├── audit_log_id: UUID (FK -> audit_logs)
├── alert_type: VARCHAR(50)
├── recipients: JSON (array of user IDs)
├── message: TEXT
├── is_acknowledged: BOOLEAN DEFAULT false
├── acknowledged_by: UUID (FK -> users)
├── acknowledged_at: TIMESTAMP
└── created_at: TIMESTAMP
```
## Event Types Reference
| 事件類型 | 說明 | 敏感度 |
|---------|------|--------|
| task.create | 建立任務 | low |
| task.update | 更新任務 | low |
| task.delete | 刪除任務 | medium |
| task.assign | 指派任務 | low |
| task.blocker | 標記阻礙 | medium |
| project.create | 建立專案 | medium |
| project.update | 更新專案 | medium |
| project.delete | 刪除專案 | high |
| user.login | 使用者登入 | low |
| user.logout | 使用者登出 | low |
| user.permission_change | 權限變更 | critical |
| attachment.upload | 上傳附件 | low |
| attachment.download | 下載附件 | low |
| attachment.delete | 刪除附件 | medium |
## Technical Notes
- 稽核日誌表設計為 append-only不允許 UPDATE 或 DELETE
- 使用資料庫觸發器或應用層中間件自動記錄變更
- 日誌保留期限依法規要求設定(建議至少 7 年)
- 考慮使用時間序列資料庫(如 TimescaleDB處理大量日誌
- Checksum 計算包含event_type + resource_id + user_id + changes + created_at

View File

@@ -0,0 +1,152 @@
# Automation
自動化系統,提供觸發器與自動報告生成功能。
## Requirements
### Requirement: Trigger-Based Automation
系統 SHALL 支援觸發器 (Triggers),當特定條件滿足時自動執行動作。
#### Scenario: 狀態變更觸發通知
- **GIVEN** 專案設定了「當任務狀態變更為待測試時,通知設備工程師」的觸發器
- **WHEN** 任務狀態變更為「待測試」
- **THEN** 系統自動發送通知給指定的設備工程師群組
#### Scenario: 截止日期觸發提醒
- **GIVEN** 專案設定了「截止日前 3 天自動提醒」的觸發器
- **WHEN** 任務距離截止日還有 3 天
- **THEN** 系統自動發送提醒給任務指派者
#### Scenario: 建立觸發器
- **GIVEN** 專案管理者需要建立自動化規則
- **WHEN** 管理者設定觸發條件與動作
- **THEN** 系統儲存觸發器規則
- **AND** 規則立即生效
### Requirement: Trigger Conditions
系統 SHALL 支援多種觸發條件類型。
#### Scenario: 欄位變更條件
- **GIVEN** 觸發器設定為「當 Status 欄位變更為特定值」
- **WHEN** 任務的 Status 欄位變更為該值
- **THEN** 觸發器被觸發
#### Scenario: 時間條件
- **GIVEN** 觸發器設定為「每週五下午 4:00」
- **WHEN** 系統時間達到設定時間
- **THEN** 觸發器被觸發
#### Scenario: 複合條件
- **GIVEN** 觸發器設定為「當 Status = 完成 且 Priority = 高」
- **WHEN** 任務同時滿足兩個條件
- **THEN** 觸發器被觸發
### Requirement: Trigger Actions
系統 SHALL 支援多種觸發動作類型。
#### Scenario: 發送通知動作
- **GIVEN** 觸發器動作設定為發送通知
- **WHEN** 觸發器被觸發
- **THEN** 系統發送通知給指定對象
- **AND** 通知內容可使用變數(如任務名稱、指派者)
#### Scenario: 更新欄位動作
- **GIVEN** 觸發器動作設定為更新欄位
- **WHEN** 觸發器被觸發
- **THEN** 系統自動更新指定欄位的值
#### Scenario: 指派任務動作
- **GIVEN** 觸發器動作設定為自動指派
- **WHEN** 觸發器被觸發
- **THEN** 系統自動將任務指派給指定人員
### Requirement: Automated Weekly Report
系統 SHALL 每週五下午 4:00 自動彙整本週「已完成」與「進行中」的任務發送給主管。
#### Scenario: 週報自動生成
- **GIVEN** 系統排程設定為每週五 16:00
- **WHEN** 到達排程時間
- **THEN** 系統彙整每位主管所屬專案的任務狀態
- **AND** 生成週報並發送給該主管
#### Scenario: 週報內容
- **GIVEN** 週報生成中
- **WHEN** 系統彙整資料
- **THEN** 週報包含:
- 本週已完成任務清單
- 進行中任務清單
- 逾期任務警示
- 阻礙中任務清單
- 下週預計完成任務
#### Scenario: 週報發送方式
- **GIVEN** 週報已生成
- **WHEN** 系統發送週報
- **THEN** 透過系統內通知發送給收件者
- **AND** 週報可在系統內查閱歷史紀錄
#### Scenario: Email 發送(可選)
- **GIVEN** 週報已生成且系統已啟用 Email 功能
- **WHEN** 系統發送週報
- **THEN** 同時透過 Email 發送給收件者
- **AND** 記錄 Email 發送狀態
#### Scenario: Email 未啟用
- **GIVEN** 週報已生成但系統未啟用 Email 功能
- **WHEN** 系統發送週報
- **THEN** 僅透過系統內通知發送
- **AND** 記錄「Email 未啟用,僅發送系統通知」
## Data Model
```
pjctrl_triggers
├── id: UUID (PK)
├── project_id: UUID (FK -> projects)
├── name: VARCHAR(200)
├── description: TEXT
├── trigger_type: ENUM('field_change', 'schedule', 'creation')
├── conditions: JSON
│ └── { "field": "status", "operator": "equals", "value": "testing" }
├── actions: JSON
│ └── [{ "type": "notify", "target": "group:equipment_engineers" }]
├── is_active: BOOLEAN DEFAULT true
├── created_by: UUID (FK -> users)
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
pjctrl_trigger_logs
├── id: UUID (PK)
├── trigger_id: UUID (FK -> triggers)
├── task_id: UUID (FK -> tasks, nullable)
├── executed_at: TIMESTAMP
├── status: ENUM('success', 'failed')
└── error_message: TEXT
pjctrl_scheduled_reports
├── id: UUID (PK)
├── report_type: ENUM('weekly', 'monthly', 'custom')
├── recipient_id: UUID (FK -> users)
├── schedule_cron: VARCHAR(50)
├── last_sent_at: TIMESTAMP
├── next_run_at: TIMESTAMP
├── is_active: BOOLEAN DEFAULT true
├── email_enabled: BOOLEAN DEFAULT false (Email 發送開關)
└── created_at: TIMESTAMP
pjctrl_report_history
├── id: UUID (PK)
├── scheduled_report_id: UUID (FK -> scheduled_reports)
├── generated_at: TIMESTAMP
├── content: JSON
├── sent_to: JSON (array of user IDs)
├── channels_used: JSON (e.g., ["in_app"] or ["in_app", "email"])
├── email_status: ENUM('sent', 'skipped', 'failed', null)
└── status: ENUM('sent', 'failed')
```
## Technical Notes
- 使用 Celery + Redis 處理排程任務
- 觸發器執行採用非同步處理,避免阻塞主流程
- 所有觸發器執行都記錄日誌供追蹤

View File

@@ -0,0 +1,126 @@
# Collaboration
協作功能系統,提供任務內討論、@提及通知與阻礙處理機制
## Requirements
### Requirement: Task Comments
系統 SHALL 支援任務內部的討論線索,減少 Email 往返。
#### Scenario: 新增留言
- **GIVEN** 使用者擁有任務的存取權限
- **WHEN** 使用者在任務中新增留言
- **THEN** 系統儲存留言並顯示在討論區
- **AND** 記錄留言者與時間戳記
#### Scenario: 回覆留言
- **GIVEN** 任務已有留言
- **WHEN** 使用者回覆特定留言
- **THEN** 系統建立巢狀回覆結構
- **AND** 通知原留言者
#### Scenario: 編輯留言
- **GIVEN** 使用者是留言的作者
- **WHEN** 使用者編輯自己的留言
- **THEN** 系統更新留言內容
- **AND** 標示「已編輯」及編輯時間
### Requirement: User Mentions
系統 SHALL 支援 @相關人員 功能,提及時發送即時通知。
#### Scenario: @提及通知
- **GIVEN** 使用者在留言中使用 @username 提及某人
- **WHEN** 留言送出
- **THEN** 被提及者收到即時通知
- **AND** 通知包含任務連結與留言摘要
#### Scenario: @提及自動完成
- **GIVEN** 使用者輸入 @ 符號
- **WHEN** 使用者繼續輸入
- **THEN** 系統顯示符合的使用者名單供選擇
- **AND** 可用鍵盤或滑鼠選擇
### Requirement: Blocker Management
系統 SHALL 提供阻礙 (Blocker) 機制,強制要求主管介入排解。
#### Scenario: 標記阻礙
- **GIVEN** 工程師的任務遇到阻礙無法進行
- **WHEN** 工程師將任務標記為 "Blocked"
- **THEN** 系統設定 Blocker_Flag = true
- **AND** 強制發送即時通知給該任務所屬專案的主管
#### Scenario: 阻礙原因說明
- **GIVEN** 任務被標記為 Blocked
- **WHEN** 使用者標記阻礙
- **THEN** 系統要求填寫阻礙原因
- **AND** 原因顯示在任務詳情與通知中
#### Scenario: 解除阻礙
- **GIVEN** 主管或被指派者處理完阻礙
- **WHEN** 使用者解除 Blocked 狀態
- **THEN** 系統清除 Blocker_Flag
- **AND** 記錄解除時間與處理說明
### Requirement: Real-time Notifications
系統 SHALL 透過 Redis 推播即時通知。
#### Scenario: 即時通知推播
- **GIVEN** 發生需要通知的事件(如:被指派任務、被 @提及、阻礙標記)
- **WHEN** 事件發生
- **THEN** 系統透過 WebSocket 即時推播通知給相關使用者
- **AND** 未讀通知顯示數量標示
#### Scenario: 通知已讀標記
- **GIVEN** 使用者有未讀通知
- **WHEN** 使用者查看通知
- **THEN** 系統標記為已讀
- **AND** 更新未讀數量
## Data Model
```
pjctrl_comments
├── id: UUID (PK)
├── task_id: UUID (FK -> tasks)
├── parent_comment_id: UUID (FK -> comments, nullable)
├── author_id: UUID (FK -> users)
├── content: TEXT
├── is_edited: BOOLEAN DEFAULT false
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
pjctrl_mentions
├── id: UUID (PK)
├── comment_id: UUID (FK -> comments)
├── mentioned_user_id: UUID (FK -> users)
├── created_at: TIMESTAMP
└── is_notified: BOOLEAN DEFAULT false
pjctrl_notifications
├── id: UUID (PK)
├── user_id: UUID (FK -> users)
├── type: ENUM('mention', 'assignment', 'blocker', 'status_change', 'comment')
├── reference_type: VARCHAR(50) (e.g., 'task', 'comment')
├── reference_id: UUID
├── title: VARCHAR(200)
├── message: TEXT
├── is_read: BOOLEAN DEFAULT false
├── created_at: TIMESTAMP
└── read_at: TIMESTAMP
pjctrl_blockers
├── id: UUID (PK)
├── task_id: UUID (FK -> tasks)
├── reported_by: UUID (FK -> users)
├── reason: TEXT
├── resolved_by: UUID (FK -> users, nullable)
├── resolution_note: TEXT
├── created_at: TIMESTAMP
└── resolved_at: TIMESTAMP
```
## Technical Notes
- 使用 Redis Pub/Sub 處理即時通知推播
- WebSocket 連線管理確保訊息送達
- 離線使用者登入後補送未讀通知

View File

@@ -0,0 +1,164 @@
# Document Management
文件管理系統,提供檔案附件、版本控制、加密存儲與浮水印功能。
## Requirements
### Requirement: File Attachments
系統 SHALL 支援任務層級的檔案附件,儲存於地端 NAS。
#### Scenario: 上傳附件
- **GIVEN** 使用者擁有任務的編輯權限
- **WHEN** 使用者上傳檔案至任務
- **THEN** 系統將檔案儲存至 NAS
- **AND** 建立附件記錄關聯至該任務
#### Scenario: 下載附件
- **GIVEN** 使用者擁有任務的存取權限
- **WHEN** 使用者下載附件
- **THEN** 系統驗證權限後提供檔案下載
- **AND** 記錄下載操作日誌
#### Scenario: 刪除附件
- **GIVEN** 使用者擁有任務的編輯權限
- **WHEN** 使用者刪除附件
- **THEN** 系統標記附件為已刪除(軟刪除)
- **AND** 保留檔案供稽核追溯
### Requirement: Version Control
系統 SHALL 支援檔案版本控制,追蹤所有版本變更。
#### Scenario: 上傳新版本
- **GIVEN** 任務已有同名附件
- **WHEN** 使用者上傳同名檔案
- **THEN** 系統建立新版本而非覆蓋
- **AND** 版本號自動遞增
#### Scenario: 查看版本歷史
- **GIVEN** 附件有多個版本
- **WHEN** 使用者查看版本歷史
- **THEN** 顯示所有版本清單
- **AND** 包含上傳者、上傳時間、檔案大小
#### Scenario: 回復舊版本
- **GIVEN** 使用者需要使用舊版本
- **WHEN** 使用者選擇回復特定版本
- **THEN** 系統將該版本設為當前版本
- **AND** 記錄回復操作
### Requirement: File Encryption
系統 SHALL 對半導體敏感圖檔進行 AES-256 加密存儲。
#### Scenario: 加密存儲
- **GIVEN** 專案設定為機密等級
- **WHEN** 使用者上傳檔案
- **THEN** 系統使用 AES-256 加密後存儲
- **AND** 加密金鑰安全管理
#### Scenario: 解密讀取
- **GIVEN** 使用者請求下載加密檔案
- **WHEN** 系統驗證權限通過
- **THEN** 系統解密檔案後提供下載
- **AND** 解密過程透明,使用者無感
#### Scenario: 加密金鑰輪換
- **GIVEN** 安全政策要求金鑰輪換
- **WHEN** 管理員執行金鑰輪換
- **THEN** 系統使用新金鑰重新加密所有檔案
- **AND** 舊金鑰安全銷毀
### Requirement: Dynamic Watermarking
系統 SHALL 在下載時自動為檔案加上使用者浮水印。
#### Scenario: 圖片浮水印
- **GIVEN** 使用者下載圖片類型附件
- **WHEN** 系統處理下載請求
- **THEN** 自動加上包含使用者姓名、工號、下載時間的浮水印
- **AND** 浮水印位置不影響主要內容
#### Scenario: PDF 浮水印
- **GIVEN** 使用者下載 PDF 類型附件
- **WHEN** 系統處理下載請求
- **THEN** 每頁加上浮水印
- **AND** 浮水印透明度適中
#### Scenario: 浮水印內容
- **GIVEN** 需要加上浮水印
- **WHEN** 系統生成浮水印
- **THEN** 浮水印包含:
- 使用者姓名
- 使用者工號
- 下載日期時間
- 機密等級標示(如適用)
### Requirement: Audit Trail
系統 SHALL 記錄所有文件操作供稽核追溯。
#### Scenario: 操作日誌記錄
- **GIVEN** 使用者對附件執行任何操作
- **WHEN** 操作完成
- **THEN** 系統記錄操作類型、操作者、時間、IP 位址
- **AND** 日誌不可竄改
#### Scenario: 稽核查詢
- **GIVEN** 稽核人員需要查詢文件操作歷史
- **WHEN** 稽核人員執行查詢
- **THEN** 顯示完整操作歷史
- **AND** 支援依時間、操作者、檔案篩選
## Data Model
```
pjctrl_attachments
├── id: UUID (PK)
├── task_id: UUID (FK -> tasks)
├── filename: VARCHAR(500)
├── original_filename: VARCHAR(500)
├── file_path: VARCHAR(1000) (NAS path)
├── file_size: BIGINT
├── mime_type: VARCHAR(100)
├── version: INT DEFAULT 1
├── is_encrypted: BOOLEAN DEFAULT false
├── encryption_key_id: UUID (FK -> encryption_keys)
├── checksum: VARCHAR(64) (SHA-256)
├── uploaded_by: UUID (FK -> users)
├── is_deleted: BOOLEAN DEFAULT false
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
pjctrl_attachment_versions
├── id: UUID (PK)
├── attachment_id: UUID (FK -> attachments)
├── version: INT
├── file_path: VARCHAR(1000)
├── file_size: BIGINT
├── checksum: VARCHAR(64)
├── uploaded_by: UUID (FK -> users)
├── is_current: BOOLEAN DEFAULT false
└── created_at: TIMESTAMP
pjctrl_encryption_keys
├── id: UUID (PK)
├── key_hash: VARCHAR(64) (for verification)
├── algorithm: VARCHAR(20) DEFAULT 'AES-256'
├── is_active: BOOLEAN DEFAULT true
├── created_at: TIMESTAMP
└── rotated_at: TIMESTAMP
pjctrl_document_audit_logs
├── id: UUID (PK)
├── attachment_id: UUID (FK -> attachments)
├── user_id: UUID (FK -> users)
├── action: ENUM('upload', 'download', 'delete', 'restore', 'version_create', 'version_restore')
├── ip_address: VARCHAR(45)
├── user_agent: VARCHAR(500)
├── details: JSON
└── created_at: TIMESTAMP
```
## Technical Notes
- 加密金鑰存儲於獨立的 Key Management Service (KMS)
- 浮水印使用 Pillow (圖片) 和 PyPDF2 (PDF) 處理
- 大檔案使用串流處理避免記憶體溢出
- NAS 存儲路徑結構:`/{project_id}/{task_id}/{attachment_id}/{version}/`

View File

@@ -0,0 +1,100 @@
# Resource Management
資源管理系統,提供負載熱圖與人員容量追蹤,協助主管進行資源分配決策。
## Requirements
### Requirement: Workload Heatmap
系統 SHALL 提供負載熱圖,自動統計每人每週分配的任務總時數。
#### Scenario: 負載正常顯示
- **GIVEN** 某人員本週被指派的任務總時數低於其容量的 80%
- **WHEN** 主管查看負載熱圖
- **THEN** 該人員顯示為綠色(正常)
#### Scenario: 負載警告顯示
- **GIVEN** 某人員本週被指派的任務總時數達到其容量的 80%-100%
- **WHEN** 主管查看負載熱圖
- **THEN** 該人員顯示為黃色(警告)
#### Scenario: 負載超載顯示
- **GIVEN** 某人員本週被指派的任務總時數超過其容量的 100%
- **WHEN** 主管查看負載熱圖
- **THEN** 該人員顯示為紅色(超載)
- **AND** 可點擊查看詳細任務分配
### Requirement: Capacity Planning
系統 SHALL 支援人員容量規劃與追蹤。
#### Scenario: 設定人員容量
- **GIVEN** 管理者需要設定人員的週工時上限
- **WHEN** 管理者更新使用者的 Capacity 值
- **THEN** 系統儲存新的容量設定
- **AND** 重新計算該人員的負載百分比
#### Scenario: 容量調整(如請假)
- **GIVEN** 人員某週有請假安排
- **WHEN** 系統計算該週負載
- **THEN** 考慮實際可用工時進行計算
### Requirement: Multi-Project Health Dashboard
系統 SHALL 提供多專案健康看板,讓主管一覽所有專案狀態。
#### Scenario: 專案健康總覽
- **GIVEN** 主管負責多個專案
- **WHEN** 主管開啟健康看板
- **THEN** 顯示所有專案的進度、風險指標、延遲任務數
- **AND** 可依風險程度排序
#### Scenario: 專案延遲警示
- **GIVEN** 專案有任務超過截止日期
- **WHEN** 主管查看健康看板
- **THEN** 該專案標示為延遲狀態
- **AND** 顯示延遲任務數量與影響
### Requirement: Team Workload Distribution
系統 SHALL 視覺化呈現團隊工作分配狀況。
#### Scenario: 部門負載總覽
- **GIVEN** 主管需要了解部門整體負載
- **WHEN** 主管查看團隊負載視圖
- **THEN** 顯示部門內每位成員的負載狀況
- **AND** 可按專案或任務類型篩選
#### Scenario: 資源分配不均警示
- **GIVEN** 團隊中存在負載差異過大的情況
- **WHEN** 系統偵測到分配不均
- **THEN** 在看板上標示警示
- **AND** 建議可重新分配的任務
## Data Model
```
pjctrl_workload_snapshots
├── id: UUID (PK)
├── user_id: UUID (FK -> users)
├── week_start: DATE
├── allocated_hours: DECIMAL
├── capacity_hours: DECIMAL
├── load_percentage: DECIMAL
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
pjctrl_project_health
├── id: UUID (PK)
├── project_id: UUID (FK -> projects)
├── snapshot_date: DATE
├── total_tasks: INT
├── completed_tasks: INT
├── overdue_tasks: INT
├── blocked_tasks: INT
├── risk_score: DECIMAL
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
```
## Calculation Rules
- **負載百分比** = (allocated_hours / capacity_hours) × 100
- **風險評分** = f(overdue_tasks, blocked_tasks, timeline_remaining)
- 快取計算結果於 Redis每小時更新或任務變更時即時更新

View File

@@ -0,0 +1,164 @@
# Task Management
任務管理核心系統,支援多層級架構、自定義欄位與多維視角。
## Requirements
### Requirement: Hierarchical Task Structure
系統 SHALL 支援多層級任務架構:空間 (Space) > 專案 (Project) > 任務 (Task) > 子任務 (Sub-task)。
#### Scenario: 建立空間
- **GIVEN** 使用者擁有建立空間的權限
- **WHEN** 使用者建立新空間
- **THEN** 系統建立空間並設定擁有者
- **AND** 空間可包含多個專案
#### Scenario: 建立專案
- **GIVEN** 使用者在某空間內擁有建立專案的權限
- **WHEN** 使用者建立新專案
- **THEN** 系統建立專案並關聯至該空間
- **AND** 設定專案的 Owner、Budget、Timeline、Security_Level
#### Scenario: 建立任務與子任務
- **GIVEN** 使用者在專案內擁有建立任務的權限
- **WHEN** 使用者建立任務或子任務
- **THEN** 系統建立任務並維護父子關係
- **AND** 子任務繼承父任務的部分屬性
### Requirement: Custom Fields
系統 SHALL 支援自定義欄位,包含下拉選單、公式、人員標籤等類型。
#### Scenario: 新增自定義欄位
- **GIVEN** 專案管理者需要追蹤特定資料(如:封裝類型、機台編號、預計良率)
- **WHEN** 管理者在專案中新增自定義欄位
- **THEN** 系統建立欄位定義並套用至該專案所有任務
#### Scenario: 公式欄位計算
- **GIVEN** 任務包含公式類型的自定義欄位
- **WHEN** 相依欄位的值發生變更
- **THEN** 系統自動重新計算公式欄位的值
#### Scenario: 人員標籤欄位
- **GIVEN** 任務包含人員標籤類型的自定義欄位
- **WHEN** 使用者選擇人員
- **THEN** 系統驗證人員存在並建立關聯
- **AND** 被標籤的人員可收到相關通知
### Requirement: Multiple Views
系統 SHALL 支援多維視角:看板 (Kanban)、甘特圖 (Gantt)、列表 (List)、行事曆 (Calendar)。
#### Scenario: 看板視角
- **GIVEN** 使用者選擇看板視角
- **WHEN** 系統載入專案任務
- **THEN** 任務依狀態分組顯示為卡片
- **AND** 支援拖拉變更狀態
#### Scenario: 甘特圖視角
- **GIVEN** 使用者選擇甘特圖視角
- **WHEN** 系統載入專案任務
- **THEN** 任務依時間軸顯示
- **AND** 顯示任務相依關係與里程碑
#### Scenario: 列表視角
- **GIVEN** 使用者選擇列表視角
- **WHEN** 系統載入專案任務
- **THEN** 任務以表格形式顯示
- **AND** 支援欄位排序與篩選
#### Scenario: 行事曆視角
- **GIVEN** 使用者選擇行事曆視角
- **WHEN** 系統載入專案任務
- **THEN** 任務依截止日期顯示在行事曆上
### Requirement: Task Status Management
系統 SHALL 管理任務狀態,包含標準狀態與自定義狀態。
#### Scenario: 狀態變更
- **GIVEN** 使用者擁有更新任務的權限
- **WHEN** 使用者變更任務狀態
- **THEN** 系統更新狀態並記錄變更時間
- **AND** 觸發相關自動化規則(如有設定)
#### Scenario: 阻礙標記
- **GIVEN** 任務遇到阻礙無法進行
- **WHEN** 工程師將任務標記為 "Blocked"
- **THEN** 系統設定 Blocker_Flag = true
- **AND** 強制發送通知給主管要求介入排解
### Requirement: Task Assignment
系統 SHALL 支援任務指派與時間估算。
#### Scenario: 指派任務
- **GIVEN** 使用者擁有指派任務的權限
- **WHEN** 使用者將任務指派給某人
- **THEN** 系統更新 Assignee 並發送通知
- **AND** 任務計入被指派者的工作負載
#### Scenario: 時間估算與追蹤
- **GIVEN** 任務已被指派
- **WHEN** 使用者設定 Original_Estimate 與回報 Time_Spent
- **THEN** 系統記錄並計算剩餘時間
- **AND** 更新資源負載統計
## Data Model
```
pjctrl_spaces
├── id: UUID (PK)
├── name: VARCHAR(200)
├── description: TEXT
├── owner_id: UUID (FK -> users)
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
pjctrl_projects
├── id: UUID (PK)
├── space_id: UUID (FK -> spaces)
├── title: VARCHAR(200)
├── description: TEXT
├── owner_id: UUID (FK -> users)
├── budget: DECIMAL
├── start_date: DATE
├── end_date: DATE
├── security_level: ENUM('public', 'department', 'confidential')
├── status: VARCHAR(50)
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
pjctrl_tasks
├── id: UUID (PK)
├── project_id: UUID (FK -> projects)
├── parent_task_id: UUID (FK -> tasks, nullable)
├── title: VARCHAR(500)
├── description: TEXT
├── assignee_id: UUID (FK -> users)
├── priority: ENUM('low', 'medium', 'high', 'urgent')
├── status: VARCHAR(50)
├── original_estimate: DECIMAL (hours)
├── time_spent: DECIMAL (hours)
├── blocker_flag: BOOLEAN DEFAULT false
├── due_date: DATETIME
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
pjctrl_custom_fields
├── id: UUID (PK)
├── project_id: UUID (FK -> projects)
├── name: VARCHAR(100)
├── field_type: ENUM('text', 'number', 'dropdown', 'date', 'person', 'formula')
├── options: JSON (for dropdown)
├── formula: TEXT (for formula type)
├── is_required: BOOLEAN
└── created_at: TIMESTAMP
pjctrl_task_custom_values
├── id: UUID (PK)
├── task_id: UUID (FK -> tasks)
├── field_id: UUID (FK -> custom_fields)
├── value: TEXT
└── updated_at: TIMESTAMP
```
## Real-time Sync
系統使用 WebSocket 實現即時同步,當多人同時編輯同一個專案看板時,狀態能即時更新而不需刷新頁面。

View File

@@ -0,0 +1,154 @@
# User Authentication & Authorization
## Purpose
使用者認證與授權系統,透過外部認證 API 進行身份驗證,提供細部權限控制。
## Requirements
### Requirement: API-Based Authentication
系統 SHALL 限定使用外部認證 API (https://pj-auth-api.vercel.app) 進行登入認證,不支援其他認證方式。
#### Scenario: API 登入成功
- **GIVEN** 使用者擁有有效的企業帳號
- **WHEN** 使用者透過前端提交憑證
- **THEN** 系統呼叫 https://pj-auth-api.vercel.app 驗證憑證
- **AND** 驗證成功後建立 session 並回傳 JWT token
#### Scenario: API 登入失敗
- **GIVEN** 使用者提供無效的憑證
- **WHEN** 使用者嘗試登入
- **THEN** 認證 API 回傳錯誤
- **AND** 系統拒絕登入並顯示錯誤訊息
- **AND** 記錄失敗的登入嘗試
#### Scenario: 認證 API 無法連線
- **GIVEN** 認證 API 服務無法連線
- **WHEN** 使用者嘗試登入
- **THEN** 系統顯示服務暫時無法使用的訊息
- **AND** 記錄連線失敗事件
### Requirement: System Administrator
系統 SHALL 預設一個系統管理員帳號,擁有所有權限。系統管理員帳號必須存在於外部認證系統,且登入流程仍需透過外部認證 API不允許本地繞過認證。
#### Scenario: 預設管理員帳號
- **GIVEN** 系統初始化完成
- **WHEN** 系統啟動
- **THEN** 存在預設管理員帳號 `ymirliu@panjit.com.tw`
- **AND** 該帳號擁有 `super_admin` 角色
- **AND** 該帳號不可被刪除或降級
#### Scenario: 管理員登入流程
- **GIVEN** 管理員帳號 `ymirliu@panjit.com.tw` 需要登入
- **WHEN** 管理員提交憑證
- **THEN** 系統仍需呼叫 https://pj-auth-api.vercel.app 驗證
- **AND** 不存在任何本地繞過認證的機制
- **AND** 驗證成功後才授予 `super_admin` 權限
#### Scenario: 管理員全域權限
- **GIVEN** 管理員帳號 `ymirliu@panjit.com.tw` 已通過 API 認證並登入
- **WHEN** 管理員存取任何資源
- **THEN** 系統允許存取,無視部門隔離限制
### Requirement: Role-Based Access Control
系統 SHALL 支援基於角色的存取控制 (RBAC)。
#### Scenario: 角色權限檢查
- **GIVEN** 使用者被指派特定角色 (如工程師、主管、PMO)
- **WHEN** 使用者嘗試存取受保護的資源
- **THEN** 系統根據角色權限決定是否允許存取
#### Scenario: 角色指派
- **GIVEN** 管理員擁有使用者管理權限
- **WHEN** 管理員為使用者指派角色
- **THEN** 系統更新使用者的角色設定
- **AND** 新權限立即生效
### Requirement: Department Isolation
系統 SHALL 實施部門級別的資料隔離,確保跨部門資料安全。
#### Scenario: 部門資料隔離
- **GIVEN** 使用者屬於研發部門
- **WHEN** 使用者嘗試存取廠務部門的專案
- **THEN** 系統拒絕存取並顯示無權限訊息
#### Scenario: 跨部門專案存取
- **GIVEN** 專案被設定為跨部門可見
- **WHEN** 不同部門的使用者嘗試存取該專案
- **THEN** 系統根據專案的 Security_Level 設定決定是否允許存取
### Requirement: Session Management
系統 SHALL 管理使用者 session包含過期與登出機制。
#### Scenario: Session 過期
- **GIVEN** 使用者已登入系統
- **WHEN** Session 超過設定的有效期限
- **THEN** 系統自動使 session 失效
- **AND** 使用者需重新登入
#### Scenario: 主動登出
- **GIVEN** 使用者已登入系統
- **WHEN** 使用者執行登出操作
- **THEN** 系統銷毀 session 並清除 token
## Data Model
```
pjctrl_users
├── id: UUID (PK)
├── email: VARCHAR(200) UNIQUE
├── name: VARCHAR(200)
├── department_id: UUID (FK)
├── role_id: UUID (FK)
├── skills: JSON
├── capacity: DECIMAL (週工時上限)
├── is_active: BOOLEAN
├── is_system_admin: BOOLEAN DEFAULT false (不可修改的系統管理員標記)
├── created_at: TIMESTAMP
└── updated_at: TIMESTAMP
pjctrl_departments
├── id: UUID (PK)
├── name: VARCHAR(100)
├── parent_id: UUID (FK, self-reference)
└── created_at: TIMESTAMP
pjctrl_roles
├── id: UUID (PK)
├── name: VARCHAR(50)
├── permissions: JSON
├── is_system_role: BOOLEAN DEFAULT false
└── created_at: TIMESTAMP
```
## Default Data (Seed)
```sql
-- 預設系統管理員角色
INSERT INTO pjctrl_roles (id, name, permissions, is_system_role) VALUES
('00000000-0000-0000-0000-000000000001', 'super_admin', '{"all": true}', true);
-- 預設系統管理員帳號
INSERT INTO pjctrl_users (id, email, name, role_id, is_active, is_system_admin) VALUES
('00000000-0000-0000-0000-000000000001', 'ymirliu@panjit.com.tw', 'System Administrator',
'00000000-0000-0000-0000-000000000001', true, true);
```
## External Dependencies
- **Authentication API**: https://pj-auth-api.vercel.app (唯一認證方式)
## Authentication Flow
```
┌─────────┐ ┌─────────────┐ ┌──────────────────────────┐
│ User │────▶│ Frontend │────▶│ pj-auth-api.vercel.app │
└─────────┘ └─────────────┘ └──────────────────────────┘
│ │
│◀────── JWT Token ───────│
┌─────────────┐
│ Backend │ (驗證 JWT, 建立 Session)
└─────────────┘
```