presseportale/dev/migration 2026/07-API-MIGRATION.md
Kevin Adametz 0a3e52d603 19-05-2026 Rebrand Pressekonto, Hub-Flux UI und Legacy-Media-Migration
Umbenennung presseportale → pressekonto in Domains, Themes und Dokumentation.
Design-Tokens, Portal-Shell, Customer-Dashboard, Auth- und Admin-PM-Views.
Artisan-Befehl migrate:legacy-media mit Tests und Hub-Flux-Entwicklungsdocs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 16:36:13 +00:00

9 KiB
Raw Permalink Blame History

07 API-Migration

1. Ausgangslage (Legacy)

  • Authentifizierung: Statischer api_key-String im User-Profil (sfGuardUserProfile.api_key).
  • Transport via HTTP-Header oder Query-Param, je nach Client (historisch gewachsen).
  • Symfony-Module pressrelease, pressreleaseimage, company, category, newsletter stellten Actions bereit (ApiActions-Basisklasse).
  • Format: JSON (teils auch XML) wurde via renderText(json_encode(...)) erzeugt.
  • CSRF für API-Requests in prepareForm() abgeschaltet.
  • Undokumentiert Schnittstellen-Wissen liegt in Server-Logs und im Symfony-Code verstreut.

Vor der finalen Migration: Zugriffs-Log des Prod-Servers der letzten 3 Monate analysieren, um vollständige Liste der tatsächlich genutzten Endpoints zu erhalten. (Aufgabe zu Beginn Phase 7.)


2. Ziel-Architektur

  • URL-Prefix: /api/v1
  • Auth: Nur Laravel Sanctum Personal Access Tokens (Bearer).
    • Entscheidung D-05 / Q-03: Kein Kompatibilitätsmodus für Legacy-Keys. Cut-over statt Keep-Alive.
  • Format: JSON only; Accept: application/json empfohlen. Validation-Fehler als RFC 7807 (Laravel Default).
  • Versionierung: v1 = neuer Stand, saubere Ressourcen (kein Legacy-Feld-Alias mehr nötig, weil Clients eh umstellen müssen).
  • Rate-Limiting: Pro Sanctum-/Bearer-Token 60/min.
  • Dokumentation: OpenAPI-YAML (docs/api/v1.yml) + optional Scribe-Renderer.

Token-Scopes (Sanctum Abilities)

Ability Zweck
press-releases:read Listen + Details lesen
press-releases:write Erstellen / Update / Delete
press-release-images:write Upload / Delete
companies:read Firmen lesen
newsletter:subscribe Newsletter-Opt-In

Token können im Customer-Portal (und Admin) mit individuellen Scopes erstellt werden.


3. Cut-Over-Strategie für Legacy-API-Clients

Kein Parallelbetrieb. Kommunikation in 3 Phasen:

Phase Zeitpunkt Aktion
Ankündigung T-30 Tage Mail an alle User mit api_key_legacy != null: neue Technik kommt, Token-Migration nötig, Link zur Doku
Erinnerung T-7 Tage Zweite Mail
Cut-over Go-Live Alter api_key wird abgewiesen. Response 410 Gone mit message: "Legacy API keys are no longer supported.", migration_url und docs_url

Aktive Kunden bekommen im Backend / Customer-Portal eine prominent platzierte Migrations-Anleitung + Token-Generator.


4. Endpoint-Mapping (vorläufig)

Legacy-Route Methode Neu Ressource
pressrelease/create POST POST /v1/press-releases PressReleaseResource
pressrelease/list GET GET /v1/press-releases?page=&status= collection
pressrelease/show/:id GET GET /v1/press-releases/{id}
pressrelease/update/:id POST PATCH /v1/press-releases/{id}
pressrelease/delete/:id POST DELETE /v1/press-releases/{id}
pressreleaseimage/upload POST POST /v1/press-releases/{id}/images multipart/form-data, max. 5 MB
pressreleaseimage/list GET GET /v1/press-releases/{id}/images collection
pressreleaseimage/delete/:id POST DELETE /v1/press-release-images/{id}
company/list GET GET /v1/companies
company/show/:id GET GET /v1/companies/{id}
category/list GET GET /v1/categories?lang=de|en
newsletter/subscribe POST POST /v1/newsletter/subscribe
newsletter/unsubscribe nicht Teil von P7 wird später im neuen Frontend mit sauberem Newsletter-Dienst neu implementiert

Beispiel: PressReleaseResource

return [
    'id' => $this->id,
    'uuid' => $this->uuid,
    'legacy' => [
        'portal' => $this->legacy_portal,
        'id'     => $this->legacy_id,
    ],
    'portal' => $this->portal->value,
    'title' => $this->title,
    'slug' => $this->slug,
    'language' => $this->language,
    'status' => $this->status->value,
    'text' => $this->text,
    'backlink_url' => $this->backlink_url,
    'keywords' => $this->keywords,
    'teaser' => [
        'begin' => $this->teaser_begin,
        'end' => $this->teaser_end,
    ],
    'category' => CategoryResource::make($this->whenLoaded('category')),
    'company' => CompanyResource::make($this->whenLoaded('company')),
    'images' => PressReleaseImageResource::collection($this->whenLoaded('images')),
    'published_at' => $this->published_at?->toIso8601String(),
    'created_at' => $this->created_at->toIso8601String(),
];

