Wann du es brauchst
Web und Mobile brauchen beide neue Daten oder eine neue Aktion, zum Beispiel die
Notizen des angemeldeten Nutzers. Die Schritte verwenden GET /v1/notes als
Beispiel.
Regeln
contracts/openapi.jsonkommt zuerst: Ändere den Vertrag, dann den Code auf beiden Seiten. Definiere den Typ nicht getrennt in TypeScript und Dart.- Eine neue Operation oder ein optionales Feld ist kompatibel und bleibt in
/v1. Ein Feld zu entfernen, umzubenennen oder seinen Typ zu ändern braucht einen neuen Pfad/v2neben dem alten. apps/mobile/lib/api/generated/product_api.dartwird nie von Hand geändert: Passe den Generator an und führenpm run contract:generateaus.- Der Server entscheidet, wer der Nutzer ist und was er lesen darf. Der Handler
akzeptiert das Sitzungscookie (Web) oder
Authorization: Bearer <token>(Mobile), und RLS begrenzt die Zeilen. Vertraue nie einer Nutzer-ID oder Rolle, die der Client schickt.
Schritte
Sind die Daten neu, lege eine Tabelle in einer neuen Datei unter
supabase/migrations/an (append-only), mit aktivierter RLS und einer Policy pro erlaubter Operation, wie insupabase/migrations/202609280001_shared_product_contract.sql. Beschreibe sie inDATABASE.md.Ergänze in
contracts/openapi.jsonden Pfad mit einer stabilenoperationIdund das Schema untercomponents.schemas. Verpacke Listen in ein Objekt, damit später ein Feld dazukommen kann, ohne Clients zu brechen:"/v1/notes": { "get": { "operationId": "listNotes", "responses": { "200": { "description": "Notes of the signed-in user", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteList" } } } } } } }NoteListhat ein PflichtarrayitemsausNote;Notehat die Pflichtfelderid(uuid),bodyundcreatedAt(date-time). Erhöheinfo.version(Minor bei einer kompatiblen Änderung).tool/generate-dart-client.mjsist ein kleiner Generator für genau diesen Vertrag: Er schreibt die Dart-Klassen aus einem festen Text und liest aus dem Vertrag nur die Enum-Werte. Ergänze in diesem Text die KlasseNotemit einer FactoryfromJson(DateTime.parsefürcreatedAt) und die MethodeFuture<List<Note>> listNotes();inProductApi. Hat das Schema ein Enum, baue die Werte aus dem Vertrag wie beiBillingStatus.Führe
npm run contract:generateaus.npm run contract:checkvergleicht nur die Dart-Datei mit der Ausgabe des Generators, deshalb erweitere auchtool/test-openapi-contract.mjs: Nimm den Pfad in die geprüfte Liste auf und prüfe die Pflichtfelder, die die Dart-Klasse liest.Lege
apps/web/app/api/v1/notes/route.tsnach dem Muster vonapps/web/app/api/v1/me/route.tsan: Lies den Bearer-Token, übergib ihn ancreateSupabaseServerClient(token), damit die Abfragen als dieser Nutzer laufen, rufeauth.getUser(token)auf und antworte ohne Nutzer mit 401. Frage die Tabelle ohne Filter auf eine Nutzer-ID aus dem Request ab und bilde die Spalten auf die Namen des Vertrags ab (created_atwird zucreatedAt).GET-Handler sind standardmäßig dynamisch: Füge kein Caching für Antworten hinzu, die vom Nutzer abhängen.Implementiere
listNotesinapps/mobile/lib/api/supabase_product_api.dartmit_get('/api/v1/notes')(Next.js stellt den Vertrag unter/apibereit) und liesitemsmitNote.fromJson.Ergänze Tests in
apps/mobile/test/supabase_product_api_test.dartmitMockClient: Der Request trägt den Bearer-Token, die Antwort wird gelesen, und ein Status ungleich 200 wirftProductApiException.Dokumentiere die Operation in
API.md.
Fertig, wenn
- die Prüfbefehle durchlaufen und
npm run contract:checknachnpm run contract:generatekeinen Unterschied meldet; - der Handler ohne Sitzung oder Token mit 401 antwortet und nur die Zeilen des angemeldeten Nutzers liefert, im Browser wie mit Bearer-Token;
API.mddie neue Operation beschreibt.
