Files
fotospiel-app/docs/prp/tenant-app-specs/api-usage.md

10 KiB
Raw Blame History

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}/admin/auth/callback (per Vite-Env anpassbar)
    • Headers: Authorization: Bearer {access_token}
    • Response: { id, email, tenant_id, role, name }

Dashboard

Stats laden

  • GET /api/v1/tenant/dashboard
    • Headers: Authorization: Bearer {token}
    • Response: { credits, active_events, new_photos, task_progress }
    • Zweck: Übersicht-Daten für Dashboard-Cards

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 }
    • Response: 201 Created mit erstelltem Event
    • Validierung: Prüft Credit-Balance (1 Credit pro Event)

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:

{
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Nicht genügend Credits verfügbar"
  }
}

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)

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)
  • VITE_REVENUECAT_PUBLIC_KEY: Optional fuer In-App-Kaeufe (RevenueCat)

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.