5. Authentifizierung (Sanctum only)

Route-Definition:

Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () {
    Route::apiResource('press-releases', PressReleaseController::class);
    Route::get('press-releases/{pressRelease}/images', [PressReleaseImageController::class, 'index']);
    Route::post('press-releases/{pressRelease}/images', [PressReleaseImageController::class, 'store']);
    Route::delete('press-release-images/{pressReleaseImage}', [PressReleaseImageController::class, 'destroy']);
    Route::apiResource('companies', CompanyController::class)->only(['index', 'show']);
    Route::get('categories', [CategoryController::class, 'index']);
    Route::post('newsletter/subscribe', [NewsletterController::class, 'subscribe']);
});

Abilities werden pro Controller-Action geprüft:

$request->user()->tokenCan('press-releases:write') or abort(403);

Legacy-Handler (One-Shot)

Ein kleiner Handler fängt Requests mit api_key-Param ab und antwortet mit einer freundlichen Fehlermeldung inkl. Link zur Migrationsdoku (statt generischem 401):

if ($request->has('api_key') || $request->header('X-Api-Key')) {
    return response()->json([
        'message' => 'Legacy API keys are no longer supported.',
        'migration_url' => url('/account/tokens'),
        'docs_url' => url('/docs/api/v1'),
    ], 410); // 410 Gone  signalisiert deutlich: "Feature ist weg, hier ist der Ersatz"
}

6. Authorization (Policies)

  • PressReleasePolicy::create → User muss press-releases:write-Ability UND Mitglied einer Company mit role ∈ {owner, responsible} sein.
  • PressReleasePolicy::update/delete → Ownership + Status-Check (nur draft / rejected editierbar).
  • PressReleaseImageController → List nur eigene Pressemitteilung, Upload/Delete nur eigene draft / rejected Pressemitteilung, Upload max. 5 MB.
  • NewsletterController::subscribe → öffentlich, aber newsletter:subscribe-Ability erforderlich bei authentifiziertem Zugriff.
  • newsletter/unsubscribe → bewusst nicht in P7 umgesetzt; neuer Newsletter-Dienst folgt nach der Migration.
  • Fehlermeldungen sprachlich konsistent (deutsch im message-Feld, abhängig von Accept-Language).

7. Fehlerformat

Standard Laravel:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "message": "The given data was invalid.",
  "errors": {
    "title": ["Der Titel wird benötigt."],
    "text": ["Der Text darf nicht leer sein."]
  }
}

Legacy-Key-Versuch:

HTTP/1.1 410 Gone
Content-Type: application/json

{
  "message": "Legacy API keys are no longer supported.",
  "migration_url": "https://pressekonto.de/account/tokens",
  "docs_url": "https://pressekonto.de/docs/api/v1"
}

8. Versioning & Zukunft

  • v1 = neuer Stand ab Go-Live (keine Legacy-Altlasten).
  • v2 erst auf Anfrage / bei breaking changes.

9. Testing

  • Feature-Tests pro Endpoint:
    • Happy-Path mit Sanctum-Token (mit passender Ability)
    • Auth-Fehlerfall (kein Token)
    • Ability-Fehlerfall (403)
    • Legacy-Key-Versuch → 410 mit migration_url
    • Validation-Fehler (422)
    • Policy-Fehler (403)
  • Smoke-Test-Skript (php artisan api:smoke) das gegen Staging 10 kritische Endpoints fährt.

10. OpenAPI-Dokumentation

Ablegen unter docs/api/v1.yml, generiert aus:

  • Scribe (knuckleswtf/scribe) PHP-Annotations → YAML, oder
  • manuell (konsistenter, wenn Team es pflegt).

Empfehlung: Mit Scribe starten, bei Bedarf manuell überschreiben.


11. Migrationsschritte in Phase 7

  1. Log-Analyse: alle in den letzten 90 Tagen in Produktion aufgerufenen URLs auflisten
  2. Endpoints priorisieren (Top-20 zuerst)
  3. Routen, Controller, Resources, Requests, Policies pro Endpoint
  4. Sanctum-Ability-Seeder für Standard-Scopes
  5. Legacy-410-Handler
  6. Integrationstests
  7. Smoke-Tests gegen Staging mit echtem Client
  8. Kommunikation an API-Partner (E-Mail-Verteiler) mit Token-Migrations-Anleitung (T-30, T-7, T-0)
  9. Self-Service Token-Management im Customer-Portal verfügbar machen

11.1 Log-Analyse-Tooling

Für Schritt 1 gibt es ein wiederholbares Artisan-Werkzeug:

php artisan api:analyze-legacy-access-logs /var/log/nginx/access.log "/var/log/nginx/access.log.*" --top=20

Der Command erkennt Legacy-API-Routen (pressrelease/*, pressreleaseimage/*, company/*, category/*, newsletter/*), zählt Requests mit api_key, maskiert Beispielrequests und speichert nur API-Key-Fingerprints statt Klartext-Keys.

Der JSON-Report landet unter:

storage/app/private/migration/legacy-api-access-*.json