From 5a352425d76fc4e1464d62e3fef728caf50f2e62 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Fri, 10 Jul 2026 13:40:24 +0000 Subject: [PATCH] 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 --- .gitignore | 2 + dev/release/api-legacy-paritaet.md | 145 ++++++++++++++++++++++++ dev/release/entwicklungsplan-release.md | 2 + 3 files changed, 149 insertions(+) create mode 100644 dev/release/api-legacy-paritaet.md diff --git a/.gitignore b/.gitignore index 760aa4c6..dc6e139b 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,8 @@ _businessportal24.com/ dev/migration/ # Produktivdaten: Legacy-SQL-Dumps bleiben lokal, nie ins Repo (Entscheidung 06.07.2026) dev/migration 2026/sql/ +# Produktivdaten: API-Aktivitaets-Logs der Altportale (Kundendaten + Alt-API-Keys), analog SQL-Dumps +dev/api/ # Rechtstext-Quellen des Users (untracked Arbeitsstand) docs/legal/ diff --git a/dev/release/api-legacy-paritaet.md b/dev/release/api-legacy-paritaet.md new file mode 100644 index 00000000..778d2cfb --- /dev/null +++ b/dev/release/api-legacy-paritaet.md @@ -0,0 +1,145 @@ +# 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()`. +- [x] **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.** diff --git a/dev/release/entwicklungsplan-release.md b/dev/release/entwicklungsplan-release.md index 7682e628..284a73dd 100644 --- a/dev/release/entwicklungsplan-release.md +++ b/dev/release/entwicklungsplan-release.md @@ -30,6 +30,8 @@ R2.2+R2.3 erledigt: Hub-Landing komplett ueberarbeitet (datengetriebenes Tarif-G **10.07. nachmittags — R2.5 + QA-Nachpruefung + R4.3 + KI-Queue-Fix + API-KI-Deklaration (Suite: 1165 gruen):** R2.5 erledigt (legal-request-Texte aus freigegebenem Meldeverfahren, s. R2.5) · QA-Checkliste B–G vertieft (s. R6.1) · R4.3 erledigt (Karten-Helper am Model, s. R4.3). **Befund KI-Pruefung:** Auto-Routing (Rot → Ablehnung, Gelb/Gruen → Auto-Publish) war laengst gebaut, lief lokal aber nie — `composer run dev` startete den Worker ohne `--queue=default,classification`; gefixt, Backlog abgearbeitet (Pipeline live verifiziert; Prod-Runbook war korrekt). **Befund + Fix API (Entscheidung User 10.07.):** v1-API hatte keine KI-Deklarationsfelder → Submit lief seit 09.07. immer auf 422 (haette alle 406 Legacy-API-Kunden blockiert; Suite uebersah es, weil die Factory deklariert). Jetzt: `ai_text_declaration` Pflicht im v1-Submit (Call gilt dokumentiert als verbindliche Bestaetigung + Archiv-Kenntnisnahme AGB § 13), `ai_declaration` Pflicht je Bild-Upload; Legacy-/service-Pfade defaulten auf none/original (Mailing R6.4 weist darauf hin, dass die weitere Nutzung diese Erklaerung darstellt); OpenAPI + /api-Seite aktualisiert. Tests: Submit-/Image-/Legacy-API-Tests erweitert. **Beobachtung fuer QA/Launch:** Test-Kauderwelsch („asdas dsada…") wurde Gruen klassifiziert und auto-publiziert — vor Go-Live Prompt/Schwelle pruefen bzw. `CLASSIFICATION_GREEN_DELAY` als Sicherheitsfenster setzen [USER-ENTSCHEIDUNG]. +**10.07. abends — API-Legacy-Paritaet (neues Arbeitsdokument `dev/release/api-legacy-paritaet.md`):** User-Vorgabe: /service-API muss beim Cutover verhaltensgleich zum Altsystem laufen. Alt-Vertrag vollstaendig aus dem Symfony-Code (`_businessportal24.com/`) extrahiert; Prod-Aktivitaets-Logs (User, `dev/api/` — enttrackt via .gitignore, Kundendaten!) ausgewertet: **ein echter Client (connektar), 5 Endpoints, Sofort-Publish beim POST belegt; 2 der 5 Endpoints (Company-/Kontakt-Create) laufen heute auf 410.** Entscheidungen: Sofort-Publish nachbilden + Admin-Schalter fuer KI-Pruefung der Legacy-Einlieferungen; Legacy-API und v1 bleiben getrennt; Ablehnungs-Rueckkanal spaeter in v1. **Naechster Schritt: S1+S2 aus der Paritaets-Doku implementieren (Compat-Controller + Statusverhalten), dann S4 Testdaten (User-Vorgaben folgen).** + **Wiedereinstieg / naechste Schritte:** 1. ~~Klaerungsliste Rechtstexte~~ **erledigt 06.07.**; ~~R3.1-Restbacklog~~ **erledigt 06.07.** (siehe oben). 2. **[EXTERN]:** Postfach `info@pressekonto.com` anlegen (Haupt-Rechts-Mail, 06.07.); CONTACT_EMAIL_*-Support-Postfaecher je Brand; REPORT_EMAIL_* optional · R1.2 Stripe-Live · R1.4 Preisfreigabe · R5.4 Favicons · anwaltliche Freigabe AGB v0.6/Datenschutz v0.3. (~~Depublikations-Preis~~ entschieden: 35 EUR netto.)