diff --git a/server/api/lists.get.ts b/server/api/lists.get.ts index 59c2d1b..da6fc4c 100644 --- a/server/api/lists.get.ts +++ b/server/api/lists.get.ts @@ -1,4 +1,5 @@ -// GET /api/lists (web) — all lists. -export default defineEventHandler(async () => { - return getLists(getSql()); +// GET /api/lists (web) — the caller's lists (plus legacy unowned). +export default defineEventHandler(async (event) => { + const rows = await getLists(getSql(), ownerOf(event)); + return rows.map((r) => ({ id: r.id, name: r.name, ownerId: r.owner_id })); }); diff --git a/server/api/lists.put.ts b/server/api/lists.put.ts index 7da538c..fa7be69 100644 --- a/server/api/lists.put.ts +++ b/server/api/lists.put.ts @@ -1,4 +1,6 @@ -// PUT /api/lists (desktop) — full-replace catalog. Upsert all supplied; delete the rest. +// PUT /api/lists (desktop) — full-replace of the caller's catalog. Upsert all supplied; +// delete the caller's lists not present. Any client-supplied ownerId is ignored — the +// server stamps ownership from the verified token. export default defineEventHandler(async (event) => { const body = await readBody(event); if ( @@ -9,6 +11,7 @@ export default defineEventHandler(async (event) => { } await replaceLists( getSql(), + ownerOf(event), body.map((l) => ({ id: l.id, name: l.name })), ); return { ok: true }; diff --git a/server/api/lists/[id]/tasks.get.ts b/server/api/lists/[id]/tasks.get.ts index 7255355..2686ca2 100644 --- a/server/api/lists/[id]/tasks.get.ts +++ b/server/api/lists/[id]/tasks.get.ts @@ -1,10 +1,11 @@ -// GET /api/lists/:id/tasks (web) — Idle tasks for a list. 404 if the list is unknown. +// GET /api/lists/:id/tasks (web) — the caller's Idle tasks for a list. 404 if the list is unknown (to the caller). export default defineEventHandler(async (event) => { const id = getRouterParam(event, "id")!; + const ownerId = ownerOf(event); const sql = getSql(); - if (!(await listExists(sql, id))) { + if (!(await listExists(sql, ownerId, id))) { throw createError({ statusCode: 404, statusMessage: "list not found" }); } - const rows = await getTasksForList(sql, id); + const rows = await getTasksForList(sql, ownerId, id); return rows.map(toTaskDto); }); diff --git a/server/api/tasks.get.ts b/server/api/tasks.get.ts index d237ff5..5f4b1bb 100644 --- a/server/api/tasks.get.ts +++ b/server/api/tasks.get.ts @@ -1,11 +1,12 @@ -// GET /api/tasks?consumed=false (desktop) — web-created tasks not yet imported. -export default defineEventHandler(async () => { - const rows = await getUnconsumed(getSql()); +// GET /api/tasks?consumed=false (desktop) — the caller's web-created tasks not yet imported. +export default defineEventHandler(async (event) => { + const rows = await getUnconsumed(getSql(), ownerOf(event)); return rows.map((r) => ({ id: r.id, listId: r.list_id, title: r.title, description: r.description, + ownerId: r.owner_id, createdAt: new Date(r.created_at).toISOString(), })); }); diff --git a/server/api/tasks.post.ts b/server/api/tasks.post.ts index 523a875..b695850 100644 --- a/server/api/tasks.post.ts +++ b/server/api/tasks.post.ts @@ -1,4 +1,4 @@ -// POST /api/tasks (web) — create an Idle task with a server-generated GUID. +// POST /api/tasks (web) — create an Idle task with a server-generated GUID, owned by the caller. export default defineEventHandler(async (event) => { const body = await readBody(event); const title = typeof body?.title === "string" ? body.title.trim() : ""; @@ -9,12 +9,13 @@ export default defineEventHandler(async (event) => { const description = typeof body?.description === "string" && body.description.trim() ? body.description : null; + const ownerId = ownerOf(event); const sql = getSql(); - if (!(await listExists(sql, listId))) { + if (!(await listExists(sql, ownerId, listId))) { throw createError({ statusCode: 404, statusMessage: "list not found" }); } - const row = await createWebTask(sql, { listId, title, description }); + const row = await createWebTask(sql, ownerId, { listId, title, description }); setResponseStatus(event, 201); return toTaskDto(row); }); diff --git a/server/api/tasks/[id].delete.ts b/server/api/tasks/[id].delete.ts index d4da7e7..afc48c6 100644 --- a/server/api/tasks/[id].delete.ts +++ b/server/api/tasks/[id].delete.ts @@ -1,6 +1,6 @@ -// DELETE /api/tasks/:id (desktop) — task left Idle on desktop. Idempotent. +// DELETE /api/tasks/:id (desktop) — task left Idle on desktop. Idempotent, scoped to the caller's rows. export default defineEventHandler(async (event) => { - await deleteTask(getSql(), getRouterParam(event, "id")!); + await deleteTask(getSql(), ownerOf(event), getRouterParam(event, "id")!); setResponseStatus(event, 204); return null; }); diff --git a/server/api/tasks/[id].put.ts b/server/api/tasks/[id].put.ts index cebdfef..c151940 100644 --- a/server/api/tasks/[id].put.ts +++ b/server/api/tasks/[id].put.ts @@ -1,4 +1,4 @@ -// PUT /api/tasks/:id (desktop) — idempotent upsert mirroring a desktop Idle task. +// PUT /api/tasks/:id (desktop) — idempotent upsert mirroring a desktop Idle task, owned by the caller. export default defineEventHandler(async (event) => { const id = getRouterParam(event, "id")!; const body = await readBody(event); @@ -9,12 +9,13 @@ export default defineEventHandler(async (event) => { } const description = typeof body?.description === "string" ? body.description : null; + const ownerId = ownerOf(event); const sql = getSql(); - if (!(await listExists(sql, listId))) { + if (!(await listExists(sql, ownerId, listId))) { throw createError({ statusCode: 404, statusMessage: "list not found" }); } - const { created } = await upsertDesktopTask(sql, id, { listId, title, description }); + const { created } = await upsertDesktopTask(sql, ownerId, id, { listId, title, description }); setResponseStatus(event, created ? 201 : 200); return { id }; }); diff --git a/server/api/tasks/[id]/consume.post.ts b/server/api/tasks/[id]/consume.post.ts index a64fe0c..7125dec 100644 --- a/server/api/tasks/[id]/consume.post.ts +++ b/server/api/tasks/[id]/consume.post.ts @@ -1,6 +1,6 @@ -// POST /api/tasks/:id/consume (desktop) — mark a web task imported. Idempotent. +// POST /api/tasks/:id/consume (desktop) — mark the caller's web task imported. Idempotent. export default defineEventHandler(async (event) => { - const ok = await consume(getSql(), getRouterParam(event, "id")!); + const ok = await consume(getSql(), ownerOf(event), getRouterParam(event, "id")!); if (!ok) { throw createError({ statusCode: 404, statusMessage: "task not found" }); } diff --git a/server/api/tasks/mirror.put.ts b/server/api/tasks/mirror.put.ts index 47750af..3254b8d 100644 --- a/server/api/tasks/mirror.put.ts +++ b/server/api/tasks/mirror.put.ts @@ -1,7 +1,8 @@ -// PUT /api/tasks/mirror (desktop) — full-replace of the desktop's current Idle backlog. +// PUT /api/tasks/mirror (desktop) — full-replace of the caller's desktop Idle backlog. // Body: [{ id, listId, title, description? }, ...] (camelCase). An empty array is valid and -// clears the desktop-owned partition. Mirrors PUT /lists. Web-created tasks awaiting pull -// (consumed=false) are never touched here. +// clears the caller's desktop-owned partition. Mirrors PUT /lists. Web-created tasks awaiting +// pull (consumed=false) and other users' rows are never touched. Any client-supplied ownerId +// on items is ignored — ownership comes from the verified token. export default defineEventHandler(async (event) => { const body = await readBody(event); if (!Array.isArray(body)) { @@ -28,16 +29,17 @@ export default defineEventHandler(async (event) => { }); } + const ownerId = ownerOf(event); const sql = getSql(); - // Every referenced list must exist (lists are full-replaced before tasks are mirrored). + // Every referenced list must exist for the caller (lists are full-replaced before tasks are mirrored). const listIds = [...new Set(items.map((t) => t.listId))]; for (const id of listIds) { - if (!(await listExists(sql, id))) { + if (!(await listExists(sql, ownerId, id))) { throw createError({ statusCode: 400, statusMessage: `unknown listId: ${id}` }); } } - await mirrorDesktopTasks(sql, items); + await mirrorDesktopTasks(sql, ownerId, items); return { ok: true, count: items.length }; });