Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.7 KiB
4.7 KiB
phase, plan, subsystem, status, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | status | tags | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 20-admin-member-editor-form-declutter | 01 | api/admin | complete |
|
|
|
|
|
|
Phase 20 Plan 01: Member-profile update route + isAdmin read Summary
PATCH /api/admin/members/:id with displayName/isAdmin partial update, D-03 last-admin guard (409), and isAdmin added to GET /members — implemented test-first.
Tasks Completed
| Task | Name | Commit | Files |
|---|---|---|---|
| 1 | RED — failing tests for member-profile route + isAdmin read | a0a82ac |
apps/api/tests/routes/admin.test.ts |
| 2 | GREEN — implement PATCH /members/:id + isAdmin in GET /members | bc48632 |
apps/api/src/routes/admin.ts, apps/api/tests/routes/admin.test.ts |
| 3 | REFACTOR — tidy + pass CI gates | (no changes needed) | — |
What Was Built
PATCH /api/admin/members/:idroute inapps/api/src/routes/admin.ts:- Accepts
{ displayName?: string; isAdmin?: boolean }viaupdateMemberSchema - Protected by router-wide
requireAdmin(no second guard — D-02) parsePositiveIntParamrejects malformed ids → 400- Existence check → 404 for unknown member ids
- D-03 last-admin guard: when demoting the only admin → 409
{ error: 'Cannot remove the last admin' } - Self-demotion with a second admin present → 200
- Partial
set()from whichever fields are present; try/catch 503 fallback noEchoHookapplied per T-20-04 consistency posture
- Accepts
isAdminfield added toGET /api/admin/membersselect and mapped response object
Test Coverage (8 scenarios, all passing)
| Test | Scenario | Status |
|---|---|---|
| A | displayName update → 200; GET reflects change | GREEN |
| B | isAdmin promote → 200; GET shows isAdmin true | GREEN |
| C | Last-admin demotion → 409; member stays admin | GREEN |
| D | Self-demotion with second admin → 200; one admin remains | GREEN |
| E | Non-admin PATCH → 403 (requireAdmin boundary) | GREEN |
| F | Wrong-type body → 400 Invalid request; malformed :id → 400 | GREEN |
| G | Non-existent member id → 404 | GREEN |
| H | GET /members includes boolean isAdmin per member | GREEN |
Full suite: 44 tests passed, 0 failed.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Test D GET called with demoted user
- Found during: Task 2 (GREEN run)
- Issue: Test D called
GET /memberswhilecurrentDevUserIdwas stilladminId1, who had just been demoted — resulting in 403 instead of 200 for the verification GET - Fix: Switched
currentDevUserId = adminId2before the GET call so the verification uses the remaining admin's session - Files modified: apps/api/tests/routes/admin.test.ts
- Commit:
bc48632
CI Gates
All gates pass for modified files:
tsc --noEmit: passeslint src/routes/admin.ts: passprettier --check: pass (both files)
TDD Gate Compliance
- RED gate commit:
a0a82ac(test(20-01): ...) — 7 tests failing for right reasons - GREEN gate commit:
bc48632(feat(20-01): ...) — all 44 tests passing - REFACTOR: no code changes needed — code was already clean from GREEN
Known Stubs
None.
Threat Flags
None — no new network surfaces beyond the planned PATCH route. All T-20-xx mitigations applied as specified.
Self-Check: PASSED
| Check | Result |
|---|---|
| apps/api/src/routes/admin.ts | FOUND |
| apps/api/tests/routes/admin.test.ts | FOUND |
| 20-01-SUMMARY.md | FOUND |
Commit a0a82ac (RED) |
FOUND |
Commit bc48632 (GREEN) |
FOUND |
| PATCH route registered | FOUND (line 230) |
| isAdmin in GET /members select | FOUND (line 109) |