docs: ownership model and ownerId in API contract
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
12
README.md
12
README.md
@@ -29,16 +29,22 @@ the API, on shared PostgreSQL, behind Zitadel auth, deployed via Coolify.
|
|||||||
## API
|
## API
|
||||||
|
|
||||||
Every `/api/**` route requires a valid Zitadel **access token** (`Authorization: Bearer …`)
|
Every `/api/**` route requires a valid Zitadel **access token** (`Authorization: Bearer …`)
|
||||||
belonging to the owner. Missing/invalid/expired → `401`. No anonymous access.
|
carrying the `user` project role. Missing/invalid/expired/role-less → `401`. No anonymous access.
|
||||||
|
|
||||||
|
**Ownership:** every row carries `owner_id` = the writer's token `sub`. All reads and writes are
|
||||||
|
scoped server-side to the caller (`owner_id = sub OR owner_id IS NULL`); full-replace endpoints
|
||||||
|
only replace the caller's partition. `owner_id IS NULL` marks legacy pre-multi-user rows — visible
|
||||||
|
to all authorized users and adopted by the next write that touches them. DTOs expose this as a
|
||||||
|
nullable `ownerId`; any client-supplied `ownerId` is ignored.
|
||||||
|
|
||||||
| Method & path | Caller | Behaviour |
|
| Method & path | Caller | Behaviour |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `PUT /api/lists` | desktop | Body `[{id,name}]` = full catalog. Upserts all, deletes lists not in payload (cascades tasks). → 200 |
|
| `PUT /api/lists` | desktop | Body `[{id,name}]` = full catalog. Upserts all, deletes lists not in payload (cascades tasks). → 200 |
|
||||||
| `GET /api/lists` | web | → 200 `[{id,name}]` |
|
| `GET /api/lists` | web | → 200 `[{id,name,ownerId}]` |
|
||||||
| `GET /api/lists/{id}/tasks` | web | → 200 tasks for the list; 404 if unknown |
|
| `GET /api/lists/{id}/tasks` | web | → 200 tasks for the list; 404 if unknown |
|
||||||
| `POST /api/tasks` | web | Body `{title, description?, listId}`. Server-generated GUID, `source=web`, `consumed=false`. 404 if listId unknown. → 201 with the created task |
|
| `POST /api/tasks` | web | Body `{title, description?, listId}`. Server-generated GUID, `source=web`, `consumed=false`. 404 if listId unknown. → 201 with the created task |
|
||||||
| `PUT /api/tasks/mirror` | desktop | Body `[{id, listId, title, description?}, ...]` = the FULL current Idle backlog (camelCase; `[]` is valid). Full-replace of the desktop-owned partition: upsert each (as `consumed=true`), delete any `consumed=true` task not in the array, leave web-created `consumed=false` tasks untouched. Mirrors `PUT /lists`. → 200 |
|
| `PUT /api/tasks/mirror` | desktop | Body `[{id, listId, title, description?}, ...]` = the FULL current Idle backlog (camelCase; `[]` is valid). Full-replace of the desktop-owned partition: upsert each (as `consumed=true`), delete any `consumed=true` task not in the array, leave web-created `consumed=false` tasks untouched. Mirrors `PUT /lists`. → 200 |
|
||||||
| `GET /api/tasks?consumed=false` | desktop | → 200 `[{id, listId, title, description, createdAt}]` web tasks not yet imported (awaiting pull) |
|
| `GET /api/tasks?consumed=false` | desktop | → 200 `[{id, listId, title, description, ownerId, createdAt}]` web tasks not yet imported (awaiting pull) |
|
||||||
| `POST /api/tasks/{id}/consume` | desktop | Sets `consumed=true` (imports a pulled web task into the desktop partition). Idempotent. → 200; 404 if unknown |
|
| `POST /api/tasks/{id}/consume` | desktop | Sets `consumed=true` (imports a pulled web task into the desktop partition). Idempotent. → 200; 404 if unknown |
|
||||||
| `PUT /api/tasks/{id}` · `DELETE /api/tasks/{id}` | desktop | Legacy per-task upsert/delete — **superseded by `PUT /tasks/mirror`**. Kept for compatibility. |
|
| `PUT /api/tasks/{id}` · `DELETE /api/tasks/{id}` | desktop | Legacy per-task upsert/delete — **superseded by `PUT /tasks/mirror`**. Kept for compatibility. |
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user