11 KiB
11 KiB
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:
422bei ungültigen Daten,429bei Rate-Limit (throttle:tenant-auth). - Hinweis:
loginakzeptiert E-Mail oder Usernamen. Erfolgreiche Logins revoken vorhandene PATs mit Nametenant-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 auspersonal_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/meverwenden.
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)
- Headers:
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 }
- Headers:
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)
- Headers:
Event-Details
- GET /api/v1/tenant/events/{slug}
- Headers:
Authorization: Bearer {token} - Response: Erweitertes Event mit
{ tasks[], members, stats { likes, views, uploads } }
- Headers:
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
- Headers:
Event archivieren
- DELETE /api/v1/tenant/events/{slug}
- Headers:
Authorization: Bearer {token},If-Match: {etag} - Response: 204 No Content (soft-delete)
- Headers:
Photos
Photos laden
- GET /api/v1/tenant/events/{event_id}/photos
- Headers:
Authorization: Bearer {token} - Params:
page=1,per_page=50status=pending|approved|rejected|featuredsort=date|likessince=cursor(für Infinite Scroll)
- Response:
{ data: Photo[], current_page, last_page } - Photo-Shape:
{ id, eventId, url, thumbnail, uploadedAt, status, likes, views, uploader, etag }
- Headers:
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 }
- Headers:
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
- Headers:
Photo löschen
- DELETE /api/v1/tenant/photos/{id}
- Headers:
Authorization: Bearer {token},If-Match: {etag} - Response: 204 No Content
- Headers:
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 }
- Headers:
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
- Headers:
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? }
- Headers:
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 }
- Headers:
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
- Headers:
Settings
Settings laden
- GET /api/v1/tenant/settings
- Headers:
Authorization: Bearer {token} - Response:
{ primaryColor, tenantName, maxEventsPerMonth, enableTasks, enableEmotions, legalPages { impressumUrl, privacyUrl } }
- Headers:
Settings updaten
- PATCH /api/v1/tenant/settings
- Headers:
Authorization: Bearer {token},Content-Type: application/json - Body: Partial Settings-Daten
- Response: Updated Settings
- Headers:
Billing
Balance laden
- GET /api/v1/tenant/credits/balance
- Headers:
Authorization: Bearer {token} - Response:
{ balance: number }
- Headers:
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? }
- Headers:
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.
- Headers:
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.
- Headers:
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
- Headers:
Allgemeine Headers
Alle API-Requests enthalten:
- Authorization:
Bearer {access_token}(Sanctum PAT mit Fähigkeittenant:{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": "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
- Backend starten: Stelle sicher, dass die Hauptapp läuft und Endpunkte verfügbar sind.
- Token erzeugen: Verwende Postman mit gültigem Access-Token.
- 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
.envmit korrekter API-URL konfigurieren.npm run devstarten.- 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
- Development:
npm run dev - Production:
npm run build - 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.