feat: implement tenant OAuth flow and guest achievements
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# 07a — Guest PWA Routes & Components
|
||||
# 07a — Guest PWA Routes & Components
|
||||
|
||||
This scaffold describes recommended routes, guards, directories, and components for the Guest PWA. It is framework-leaning (React Router v6 + Vite), but adaptable.
|
||||
|
||||
@@ -8,25 +8,26 @@ Routing Principles
|
||||
- Prefer modal routes (photo detail) layered over the gallery.
|
||||
|
||||
Route Map (proposed)
|
||||
- `/` — Landing (QR/PIN input; deep-link handler)
|
||||
- `/setup` — Profile Setup (name/avatar; skippable)
|
||||
- `/e/:slug` — Home/Feed (default gallery view + info bar)
|
||||
- `/e/:slug/tasks` — Task Picker (random/emotion)
|
||||
- `/e/:slug/tasks/:taskId` — Task Detail (card)
|
||||
- `/e/:slug/upload` — Upload Picker (camera/library + tagging)
|
||||
- `/e/:slug/queue` — Upload Queue (progress/retry)
|
||||
- `/e/:slug/gallery` — Gallery index (alias of Home or dedicated page)
|
||||
- `/e/:slug/photo/:photoId` — Photo Lightbox (modal over gallery)
|
||||
- `/e/:slug/achievements` — Achievements (optional)
|
||||
- `/e/:slug/slideshow` — Slideshow (optional, read-only)
|
||||
- `/settings` — Settings (language/theme/cache/legal)
|
||||
- `/legal/:page` — Legal pages (imprint/privacy/terms)
|
||||
- `*` — NotFound
|
||||
- `/` — Landing (QR/PIN input; deep-link handler)
|
||||
- `/setup` — Profile Setup (name/avatar; skippable)
|
||||
- `/e/:slug` — Home/Feed (default gallery view + info bar)
|
||||
- `/e/:slug/tasks` — Task Picker (random/emotion)
|
||||
- `/e/:slug/tasks/:taskId` — Task Detail (card)
|
||||
- `/e/:slug/upload` — Upload Picker (camera/library + tagging)
|
||||
- `/e/:slug/queue` — Upload Queue (progress/retry)
|
||||
- `/e/:slug/gallery` — Gallery index (alias of Home or dedicated page)
|
||||
- `/e/:slug/photo/:photoId` — Photo Lightbox (modal over gallery)
|
||||
- `/e/:slug/achievements` — Achievements (optional)
|
||||
- `/e/:slug/slideshow` — Slideshow (optional, read-only)
|
||||
- `/legal/:page` — Legal pages (imprint/privacy/terms)
|
||||
- `*` — NotFound
|
||||
|
||||
Note: The settings experience is handled via the header sheet (no dedicated route; legal pages stay routable under /legal/:page).
|
||||
|
||||
Guards & Loaders
|
||||
- `EventGuard` — verifies event token in storage; attempts refresh; otherwise redirects to `/`.
|
||||
- `PrefetchEvent` — loads event metadata/theme on `:slug` routes.
|
||||
- `OfflineFallback` — surfaces offline banner and queues mutations.
|
||||
- `EventGuard` — verifies event token in storage; attempts refresh; otherwise redirects to `/`.
|
||||
- `PrefetchEvent` — loads event metadata/theme on `:slug` routes.
|
||||
- `OfflineFallback` — surfaces offline banner and queues mutations.
|
||||
|
||||
Suggested Directory Structure
|
||||
```
|
||||
@@ -46,7 +47,7 @@ apps/guest-pwa/
|
||||
PhotoLightbox.tsx // modal route
|
||||
AchievementsPage.tsx
|
||||
SlideshowPage.tsx
|
||||
SettingsPage.tsx
|
||||
SettingsSheet.tsx
|
||||
LegalPage.tsx
|
||||
NotFoundPage.tsx
|
||||
components/
|
||||
@@ -116,7 +117,7 @@ export const router = createBrowserRouter([
|
||||
{ path: 'slideshow', element: <SlideshowPage /> },
|
||||
],
|
||||
},
|
||||
{ path: '/settings', element: <SettingsPage /> },
|
||||
// Settings sheet is rendered inside Header; no standalone route.
|
||||
{ path: '/legal/:page', element: <LegalPage /> },
|
||||
{ path: '*', element: <NotFoundPage /> },
|
||||
]);
|
||||
@@ -124,11 +125,13 @@ export const router = createBrowserRouter([
|
||||
|
||||
Component Checklist
|
||||
- Layout
|
||||
- `Header`, `InfoBar` (X Gäste online • Y Aufgaben gelöst), `BottomNav`, `Toast`.
|
||||
- `Header`, `InfoBar` (X Gäste online • Y Aufgaben gelöst), `BottomNav`, `Toast`.
|
||||
- Entry
|
||||
- `QRPinForm` (QR deep link or PIN fallback), `ProfileForm` (name/avatar).
|
||||
- Home/Feed
|
||||
- `CTAButtons` (Random Task, Emotion Picker, Quick Photo), `GalleryMasonry`, `FiltersBar`, `PhotoCard`.
|
||||
- `HeroCard` (Willkommensgruess + Eventtitel) und `StatTiles` (online Gaeste, geloeste Aufgaben, letztes Upload).
|
||||
- `CTAButtons` (Aufgabe ziehen, Direkt-Upload, Galerie) + `UploadQueueLink` fuer Warteschlange.
|
||||
- `EmotionPickerGrid` und `GalleryPreview` als inhaltlicher Einstieg.
|
||||
- Tasks
|
||||
- `EmotionPickerGrid`, `TaskCard` (shows duration, group size, actions).
|
||||
- Capture/Upload (photos only)
|
||||
@@ -136,7 +139,7 @@ Component Checklist
|
||||
- Photo View
|
||||
- `PhotoLightbox` (modal), like/share controls, emotion tags.
|
||||
- Settings & Legal
|
||||
- `SettingsPage` sections, `LegalPage` renderer.
|
||||
- `SettingsSheet` (Header-Overlay mit Namenseditor, eingebetteten Rechtsdokumenten, Cache-Leeren), `LegalPage` Renderer.
|
||||
|
||||
State & Data
|
||||
- TanStack Query for server data (events, photos); optimistic updates for likes.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 07 — Guest PWA
|
||||
# 07 — Guest PWA
|
||||
|
||||
Goal
|
||||
- Delight guests with a frictionless, installable photo experience that works offline, respects privacy, and requires no account.
|
||||
@@ -7,13 +7,13 @@ Non-Goals (MVP)
|
||||
- No comments or chat. No facial recognition. No public profiles. No videos.
|
||||
|
||||
Personas
|
||||
- Guest (attendee) — scans QR, uploads photos, browses and likes.
|
||||
- Host (tenant) — optionally shares event PIN with guests; moderates via Tenant Admin PWA.
|
||||
- Guest (attendee) — scans QR, uploads photos, browses and likes.
|
||||
- Host (tenant) — optionally shares event PIN with guests; moderates via Tenant Admin PWA.
|
||||
|
||||
Top Journeys
|
||||
- Join: Scan QR → Open event → Accept terms → Optional PIN → Land on Gallery.
|
||||
- Upload: Add photos → Review → Submit → Background upload → Success/Retry.
|
||||
- Browse: Infinite gallery → Filter (emotion/task) → Open photo → Like/Share → Back.
|
||||
- Join: Scan QR → Open event → Accept terms → Optional PIN → Land on Gallery.
|
||||
- Upload: Add photos → Review → Submit → Background upload → Success/Retry.
|
||||
- Browse: Infinite gallery → Filter (emotion/task) → Open photo → Like/Share → Back.
|
||||
|
||||
Core Features
|
||||
- Event access
|
||||
@@ -25,14 +25,14 @@ Core Features
|
||||
- Capture & upload
|
||||
- Choose from camera or library; limit file size; show remaining upload cap.
|
||||
- Client-side resize to sane max (e.g., 2560px longest edge); EXIF stripped client-side if available.
|
||||
- Assign optional emotion/task before submit; default to “Uncategorized”.
|
||||
- Assign optional emotion/task before submit; default to “Uncategorizedâ€.
|
||||
- Gallery
|
||||
- Masonry grid, lazy-load, pull-to-refresh; open photo lightbox with swipe.
|
||||
- Like (heart) with optimistic UI; share system sheet (URL to CDN variant).
|
||||
- Filters: emotion, featured, mine (local-only tag for items uploaded from this device).
|
||||
- Safety & abuse controls
|
||||
- Rate limits per device and IP; content-length checks; mime/type sniffing.
|
||||
- Upload moderation state: pending → approved/hidden; show local status.
|
||||
- Upload moderation state: pending → approved/hidden; show local status.
|
||||
- Privacy & legal
|
||||
- First run shows legal links (imprint/privacy); consent for push if enabled.
|
||||
- No PII stored; guest name is optional free text and not required by default.
|
||||
@@ -44,7 +44,7 @@ Screens
|
||||
- Upload Picker: camera/library, selection preview, emotion/task tagging.
|
||||
- Upload Queue: items with progress, retry, remove; background sync toggle.
|
||||
- Photo Lightbox: zoom, like, share; show emotion tags.
|
||||
- Settings: language, theme (system), clear cache, legal pages.
|
||||
- Settings Sheet: Gear-Icon im Header oeffnet eine Sheet-Ansicht mit editierbarem Gastnamen, eingebetteten Rechtsdokumenten (inkl. Zurueck-Navigation) und Cache-Leeren; Theme-Umschalter bleibt im Header.
|
||||
|
||||
Wireframes
|
||||
- See wireframes file at docs/wireframes/guest-pwa.md for low-fidelity layouts and flows.
|
||||
@@ -55,28 +55,27 @@ Core Pages (Pflichtseiten)
|
||||
- UI: Single input (QR deep link preferred, fallback PIN field) and Join button.
|
||||
- States: invalid/expired token, event closed, offline (allow PIN entry and queue attempt).
|
||||
- Profil-Setup (Name/Avatar)
|
||||
- Purpose: Optional personalization for likes/attribution.
|
||||
- UI: Name text field, optional avatar picker; one-time before first entry to event.
|
||||
- Behavior: Skippable; editable later in Settings.
|
||||
- Purpose: Optional personalisation fuer Likes/Statistiken; Name wird lokal im Browser gespeichert.
|
||||
- UI: Name-Feld mit Sofort-Validierung; Avatar folgt spaeter.
|
||||
- Behavior: Nicht verpflichtend, aber empfohlen; Name kann jederzeit im Settings Sheet angepasst oder geloescht werden.
|
||||
- Startseite (Home/Feed)
|
||||
- Purpose: Central hub; show event title/subheadline and CTAs.
|
||||
- Header: Event name + subheadline (e.g., “Dein Fotospiel zur Hochzeit”).
|
||||
- Info bar: “X Gäste online • Y Aufgaben gelöst”.
|
||||
- CTAs: „Aufgabe ziehen“ (random), „Wie fühlst du dich?“ (emotion picker), small link “Einfach ein Foto machen”.
|
||||
- Content: Gallery/Feed with photos + likes.
|
||||
- Purpose: Central hub; begruesst Gaeste mit ihrem hinterlegten Namen und fuehrt zu den wichtigsten Aktionen.
|
||||
- Header: Eventtitel plus Live-Kennzahlen (online Gaeste, geloeste Aufgaben); hero-card zeigt "Hey {Name}!".
|
||||
- Highlights: Drei CTA-Karten fuer Aufgabe ziehen, Direkt-Upload und Galerie sowie ein Button fuer die Upload-Warteschlange.
|
||||
- Content: EmotionPicker und GalleryPreview bilden weiterhin den Einstieg in Spielstimmung und aktuelle Fotos.
|
||||
- Aufgaben-Flow
|
||||
- Aufgaben-Picker: Choose random task or emotion mood.
|
||||
- Aufgaben-Detail (Karte): Task text, emoji tag, estimated duration, suggested group size; actions: take photo, new task (same mood), change mood.
|
||||
- Foto-Interaktion
|
||||
- Kamera/Upload: Capture or pick; progress + success message on completion; background sync.
|
||||
- Galerie/Übersicht: Grid/Feed; filters: Neueste, Beliebt, Meine; Like hearts.
|
||||
- Galerie/Übersicht: Grid/Feed; filters: Neueste, Beliebt, Meine; Like hearts.
|
||||
- Foto-Detailansicht: Fullscreen; likes/reactions; linked task + (optional) uploader name.
|
||||
- Motivation & Spiel
|
||||
- Achievements/Erfolge: Badges (e.g., Erstes Foto, 5 Aufgaben, Beliebtestes Foto); personal progress.
|
||||
- Optionale Ergänzungen
|
||||
- Slideshow/Präsentationsmodus: Auto-rotating gallery for TV/Beamer with likes/task overlay.
|
||||
- Onboarding: 1–2 “So funktioniert das Spiel” hints the first time.
|
||||
- Event-Abschluss: “Danke fürs Mitmachen”, summary stats, link/QR to online gallery.
|
||||
- Optionale Ergänzungen
|
||||
- Slideshow/Präsentationsmodus: Auto-rotating gallery for TV/Beamer with likes/task overlay.
|
||||
- Onboarding: 1–2 “So funktioniert das Spiel†hints the first time.
|
||||
- Event-Abschluss: “Danke fürs Mitmachenâ€, summary stats, link/QR to online gallery.
|
||||
|
||||
Technical Notes
|
||||
- Installability: PWA manifest + service worker; prompt A2HS on supported browsers.
|
||||
@@ -84,15 +83,15 @@ Technical Notes
|
||||
- Background Sync: use Background Sync API when available; fallback to retry on app open.
|
||||
- Accessibility: large tap targets, high contrast, keyboard support, reduced motion.
|
||||
- i18n: default `de`, fallback `en`; all strings in locale files; RTL not in MVP.
|
||||
- Media types: Photos only (no videos) — decision locked for MVP and v1.
|
||||
- Media types: Photos only (no videos) — decision locked for MVP and v1.
|
||||
- Realtime model: periodic polling (no WebSockets). Home counters every 10s; gallery delta every 30s with exponential backoff when tab hidden or offline.
|
||||
|
||||
API Touchpoints
|
||||
- GET `/api/v1/events/{slug}` — public event metadata (when open) + theme.
|
||||
- GET `/api/v1/events/{slug}/photos` — paginated gallery (approved only).
|
||||
- POST `/api/v1/events/{slug}/photos` — signed upload initiation; returns URL + fields.
|
||||
- POST (S3) — direct upload to object storage; then backend finalize call.
|
||||
- POST `/api/v1/photos/{id}/like` — idempotent like with device token.
|
||||
- GET `/api/v1/events/{slug}` — public event metadata (when open) + theme.
|
||||
- GET `/api/v1/events/{slug}/photos` — paginated gallery (approved only).
|
||||
- POST `/api/v1/events/{slug}/photos` — signed upload initiation; returns URL + fields.
|
||||
- POST (S3) — direct upload to object storage; then backend finalize call.
|
||||
- POST `/api/v1/photos/{id}/like` — idempotent like with device token.
|
||||
|
||||
Limits (MVP defaults)
|
||||
- Max uploads per device per event: 50
|
||||
@@ -100,10 +99,10 @@ Limits (MVP defaults)
|
||||
- Max resolution: 2560px longest edge per photo
|
||||
|
||||
Edge Cases
|
||||
- Token expired/invalid → Show “Event closed/invalid link”; link to retry.
|
||||
- No connectivity → Queue actions; show badge; retry policy with backoff.
|
||||
- Storage full → Offer to clear cache or deselect files.
|
||||
- Permission denied (camera/photos) → Explain and offer system shortcut.
|
||||
- Token expired/invalid → Show “Event closed/invalid linkâ€; link to retry.
|
||||
- No connectivity → Queue actions; show badge; retry policy with backoff.
|
||||
- Storage full → Offer to clear cache or deselect files.
|
||||
- Permission denied (camera/photos) → Explain and offer system shortcut.
|
||||
|
||||
Decisions
|
||||
- Videos are not supported (capture/upload strictly photos).
|
||||
|
||||
@@ -7,7 +7,7 @@ This document outlines the authentication requirements and implementation detail
|
||||
## Authentication Flow
|
||||
|
||||
### 1. Authorization Request
|
||||
- **Endpoint**: `POST /oauth/authorize`
|
||||
- **Endpoint**: `GET /api/v1/oauth/authorize`
|
||||
- **Method**: GET (redirect from frontend)
|
||||
- **Parameters**:
|
||||
- `client_id`: Fixed client ID for tenant-admin-app (`tenant-admin-app`)
|
||||
@@ -21,7 +21,7 @@ This document outlines the authentication requirements and implementation detail
|
||||
**Response**: Redirect to frontend with authorization code and state parameters.
|
||||
|
||||
### 2. Token Exchange
|
||||
- **Endpoint**: `POST /oauth/token`
|
||||
- **Endpoint**: `POST /api/v1/oauth/token`
|
||||
- **Method**: POST
|
||||
- **Content-Type**: `application/x-www-form-urlencoded`
|
||||
- **Parameters**:
|
||||
@@ -44,7 +44,7 @@ This document outlines the authentication requirements and implementation detail
|
||||
```
|
||||
|
||||
### 3. Token Refresh
|
||||
- **Endpoint**: `POST /oauth/token`
|
||||
- **Endpoint**: `POST /api/v1/oauth/token`
|
||||
- **Method**: POST
|
||||
- **Content-Type**: `application/x-www-form-urlencoded`
|
||||
- **Parameters**:
|
||||
@@ -102,6 +102,19 @@ This document outlines the authentication requirements and implementation detail
|
||||
|
||||
### oauth_clients Table
|
||||
```sql
|
||||
CREATE TABLE oauth_clients (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
client_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
client_secret VARCHAR(255),
|
||||
tenant_id BIGINT UNSIGNED NULL,
|
||||
redirect_uris JSON NULL,
|
||||
scopes JSON NULL,
|
||||
is_active TINYINT(1) DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CONSTRAINT oauth_clients_tenant_id_foreign FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE SET NULL
|
||||
);
|
||||
```sql
|
||||
CREATE TABLE oauth_clients (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
client_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
@@ -133,6 +146,20 @@ CREATE TABLE oauth_codes (
|
||||
|
||||
### refresh_tokens Table
|
||||
```sql
|
||||
CREATE TABLE refresh_tokens (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
tenant_id VARCHAR(255) NOT NULL,
|
||||
client_id VARCHAR(255),
|
||||
token VARCHAR(255) UNIQUE NOT NULL,
|
||||
access_token VARCHAR(255),
|
||||
scope TEXT,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
expires_at TIMESTAMP,
|
||||
revoked_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```sql
|
||||
CREATE TABLE refresh_tokens (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
tenant_id VARCHAR(255) NOT NULL,
|
||||
@@ -261,18 +288,22 @@ VITE_OAUTH_CLIENT_ID=tenant-admin-app
|
||||
- Revoke old refresh token immediately
|
||||
- Limit refresh tokens per tenant to 5 active
|
||||
|
||||
### 3. Rate Limiting
|
||||
### 3. Key Management
|
||||
- RSA key pairs for signing are generated on demand and stored in storage/app/private.key (private) and storage/app/public.key (public).
|
||||
- Treat the private key as a secret; rotate it alongside deploys that invalidate tenant tokens.
|
||||
|
||||
### 4. Rate Limiting
|
||||
- Authorization requests: 10/minute per IP
|
||||
- Token exchanges: 5/minute per IP
|
||||
- Token validation: 100/minute per tenant
|
||||
|
||||
### 4. Logging and Monitoring
|
||||
### 5. Logging and Monitoring
|
||||
- Log all authentication attempts (success/failure)
|
||||
- Monitor token usage patterns
|
||||
- Alert on unusual activity (multiple failed attempts, token anomalies)
|
||||
- Track refresh token usage for security analysis
|
||||
|
||||
### 5. Database Cleanup
|
||||
### 6. Database Cleanup
|
||||
- Cron job to remove expired authorization codes (daily)
|
||||
- Remove expired refresh tokens (weekly)
|
||||
- Clean blacklisted tokens after expiry (daily)
|
||||
@@ -315,4 +346,10 @@ VITE_OAUTH_CLIENT_ID=tenant-admin-app
|
||||
- Alert on PKCE validation failures
|
||||
- Log all security-related events
|
||||
|
||||
This implementation provides secure, scalable authentication for the Fotospiel tenant system, following OAuth2 best practices with PKCE for public clients.
|
||||
This implementation provides secure, scalable authentication for the Fotospiel tenant system, following OAuth2 best practices with PKCE for public clients.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# API-Nutzung der Tenant Admin App
|
||||
# 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.
|
||||
|
||||
@@ -19,6 +19,7 @@ Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit
|
||||
- **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 }`
|
||||
|
||||
@@ -51,18 +52,18 @@ Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit
|
||||
- **Validierung**: Prüft Credit-Balance (1 Credit pro Event)
|
||||
|
||||
### Event-Details
|
||||
- **GET /api/v1/tenant/events/{id}**
|
||||
- **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/{id}**
|
||||
- **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/{id}**
|
||||
- **DELETE /api/v1/tenant/events/{slug}**
|
||||
- **Headers**: `Authorization: Bearer {token}`, `If-Match: {etag}`
|
||||
- **Response**: 204 No Content (soft-delete)
|
||||
|
||||
@@ -156,12 +157,26 @@ Diese Dokumentation beschreibt alle API-Endpunkte, die die Tenant Admin App mit
|
||||
- **Response**: `{ balance: number }`
|
||||
|
||||
### Ledger-Verlauf
|
||||
- **GET /api/v1/tenant/ledger**
|
||||
- **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`
|
||||
@@ -242,8 +257,9 @@ curl -H "Authorization: Bearer {token}" \
|
||||
## Deployment
|
||||
|
||||
### Environment-Variablen
|
||||
- **REACT_APP_API_URL**: Backend-API-URL (Pflicht)
|
||||
- **REACT_APP_OAUTH_CLIENT_ID**: OAuth-Client-ID (Pflicht)
|
||||
- **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`
|
||||
@@ -254,4 +270,5 @@ curl -H "Authorization: Bearer {token}" \
|
||||
- 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.
|
||||
Für weitere Details siehe die spezifischen Dokumentationsdateien.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user