Files
fotospiel-app/docs/prp/tenant-app-specs/api-usage.md
2025-11-20 10:44:29 +01:00

279 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 der Tenant Admin App. Alle Requests sind tenant-scoped und erfordern ein Sanctum Personal Access Token (PAT) mit der Fähigkeit `tenant-admin` bzw. `tenant:\{id\}`.
## Authentifizierung
### Passwort-Login (PAT)
- **Endpoint**: `POST /api/v1/tenant-auth/login`
- **Body** (`application/json`): `{ "login": "username oder email", "password": "••••" }`
- **Response**: `{ token, token_type, abilities[], user { ... } }`
- **Fehler**: `422` bei ungültigen Daten, `429` bei Rate-Limit (`throttle:tenant-auth`).
- **Hinweis**: `login` akzeptiert E-Mail _oder_ Usernamen. Erfolgreiche Logins revoken vorhandene PATs mit Name `tenant-admin`.
### Session→PAT Exchange (Google/Login-Shell)
- **Endpoint**: `POST /api/v1/tenant-auth/exchange`
- **Middleware**: Sanctum stateful cookies (`SANCTUM_STATEFUL_DOMAINS`), `throttle:tenant-auth`.
- **Use-Case**: Nach Google-Login oder Marketing-Checkout Session wird das PAT ohne erneute Passwort-Eingabe ausgegeben.
- **Response**: identisch zum Passwort-Login.
### Token Status & Logout
- **`GET /api/v1/tenant-auth/me`** liefert `{ user, tenant, abilities }` und spiegelt die PAT-Fähigkeiten wider.
- **`POST /api/v1/tenant-auth/logout`** löscht das aktuelle PAT aus `personal_access_tokens`.
### Legacy Payload `/tenant/me`
- **Endpoint**: `GET /api/v1/tenant/me`
- **Response**: historisches Format (`tenant_id`, `remaining_events`, `scopes`), betrieben über denselben PAT.
- **Kompatibilität**: Dient älteren Clients als Übergang; neue Features sollten `/tenant-auth/me` verwenden.
## 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\}` (Sanctum PAT mit Fähigkeit `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**: Middleware vergleicht PAT-Fähigkeit (`tenant:\{id\}`) mit dem angefragten Tenant
- **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_ENABLE_TENANT_SWITCHER**: Dev-Flag um Tenants im Header auszuwählen (optional, Default `false`)
- **VITE_REVENUECAT_PUBLIC_KEY**: Optional für In-App-Käufe (RevenueCat)
- **SANCTUM_STATEFUL_DOMAINS** (Backend): Enthält die Origins des Admin-PWA/TWA, damit der Session→PAT-Exchange funktioniert.
- **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.