From 853d56f76414c8b24167c5430d22f94d85a6b1c0 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Tue, 1 Sep 2026 23:49:52 +0200 Subject: [PATCH] Standardreihenfolge je Universum festlegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ein Universum kann mehrere Reihenfolgen haben, aber welche beim Oeffnen erschien, liess sich nicht bestimmen — es war immer die Erscheinungsreihenfolge. Das Feld isDefault gab es schon und wurde beim Oeffnen auch ausgewertet; es fehlte nur der Weg, es zu setzen. Der Standard gehoert dem Universum, nicht der einzelnen Person: wer Schreibrecht hat, legt ihn fuer alle fest — so wie die Reihenfolgen selbst auch geteilt sind. Die Rangfolge bleibt dadurch die schlichte: die Adresse schlaegt den Standard schlaegt die erste. setDefaultOrder() setzt zurueck und neu in einer Transaktion, damit nie zwei Standards nebeneinander stehen. deleteOrder() faengt jetzt den Fall ab, dass die Standardreihenfolge geloescht wird — dann uebernimmt die Erscheinungsreihenfolge, statt dass die Auswahl auf "die erste" faellt. Ueber die Schnittstelle geht es per PATCH /api/v1/universen/{id}. Der Endpunkt prueft, dass die Reihenfolge zu genau diesem Universum gehoert: ohne das setzte ein PATCH auf A den Standard in B, weil die Domaenenschicht das Universum aus der Reihenfolge ableitet. Vier Tests dazu: Umstellen samt Eindeutigkeit, Vorrang der Adresse, Rueckfall beim Loeschen, Rechtepruefung. Klickweg und Endpunkt gegen einen Produktionsbau geprueft. Refs #4 --- docs/API.md | 23 +++++-- src/app/actions/universes.ts | 8 +++ src/app/api/v1/openapi.json/route.ts | 30 +++++++++ src/app/api/v1/universen/[id]/route.ts | 25 +++++++- src/components/universes/UniverseTools.tsx | 34 +++++++++++ src/lib/universes.ts | 45 +++++++++++++- tests/universes.test.ts | 71 ++++++++++++++++++++++ 7 files changed, 228 insertions(+), 8 deletions(-) diff --git a/docs/API.md b/docs/API.md index 4297673..2e3ff6e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -145,15 +145,28 @@ Liefert `totals`, `monthly`, `providers`, `genres` und `yearReview` auf einmal. ### Universen ```http -GET /api/v1/universen -POST /api/v1/universen -GET /api/v1/universen/{id}?reihenfolge={orderId} -POST /api/v1/universen/{id} +GET /api/v1/universen +POST /api/v1/universen +GET /api/v1/universen/{id}?reihenfolge={orderId} +POST /api/v1/universen/{id} +PATCH /api/v1/universen/{id} ``` `GET /api/v1/universen/{id}` gibt die Einträge in der gewählten Reihenfolge zurück, dazu `progress` und `nextUp`. Ohne `reihenfolge` gilt die -Erscheinungsreihenfolge. +Standardreihenfolge des Universums — in `orders` trägt sie `isDefault`. + +`PATCH` legt fest, welche das ist. Die Reihenfolge muss zu diesem Universum +gehören, und es braucht Schreibrecht: + +```bash +curl -X PATCH -H "Authorization: Bearer $KEY" -H "content-type: application/json" \ + -d '{"defaultOrderId": "…"}' \ + https://flimmer.example.com/api/v1/universen/$ID +``` + +Je Universum trägt immer genau eine Reihenfolge den Standard. Wird die +Standardreihenfolge gelöscht, übernimmt die Erscheinungsreihenfolge. ### Export diff --git a/src/app/actions/universes.ts b/src/app/actions/universes.ts index 14e5613..5c9a18d 100644 --- a/src/app/actions/universes.ts +++ b/src/app/actions/universes.ts @@ -12,6 +12,7 @@ import { removeTitle, renameOrder, renameUniverse, + setDefaultOrder, setOrderPositions, shareUniverse, trashUniverse, @@ -124,6 +125,13 @@ export async function deleteOrderAction(orderId: string) { }); } +export async function setDefaultOrderAction(orderId: string) { + return run(async (userId) => { + await setDefaultOrder(userId, id.parse(orderId)); + return { message: "Standardreihenfolge gesetzt" }; + }); +} + export async function reorderAction(orderId: string, itemIds: string[]) { return run(async (userId) => { await setOrderPositions(userId, id.parse(orderId), z.array(id).max(1000).parse(itemIds)); diff --git a/src/app/api/v1/openapi.json/route.ts b/src/app/api/v1/openapi.json/route.ts index 9d61d71..39758e2 100644 --- a/src/app/api/v1/openapi.json/route.ts +++ b/src/app/api/v1/openapi.json/route.ts @@ -287,6 +287,9 @@ export function GET() { parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], get: { summary: "Universum mit Reihenfolge und Fortschritt", + description: + "Ohne 'reihenfolge' gilt die Standardreihenfolge des Universums — sie trägt in " + + "'orders' das Kennzeichen 'isDefault'.", parameters: [{ name: "reihenfolge", in: "query", schema: { type: "string" } }], responses: { "200": { description: "Universum" }, "404": { description: "Unbekannt" } }, }, @@ -306,6 +309,33 @@ export function GET() { }, responses: { "200": { description: "Aufgenommen" } }, }, + patch: { + summary: "Standardreihenfolge festlegen", + description: + "Bestimmt, welche Reihenfolge beim Öffnen erscheint. Braucht Schreibrecht; je " + + "Universum trägt immer genau eine Reihenfolge den Standard.", + requestBody: { + required: true, + content: { + "application/json": { + schema: { + type: "object", + properties: { + defaultOrderId: { + type: "string", + description: "Muss eine Reihenfolge dieses Universums sein.", + }, + }, + required: ["defaultOrderId"], + }, + }, + }, + }, + responses: { + "200": { description: "Gesetzt" }, + "404": { description: "Die Reihenfolge gehört nicht zu diesem Universum" }, + }, + }, }, "/export": { get: { diff --git a/src/app/api/v1/universen/[id]/route.ts b/src/app/api/v1/universen/[id]/route.ts index f670157..d39580d 100644 --- a/src/app/api/v1/universen/[id]/route.ts +++ b/src/app/api/v1/universen/[id]/route.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { apiError, readJson, withApi } from "@/lib/api"; -import { addTitle, getUniverse } from "@/lib/universes"; +import { prisma } from "@/lib/db"; +import { addTitle, getUniverse, setDefaultOrder } from "@/lib/universes"; /** GET /api/v1/universen/{id}?reihenfolge=… — Inhalt und Fortschritt. */ export const GET = withApi(async (request, user, context) => { @@ -25,3 +26,25 @@ export const POST = withApi(async (request, user, context) => { await addTitle(user.id, id, body.titleId); return { ok: true }; }); + +const patchSchema = z.object({ defaultOrderId: z.string().min(1) }); + +/** PATCH /api/v1/universen/{id} — festlegen, welche Reihenfolge beim Öffnen erscheint. */ +export const PATCH = withApi(async (request, user, context) => { + const { id } = await context.params; + if (!id) return apiError("Es fehlt die Kennung des Universums.", 400); + + const body = await readJson(request, patchSchema); + + // Die Reihenfolge muss zu genau diesem Universum gehören. Ohne diese Prüfung + // setzte ein PATCH auf Universum A den Standard in Universum B — die + // Domänenschicht leitet das Universum ja aus der Reihenfolge ab. + const order = await prisma.universeOrder.findFirst({ + where: { id: body.defaultOrderId, universeId: id }, + select: { id: true }, + }); + if (!order) return apiError("Diese Reihenfolge gehört nicht zu diesem Universum.", 404); + + await setDefaultOrder(user.id, order.id); + return { ok: true }; +}); diff --git a/src/components/universes/UniverseTools.tsx b/src/components/universes/UniverseTools.tsx index 2438537..a711c97 100644 --- a/src/components/universes/UniverseTools.tsx +++ b/src/components/universes/UniverseTools.tsx @@ -9,6 +9,7 @@ import { createOrderAction, createUniverseAction, deleteOrderAction, + setDefaultOrderAction, shareUniverseAction, trashUniverseAction, unshareUniverseAction, @@ -109,6 +110,24 @@ export function OrderTabs({ style={isActive ? { backgroundImage: "var(--fl-gradient)" } : undefined} > {order.name} + {order.isDefault && ( + <> + {/* Häkchen an der Reihenfolge, die beim Öffnen erscheint. Das + Wort dahinter steht nur für Screenreader — sichtbar würde + es die Reiter auseinanderziehen. */} + + + + (Standard) + + )} ); })} @@ -158,6 +177,21 @@ export function OrderTabs({ )} + {active && !active.isDefault && ( + + )} + {active && active.kind === "CUSTOM" && (