From bafdb88f5dbb05a44dac502ac30d8bf49a2c8f4f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 11 Jun 2026 08:28:44 +0000 Subject: [PATCH] docs: ownership model and ownerId in API contract Co-Authored-By: Claude Fable 5 --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 506a0e5..1b110c7 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,22 @@ the API, on shared PostgreSQL, behind Zitadel auth, deployed via Coolify. ## API 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 | |---|---|---| | `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 | | `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 | -| `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 | | `PUT /api/tasks/{id}` · `DELETE /api/tasks/{id}` | desktop | Legacy per-task upsert/delete — **superseded by `PUT /tasks/mirror`**. Kept for compatibility. |