Files
fotospiel-app/docs/prp/tenant-app-specs/api-usage.md
Codex Agent a949c8d3af - Wired the checkout wizard for Google “comfort login”: added Socialite controller + dependency, new Google env
hooks in config/services.php/.env.example, and updated wizard steps/controllers to store session payloads,
attach packages, and surface localized success/error states.
- Retooled payment handling for both Stripe and PayPal, adding richer status management in CheckoutController/
PayPalController, fallback flows in the wizard’s PaymentStep.tsx, and fresh feature tests for intent
creation, webhooks, and the wizard CTA.
- Introduced a consent-aware Matomo analytics stack: new consent context, cookie-banner UI, useAnalytics/
useCtaExperiment hooks, and MatomoTracker component, then instrumented marketing pages (Home, Packages,
Checkout) with localized copy and experiment tracking.
- Polished package presentation across marketing UIs by centralizing formatting in PresentsPackages, surfacing
localized description tables/placeholders, tuning badges/layouts, and syncing guest/marketing translations.
- Expanded docs & reference material (docs/prp/*, TODOs, public gallery overview) and added a Playwright smoke
test for the hero CTA while reconciling outstanding checklist items.
2025-10-19 11:41:03 +02:00

277 lines
11 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API-Nutzung der Tenant Admin App
Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit der Backend-Hauptapp kommuniziert. Alle Requests sind tenant-scoped und erfordern JWT-Authentifizierung.
## Authentifizierung
### OAuth2 Flow (PKCE)
- **Start**: `GET /oauth/authorize` (Browser-Redirect mit PKCE-Challenge)
- **Params**: `client_id`, `redirect_uri`, `scope=tenant:read tenant:write`, `state`, `code_challenge`, `code_challenge_method=S256`
- **Response**: Authorization Code
- **Token Exchange**: `POST /oauth/token`
- **Body**: `grant_type=authorization_code`, `client_id`, `code`, `redirect_uri`, `code_verifier`
- **Response**: `{ access_token, refresh_token, expires_in, token_type }`
- **Headers**: `Content-Type: application/x-www-form-urlencoded`
- **Token Refresh**: `POST /oauth/token`
- **Body**: `grant_type=refresh_token`, `client_id`, `refresh_token`
- **Response**: Neuer Access/Refresh-Token
- **Token Validation**: `GET /api/v1/tenant/me`
- **Redirect URI**: Standardmaessig `${origin}/event-admin/auth/callback` (per Vite-Env anpassbar)
- **Headers**: `Authorization: Bearer {access_token}`
- **Response**: `{ id, email, tenant_id, role, name }`
- **Hinweis**: `client_id` entspricht `VITE_OAUTH_CLIENT_ID`; Seeder `OAuthClientSeeder` synchronisiert Frontend und Backend.
## Dashboard
### Stats laden
- **GET /api/v1/tenant/dashboard**
- **Headers**: `Authorization: Bearer {token}`
- **Response**: `{ active_package, active_events, new_photos, task_progress }`
- **Zweck**: Übersicht-Daten für Dashboard-Cards (active_package: current tenant package info)
## Events
### Events-Liste
- **GET /api/v1/tenant/events**
- **Headers**: `Authorization: Bearer {token}`
- **Params**:
- `page=1` (Pagination)
- `per_page=50` (max für Mobile)
- `status=draft|active|archived` (Filter)
- `search=query` (Suche in Titel/Ort)
- **Response**: `{ data: Event[], current_page, last_page, total }`
- **Event-Shape**: `{ id, title, date, location, status, photoCount, slug }`
### Event erstellen
- **POST /api/v1/tenant/events**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **Body**: `{ title, date, location, description, package_id }`
- **Response**: 201 Created mit erstelltem Event
- **Validierung**: Prüft Tenant-Package (Reseller-Limit) und erstellt Event-Package (Einmalkauf oder Free)
### Event-Details
- **GET /api/v1/tenant/events/{slug}**
- **Headers**: `Authorization: Bearer {token}`
- **Response**: Erweitertes Event mit `{ tasks[], members, stats { likes, views, uploads } }`
### Event updaten
- **PATCH /api/v1/tenant/events/{slug}**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`, `If-Match: {etag}`
- **Body**: Partial Event-Daten (title, date, location, description)
- **Response**: Updated Event
### Event archivieren
- **DELETE /api/v1/tenant/events/{slug}**
- **Headers**: `Authorization: Bearer {token}`, `If-Match: {etag}`
- **Response**: 204 No Content (soft-delete)
## Photos
### Photos laden
- **GET /api/v1/tenant/events/{event_id}/photos**
- **Headers**: `Authorization: Bearer {token}`
- **Params**:
- `page=1`, `per_page=50`
- `status=pending|approved|rejected|featured`
- `sort=date|likes`
- `since=cursor` (für Infinite Scroll)
- **Response**: `{ data: Photo[], current_page, last_page }`
- **Photo-Shape**: `{ id, eventId, url, thumbnail, uploadedAt, status, likes, views, uploader, etag }`
### Upload-URL anfordern
- **POST /api/v1/tenant/events/{event_id}/photos**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **Body**: `{ file_name, description? }`
- **Response**: `{ id, upload_url (S3 signed), thumbnail_url }`
### Photo moderieren
- **PATCH /api/v1/tenant/photos/{id}**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`, `If-Match: {etag}`
- **Body**: `{ status: 'approved'|'rejected'|'featured', featured?, reason? }`
- **Response**: Updated Photo
### Photo löschen
- **DELETE /api/v1/tenant/photos/{id}**
- **Headers**: `Authorization: Bearer {token}`, `If-Match: {etag}`
- **Response**: 204 No Content
## Members
### Mitglieder laden
- **GET /api/v1/tenant/events/{event_id}/members**
- **Headers**: `Authorization: Bearer {token}`
- **Params**: `page`, `per_page`, `status=pending|active|invited`
- **Response**: `{ data: Member[], current_page, last_page }`
- **Member-Shape**: `{ id, name, email, role, joinedAt, avatar?, status }`
### Mitglied einladen
- **POST /api/v1/tenant/events/{event_id}/members**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **Body**: `{ email, role: 'member'|'guest', name? }`
- **Response**: 201 Created, E-Mail wird versendet
## Tasks
### Tasks laden
- **GET /api/v1/tasks**
- **Headers**: `Authorization: Bearer {token}`
- **Params**:
- `global=true/false` (globale vs. tenant Tasks)
- `tenant_id=me` (nur eigene Tasks)
- `category=setup|photo|event|cleanup`
- **Response**: `{ data: Task[], global: boolean }`
- **Task-Shape**: `{ id, title, description?, category, isGlobal, tenantId?, createdAt, color? }`
### Event-Tasks laden
- **GET /api/v1/tenant/events/{event_id}/tasks**
- **Headers**: `Authorization: Bearer {token}`
- **Response**: `{ data: EventTask[], overall_progress }`
- **EventTask-Shape**: `{ id, eventId, taskId, task: Task, order, completed, assignedTo?, progress }`
### Tasks bulk zuweisen
- **POST /api/v1/tenant/events/{event_id}/tasks/bulk**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **Body**: `{ task_ids: string[], order: number[] }`
- **Response**: Updated EventTasks mit neuer Reihenfolge
## Settings
### Settings laden
- **GET /api/v1/tenant/settings**
- **Headers**: `Authorization: Bearer {token}`
- **Response**: `{ primaryColor, tenantName, maxEventsPerMonth, enableTasks, enableEmotions, legalPages { impressumUrl, privacyUrl } }`
### Settings updaten
- **PATCH /api/v1/tenant/settings**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **Body**: Partial Settings-Daten
- **Response**: Updated Settings
## Billing
### Balance laden
- **GET /api/v1/tenant/credits/balance**
- **Headers**: `Authorization: Bearer {token}`
- **Response**: `{ balance: number }`
### Ledger-Verlauf
- **GET /api/v1/tenant/credits/ledger**
- **Headers**: `Authorization: Bearer {token}`
- **Params**: `page`, `per_page` (Pagination)
- **Response**: `{ data: LedgerEntry[], current_page, last_page }`
- **LedgerEntry**: `{ id, type, amount, credits, date, description, transactionId? }`
### Credits kaufen (In-App)
- **POST /api/v1/tenant/credits/purchase**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **Body**: `{ package_id: string, credits_added: number, platform?: 'capacitor'|'web', transaction_id?: string, subscription_active?: boolean }`
- **Response**: `{ message, balance, subscription_active }`
- **Hinweis**: Wird nach erfolgreichen In-App-Kuferfolgen aufgerufen, aktualisiert Balance & Ledger.
### Credits synchronisieren
- **POST /api/v1/tenant/credits/sync**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **Body**: `{ balance: number, subscription_active: boolean, last_sync: ISODateString }`
- **Response**: `{ balance, subscription_active, server_time }`
- **Hinweis**: Client meldet lokalen Stand; Server gibt Quelle-der-Wahrheit zurcck.
### Kauf-Intent erstellen
- **POST /api/v1/tenant/purchases/intent**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **Body**: `{ package_id }`
- **Response**: `{ checkout_url: string }` (Stripe-Checkout)
- **Nach dem Kauf**: Webhook-Handling auf Backend für Balance-Update
## Allgemeine Headers
Alle API-Requests enthalten:
- **Authorization**: `Bearer {access_token}` (JWT mit tenant_id)
- **Content-Type**: `application/json` (für POST/PATCH)
- **If-Match**: `{etag}` (für Concurrency-Control bei Updates)
- **Accept**: `application/json`
## Error-Handling
**Standard-Error-Response:**
```json
{
"error": {
"code": "PACKAGE_LIMIT_EXCEEDED",
"message": "Package-Limit überschritten (z.B. max_photos)"
}
}
```
**HTTP Status Codes:**
- 200: Erfolg
- 201: Created
- 400: Bad Request (Validierungsfehler)
- 401: Unauthorized (Token-Refresh wird versucht)
- 403: Forbidden (RBAC-Verletzung)
- 404: Not Found
- 422: Unprocessable Entity
- 429: Rate Limited (Client retry mit Backoff)
## Pagination
Alle Listen-Endpunkte unterstützen:
- **page**: Aktuelle Seite (default 1)
- **per_page**: Einträge pro Seite (default 20, max 50 für Mobile)
- **Response**: `{ data: [], current_page, last_page, per_page, total }`
## Headers für Concurrency
Mutierende Endpunkte (PATCH/DELETE) erfordern:
- **If-Match**: `{etag}` aus GET-Response
- **Response**: 412 Precondition Failed bei Conflict
## Sicherheit
- **Tenant-Isolation**: Alle Endpunkte prüfen JWT-tenant_id gegen request tenant_id
- **RBAC**: Nur tenant_admin kann mutieren, member kann nur lesen/hochladen
- **Rate Limiting**: 100 Requests/Minute pro Tenant
- **ETag**: Automatische Concurrency-Control
- **No PII-Logging**: Keine sensiblen Daten in Logs
## Testing
### API-Test-Setup
1. **Backend starten**: Stelle sicher, dass die Hauptapp läuft und Endpunkte verfügbar sind.
2. **Token erzeugen**: Verwende Postman mit gültigem Access-Token.
3. **Endpoints testen**: Jeder Endpunkt einzeln mit curl oder Postman.
### Beispiel curl (mit Token)
```bash
curl -H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
https://api.fotospiel.com/api/v1/tenant/events
```
### Frontend-Test
1. `.env` mit korrekter API-URL konfigurieren.
2. `npm run dev` starten.
3. Browser-Network-Tab überprüfen für API-Calls.
## Deployment
### Environment-Variablen
- **VITE_API_URL**: Backend-API-URL (Pflicht)
- **VITE_OAUTH_CLIENT_ID**: OAuth-Client-ID (Pflicht, muss mit `config/services.php` übereinstimmen der Seeder legt damit den Client in `oauth_clients` an)
- **VITE_REVENUECAT_PUBLIC_KEY**: Optional fuer In-App-Kaeufe (RevenueCat)
- **REVENUECAT_WEBHOOK_SECRET / REVENUECAT_PRODUCT_MAPPINGS / REVENUECAT_APP_USER_PREFIX / REVENUECAT_WEBHOOK_QUEUE**: Backend-Konfiguration für RevenueCat-Webhooks, siehe `config/services.php`.
### Build & Deploy
1. **Development**: `npm run dev`
2. **Production**: `npm run build`
3. **Vorschau**: `npm run preview`
### PWA-Installation
- App ist PWA-fähig (Manifest, Service Worker).
- Installierbar auf Desktop/Mobile via "Zum Startbildschirm hinzufügen".
Für weitere Details siehe die spezifischen Dokumentationsdateien.