Alle PlaybooksNext.js + Flutter Product Monorepo

Einen Endpunkt zum gemeinsamen Vertrag hinzufügen

Eine API-Operation in den OpenAPI-Vertrag aufnehmen, sie aus einem Next.js Route Handler hinter RLS bereitstellen und sie in Flutter über den generierten Client aufrufen.

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.json kommt 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 /v2 neben dem alten.
  • apps/mobile/lib/api/generated/product_api.dart wird nie von Hand geändert: Passe den Generator an und führe npm run contract:generate aus.
  • 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

  1. 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 in supabase/migrations/202609280001_shared_product_contract.sql. Beschreibe sie in DATABASE.md.

  2. Ergänze in contracts/openapi.json den Pfad mit einer stabilen operationId und das Schema unter components.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" } } } } }
      }
    }
    

    NoteList hat ein Pflichtarray items aus Note; Note hat die Pflichtfelder id (uuid), body und createdAt (date-time). Erhöhe info.version (Minor bei einer kompatiblen Änderung).

  3. tool/generate-dart-client.mjs ist 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 Klasse Note mit einer Factory fromJson (DateTime.parse für createdAt) und die Methode Future<List<Note>> listNotes(); in ProductApi. Hat das Schema ein Enum, baue die Werte aus dem Vertrag wie bei BillingStatus.

  4. Führe npm run contract:generate aus. npm run contract:check vergleicht nur die Dart-Datei mit der Ausgabe des Generators, deshalb erweitere auch tool/test-openapi-contract.mjs: Nimm den Pfad in die geprüfte Liste auf und prüfe die Pflichtfelder, die die Dart-Klasse liest.

  5. Lege apps/web/app/api/v1/notes/route.ts nach dem Muster von apps/web/app/api/v1/me/route.ts an: Lies den Bearer-Token, übergib ihn an createSupabaseServerClient(token), damit die Abfragen als dieser Nutzer laufen, rufe auth.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_at wird zu createdAt). GET-Handler sind standardmäßig dynamisch: Füge kein Caching für Antworten hinzu, die vom Nutzer abhängen.

  6. Implementiere listNotes in apps/mobile/lib/api/supabase_product_api.dart mit _get('/api/v1/notes') (Next.js stellt den Vertrag unter /api bereit) und lies items mit Note.fromJson.

  7. Ergänze Tests in apps/mobile/test/supabase_product_api_test.dart mit MockClient: Der Request trägt den Bearer-Token, die Antwort wird gelesen, und ein Status ungleich 200 wirft ProductApiException.

  8. Dokumentiere die Operation in API.md.

Fertig, wenn

  • die Prüfbefehle durchlaufen und npm run contract:check nach npm run contract:generate keinen 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.md die neue Operation beschreibt.