275 lines
10 KiB
Markdown
275 lines
10 KiB
Markdown
# 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:**
|
||
```json
|
||
{
|
||
"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)
|
||
```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)
|
||
- **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.
|
||
|