Files
fotospiel-app/docs/prp/03-api.md
2025-10-04 16:38:42 +02:00

86 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
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.
# 03 — API Contract
- Base URL: `/api/v1`
- Auth
- Tenant apps: OAuth2 Authorization Code + PKCE, refresh tokens.
- Super Admin: session-authenticated Filament (web only).
- Common
- Pagination: `page`, `per_page` (max 100).
- Errors: `{ error: { code, message, trace_id }, details?: {...} }`.
- Rate limits: per-tenant and per-device for tenant apps; 429 with `x-rate-limit-*` headers.
Key Endpoints (abridged)
- Auth: `/oauth/authorize`, `/oauth/token`, `/oauth/token/refresh`.
- Tenants (Super Admin only): list/read; no create via API for MVP.
- Events (tenant): CRUD, publish, archive; unique by `tenant_id + slug`.
- Photos (tenant): signed upload URL, create, list, moderate, feature.
- Emotions & Tasks: list, tenant overrides; task library scoping.
- Purchases & Ledger: create purchase intent, webhook ingest, ledger list.
- Settings: read/update tenant theme, limits, legal page links.
Guest Polling (no WebSockets in v1)
- GET `/events/{slug}/stats` — lightweight counters for Home info bar.
- Response: `{ online_guests: number, tasks_solved: number, latest_photo_at: ISO8601 }`.
- Cache: `Cache-Control: no-store`; include `ETag` for conditional requests.
- GET `/events/{slug}/photos?since=<ISO8601|cursor>` — incremental gallery refresh.
- Response: `{ data: Photo[], next_cursor?: string, latest_photo_at: ISO8601 }`.
- Use `If-None-Match` or `If-Modified-Since` to return `304 Not Modified` when unchanged.
Webhooks
- Payment provider events, media pipeline status, and deletion callbacks. All signed with shared secret per provider.
## Purchase Wizard Endpoints (Marketing Flow)
These endpoints support the frontend purchase wizard for package selection, authentication, and payment. They are web routes under `/purchase/` (not `/api/v1`), designed for Inertia.js integration with JSON responses for AJAX/fetch calls. No tenant middleware for auth steps (pre-tenant creation); auth required for payment.
### Flow Overview
1. **Package Selection**: User selects package via marketing page; redirects to wizard with package ID.
2. **Auth (Login/Register)**: Handle user creation/login; creates tenant if registering. Returns user data and next_step ('payment' or 'success' for free packages).
3. **Payment**: Create intent/order, complete via provider callback, finalize purchase (assign package, update tenant).
4. **Success**: Redirect to success page; email welcome if new user.
Error Handling:
- 422 Validation: `{ errors: { field: ['message'] }, message: 'Summary' }` display in forms without reload.
- 401/403: `{ error: 'Auth required' }` show login prompt.
- 500/Other: `{ error: 'Server error' }` generic alert, log trace_id.
- Non-JSON (e.g., 404): Frontend catches "unexpected end of data" and shows "Endpoint not found" or retry.
All responses: JSON only for AJAX; CSRF-protected.
### Endpoints
- **POST /purchase/auth/login**
- Body: `{ login: string (email/username), password: string, remember?: boolean }`
- Response (200): `{ status: 'authenticated', user: { id, email, name, pending_purchase, email_verified }, next_step: 'payment', needs_verification: boolean }`
- Errors: 422 `{ errors: { login: ['Invalid credentials'] } }`
- **POST /purchase/auth/register**
- Body: `{ username, email, password, password_confirmation, first_name, last_name, address, phone, privacy_consent: boolean, package_id?: number }`
- Response (200): `{ status: 'registered', user: { ... }, next_step: 'payment'|'success', needs_verification: boolean, package?: { id, name, price, type } }`
- Errors: 422 `{ errors: { email: ['Taken'], password: ['Too weak'] } }`; creates tenant/user on success.
- **POST /purchase/stripe/intent** (auth required)
- Body: `{ package_id: number }`
- Response (200): `{ client_secret: string, payment_intent_id: string }`
- Errors: 422 `{ errors: { package_id: ['Invalid'] } }`
- **POST /purchase/stripe/complete** (auth required)
- Body: `{ package_id: number, payment_intent_id: string }`
- Response (200): `{ status: 'completed' }`
- Errors: 422 `{ errors: { payment: ['Not succeeded'] } }` finalizes purchase.
- **POST /purchase/paypal/order** (auth required)
- Body: `{ package_id: number }`
- Response (200): `{ order_id: string, status: 'CREATED' }`
- Errors: 422 `{ error: 'Order creation failed' }`
- **POST /purchase/paypal/capture** (auth required)
- Body: `{ order_id: string, package_id: number }`
- Response (200): `{ status: 'captured' }`
- Errors: 422 `{ error: 'Capture incomplete' }` finalizes purchase.
- **POST /purchase/free** (auth required)
- Body: `{ package_id: number }`
- Response (200): `{ status: 'assigned' }`
- Errors: 422 `{ errors: { package_id: ['Not free'] } }` assigns for zero-price packages.