Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9d1ef53
docs(spec): complete Phase 1 design for unified Player entity
andrewck24 Dec 20, 2025
2177f13
docs(spec): generate Phase 2 tasks for unified Player entity
andrewck24 Dec 20, 2025
1bb9d76
feat(player): complete Phase 1 setup - Initialize Player feature infr…
andrewck24 Dec 20, 2025
2ba15ee
feat(player): complete Phase 2.1 - Entity, Validation, Schema, and Re…
andrewck24 Dec 20, 2025
7d5e3d6
feat(player): complete Phase 2.2 - Authorization service and DI conta…
andrewck24 Dec 20, 2025
ed488ce
feat(player): implement core use cases for US1, US2, US3 (T017-T024, …
andrewck24 Dec 20, 2025
72c8e8b
feat(player): implement API routes and integration tests for US1 (T01…
andrewck24 Dec 20, 2025
69f5687
feat(player): implement API routes for US2 and US3 (T034, T039, T045-…
andrewck24 Dec 20, 2025
3a710f4
docs(tasks): mark API route tasks complete (T019-T020, T025-T026, T03…
andrewck24 Dec 20, 2025
736510a
fix(player): export use case implementations from index for DI container
andrewck24 Dec 20, 2025
0eaf7de
fix(player): resolve build errors and standardize import paths
andrewck24 Dec 20, 2025
356a377
feat(player): implement Phase 3 UI components for inviting members (T…
andrewck24 Dec 20, 2025
6465211
feat(player): implement Phase 4 UI components for accepting/rejecting…
andrewck24 Dec 20, 2025
ebf4ed6
feat(player): implement Phase 5 UI components for viewing team member…
andrewck24 Dec 20, 2025
c8a8e97
refactor(authorization): optimize player queries with direct team+use…
andrewck24 Dec 20, 2025
9c92597
fix(player): resolve TypeScript errors by improving Mongoose schema t…
andrewck24 Dec 20, 2025
98e5da4
docs(tasks): add Phase 5.5 for MVP hotfixes and renumber subsequent p…
andrewck24 Dec 20, 2025
77ab98d
feat(player): implement Phase 5.5 MVP hotfixes and security enhancements
andrewck24 Dec 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,12 @@ src/
- `AUTH_GOOGLE_ID` - Google OAuth client ID
- `AUTH_GOOGLE_SECRET` - Google OAuth client secret
- `MONGODB_URI` - MongoDB connection string

## Active Technologies

- TypeScript 5.x, Node.js 20+, React 19 + Next.js 15+, Mongoose ODM, Better Auth, Redux Toolkit, SWR, Zod, InversifyJS (001-unify-player)
- MongoDB (Atlas) (001-unify-player)

## Recent Changes

- 001-unify-player: Added TypeScript 5.x, Node.js 20+, React 19 + Next.js 15+, Mongoose ODM, Better Auth, Redux Toolkit, SWR, Zod, InversifyJS
10 changes: 10 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,15 @@ jest.mock("mongoose", () => {
plugin: jest.fn(),
pre: jest.fn(),
post: jest.fn(),
virtual: jest.fn().mockReturnValue({
get: jest.fn().mockReturnThis(),
set: jest.fn().mockReturnThis(),
}),
virtualpath: jest.fn(),
virtuals: {},
methods: {},
statics: {},
getIndexes: jest.fn().mockReturnValue([]),
}));

// Add Types to the Schema constructor function
Expand All @@ -195,6 +202,9 @@ jest.mock("mongoose", () => {
findByIdAndDelete: jest
.fn()
.mockReturnValue({ exec: jest.fn().mockResolvedValue({}) }),
countDocuments: jest
.fn()
.mockReturnValue({ exec: jest.fn().mockResolvedValue(0) }),
};

return {
Expand Down
85 changes: 85 additions & 0 deletions specs/001-unify-player/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Requirements Checklist: 統一 Player 實體重構

**Purpose**: 驗證規格文件品質,確保所有必要元素完整且可實作
**Created**: 2025-12-18
**Feature**: [spec.md](../spec.md)

## Completeness

- [x] CHK001 包含所有必要章節(User Scenarios、Requirements、Success Criteria)
- [x] CHK002 每個 User Story 都有優先級標記(P1-P3)
- [x] CHK003 每個 User Story 都有獨立測試說明
- [x] CHK004 每個 User Story 都有 Acceptance Scenarios(Given/When/Then)
- [x] CHK005 包含 Edge Cases 區段
- [x] CHK006 包含 Key Entities 定義
- [x] CHK007 包含 Assumptions 區段

## User Stories Quality

- [x] CHK008 P1 故事可獨立交付價值(邀請成員、接受/拒絕邀請、查看成員列表)
- [x] CHK009 故事優先級排序合理(核心功能 P1 > 輔助功能 P2 > 次要功能 P3)
- [x] CHK010 每個故事都清楚說明 "Why this priority"
- [x] CHK011 Acceptance Scenarios 涵蓋正常流程和異常處理
- [x] CHK012 User Story 1-3 為 P1,可組成最小可行產品

## Requirements Quality

- [x] CHK013 功能需求使用 MUST/SHOULD 明確表達
- [x] CHK014 需求可追溯至對應的 User Story
- [x] CHK015 需求無內部矛盾
- [x] CHK016 需求技術中立(不指定實作細節)
- [x] CHK017 FR-001 至 FR-016 涵蓋所有核心功能

## Success Criteria Quality

- [x] CHK018 成功標準可量化測量(時間、完整性)
- [x] CHK019 成功標準涵蓋效能(SC-001 至 SC-003)
- [x] CHK020 成功標準涵蓋資料完整性(SC-004)
- [x] CHK021 成功標準涵蓋功能退化檢查(SC-005)
- [x] CHK022 成功標準涵蓋 API 遷移驗證(SC-006)

## Entity Design Quality

- [x] CHK023 Player 實體屬性定義完整(name, number, position, teamId, userId, email, role)
- [x] CHK024 PlayerRole 狀態機清晰(PENDING → MEMBER/ADMIN/OWNER 或 null)
- [x] CHK025 實體間關係明確(Player ↔ Team, Player ↔ User/Profile)
- [x] CHK026 與現有 Record 實體整合方案清晰(RecordPlayer 快照)

## Assumptions & Constraints

- [x] CHK027 明確聲明 _id → id 轉換延後處理
- [x] CHK028 明確聲明無需向後相容(0.x.x 版本)
- [x] CHK029 明確聲明邀請無過期機制
- [x] CHK030 明確聲明背號可重複
- [x] CHK031 明確區分純球員與系統成員的顯示邏輯

## Edge Cases Coverage

- [x] CHK032 多隊伍邀請場景
- [x] CHK033 未註冊使用者邀請場景
- [x] CHK034 使用者多隊伍成員資格場景
- [x] CHK035 隊伍刪除級聯處理
- [x] CHK036 使用者刪除關聯處理
- [x] CHK037 Player 刪除條件(無比賽紀錄)
- [x] CHK038 邀請拒絕/取消時保留 Player 記錄
- [x] CHK039 OWNER 權限獨立移轉

## Business Logic Validation

- [x] CHK040 US2 AS2: 拒絕邀請時保留 Player,僅清除 email(role 維持不變)
- [x] CHK041 US5: OWNER 和 ADMIN 都可修改成員角色與資訊
- [x] CHK042 US5: 角色選項不顯示 OWNER(需使用權限移轉功能)
- [x] CHK043 US5: ADMIN 可降級自己,OWNER 不可
- [x] CHK044 US6: OWNER 權限移轉可獨立於離隊使用
- [x] CHK045 US6: Player 只需無比賽紀錄即可刪除
- [x] CHK046 US6: 唯一成員(OWNER)離開時,比賽紀錄遷移至臨打球員,Team 和 Player 被刪除
- [x] CHK047 US7: 只對待處理邀請(email 存在但無 userId)的 Player 顯示取消邀請選項
- [x] CHK048 PlayerRole 不包含 PENDING,邀請狀態由 email/userId 欄位組合推斷
- [x] CHK049 role 只會受到權限調整而改變,不會因邀請拒絕/取消或離隊而改變
- [x] CHK050 邀請被接受後,email 欄位保留供聯絡使用

## Notes

- 所有檢查項目均通過驗證
- 規格文件品質符合實作標準
- 可進入下一階段:`/speckit.plan` 或 `/speckit.clarify`
268 changes: 268 additions & 0 deletions specs/001-unify-player/contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
# API Contracts

本目錄包含統一 Player 實體的 API 合約定義。

## 檔案說明

### 1. `players-api.yaml`

OpenAPI 3.1 規格文件,定義所有 Player 相關的 API 端點。

**用途**:
- API 文件生成
- Mock Server 建立
- API 測試工具整合(Postman, Insomnia)
- 前後端開發合約

**線上檢視**:
```bash
# 使用 Swagger Editor
npx swagger-editor-dist players-api.yaml

# 或使用 Redoc
npx @redocly/cli preview-docs players-api.yaml
```

### 2. `schemas.json`

JSON Schema 定義,用於資料驗證與型別生成。

**用途**:
- 前端 TypeScript 型別生成
- API 請求/回應驗證
- 測試資料生成

**整合範例**:

#### TypeScript 型別生成

```bash
# 使用 json-schema-to-typescript
npm install -D json-schema-to-typescript

# 生成型別
json2ts contracts/schemas.json > src/types/player-api.generated.ts
```

#### Zod Schema 生成

```typescript
// 使用 json-schema-to-zod
import { jsonSchemaToZod } from 'json-schema-to-zod';
import schemas from './schemas.json';

const zodSchema = jsonSchemaToZod(schemas.definitions.CreatePlayerRequest);
```

## API 端點總覽

### Player Operations (球員個體操作)

| 方法 | 端點 | 描述 | 權限 |
|------|------|------|------|
| GET | `/api/players/{playerId}` | 取得球員詳細資訊 | OWNER/ADMIN/MEMBER |
| DELETE | `/api/players/{playerId}` | 刪除球員 | OWNER/ADMIN |
| PATCH | `/api/players/{playerId}/info` | 更新基本資訊 | OWNER/ADMIN/本人 |
| PATCH | `/api/players/{playerId}/role` | 更新角色 | OWNER |
| PATCH | `/api/players/{playerId}/status` | 狀態轉換 | 視 action 而定 |

### Team Players (隊伍成員管理)

| 方法 | 端點 | 描述 | 權限 |
|------|------|------|------|
| GET | `/api/teams/{teamId}/players` | 取得隊伍所有球員 | OWNER/ADMIN/MEMBER |
| POST | `/api/teams/{teamId}/players` | 建立球員(含邀請) | OWNER/ADMIN |

### User Players (使用者球員關聯)

| 方法 | 端點 | 描述 | 權限 |
|------|------|------|------|
| GET | `/api/users/{userId}/players` | 取得使用者的所有球員 | 本人 |

## 狀態轉換 Actions

`PATCH /api/players/{playerId}/status` 支援的 actions:

| Action | 狀態轉換 | 權限 | Request Body |
|--------|----------|------|--------------|
| `invite` | PURE_PLAYER → INVITED | OWNER/ADMIN | `{ action: "invite", email: "..." }` |
| `accept` | INVITED → JOINED | 被邀請者本人 | `{ action: "accept" }` |
| `reject` | INVITED → deleted | 被邀請者本人 | `{ action: "reject" }` |
| `cancel` | INVITED → deleted | OWNER/ADMIN | `{ action: "cancel" }` |
| `leave` | JOINED → deleted | 本人(非 OWNER) | `{ action: "leave" }` |

## 錯誤代碼

| 代碼 | HTTP Status | 說明 |
|------|-------------|------|
| `UNAUTHORIZED` | 401 | 未登入 |
| `FORBIDDEN` | 403 | 權限不足 |
| `NOT_FOUND` | 404 | 資源不存在 |
| `VALIDATION_ERROR` | 400 | 資料驗證失敗 |
| `DUPLICATE_INVITATION` | 409 | Email 已被邀請 |
| `INVALID_STATE_TRANSITION` | 409 | 無效的狀態轉換 |
| `PLAYER_IN_USE` | 409 | 球員已被比賽記錄引用 |
| `INVALID_OPERATION` | 403 | 無效操作(如直接變更 OWNER) |

## 開發工作流程

### 1. 前端開發

```typescript
// 使用生成的型別
import type {
Player,
CreatePlayerRequest,
UpdatePlayerStatusRequest
} from '@/types/player-api.generated';

// SWR Hook
export function useTeamPlayers(teamId: string) {
return useSWR<GetTeamPlayersResponse>(
`/api/teams/${teamId}/players`,
fetcher
);
}

// Mutation Hook
export function usePlayerStatusMutation(playerId: string) {
return useSWRMutation<Player, Error, string, UpdatePlayerStatusRequest>(
`/api/players/${playerId}/status`,
async (url, { arg }) => {
const res = await fetch(url, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(arg),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
);
}
```

### 2. 後端開發

```typescript
// API Route Handler
import { PlayerSchema, UpdatePlayerInfoSchema } from '@/lib/validations/player';
import type { NextRequest } from 'next/server';

export async function PATCH(
req: NextRequest,
{ params }: { params: { playerId: string } }
) {
// 驗證請求
const body = await req.json();
const validated = UpdatePlayerInfoSchema.parse(body);

// 業務邏輯
const useCase = container.get<UpdatePlayerInfoUseCase>(TYPES.UpdatePlayerInfoUseCase);
const player = await useCase.execute(params.playerId, validated);

// 回應驗證
return Response.json(PlayerSchema.parse(player));
}
```

### 3. 測試開發

```typescript
// Integration Test
import { describe, it, expect } from '@jest/globals';

describe('POST /api/teams/{teamId}/players', () => {
it('should create invited player when email is provided', async () => {
const response = await fetch(`/api/teams/${teamId}/players`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: '王小明',
email: '[email protected]',
number: 12,
position: 'OH',
}),
});

expect(response.status).toBe(201);
const player = await response.json();
expect(player).toMatchObject({
name: '王小明',
email: '[email protected]',
number: 12,
position: 'OH',
role: 'MEMBER',
});
expect(player.userId).toBeUndefined(); // INVITED 狀態
});
});
```

## Zod Schema 對照

本專案使用 Zod 進行驗證,對應的 schema 定義於:

- **Entity Schema**: `src/entities/player.ts`
- **Validation Schema**: `src/lib/validations/player.ts`

與 JSON Schema 的對應關係:

| JSON Schema | Zod Schema | 用途 |
|-------------|------------|------|
| `CreatePlayerRequest` | `CreatePlayerSchema` | POST 請求驗證 |
| `UpdatePlayerInfoRequest` | `UpdatePlayerInfoSchema` | PATCH info 驗證 |
| `UpdatePlayerRoleRequest` | `UpdatePlayerRoleSchema` | PATCH role 驗證 |
| `UpdatePlayerStatusRequest` | `UpdatePlayerStatusSchema` | PATCH status 驗證 |
| `Player` | `PlayerSchema` | 回應驗證 |

## 未來整合點

### 通知系統

以下端點在未來整合通知系統時會觸發通知:

| 端點 | 觸發時機 | 通知對象 |
|------|----------|----------|
| `POST /api/teams/{teamId}/players` | email 存在時 | 被邀請者 |
| `PATCH /status` (invite) | 執行後 | 被邀請者 |
| `PATCH /status` (accept/reject) | 執行後 | 隊伍 OWNER/ADMIN |
| `PATCH /status` (cancel) | 執行後 | 被邀請者 |
| `PATCH /role` | 執行後 | 被變更者 |

### Prisma 遷移

未來遷移至 PostgreSQL + Prisma 時:

1. 使用 `zod-prisma-types` 自動生成 Zod schema
2. 更新 JSON Schema 以反映 Prisma 的型別定義
3. 將 `_id` 改為 `id`(cuid)
4. Enum 值保持不變(已使用字串 enum)

## 驗證與測試

### OpenAPI 驗證

```bash
# 使用 Redocly CLI
npx @redocly/cli lint players-api.yaml

# 檢查規格有效性
npx swagger-cli validate players-api.yaml
```

### JSON Schema 驗證

```bash
# 使用 AJV CLI
npm install -g ajv-cli

# 驗證範例資料
ajv validate -s schemas.json -d examples/create-player.json
```

## 參考資料

- [OpenAPI 3.1 Specification](https://spec.openapis.org/oas/v3.1.0)
- [JSON Schema Draft 7](https://json-schema.org/draft-07/schema)
- [Zod Documentation](https://zod.dev/)
- [SWR Documentation](https://swr.vercel.app/)
Loading