presseportale/dev/release/api-legacy-paritaet.md
Kevin Adametz 5a352425d7 API-Legacy-Paritaet: Arbeitsdokument mit Alt-Vertrag, Log-Auswertung, Schrittplan
- Alt-Vertrag komplett aus dem Symfony-Code extrahiert (Routen, Params
  verschachtelt, Response-Shapes, Sofort-Publish-Logik, Fehlerformat)
- Prod-Logs (dev/api, User) ausgewertet: ein Client (connektar),
  5 Endpoints; Company-/Kontakt-Create heute 410 -> muessen nachgebaut
  werden; Sofort-Publish beim POST belegt
- Entscheidungen 10.07.: Sofort-Publish + Admin-Schalter KI-Pruefung,
  getrennte Legacy-/v1-API, Ablehnungs-Rueckkanal spaeter
- dev/api/ enttrackt (.gitignore, Produktivdaten analog SQL-Dumps)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:40:24 +00:00

13 KiB
Raw Blame History

API-Legacy-Parität — Arbeitsdokument (Stand 10.07.2026)

Ziel (User-Vorgabe 10.07.): Beim Cutover muss die API-Schnittstelle für Bestandskunden erst einmal genauso funktionieren wie vorher. Dieses Dokument hält den belegten Alt-Vertrag fest, listet die Paritäts-Lücken und führt die einzelnen Schritte als Checkliste. Testdaten-Wünsche des Users folgen (Abschnitt „Testdaten", unten).

Quellenlage: Der komplette Alt-Code liegt im Repo unter _businessportal24.com/ (Symfony 1.4). Analysiert am 10.07.: apps/frontend/config/routing.yml (service_-Routen), plugins/PressePortalPlugin/modules/pressrelease/lib/pluginPressReleaseActions.class.php, modules/pressreleaseimage/actions/actions.class.php, modules/company/actions/actions.class.php, lib/ApiActions.class.php, lib/filters/ApiKeyGuardAuthFilter|SecureApiAccessFilter|ApiAccessFilter, modules/tnHttpError/templates/*.json.php, JSON-Templates der Module.


1. Belegter Alt-Vertrag (aus dem Symfony-Code)

Authentifizierung & Zugriff

  • X-ApiKey-Header (Filter ApiKeyGuardAuthFilter liest sfGuardUserProfile.api_key); Web-Session konnte API-Routen ebenfalls nutzen.
  • GET/HEAD verlangt Credential apiReadAccess, POST/PUT/DELETE verlangt apiWriteAccess, andere Methoden → 405 (SecureApiAccessFilter).
  • X-ApiKey auf Nicht-API-Routen → 501 (ApiAccessFilter).
  • CSRF für API-Requests deaktiviert.

Endpoints (12 API-Routen laut routing.yml, alle mit .json-Suffix!)

Alt-URL (Methode) Verhalten
POST /service/pressreleases.json PM anlegen. Params verschachtelt: press_release[company_id] (Pflicht), press_release[category_id], press_release[language], press_release[title], press_release[text], press_release[contacts_list][], ggf. press_release[backlink_url]. Status-Logik: Standard new (= Redaktions-Queue); hatte die Firma hasActivePerPresskitPayment() oder der User die Permission immediatePublication → sofort published. Response: 201 + Location-Header auf die Show-URL, Body {}. Es gab keinen separaten Submit-Call — anlegen = einreichen.
GET /service/pressreleases.json Öffentlicher Index: nur status=published, portalweit (NICHT auf den User beschränkt!), Filter query[languages][], query[categories][], query[offset], query[limit], sortiert created_at DESC. Response: JSON-Array von Show-URLs (Strings), keine Objekte.
GET /service/pressreleases/{id}.json Nur published (sonst 404; inaktive Firma → 404). Zählt Hits (session-dedupliziert). Response: flaches Objekt {id, title, text, teaser_end, language, category_id, view_url, company: {viewUrl, name}}.
GET /service/pressreleases/{id}/images.json Nur bei published PM. Response: Array von Bild-Show-URLs.
POST /service/pressreleases/{id}/images.json Nur Eigentümer (sonst 403 „You are not the creator …"). Multipart, Felder verschachtelt press_release_image[image] (+ title/description/copyright). Response: 201 + Location, Body {}.
GET /service/pressreleases/{id}/images/{imgId}.json Response: {id, title, description, copyright, image_file_url}.
GET /service/users/me/companies.json Eigene Firmen; limit/offset direkt ODER query[limit]/query[offset]. Response: Array von Show-URLs.
POST /service/users/me/companies.json Firma anlegen (201 + Location, Body {}).
GET /service/users/me/companies/{id}.json Nur eigene (sonst 403).
GET/POST /service/users/me/companies/{id}/contacts.json + GET …/contacts/{id}.json Pressekontakte lesen/anlegen.

Fehlerformat

  • 422: { "global_errors": [...], "field_errors": {...} }
  • andere Fehler: { "global_errors": [...] }

2. Ist-Stand der neuen Aliasse (routes/legacy-api.php)

Schon da: Key-Import (legacy:import-api-keys, 406 Tokens), Auth-Übersetzung X-ApiKey/api_key → Bearer, unbekannte Keys 410 + Hinweis, Rate-Limit, Eligibility-Checks, Alias-Routen für pressreleases (GET/POST), pressreleases/{id} (GET), images (GET/POST), users/me/companies (GET, Show), Rest → 410-Fallback.

Aber: Die Aliasse zeigen direkt auf die v1-Controller — Request- und Response-Verträge sind v1, nicht Alt. Für „funktioniert genauso wie vorher" fehlt eine Kompatibilitätsschicht.

3. Paritäts-Lücken (Befund 10.07.)

# Lücke Wirkung auf Alt-Clients
L1 .json-Suffix: Alt-URLs enden auf .json, unsere Aliasse matchen nur ohne Suffix Calls wie /service/pressreleases.json landen im 410-Fallback → Totalausfall
L2 Verschachtelte Params: press_release[...] / press_release_image[...] / query[...] vs. flache v1-Felder POST/Filter validieren nicht → 422 → Anlegen unmöglich
L3 Kein Auto-Submit: alt = anlegen ist einreichen (new → Redaktion bzw. sofort published); neu = Draft, Submit-Endpoint existiert im /service-Raum gar nicht PMs bleiben unsichtbar als Entwürfe liegen → Kernfunktion kaputt
L4 Index-Semantik: alt = öffentliche published-Liste (portalweit) als URL-Array; neu = eigene PMs als Objekt-Collection Clients, die den Index parsen/crawlen, brechen
L5 Show-Shape: alt = flaches Objekt mit view_url, company.viewUrl; neu = v1-Resource (andere Felder/Nesting), owner-scoped statt published-only Parser brechen; nicht-eigene published PMs sind nicht mehr abrufbar
L6 Create-Response: alt = 201 + Location + {}; neu = 201 + volle Resource vermutlich unkritisch (Location fehlt aber)
L7 Fehlerformat: alt = global_errors/field_errors; neu = Laravel message/errors Fehlerbehandlung der Clients bricht (weich)
L8 Nicht abgebildete Endpoints: Company-CREATE, Presscontact-Index/Create/Show, Einzelbild-GET → heute 410 (bewusste Entscheidung 06.07.) Clients, die Firmen/Kontakte per API anlegen, brechen
L9 KI-Deklarations-Default für /service ist umgesetzt (none/original, 10.07.) — bei Auto-Submit (L3) mitdenken erledigt, nur verdrahten

Einordnung: Der bisherige „end-to-end verifiziert (200)"-Test vom 06.07. prüfte Auth + Statuscode der suffix-losen Pfade, nicht den Alt-Vertrag. Verhaltensparität ist mit dem Alt-Code jetzt exakt herstellbar.


3a. Log-Auswertung Prod (User hat Logs geliefert → dev/api/, 10.07.)

Strukturierte Aktivitäts-Logs beider Portale (Mai–Juli 2026, b24_activity + pe_activity; 8.622 erfolgreiche API-Auths, 619 Publikationen). Ergebnis:

  • Genau EIN echter Client: connektar (connekt.connektar.de, User info@connektar.de, Legacy-ID 11768 auf bp24). Daneben nur eine tote Integration mit ungültigem Key (login_failed, kein User-Agent).
  • Tatsächlich genutzte Endpoints (alle OHNE .json-Suffix!):
    Calls Endpoint Status heute
    5.935 POST /service/users/me/companies 410 — fehlt!
    976 POST /service/users/me/companies/{id}/contacts 410 — fehlt!
    594 POST /service/pressreleases Alias vorhanden, Params/Verhalten abweichend (L2/L3)
    594 GET /service/pressreleases/{id} Alias vorhanden, Response/Sichtbarkeit abweichend (L5)
    523 POST /service/pressreleases/{id}/images Alias vorhanden, Params abweichend (L2)
  • pressrelease_published feuert direkt beim POST — connektars PMs gehen sofort live (Sofort-Publish-Privileg bestätigt; Flow: Firma anlegen → Kontakte → PM (sofort published) → Bilder → GET zur Kontrolle).
  • Damit entschärft: L1 (.json-Suffix wird real nicht genutzt), L4 (kein Index-Call). Verschärft: L8 — Company-/Kontakt-Create sind die meistgenutzten Calls überhaupt und heute tot.

3b. Entscheidungen des Users (10.07.)

  1. Zwei APIs: Die Legacy-API (/service/…) bildet für Bestandskunden das Alt-Verhalten ab; die v1-API bleibt die neue, saubere Schnittstelle mit den neuen Funktionen (KI-Deklarationsfelder etc.).
  2. Sofort-Publish nachbilden + Admin-Schalter: Standard = eingehende Legacy-PMs werden wie im Altsystem sofort veröffentlicht. Über einen Schalter im Admin kann stattdessen die KI-Prüfung aktiviert werden: dann Status review → KI routet (Gelb/Grün sofort veröffentlichen, Rot nicht).
  3. Rückmeldung bei Ablehnung: War im Altsystem nicht vorhanden (connektar pollt GET — 404 bis published). Ablehnungs-Mail an den Einlieferer existiert im neuen System bereits (notifyAuthor); ein maschinenlesbarer Rückkanal (Status-Feld/Webhook) kommt später als Feature der neuen API — Integration hier nur vormerken, nicht Teil der Parität.

4. Schritte (Checkliste — hier abhaken + dokumentieren)

Scope nach Log-Auswertung (§3a): die 5 connektar-Endpoints, verhaltensgleich.

  • S1 — Legacy-Compat-Controller /service/… (ersetzt die direkten v1-Aliasse):
    • POST /service/users/me/companies — Felder company[name|address|country_id|phone|fax|email|website] (+ logo-Datei); legacy country_id → neuer country_code; Response 201 + Location + {}; Alt-Fehlerformat.
    • POST /service/users/me/companies/{id}/contacts — Felder contact[salutation_id|title|first_name|last_name|responsibility|phone|fax|email]; Zugriff nur eigene Firma (403-Wortlaut s. §1); Response wie oben.
    • POST /service/pressreleases — Felder press_release[company_id|contacts_list[]|category_id|language|title|text|backlink_url|…]; Statusverhalten s. S2; KI-Deklarations-Default none (erledigt 10.07.); Response 201 + Location + {}.
    • POST /service/pressreleases/{id}/images — Multipart press_release_image[image|title|description|copyright]; Eigentümer-Check (403 „You are not the creator of this press release"); ai_declaration-Default original (erledigt 10.07.); Response 201 + Location + {}.
    • GET /service/pressreleases/{id} — nur published (sonst 404 — connektars Polling-Kontrolle!), Hit-Zählung, flaches Alt-Objekt {id,title,text,teaser_end,language,category_id,view_url,company:{viewUrl,name}}.
    • Alt-Fehlerformat überall: {"global_errors":[…],"field_errors":{…}} (422) bzw. {"global_errors":[…]}.
    • Pest-Paritäts-Tests je Endpoint gegen den §1-Vertrag (inkl. connektar-Flow end-to-end: Firma → Kontakt → PM → Bild → GET).
    • Nicht nachgebaut (real ungenutzt, bleiben 410): GET-Index-Routen, Einzelbild-GET, Company-GET/Show, Kontakt-GET, .json-Suffix-Formen. GET users/me/companies + Show bleiben als vorhandene Aliasse bestehen (real nur von der toten Integration gecallt, schadet nicht).
  • S2 — Statusverhalten create (Entscheidung 10.07., §3b): Default Sofort-Publish (Alt-Verhalten, per Log belegt); Admin-Schalter „KI-Prüfung für Legacy-API" → Status review + KI-Routing (Gelb/Grün auto-publish, Rot Ablehnung + Mail). Umsetzung: Setting (config + Admin-UI-Toggle), Legacy-Create wertet es aus. Blacklist/Slot-Verbrauch laufen in beiden Wegen über PressReleaseService::publish().
  • S3 — Endpoint-Scope entschieden (10.07.): per Log-Auswertung (§3a) — nur die 5 echten connektar-Endpoints nachbauen, Rest bleibt 410. (User hat Logs in dev/api/ geliefert; ersetzt das frühere [EXTERN] Log-Beschaffen.)
  • S4 — Testdaten (User liefert Vorgaben, s. Abschnitt unten): Vorschlag: Artisan-Command legacy:seed-api-testdata — legt API-Testkunden nach connektar-Muster an (aktiv, grandfathered, Legacy-Key) inkl. Firma, Kategorie, Beispiel-PMs und gibt Key + curl-Sammlung aus; nur local/staging.
  • S5 — Paritäts-Smoke auf Staging: curl-Sammlung des connektar-Flows (X-ApiKey, verschachtelte Params) gegen Staging; Vergleich gegen Alt-System-Responses.
  • S6 — Rückkanal Ablehnung (nachgelagert, neue API): Status-Feld/Webhook in v1 für „abgelehnt" — Design offen; bis dahin gilt Alt-Verhalten (GET pollt, 404 = nicht veröffentlicht) + Ablehnungs-Mail.
  • S7 — R6.4-Mailing ergänzen: Legacy-Keys laufen weiter, Verhalten unverändert (Sofort-Publish bzw. KI-Schalter), KI-Deklarations-Default (Erklärungswirkung), Hinweis auf neue v1-API.

5. Testdaten (Platzhalter — Vorgaben vom User folgen)

User (10.07.): „hier hätte ich gerne auch selber ein paar Testdaten — dazu gleich mehr". Sobald die Wünsche da sind, hier dokumentieren (welche Kunden, welche PMs/Firmen, welcher Kanal: /service-Alt-Calls und/oder v1) und in S4 umsetzen.

6. Protokoll

  • 10.07.2026: Dokument angelegt; Alt-Vertrag vollständig aus dem Symfony-Code extrahiert (§1); Lücken L1–L9 identifiziert (§3). Vorarbeiten heute bereits erledigt: KI-Deklarations-Defaults für /service (L9), Dev-Queue-Fix (KI-Pipeline lokal lauffähig).
  • 10.07.2026 (2): User liefert Prod-Aktivitäts-Logs (dev/api/) → Auswertung §3a: ein Client (connektar), 5 Endpoints, Sofort-Publish belegt; zwei der fünf laufen heute auf 410. User-Entscheidungen §3b festgehalten (Sofort-Publish + Admin-Schalter KI-Prüfung, zwei getrennte APIs, Rückkanal später). Schrittplan S1–S7 auf den echten Scope zugeschnitten; S3 damit entschieden. Nächster Schritt: S1 + S2 implementieren.