adding claude file and some plans

This commit is contained in:
2026-08-12 18:55:18 -06:00
parent b206964417
commit 4da16c7573
19 changed files with 1294 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
# 04 — API Surface
## Conventions
- [ ] Base path `/api/v1`; JSON in/out; UTF-8
- [ ] Auth: session cookie (scs) **or** `Authorization: Bearer solopm_…` PAT — same middleware resolves both ([01-architecture.md](01-architecture.md))
- [ ] Pagination: `?page=1&per_page=50` (max 100); list responses `{items: [...], page, per_page, total}`
- [ ] Filtering: documented per endpoint below; multiple values comma-separated (`status=backlog,planned`)
- [ ] Sorting: `?sort=field` / `?sort=-field` (descending)
- [ ] Errors: `{"error": {"code", "message", "fields?"}}`; non-member project access → **404**
- [ ] Issues are addressed by `project_issue_id` (`{num}`) within their project, never by global id, in all `/projects/{id}/issues/{num}` routes
- [ ] Mutating endpoints emit domain events per [03-domain-events.md](03-domain-events.md)
## Auth & account
- [ ] `POST /api/v1/auth/register` — email, username, name, password; first user becomes admin
- [ ] `POST /api/v1/auth/login` — email + password → session cookie
- [ ] `POST /api/v1/auth/logout` — destroy session
- [ ] `GET /api/v1/auth/oauth/{provider}` — redirect to GitHub/Gitea authorize (`provider ∈ github|gitea`)
- [ ] `GET /api/v1/auth/oauth/{provider}/callback` — code exchange → upsert oauth_accounts → session cookie → redirect to SPA
- [ ] `GET /api/v1/me` — current user + memberships summary + unread notification count
- [ ] `PATCH /api/v1/me` — name, avatar_url, email_mode
- [ ] `PUT /api/v1/me/password` — requires current password
- [ ] `GET /api/v1/me/tokens` — list PATs (prefix, name, last_used_at; never the token)
- [ ] `POST /api/v1/me/tokens` — create PAT; plaintext token returned exactly once
- [ ] `DELETE /api/v1/me/tokens/{id}` — revoke
- [ ] `GET /api/v1/users` — pickable users for member/assignee selectors (excludes system, zombie, deleted)
## Admin (RequireAdmin)
- [ ] `GET /api/v1/admin/users` — all users incl. soft-deleted
- [ ] `PATCH /api/v1/admin/users/{id}` — toggle is_admin
- [ ] `DELETE /api/v1/admin/users/{id}` — soft-delete user (blocked with `conflict` if sole owner of any project — transfer first)
- [ ] `POST /api/v1/admin/cull?dry_run=true|false` — run retention job; dry-run returns would-delete report ([12-retention.md](12-retention.md))
- [ ] `POST /api/v1/admin/search/reindex` — full Meilisearch rebuild
- [ ] `GET /api/v1/admin/jobs?status=dead` — inspect job queue
## Projects
- [ ] `GET /api/v1/projects` — my projects, favorites first then recent activity; filters: `status`, `q`
- [ ] `POST /api/v1/projects` — creates project + owner membership + seeded labels
- [ ] `GET /api/v1/projects/{id}` — detail incl. members, labels, dependencies, my-favorite flag
- [ ] `PATCH /api/v1/projects/{id}` — fields incl. status (sets/clears completed_at)
- [ ] `DELETE /api/v1/projects/{id}` — soft delete (owner only)
- [ ] `PUT /api/v1/projects/{id}/owner` — transfer ownership to another member (owner only)
- [ ] `PUT /api/v1/projects/{id}/favorite` / `DELETE …/favorite`
- [ ] `GET /api/v1/projects/{id}/members` / `POST` (owner only; body: user_id, role) / `PATCH …/members/{userId}` (role) / `DELETE …/members/{userId}` (owner only; owner cannot remove self without transfer)
- [ ] `GET /api/v1/projects/{id}/dependencies` / `POST` (409 on cycle) / `DELETE …/dependencies/{depId}`
- [ ] `GET /api/v1/projects/{id}/labels` / `POST` / `PATCH …/labels/{labelId}` / `DELETE …/labels/{labelId}` (soft)
- [ ] `GET /api/v1/projects/{id}/activity` — paged audit feed for the project
- [ ] `GET /api/v1/dashboard/activity` — cross-project feed over my projects
- [ ] `GET /api/v1/projects/{id}/export?format=json|csv` — streaming download ([11-integrations.md](11-integrations.md))
## Epics
- [ ] `GET /api/v1/projects/{id}/epics` — list with progress (issue counts by status + story-point totals); filter `status`
- [ ] `POST /api/v1/projects/{id}/epics`
- [ ] `GET /api/v1/projects/{id}/epics/{epicId}` — detail incl. ordered issues
- [ ] `PATCH /api/v1/projects/{id}/epics/{epicId}` / `DELETE` (soft; issues keep existing but epic_id set NULL at cull time, not delete time)
- [ ] `PUT /api/v1/projects/{id}/epics/{epicId}/issues/{num}` — attach/move issue into epic (body: `after_rank` for position)
- [ ] `DELETE /api/v1/projects/{id}/epics/{epicId}/issues/{num}` — detach
## Issues
- [ ] `GET /api/v1/projects/{id}/issues` — filters: `status`, `assignee`, `label`, `priority`, `epic`, `parent`, `q` (title substring); sort: any column; `?view=board` returns grouped-by-status arrays ordered by board_rank
- [ ] `POST /api/v1/projects/{id}/issues` — mints `project_issue_id`; optional epic_id, parent, assignee, labels
- [ ] `GET /api/v1/projects/{id}/issues/{num}` — full detail (labels, watchers, blockers, sub-issues, epic, attachment/link lists)
- [ ] `PATCH /api/v1/projects/{id}/issues/{num}` — any field; status→done sets completed_at; board moves send `{status, after_rank}`
- [ ] `DELETE /api/v1/projects/{id}/issues/{num}` — soft delete
- [ ] `GET /api/v1/projects/{id}/issues/{num}/activity` — audit entries comments, ascending
- [ ] `GET /api/v1/projects/{id}/issues/{num}/blockers` / `POST` (409 on cycle) / `DELETE …/blockers/{blockerId}`
- [ ] `PUT /api/v1/projects/{id}/issues/{num}/labels/{labelId}` / `DELETE`
- [ ] `PUT /api/v1/projects/{id}/issues/{num}/watch` — watch (clears muted) / `DELETE` — unwatch (sets muted)
- [ ] `GET /api/v1/projects/{id}/issues/{num}/comments` / `POST`
- [ ] `PATCH /api/v1/comments/{commentId}` (author only) / `DELETE` (soft; author or project owner)
- [ ] `PUT /api/v1/comments/{commentId}/resolve` / `DELETE …/resolve`
## Links & attachments (project | issue | comment)
- [ ] `POST /api/v1/projects/{id}/links` · `POST /api/v1/projects/{id}/issues/{num}/links` · `POST /api/v1/comments/{commentId}/links`
- [ ] `DELETE /api/v1/links/{linkId}` (membership checked via owning resource)
- [ ] `POST /api/v1/projects/{id}/attachments` · `POST /api/v1/projects/{id}/issues/{num}/attachments` · `POST /api/v1/comments/{commentId}/attachments` — multipart, size ≤ `MAX_UPLOAD_MB`
- [ ] `DELETE /api/v1/attachments/{attachmentId}` (soft; file removed at cull)
- [ ] `GET /files/{path}` — auth-checked attachment serving (verify requester is member of the owning project; no directory listing; `Content-Disposition` from original_filename)
## Wiki
- [ ] `GET /api/v1/projects/{id}/wiki` — page list (title, slug, updated_by, updated_at)
- [ ] `POST /api/v1/projects/{id}/wiki` — create (slug from title; 409 on duplicate)
- [ ] `GET /api/v1/projects/{id}/wiki/{slug}` / `PATCH` / `DELETE` (soft)
## Notifications & real-time
- [ ] `GET /api/v1/notifications?unread=true` — paged inbox
- [ ] `GET /api/v1/notifications/unread_count`
- [ ] `PUT /api/v1/notifications/{id}/read` / `PUT /api/v1/notifications/read_all`
- [ ] `GET /api/v1/stream` — SSE; frames `notification`, `data`, `ping` ([09-notifications-realtime.md](09-notifications-realtime.md))
## Search
- [ ] `GET /api/v1/search?q=&types=issue,epic,project,comment,wiki&project_id=` — Meilisearch proxy, results filtered to caller's memberships ([10-search.md](10-search.md)); 503 when Meilisearch unconfigured
## Webhooks ([11-integrations.md](11-integrations.md))
- [ ] `POST /webhooks/incoming/{provider}` — unauthenticated endpoint, HMAC-SHA256 verified, writes receipt, processes (outside `/api/v1`)
- [ ] `GET /api/v1/projects/{id}/webhook_receipts` — paged receipts
- [ ] `POST /api/v1/projects/{id}/webhook_receipts/{rid}/replay`
- [ ] `GET /api/v1/projects/{id}/webhooks` / `POST` / `PATCH …/webhooks/{whId}` / `DELETE` (all owner only)
- [ ] `POST /api/v1/projects/{id}/webhooks/{whId}/test` — send a signed test payload now
- [ ] `GET /api/v1/projects/{id}/webhooks/{whId}/deliveries` — delivery log
## Ops
- [ ] `GET /healthz` — 200 with `{db: ok, meilisearch: ok|absent, smtp: configured|absent}`
- [ ] All non-`/api`, non-`/webhooks`, non-`/files` GETs → embedded SPA `index.html`