Skip to content

Selbstauskunft-Endpunkt /api/me - #56

Merged
dergel merged 7 commits into
mainfrom
feature/self-description-endpoint
Aug 23, 2026
Merged

Selbstauskunft-Endpunkt /api/me#56
dergel merged 7 commits into
mainfrom
feature/self-description-endpoint

Conversation

@dergel

@dergel dergel commented Aug 23, 2026

Copy link
Copy Markdown
Member

Löst #55: GET /api/me sagt dem Aufrufer, was er darf — gelistet werden nur Endpunkte mit vorhandenem Scope, mit Pfad, Methoden, Beschreibung und Parametern (path/query/body inkl. Typ, required, Default). Default ist das kompakte Format, ?format=openapi liefert dieselbe Menge als gefilterte OpenAPI-3.0-Spec über den bestehenden Generator.

  • Kein eigener Scope nötig: jedes gültige Token bekommt Antwort, sonst fehlt die Auskunft genau dort, wo der Scope vergessen wurde. Solche Routen erscheinen nicht auf der Token-Seite.
  • Backend-Spiegel GET /api/backend/me; dort ohne Vorfilterung, weil Permissions pro Request geprüft werden (Hinweis in meta.note).
  • 401 bei gültigem Token ohne Scope nennt jetzt required_scope — Statuscode unverändert.
  • handle() klont Routen vor dem Präfixen, damit getRoutes()-Leser /api nicht doppelt sehen.

Response-Schemas pro Route (Listen- vs. Detailform) sind bewusst nicht Teil des PRs.

Tests: 184 Tests / 1418 Assertions grün gegen eine lokale Instanz, inkl. 10 neuer Bearer- und 3 neuer Backend-Tests.


Dieser Text wurde durch eine KI erstellt.

dergel added 3 commits August 23, 2026 12:13
Clients und Agenten konnten bisher nicht ermitteln, welche Endpunkte ein
Token nutzen darf: Die OpenAPI-Spec lag nur hinter der Backend-Seite
(page=api/openapi, perm api[]), und ein Token hatte keine Selbstauskunft
über seine Scopes. Die Pfade mussten damit außerhalb der API mitgeteilt
werden, weil sie sich nicht zuverlässig aus dem Scope-Namen ableiten
lassen.

- GET /api/me listet ausschließlich die Endpunkte, deren Scope vorhanden
  ist, mit Methoden, Pfad, Beschreibung und Parametern (path/query/body
  inkl. Typ, required, Default, Beschreibung). Default-Format kompakt,
  ?format=openapi liefert dieselbe Menge als gefilterte OpenAPI-3.0-Spec
  über den bestehenden OpenAPIConfig-Generator.
- Der Endpunkt braucht keinen eigenen Scope: Auth::requiresScope() und
  new BearerAuth(false) autorisieren jedes gültige Token, sonst fehlte
  die Auskunft genau bei den Tokens, bei denen der Scope vergessen wurde.
  Token::getAvailableScopes() filtert solche Routen aus der Token-Seite.
- Backend-Spiegel GET /api/backend/me für Session-Zugriffe. Dort wird
  nicht vorab gefiltert, da Backend-Permissions pro Request geprüft
  werden; der Hinweis steht in meta.note.
- RouteCollection::handle() klont die Routen vor dem Präfixen, damit die
  registrierten Route-Objekte ihren unpräfixierten Pfad behalten und
  Controller, die getRoutes() auslesen, /api nicht doppelt sehen.
- Bei gültigem Token ohne passenden Scope nennt die 401-Antwort den
  fehlenden Scope (required_scope); bei ungültigem Token bleibt das Feld
  weg. Statuscode unverändert.

Refs #55
Die Spezifikation wird durch /api/me?format=openapi jetzt maschinenlesbar
ausgeliefert und nicht mehr nur von Swagger UI gerendert. Dabei fielen zwei
Punkte auf, die getypte Parser stolpern lassen:

- "tags" war ein nach Tag-Namen indiziertes Objekt statt eines Arrays und
  damit kein gültiges OpenAPI-Dokument (array_values).
- Fehlt für einen Tag der Sprachschlüssel, stand "[translate:...]" als
  Beschreibung in der Ausgabe. Tags können von fremden AddOns kommen, deren
  Schlüssel das api-AddOn nicht kennt — ohne Übersetzung bleibt die
  Beschreibung jetzt leer.

Zusätzlich die Sprachschlüssel für die eigenen Tags "default" und "backend"
ergänzt und ein Test, der beides für /api/me?format=openapi absichert.
OpenAPI verlangt Typ und Default eines Parameters innerhalb von "schema";
bisher stand "default" direkt am Parameter und "schema" fehlte bei allen
einfachen Query-Parametern ganz. Die Typen der Route-Definitionen (int,
bool, float) werden dabei auf die OpenAPI-Namen gemappt, ein Default von
null wird weggelassen, weil er dem deklarierten Typ widerspricht.

