feat(api): owner card move route using the caller's own token
This commit is contained in:
parent
f4568e4745
commit
4649c82432
1 changed files with 69 additions and 0 deletions
69
src/app/api/owner/cards/[id]/move/route.ts
Normal file
69
src/app/api/owner/cards/[id]/move/route.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { getServerPb } from "@/lib/pb/server";
|
||||
import { requireSession } from "@/lib/auth/session";
|
||||
import { AccessError } from "@/lib/auth/access";
|
||||
import { jsonError, jsonOk } from "@/lib/http/json";
|
||||
import type { CardRecord } from "@/lib/pb/types";
|
||||
|
||||
/**
|
||||
* PATCH /api/owner/cards/:id/move — the owner drops a card in a new place.
|
||||
*
|
||||
* Unlike the /api/client/* routes, this one uses the caller's OWN PocketBase
|
||||
* token, not a superuser's. The owner-only write rule from Plan 1a is what
|
||||
* authorises the update, so a client manager reaching this route is refused by
|
||||
* the database even though the handler itself is simple.
|
||||
*
|
||||
* That is why there is no field whitelist here: PocketBase is already
|
||||
* enforcing who may write. Only the two positional fields are forwarded
|
||||
* regardless, so a stray body field cannot ride along.
|
||||
*/
|
||||
export async function PATCH(
|
||||
request: Request,
|
||||
context: { params: Promise<{ id: string }> }
|
||||
): Promise<Response> {
|
||||
try {
|
||||
const session = await requireSession();
|
||||
const { id } = await context.params;
|
||||
|
||||
if (session.role !== "owner") {
|
||||
return jsonError("Card not found", 404);
|
||||
}
|
||||
|
||||
let body: unknown;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return jsonError("Invalid request body", 400);
|
||||
}
|
||||
|
||||
const record = body as Record<string, unknown> | null;
|
||||
const column = record?.column;
|
||||
const position = record?.position;
|
||||
|
||||
if (typeof column !== "string" || !column) {
|
||||
return jsonError("A destination column is required", 400);
|
||||
}
|
||||
if (typeof position !== "number" || !Number.isFinite(position)) {
|
||||
return jsonError("A numeric position is required", 400);
|
||||
}
|
||||
|
||||
const pb = await getServerPb();
|
||||
|
||||
const updated = await pb
|
||||
.collection("cards")
|
||||
.update<CardRecord>(id, { column, position });
|
||||
|
||||
return jsonOk({
|
||||
card: {
|
||||
id: updated.id,
|
||||
column: updated.column,
|
||||
position: updated.position,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof AccessError) {
|
||||
return jsonError(error.message, error.status);
|
||||
}
|
||||
console.error("[owner-move] failed", error);
|
||||
return jsonError("Could not move the card", 500);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue