# 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.