Bei den deepObject-Parametern (filter[...]) trugen die Properties
"required" als Bool — dort gehört es als Liste auf Objektebene. Außerdem
kamen "description" und "required" des Objekt-Parameters selbst aus der
letzten Iteration der Feld-Schleife, also vom letzten Filterfeld.

Test prüft für /api/me?format=openapi, dass jeder Parameter ein schema mit
gültigem Typ hat und kein default auf Parameter-Ebene trägt.
@dergel

dergel commented Aug 23, 2026

Copy link
Copy Markdown
Member Author

Nachtrag nach ausführlichem Variantentest gegen eine laufende Instanz (Token ohne Scopes, deaktiviertes Token, verwaister Scope, falsche Methode, Trailing-Slash, doppelte Query-Parameter, Backend-Session als Admin und als eingeschränkter User). Verhalten überall wie erwartet; dabei sind drei Fehler in OpenAPIConfig aufgefallen, die vorher nur nicht auffielen, weil die Spec ausschließlich Swagger UI gefüttert hat:

  • tags war ein nach Tag-Namen indiziertes Objekt statt eines Arrays — damit kein gültiges OpenAPI-Dokument.
  • Fehlte der Sprachschlüssel eines Tags, stand [translate:…] als Beschreibung in der Ausgabe. Tags können aus fremden AddOns kommen (hier: yrewrite), deren Schlüssel dieses AddOn nicht kennt.
  • Query-Parameter trugen default direkt am Parameter und hatten kein schema. Bei den filter[...]-Parametern kamen zusätzlich description und required des Objekts aus der letzten Iteration der Feld-Schleife, also vom letzten Filterfeld.

Alles drei behoben und mit Tests abgedeckt. Fremde AddOn-Routen erscheinen übrigens automatisch in /api/me — bei yrewrite hat das direkt funktioniert.

Suite: 186 Tests / 1806 Assertions grün.


Dieser Text wurde durch eine KI erstellt.

dergel added 4 commits August 23, 2026 13:06
- $requireScope war eine promoted Constructor-Property und damit auf
  Klassenebene ohne Default. Erweitert ein anderes AddOn BearerAuth und
  überschreibt __construct() ohne parent::__construct(), lief
  requiresScope() in "Typed property must not be accessed before
  initialization". Property jetzt klassisch mit Default deklariert.
- Die Parameter-Beschreibung stand doppelt in der Spec: am Parameter und
  im schema. Im schema bleibt sie nur für Objekt-Properties, die keine
  Parameter-Ebene haben.
Beschreibungen können sehr lang werden — die Routen des ai_platform-AddOns
liegen bei bis zu 2095 Zeichen und schieben die Zeile der Endpunktliste
mehrzeilig auseinander. In der Zeile stehen jetzt 50 Zeichen, der volle
Text sitzt im title-Attribut und erscheint beim Hover.

Nur Darstellung: die ausgelieferte Spezifikation bleibt unverändert, damit
Clients die vollständige Beschreibung weiter bekommen. Ein MutationObserver
fängt das Neu-Rendern beim Auf- und Zuklappen ab; das title-Attribut dient
dabei als Quelle des Originaltexts, nicht ein Merker-Flag.
…Entwickler

README: die Kürzung der Beschreibungen in der Endpunktliste (Anzeige, nicht
Spec), und was AddOn-Entwickler von der Selbstauskunft haben — eigene Routen
erscheinen automatisch, ausgegeben wird aber nur, was die Route deklariert;
Datei-Uploads brauchen 'type' => 'file', eigene Tags einen Sprachschlüssel.

CLAUDE.md: Konventionen des OpenAPI-Generators (Typmapping über getSchema,
tags als Array, Tag-Beschreibung ohne Sprachschlüssel leer) inklusive der
bekannten Lücke im Body-Zweig, plus der Hintergrund zur Swagger-UI-Kürzung.
Der Body-Zweig übernahm den Typ wörtlich aus der Route-Definition, dort
stehen aber die Typnamen von rex_type::cast(): "int" ist kein OpenAPI-Typ,
Client-Generatoren verlieren damit die Typinformation. Betroffen waren 13
Properties in Structure und Metainfo.

Zwei weitere Verstöße an derselben Stelle behoben:

- "required" stand als Bool in der Property; die Liste auf Objektebene wird
  ohnehin korrekt gefüllt, der Bool war eine Doppelung in falscher Form
  (406 Properties).
- Hat eine Route kein Pflichtfeld, entstand "required": [] — JSON Schema
  verlangt mindestens ein Element, der Schlüssel bleibt jetzt weg
  (21 Operationen).

Der file-Sonderfall bleibt: 'type' => 'file' ergänzt format: binary und
schaltet den Content-Type auf multipart/form-data. Als Nebeneffekt stehen
Defaults jetzt auch im Body-Schema — Swagger UI zeigt für Slices
"module_id": 0 und "ctype_id": 1 statt "string".
@dergel
dergel merged commit 7cb35dc into main Aug 23, 2026
@dergel
dergel deleted the feature/self-description-endpoint branch August 23, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant