huge documentaton restructure for docusaurus

This commit is contained in:
Codex Agent
2025-11-20 10:44:29 +01:00
parent 0127114e59
commit 6afa44d947
87 changed files with 18867 additions and 4102 deletions

View File

@@ -18,4 +18,4 @@
## Operational Notes
- Keep changes focused and reversible; document significant behavior shifts in the relevant `docs/*` or PRP files when surfaces change.
- When tasks exceed scope, record follow-ups in `docs/todo/` rather than expanding the current patch set.
- When tasks exceed scope, record follow-ups in `docs/process/todo/` rather than expanding the current patch set.

12
docs/archive/README.md Normal file
View File

@@ -0,0 +1,12 @@
# Archive
Legacy documents that are no longer part of the active Fotospiel specification live here to keep the primary `/docs` tree focused. Nothing inside this folder should be referenced by the Docusaurus site unless explicitly linked.
Current contents came from the repository root and include:
- `fotospiel_prp.md` — monolithic pre-split PRP kept for historical reference.
- Miscellaneous planning notes (`Paketbeschreibungen.txt`, `packages-business-model-plan.md`, `checkout wizard flow.txt`, `capacitor-tenant-app.txt`).
- Deprecated implementation details, e.g., the `piwik-trackingcode.txt` snippet.
- `implementation-roadmap.md` — pre-split backend roadmap kept for historical context; superseded by `docs/process/roadmap.md`.
Feel free to move additional legacy files here, but document them in this README when you do so future contributors understand why theyre retained.

View File

@@ -51,64 +51,67 @@ Packages ersetzen Credits: Vordefinierte Bündel mit Limits/Features. Kauf bei E
## 3. DB-Schema & Migrationen
### Neue Tabellen (Migration: create_packages_tables.php)
- **packages (global):**
```php
$table->id();
$table->string('name');
$table->enum('type', ['endcustomer', 'reseller']);
$table->decimal('price', 8, 2);
$table->integer('max_photos')->nullable();
$table->integer('max_guests')->nullable();
$table->integer('gallery_days')->nullable();
$table->integer('max_tasks')->nullable();
$table->boolean('watermark_allowed')->default(true);
$table->boolean('branding_allowed')->default(false);
$table->integer('max_events_per_year')->nullable();
$table->timestamp('expires_after')->nullable(); // Für Subscriptions
$table->json('features')->nullable(); // ['live_slideshow', 'analytics']
$table->timestamps();
$table->index(['type', 'price']); // Für Queries
```
- **event_packages (pro Event):**
```php
$table->id();
$table->foreignId('event_id')->constrained()->cascadeOnDelete();
$table->foreignId('package_id')->constrained()->cascadeOnDelete();
$table->decimal('purchased_price', 8, 2);
$table->timestamp('purchased_at');
$table->integer('used_photos')->default(0); // Counter
$table->timestamps();
$table->index('event_id');
```
- **tenant_packages (Reseller):**
```php
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->foreignId('package_id')->constrained()->cascadeOnDelete();
$table->decimal('price', 8, 2);
$table->timestamp('purchased_at');
$table->timestamp('expires_at');
$table->integer('used_events')->default(0);
$table->boolean('active')->default(true);
$table->timestamps();
$table->index(['tenant_id', 'active']);
```
- **package_purchases (Ledger):**
```php
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained();
$table->foreignId('event_id')->nullable()->constrained();
$table->foreignId('package_id')->constrained();
$table->string('provider_id'); // Paddle ID
$table->decimal('price', 8, 2);
$table->enum('type', ['endcustomer_event', 'reseller_subscription']);
$table->json('metadata'); // {event_id, ip_address}
$table->string('ip_address')->nullable();
$table->string('user_agent')->nullable();
$table->boolean('refunded')->default(false);
$table->timestamps();
$table->index(['tenant_id', 'purchased_at']);
```
#### packages (global)
```php
$table->id();
$table->string('name');
$table->enum('type', ['endcustomer', 'reseller']);
$table->decimal('price', 8, 2);
$table->integer('max_photos')->nullable();
$table->integer('max_guests')->nullable();
$table->integer('gallery_days')->nullable();
$table->integer('max_tasks')->nullable();
$table->boolean('watermark_allowed')->default(true);
$table->boolean('branding_allowed')->default(false);
$table->integer('max_events_per_year')->nullable();
$table->timestamp('expires_after')->nullable(); // Für Subscriptions
$table->json('features')->nullable(); // ['live_slideshow', 'analytics']
$table->timestamps();
$table->index(['type', 'price']); // Für Queries
```
#### event_packages (pro Event)
```php
$table->id();
$table->foreignId('event_id')->constrained()->cascadeOnDelete();
$table->foreignId('package_id')->constrained()->cascadeOnDelete();
$table->decimal('purchased_price', 8, 2);
$table->timestamp('purchased_at');
$table->integer('used_photos')->default(0); // Counter
$table->timestamps();
$table->index('event_id');
```
#### tenant_packages (Reseller)
```php
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->foreignId('package_id')->constrained()->cascadeOnDelete();
$table->decimal('price', 8, 2);
$table->timestamp('purchased_at');
$table->timestamp('expires_at');
$table->integer('used_events')->default(0);
$table->boolean('active')->default(true);
$table->timestamps();
$table->index(['tenant_id', 'active']);
```
#### package_purchases (Ledger)
```php
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained();
$table->foreignId('event_id')->nullable()->constrained();
$table->foreignId('package_id')->constrained();
$table->string('provider_id'); // Paddle ID
$table->decimal('price', 8, 2);
$table->enum('type', ['endcustomer_event', 'reseller_subscription']);
$table->json('metadata'); // {event_id, ip_address}
$table->string('ip_address')->nullable();
$table->string('user_agent')->nullable();
$table->boolean('refunded')->default(false);
$table->timestamps();
$table->index(['tenant_id', 'purchased_at']);
```
### Migration-Strategie (php artisan make:migration migrate_to_packages)
- **Schritt 1:** Neue Tabellen erstellen + Seeder für Standard-Packages (php artisan make:seeder PackageSeeder).
@@ -161,7 +164,7 @@ Packages ersetzen Credits: Vordefinierte Bündel mit Limits/Features. Kauf bei E
- **Controllers:**
- `PackagesController` (index: Liste mit Cache, show: Details, store: Intent für Kauf).
- `PurchasesController` (intent: Erstelle Stripe-Session oder Paddle-Order basierend auf method; store: Nach Webhook).
- **Middleware:** `PackageMiddleware` (für Events: Check event_packages.used_photos < max_photos; für Tenant: used_events < max_events_per_year).
- **Middleware:** `PackageMiddleware` (für Events: Check event_packages.used_photos &lt; max_photos; für Tenant: used_events &lt; max_events_per_year).
- **Models:** `Package` (Relationships: hasMany EventPackage/TenantPackage), `EventPackage` (incrementUsedPhotos-Method), `TenantPackage` (isActive-Scope, Observer für Expiry: E-Mail + active=false).
- **API-Endpunkte (routes/api.php, tenant-group):**
- GET /packages (Liste, filter by type).
@@ -238,4 +241,4 @@ Packages ersetzen Credits: Vordefinierte Bündel mit Limits/Features. Kauf bei E
- [ ] Tests.
- [ ] Deployment.
**Nächster Schritt:** Wechsel zu Code-Mode für Implementation (start with DB-Migrationen). Kontaktieren Sie für Änderungen.
**Nächster Schritt:** Wechsel zu Code-Mode für Implementation (start with DB-Migrationen). Kontaktieren Sie für Änderungen.

View File

@@ -4,21 +4,21 @@ Owner: Codex (handoff)
## Context
- Blend the immersive welcome + ordering journey from fotospiel-tenant-app/tenant-admin-app with the new management modules under /event-admin.
- Align with the detailed PRP (docs/prp/tenant-app-specs/*.md) and onboarding plan notes (docs/changes/2025-10-10-tenant-admin-onboarding-plan.md).
- Align with the detailed PRP (docs/prp/tenant-app-specs/*.md) and onboarding plan notes (docs/process/changes/2025-10-10-tenant-admin-onboarding-plan.md).
- Preserve existing dashboard/events/tasks/billing screens while introducing a guided first-run experience and mobile-first polish.
## References
- docs/prp/tenant-app-specs/README.md
- docs/prp/tenant-app-specs/pages-ui.md
- docs/prp/tenant-app-specs/functional-specs.md
- docs/changes/2025-10-10-tenant-admin-onboarding-plan.md
- docs/process/changes/2025-10-10-tenant-admin-onboarding-plan.md
- resources/js/admin/router.tsx
- resources/js/admin/components/AdminLayout.tsx
- resources/js/admin/pages/DashboardPage.tsx
- fotospiel-tenant-app/tenant-admin-app/src/App.tsx (legacy welcome flow)
## Priority: Now ( unblock design + scope )
- [x] Audit the legacy Capacitor app assets (intro carousel, package picker, CTA cards, animation helpers) and list what should be ported or rebuilt in Tailwind/React. Capture findings in docs/changes/2025-10-10-tenant-admin-onboarding-plan.md with component parity notes. See "Component Audit - 2025-10-10".
- [x] Audit the legacy Capacitor app assets (intro carousel, package picker, CTA cards, animation helpers) and list what should be ported or rebuilt in Tailwind/React. Capture findings in docs/process/changes/2025-10-10-tenant-admin-onboarding-plan.md with component parity notes. See "Component Audit - 2025-10-10".
- [x] Define shared onboarding design primitives inside Laravel PWA (e.g. gradient backgrounds, full-height hero layout, swipeable stepper). Propose implementation sketch for new components such as TenantWelcomeLayout and WelcomeStepCard. Documented under "Proposed Laravel PWA Welcome Primitives".
## Priority: Next ( build the welcome flow )
@@ -48,4 +48,3 @@ Owner: Codex (handoff)
- [x] Expose QR invite generation in Filament via a dedicated page/component that reuses the join-token flow from `EventDetailPage.tsx`, ensuring tokens stay in sync between PWA and Filament.
- [x] Update PRP/docs to cover die neue Welcome Journey, OAuth-Ausrichtung, Filament-Onboarding und QR-Tooling; Regression Notes + Tests dokumentiert.

View File

@@ -110,7 +110,7 @@ Fotospiel ist für jede Veranstaltung gedacht, auf der Menschen **Momente teilen
- **QRCode drucken:** Tischnummern, Aufsteller, EinlassBadges, Plakate.
- **Digital teilen:** EMailEinladung, Messenger, EventWebsite, BeamerFolie.
- **Best Practice Textbaustein (kopierfertig):**
> *„Teile deine Fotos mit uns! Scanne einfach diesen QRCode oder öffne **{{DEINLINK}}** im Browser, mach ein Foto und lade es hoch. Kein AppDownload nötig.“*
> *„Teile deine Fotos mit uns! Scanne einfach diesen QRCode oder öffne **\{\{DEINLINK\}\}** im Browser, mach ein Foto und lade es hoch. Kein AppDownload nötig.“*
### 3.2 UploadFlow für Gäste
1. QRCode scannen oder Link öffnen.
@@ -219,13 +219,13 @@ Fotospiel setzt auf **Privacy by Design** und **Transparenz**.
### 6.2 Vorlagen & Textbausteine
- **Einladung per Messenger/EMail:**
> *„Teile deine Fotos mit uns! Scanne beim Event den QRCode oder öffne **{{DEINLINK}}**. Kein AppDownload nötig einfach Foto machen & hochladen. Danke fürs Mitmachen!“*
> *„Teile deine Fotos mit uns! Scanne beim Event den QRCode oder öffne **\{\{DEINLINK\}\}**. Kein AppDownload nötig einfach Foto machen & hochladen. Danke fürs Mitmachen!“*
- **Ansage vor Ort (MC):**
> *„Wer Lust hat, unsere Erinnerungen festzuhalten: Nehmt euer Handy, scannt den QRCode auf den Tischen und ladet eure Lieblingsmomente hoch. Später gibts eine LiveSlideshow!“*
- **Followup am nächsten Tag:**
> *„Danke, dass ihr dabei wart! Hier ist der Link zur Galerie: **{{DEINLINK}}**. Stöbert in den Fotos und ladet euch eure Favoriten als ZIP herunter.“*
> *„Danke, dass ihr dabei wart! Hier ist der Link zur Galerie: **\{\{DEINLINK\}\}**. Stöbert in den Fotos und ladet euch eure Favoriten als ZIP herunter.“*
### 6.3 Checkliste (druckfertig)
- [ ] Event angelegt, Einstellungen geprüft

View File

@@ -11,17 +11,24 @@ This folder defines the bilingual help center that serves both guest app users a
## Directory Layout
```
docs/help/
├── README.md # This blueprint
├── templates/ # Authoring templates per locale
├── guest/ # Guest-focused articles (paired locales)
│ ├── index.en.md
│ └── index.de.md
└── admin/ # Customer admin articles (paired locales)
├── index.en.md
└── index.de.md
├── README.md
├── en/
│ ├── admin/
│ ├── index.md
│ └──
│ ├── guest/
│ │ ├── index.md
│ │ └──
│ └── templates/
│ └── article.md
└── de/
├── admin/
├── guest/
└── templates/
```
- Articles live in the audience folder and follow the naming pattern `<slug>.<locale>.md` (e.g., `offline-sync.en.md`).
- Each article includes YAML front matter to describe metadata used by the app and Filament resource.
- English and German live under their respective locale folders, which keeps slugs unique for the Docusaurus build (`/help/en/admin/...`, `/help/de/guest/...`).
- Inside each locale folder, article filenames match their slug (e.g., `post-event-wrapup.md`). Paired locales keep the same slug and metadata.
- Every article includes the YAML front matter shown below so the apps and Filament know how to render status, owners, etc.
## Front Matter Contract
```yaml
@@ -72,7 +79,7 @@ related:
- **Standalone Web**: optional `/help` public site generated from the same Markdown using Vite/React and server-side rendering for SEO.
## Governance & Backlog
- Track work in `docs/todo/help.md` (create if needed) and link issues to article slugs.
- Track work in `docs/process/todo/help.md` (create if needed) and link issues to article slugs.
- Add CI checks:
- Ensure every `.en.md` file has a matching `.de.md` file with equal `slug`/`version_introduced`.
- Validate required front matter keys.

View File

@@ -27,7 +27,7 @@ related:
## Optionaler Follow-up
- Event als Vorlage duplizieren für zukünftige Produktionen.
- Erkenntnisse oder Verbesserungswünsche in `docs/todo/` festhalten.
- Erkenntnisse oder Verbesserungswünsche in `docs/process/todo/` festhalten.
### Weitere Hilfe
Wende dich an success@fotospiel.app oder konsultiere die Legal-Pages-Ressource für Compliance-Formulierungen.

View File

@@ -28,7 +28,7 @@ Direkt nach dem Zugriff auf einen neuen Kunden oder wenn neue Mitarbeitende eing
## Best Practices
- Mindestens zwei Owner-Rollen für Redundanz halten.
- Branding oder Automationen zuerst im Staging-Kundenkonto testen.
- Einladungen im Änderungslog (`docs/changes/`) dokumentieren.
- Einladungen im Änderungslog (`docs/process/changes/`) dokumentieren.
### Weitere Hilfe
Siehe `event-prep-checklist` für Event-Vorbereitung oder kontaktiere cx-team@fotospiel.app für Onboarding-Support.

View File

@@ -31,7 +31,7 @@ related:
- **Vor Ort**: Event-Personal ansprechen; sie eskalieren über die Admin-App.
## Antwortzeiten
- Kritische Probleme (Uploads für gesamtes Event gestört): <15 Minuten.
- Kritische Probleme (Uploads für gesamtes Event gestört): &lt;15 Minuten.
- Individuelle Lösch- oder Datenschutzanfragen: innerhalb von 48 Stunden.
### Weitere Hilfe

View File

@@ -27,7 +27,7 @@ related:
## Optional follow-up
- Duplicate the event as a template for future productions.
- Update `docs/todo/` with learnings or improvements for the product team.
- Update `docs/process/todo/` with learnings or improvements for the product team.
### Need more help?
Reach success@fotospiel.app or consult the Legal Pages resource for compliance wording.

View File

@@ -28,7 +28,7 @@ Right after receiving access to a new customer account or when onboarding new st
## Best practices
- Keep at least two Owner-level accounts for redundancy.
- Use the staging customer account to test branding or automation before touching production.
- Document invitations in the change log (`docs/changes/`).
- Document invitations in the change log (`docs/process/changes/`).
### Need more help?
See `event-prep-checklist` for event-level prep or contact cx-team@fotospiel.app for onboarding assistance.

View File

@@ -31,7 +31,7 @@ related:
- **On-site**: Ask the event staff to escalate via the customer admin app.
## Response times
- Critical issues (uploads failing for entire event): <15 minutes.
- Critical issues (uploads failing for entire event): &lt;15 minutes.
- Individual deletion or privacy questions: within 48 hours.
### Need more help?

View File

@@ -108,7 +108,7 @@ Der Anbieter ist berechtigt, Inhalte oder Konten zu löschen oder zu sperren, we
## 14. Streitbeilegung und Gerichtsstand
1. Es gilt deutsches Recht unter Ausschluss des UN-Kaufrechts.
2. Gerichtsstand für Kaufleute ist Neustadt-Glewe.
3. Die EU-Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: <https://ec.europa.eu/consumers/odr>
3. Die EU-Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: [https://ec.europa.eu/consumers/odr](https://ec.europa.eu/consumers/odr)
Der Anbieter ist nicht verpflichtet und nicht bereit, an einem Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.
---

View File

@@ -109,7 +109,7 @@ If the Customer does not object within one month, the new terms are deemed accep
## 14. Dispute Resolution and Jurisdiction
1. German law applies, excluding the UN Convention on Contracts for the International Sale of Goods.
2. For merchants, the place of jurisdiction is Neustadt-Glewe, Germany.
3. The EU Commission provides an online dispute resolution (ODR) platform: <https://ec.europa.eu/consumers/odr>
3. The EU Commission provides an online dispute resolution (ODR) platform: [https://ec.europa.eu/consumers/odr](https://ec.europa.eu/consumers/odr)
The Provider is neither obliged nor willing to participate in consumer arbitration proceedings.
---

13
docs/ops/README.md Normal file
View File

@@ -0,0 +1,13 @@
# Operations Hub
This section consolidates everything platform operators need: deployment guides, worker management, storage specs, photobooth ingest, and response playbooks. Use it as the entry point when publishing the docs site.
## Structure
- `deployment/` — Docker, Dokploy, and incident playbooks previously under `docs/deployment/`.
- `photobooth/` — FTP ingest service docs and ops playbooks.
- `media-storage-spec.md` — Upload/archival flow overview.
- `guest-notification-ops.md` — Push notification queue monitoring.
- `queue-workers.md` — Worker container instructions referencing scripts in `/docs/queue-supervisor/`.
Future additions (e.g., escalations, on-call checklists, Terraform notes) should live here as well so all ops content remains in one location.

View File

@@ -2,7 +2,7 @@
This guide describes the recommended, repeatable way to run the Fotospiel platform in Docker for production or high-fidelity staging environments. It pairs a multi-stage build (PHP-FPM + asset pipeline) with a Compose stack that includes Nginx, worker processes, Redis, and MySQL.
> **Dokploy users:** see `docs/deployment/dokploy.md` for service definitions, secrets, and how to wire the same containers (web, queue, scheduler, vsftpd) inside Dokploy. That document builds on the base Docker instructions below.
> **Dokploy users:** see `docs/ops/deployment/dokploy.md` for service definitions, secrets, and how to wire the same containers (web, queue, scheduler, vsftpd) inside Dokploy. That document builds on the base Docker instructions below.
## 1. Prerequisites
@@ -117,4 +117,11 @@ Because the app image keeps the authoritative copy of the code, each container r
- Configure backups for the `storage` directories and database dumps.
- Hook into your observability stack (e.g., ship container logs to Loki or ELK).
## 9. Internal docs publishing
- Build the static docs site locally or in CI via `./scripts/build-docs-site.sh` (runs `npm ci && npm run build` in `docs/site/` and outputs to `docs/site/build`).
- The Nginx container mounts that build directory and serves it at `/internal-docs/`. History fallback is already configured in `docker/nginx/default.conf`.
- Access is protected with HTTP Basic Auth. Update `docker/nginx/.htpasswd-docs` with real credentials (`htpasswd -c docker/nginx/.htpasswd-docs <user>`). The repo ships with a weak default for local use—never reuse it in production.
- Optionally run the build script inside your CI pipeline before deploying so the static output is always up to date. A typical GitHub Actions job would simply call the script after checkout and upload `docs/site/build` as an artifact or rsync it to the server.
With the provided configuration you can bootstrap a consistent Docker-based deployment across environments while keeping queue workers, migrations, and asset builds manageable. Adjust service definitions as needed for staging vs. production.

View File

@@ -120,3 +120,10 @@ Only SuperAdmins should have access to these widgets. If you rotate the API key,
7. Webhooks/alerts configured for failed deployments or unhealthy containers.
With this setup the Fotospiel team can manage deployments, restarts, and metrics centrally through Dokploy while Laravels scheduler and workers continue to run within the same infrastructure.
## 8. Internal docs publishing in Dokploy
- Build the static docs site during CI/CD by running `./scripts/build-docs-site.sh`. Upload the resulting `docs/site/build` directory as part of the deployment artifact or copy it into the Dokploy server before redeploying the stack.
- `docker-compose.dokploy.yml` mounts that directory into the Nginx container and exposes it at `/internal-docs/`, protected by HTTP Basic Auth.
- Update `docker/nginx/.htpasswd-docs` with production credentials (use `htpasswd -c docker/nginx/.htpasswd-docs <user>` locally, then redeploy). The repository ships with a weak default only for development—always override it in Dokploy.
- If Dokploy builds the image itself, add a post-build hook or automation step that runs the docs build and copies it into the shared storage volume before restarting the `web` service.

View File

@@ -3,11 +3,10 @@
| Service | URL / Port | Notes |
|----------------|--------------------------------|-------|
| Laravel app | http://localhost:8000 | Default web UI; Horizon dashboard at /horizon if laravel/horizon is installed. |
| Vite dev server| http://localhost:5173 | Hot module reload for the marketing/guest frontend.
|
| Vite dev server| http://localhost:5173 | Hot module reload for the marketing/guest frontend. |
| Mailpit UI | http://localhost:8025 | No auth; SMTP listening on port 1025. |
| Grafana | http://localhost:3000 | Anonymous admin already enabled; dashboards will show Loki logs once you add Loki as a data source (URL http://loki:3100). |
| Loki API | http://localhost:3100 | Used by Grafana/Promtail; direct browsing usually not needed. |
| Portainer | https://localhost:9443 | First visit prompts you to set an admin password; point it to /var/run/docker.sock (already mounted from ${PODMAN_SOCKET}). |
| Portainer | https://localhost:9443 | First visit prompts you to set an admin password; point it to /var/run/docker.sock (already mounted from the `PODMAN_SOCKET` path). |
| Redis | Bound to localhost:6379 | Matches QUEUE_CONNECTION=redis. |
| Promtail | Internal only (port 9080) | Tails storage/logs and pushes to Loki. |
| Promtail | Internal only (port 9080) | Tails storage/logs and pushes to Loki. |

View File

@@ -64,5 +64,4 @@ After enabling push:
2. Trigger a host broadcast or upload-queue alert; confirm the browser shows a native notification and that the notification drawer refreshes without polling.
3. Temporarily stop the upload workers to create ≥5 pending assets; re-run `storage:check-upload-queues` and verify guests receive the “Uploads werden noch verarbeitet …” message.
Document any deviations in `docs/changes/` for future regressions.
Document any deviations in `docs/process/changes/` for future regressions.

View File

@@ -0,0 +1,99 @@
# Media Storage Ops Specification
## Purpose
This document explains how customer photo uploads move through the Fotospiel platform, which services handle each stage, and what operators must monitor. It complements the PRP storage chapters by focusing on day-to-day operational expectations.
## High-Level Flow
1. **Upload (web / API containers)**
- Guest/Tenant requests hit the Laravel app container.
- `EventStorageManager::getHotDiskForEvent()` resolves the current *hot* storage target for the event (usually the local disk mounted at `/var/www/html/storage/app/private`).
- Controllers call `Storage::disk($hotDisk)->putFile("events/{event}/photos", $file)` and immediately write the original file plus a generated thumbnail to the hot disk.
- `EventMediaAsset` rows are created with `status = hot`, capturing disk key, relative path, checksum, size, and variant metadata.
- Every upload also dispatches `ProcessPhotoSecurityScan` to the `media-security` queue for antivirus and EXIF stripping.
2. **Metadata & accounting**
- `photos` table stores user-facing references (URLs, thumbnail URLs, guest metadata).
- The linked `event_media_assets` records keep canonical storage information and are used later for archival/restores.
- `media_storage_targets` and `event_storage_assignments` tables describe which disk key is considered “hot” vs “archive” per event.
3. **Asynchronous archival**
- The scheduler (either `php artisan schedule:work` container or host cron) runs `storage:archive-pending`.
- That command finds events whose galleries expired or were manually archived and dispatches `ArchiveEventMediaAssets` jobs onto the `media-storage` queue.
- `ArchiveEventMediaAssets` streams each file from the current disk to the archive disk resolved by `EventStorageManager::getArchiveDiskForEvent()`, updates the asset row to `status = archived`, and optionally deletes the hot copy.
- Archive storage usually maps to an object store bucket using the prefix `tenants/{tenant_uuid}/events/{event_uuid}/photos/{photo_uuid}/` (see `docs/prp/10-storage-media-pipeline.md`).
4. **Cold storage / restore**
- When an event is re-opened or media needs to be rehydrated, jobs mark affected assets `restoring` and copy them back to a hot disk.
- Restores reuse the same `event_media_assets` bookkeeping so URLs and permissions stay consistent.
## Container & Service Responsibilities
| Component | Role |
| --- | --- |
| `app` (Laravel FPM) | Accepts uploads, writes to the hot disk, and records metadata. |
| `media-storage-worker` | Runs `/docs/queue-supervisor/queue-worker.sh media-storage`; consumes archival/restoration jobs and copies data between disks. Shares the same `app-code` volume so it sees `/var/www/html/storage`. |
| `queue` workers | Default queue consumers for non-storage background jobs. |
| `media-security-worker` | Processes `ProcessPhotoSecurityScan` jobs (antivirus + EXIF scrub). |
| `scheduler` | Runs `php artisan schedule:work`, triggering `storage:archive-pending`, `storage:monitor`, queue health checks, etc. |
| `horizon` | Optional dashboard / supervisor for queue throughput. |
| Redis | Queue backend for all worker containers. |
## Key Commands & Jobs
| Command / Job | Description |
| --- | --- |
| `storage:archive-pending` | Scans for expired/archived events and dispatches `ArchiveEventMediaAssets` jobs (`media-storage` queue). |
| `storage:monitor` | Aggregates capacity/queue stats and emails alerts when thresholds are exceeded (`config/storage-monitor.php`). |
| `media:backfill-thumbnails` | Regenerates thumbnails for existing assets; useful before enabling archival on migrated data. |
| `ProcessPhotoSecurityScan` | Runs antivirus/EXIF stripping for a photo; default queue is `media-security`. |
| `ArchiveEventMediaAssets` | Copies hot assets to the archive disk, updates statuses, and deletes hot copies if configured. |
## Configuration Reference
| Setting | Location | Notes |
| --- | --- | --- |
| Default disk | `.env` `FILESYSTEM_DISK` & `config/filesystems.php` | Hot uploads default to `local` (`/var/www/html/storage/app/private`). |
| Storage targets | `media_storage_targets` table | Each row stores `key`, `driver`, and JSON config; `EventStorageManager` registers them as runtime disks. |
| Security queue | `.env SECURITY_SCAN_QUEUE` & `config/security.php` | Defaults to `media-security`. |
| Archive scheduling | `config/storage-monitor.php['archive']` | Controls grace days, chunk size, locking, and dispatch caps. |
| Queue health alerts | `config/storage-monitor.php['queue_health']` | Warning/critical thresholds for `media-storage` and `media-security` queues. |
| Container volumes | `docker-compose.yml` | `app`, workers, and scheduler share the `app-code` volume so `/var/www/html/storage` is common. |
## Operational Checklist
- **Before enabling archival in a new environment**
- Seed storage targets: `php artisan db:seed --class=MediaStorageTargetSeeder`.
- Run migrations so `event_media_assets` and related tables exist.
- Backfill existing photos into `event_media_assets` (custom script or artisan command) so archival jobs know about historical files.
- **Monitoring**
- Watch `storage:monitor` output (email or logs) for capacity warnings on hot disks.
- Use Horizon or Redis metrics to verify `media-storage` queue depth; thresholds live in `config/storage-monitor.php`.
- Review `/var/www/html/storage/logs/storage-jobs.log` (if configured) for archival failures.
- Ensure `media-security` queue stays below critical thresholds so uploads arent blocked awaiting security scans.
- **Troubleshooting uploads**
- Confirm hot disk is mounted and writable (`/var/www/html/storage/app/private/events/...`).
- Verify `media_storage_targets` contains an active `is_hot=true` entry; `EventStorageManager` falls back to default disk if none is assigned.
- Check Redis queue lengths; stalled `media-security` jobs prevent photos from being marked clean.
- **Troubleshooting archival**
- Run `php artisan storage:archive-pending --event=<ID> --force` to enqueue a specific event.
- Tail `docker compose logs -f media-storage-worker` for copy failures.
- Verify archive disk credentials (e.g., S3 keys) via `media_storage_targets.config`; missing or invalid settings surface as job failures with `status=failed`.
- If hot copies must remain, dispatch `ArchiveEventMediaAssets` with `$deleteSource=false` (custom job call).
- **Restoring media**
- Assign a hot storage target if none is active (`EventStorageManager::ensureAssignment($event)`).
- Dispatch a restore job or manually copy assets back from the archive disk, updating `event_media_assets.status` to `hot` or `restoring`.
## Related Documentation
- `docs/prp/10-storage-media-pipeline.md` — canonical architecture diagram for storage tiers.
- `docs/ops/queue-workers.md` — how to run `media-storage` and `media-security` workers (scripts in `/docs/queue-supervisor/`).
- `docs/ops/deployment/docker.md` / `docs/ops/deployment/dokploy.md` — container topology and volumes.
- `config/security.php`, `config/storage-monitor.php`, and `config/filesystems.php` for runtime knobs.
Keep this spec updated whenever the storage pipeline, queue names, or archive policies change so ops can quickly understand the flow end-to-end.

View File

@@ -50,4 +50,4 @@ php artisan photobooth:cleanup-expired
4. **Audit** review `photobooth_metadata` on events and `photos.ingest_source`.
5. **Communicate** notify tenant admins via in-app message or email template referencing incident ID.
Keep this playbook updated whenever infra/process changes. PRs to `/docs/photobooth_ftp` welcome.
Keep this playbook updated whenever infra/process changes. PRs to `/docs/ops/photobooth` welcome.

View File

@@ -1,6 +1,6 @@
## Docker Queue & Horizon Setup
This directory bundles ready-to-use entrypoint scripts and deployment notes for running Fotospiels queue workers inside Docker containers. The examples assume you already run the main application in Docker (e.g. via `docker-compose.yml`) and share the same application image for workers.
This directory bundles ready-to-use entrypoint scripts and deployment notes for running Fotospiels queue workers inside Docker containers. The examples assume you already run the main application in Docker (e.g. via `docker-compose.yml`) and share the same application image for workers. The shell scripts referenced below remain under `/docs/queue-supervisor/` so existing Dockerfile references stay valid.
### 1. Prepare the application image

18
docs/process/README.md Normal file
View File

@@ -0,0 +1,18 @@
# Process & Planning Hub
This directory centralizes living planning artefacts that previously sat in scattered folders.
## Structure
- `changes/` — dated session logs, retro notes, and ad-hoc findings. Drop a new Markdown file per session (`YYYY-MM-DD-topic.md`) and reference it from epics when relevant.
- `todo/` — backlog of active epics or initiatives. Each file focuses on a single problem space (e.g. `security-hardening-epic.md`) and contains goals, status, and actionable checklists.
- `roadmap.md` — top-level view that summarizes what is in progress, what is queued up next, and which epics have been completed recently.
## Workflow
1. After every planning or incident-review session, capture the outcome in `changes/`.
2. Update the relevant `todo/*.md` files with new tasks, decisions, or links back to change logs.
3. Keep `roadmap.md` in sync with the current quarters priorities so product/ops can scan it quickly.
4. When an initiative is completed and no longer useful operationally, move its Markdown file to `docs/archive/` to keep the active backlog lightweight.
> Tip: Link to concrete specs (PRP chapters, ops runbooks) from each epic so contributors know where implementation details live.

View File

@@ -2,7 +2,7 @@
Summary
- Split PRP into docs/prp/* and added addendum + ADR for Tenant Admin PWA.
- Guest PWA: routes/components scaffold, bottom nav, header + settings sheet, theme toggle; polling hooks; upload (client compress <1.5 MB), offline queue + BG sync; gallery filters + lightbox; likes API/UI; SW runtime cache.
- Guest PWA: routes/components scaffold, bottom nav, header + settings sheet, theme toggle; polling hooks; upload (client compress &lt;1.5 MB), offline queue + BG sync; gallery filters + lightbox; likes API/UI; SW runtime cache.
- Super Admin (Filament 4): resources for Tenants, Events, Photos, Legal Pages; dashboard widgets; photo moderation; event toggle; join link + selfhosted QR.
- CSV imports + templates for Emotions and Tasks with de/en localization; forms updated to JSON casts.
- Backend public API: stats/photos with ETag/304; upload endpoint; photo like.

View File

@@ -1,7 +1,7 @@
# Registrierungs-Fixes: Redirect, Error-Clearing und Role-Handling (2025-10-02)
## Problem-Beschreibung
- **Redirect-Fehler**: Bei erfolgreicher Registrierung (free oder paid Package) wurde onSuccess in Register.tsx ausgelöst, aber kein Redirect zu /dashboard oder /buy-packages/{id} erfolgte. Ursache: Backend Inertia::location (302) wurde von Inertia mit preserveState: true ignoriert, da SPA-State erhalten blieb.
- **Redirect-Fehler**: Bei erfolgreicher Registrierung (free oder paid Package) wurde onSuccess in Register.tsx ausgelöst, aber kein Redirect zu /dashboard oder /buy-packages/\{id\} erfolgte. Ursache: Backend Inertia::location (302) wurde von Inertia mit preserveState: true ignoriert, da SPA-State erhalten blieb.
- **Persistente Errors**: Server-Errors (z.B. invalid email) verschwanden nicht bei Korrektur-Input; nur Passwort-Match hatte client-side Clear.
- **Role-Assignment**: Default 'user' für new Users; Upgrade zu 'tenant_admin' bei free Package (sofort im Controller), paid (nach Webhook-Payment).
- **Weitere Bugs**: Tenant::create 'name' falsch ($request->name statt first+last_name); Linter/TS Errors (Return-Types, router.visit unknown).
@@ -14,9 +14,9 @@
- **Return-Type**: store() zu JsonResponse (Zeile 44); use JsonResponse hinzugefügt (Zeile 22).
### Frontend (Register.tsx)
- **onSuccess-Handling**: Prüfe page.props.success && router.visit(page.props.redirect as string) (Zeile 66-68); Fallback zu data.package_id ? `/buy-packages/${data.package_id}` : '/dashboard' (Zeile 71-75); console.log für Debug (Zeile 67, 74).
- **onSuccess-Handling**: Prüfe page.props.success && router.visit(page.props.redirect as string) (Zeile 66-68); Fallback zu data.package_id ? `` `/buy-packages/\${data.package_id}` `` : '/dashboard' (Zeile 71-75); console.log für Debug (Zeile 67, 74).
- **Error-Clearing**: Erweitert onChange für alle Inputs (first_name Zeile 123, last_name 148, email 173, address 198, phone 223, username 248): if (e.target.value.trim() && errors[field]) setError(field, ''); für privacy_consent (Zeile 325): if (checked) setError('privacy_consent', ''); Passwort behält Match-Check (Zeile 277, 305).
- **General Errors Key**: <div key={`general-errors-${Object.keys(errors).join('-')}`} (Zeile 347) für Re-Render bei Error-Änderungen.
- **General Errors Key**: nutze `&lt;div key={"general-errors-" + Object.keys(errors).join('-')}&gt;` (Zeile 347) für Re-Render bei Error-Änderungen.
### Tests (RegistrationTest.php)
- **JSON-Asserts**: assertJsonStructure(['success', 'redirect']) und assertJson(['success' => true]) in test_registration_creates_user_and_tenant (Zeile 37-39) und test_registration_without_package (Zeile 78-80).
@@ -37,4 +37,4 @@
- Error-Clearing: Client-side onChange clear für UX (non-empty Input); Keys für conditional Elements (Re-Render).
- GDPR: Privacy-Consent required; no PII in Logs.
Date: 2025-10-02
Date: 2025-10-02

View File

@@ -21,7 +21,7 @@
### Quality & Rollout
- [x] Expand automated coverage: Playwright end-to-end scenarios for auth, payment success/failure, Google login; PHPUnit and webhook tests for new checkout endpoints. *(Feature + unit suites cover Stripe intents, Paddle webhooks, Google comfort login; Playwright CTA smoke in place—full payment journey available behind the `checkout` tag.)*
- [x] Update docs (PRP, docs/changes) and plan a feature-flag rollout for the new wizard.
- [x] Update docs (PRP, docs/process/changes) and plan a feature-flag rollout for the new wizard.
## Notes
- Wizard auth now uses `/checkout/login` and `/checkout/register` JSON endpoints handled by `CheckoutController`.

31
docs/process/roadmap.md Normal file
View File

@@ -0,0 +1,31 @@
# Product & Ops Roadmap
_Last updated: 2025-10-25_
This high-level view connects the active epics in `docs/process/todo/` so stakeholders can scan what is shipping now versus what is queued next. Detailed checklists live in the linked TODO files, and day-to-day notes go into `docs/process/changes/`.
-## Now (Q4 2025)
-
- **Security Hardening Epic** (`docs/process/todo/security-hardening-epic.md`)
Rolling out dual-key auth, hashed join tokens, signed asset URLs, streaming uploads, and webhook hardening. Tracks six workstreams (identity, tokens, API resilience, media services, billing, frontend/CSP).
- **Streaming Upload Refactor** (`docs/process/todo/media-streaming-upload-refactor.md`)
Designing the chunked upload/session pipeline (SEC-MS-02) to lift photo size caps and improve reliability before updating the guest PWA.
- **Paddle Billing Migration & Catalog Sync**
- Platform migration plan tracked in `docs/process/todo/paddle-migration.md` (env/config, service layer, admin billing).
- Catalog synchronization and Filament UX tracked in `docs/process/todo/paddle-catalog-sync.md`.
## Next Up (Q1 2026)
- **Localized SEO & Hreflang Strategy** (`docs/process/todo/localized-seo-hreflang-strategy.md`)
Route-prefix migration, hreflang/canonical cleanup, and sitemap realignment for the marketing site and checkout.
- **Paddle Customer Success Metrics** (spin-off from the migration tasks)
Finalize tenant ↔ Paddle sync, sandbox catalog seeding, and rollout/rollback procedures before GA. Captured in the remaining unchecked items of the Paddle TODO files.
## Recently Completed / Monitoring
- **Tenant Admin Onboarding Fusion** (`docs/archive/process/todo/tenant-admin-onboarding-fusion.md`) — Flow merged into the new PWA/TWA stack; keep monitoring localization coverage and checkout UX alignment.
- **Event Join Token Hardening** (`docs/archive/process/todo/event-join-token-hardening.md`) — All phases completed; continue monitoring default lifetime/rotation decisions.
- **Package Limit Experience Overhaul** (`docs/archive/process/todo/package-limit-experience-overhaul.md`) — Foundation live; alerts/reporting improvements now handled opportunistically.
- **Checkout Refactor & Package Limits** (`docs/process/changes/2025-10-05-checkout-refactor-todo.md`) — keep feature flags and alert thresholds under review but no net-new roadmap investment needed right now.
For historical roadmaps predating the split PRP, see `docs/archive/implementation-roadmap.md`. New initiatives should always start as a `docs/process/todo/*.md` file and be referenced here once prioritized.

View File

@@ -29,7 +29,7 @@ Raise the baseline security posture across guest APIs, checkout, media storage,
- Add synthetic monitors for `/api/v1/gallery/*` and upload endpoints.
- **Tickets**
- `SEC-API-01` — Signed URL middleware + asset migration (Week 1).
- `SEC-API-02` — Incident response playbook draft + review (Week 2). *(Runbook: `docs/deployment/public-api-incident-playbook.md`, added 2025-10-23)*
- `SEC-API-02` — Incident response playbook draft + review (Week 2). *(Runbook: `docs/ops/deployment/public-api-incident-playbook.md`, added 2025-10-23)*
- `SEC-API-03` — Synthetic monitoring + alert config (Week 3).
4. **Media Pipeline & Storage (Media Services)**
@@ -38,7 +38,7 @@ Raise the baseline security posture across guest APIs, checkout, media storage,
- Surface storage target health (capacity, latency) in Super Admin dashboards.
- **Tickets**
- `SEC-MS-01` — AV + EXIF scrubber worker integration (Week 1). *(Job: `ProcessPhotoSecurityScan`, queue: `media-security`)*
- `SEC-MS-02` — Streaming upload refactor + tests (Week 2). *(Requirements draft: `docs/todo/media-streaming-upload-refactor.md`, 2025-10-23)*
- `SEC-MS-02` — Streaming upload refactor + tests (Week 2). *(Requirements draft: `docs/process/todo/media-streaming-upload-refactor.md`, 2025-10-23)*
- `SEC-MS-03` — Checksum validation + alert thresholds (Week 3).
- `SEC-MS-04` — Storage health widget in Super Admin (Week 4).

View File

@@ -81,7 +81,7 @@ apps/guest-pwa/
usePollStats.ts // polls /events/:slug/stats every 10s
usePollGalleryDelta.ts // polls /events/:slug/photos?since=...
i18n/
config.ts // i18next init with react-i18next, backend loadPath '/lang/{{lng}}/guest.json'
config.ts // i18next init with react-i18next, backend loadPath '/lang/\{\{lng\}\}/guest.json'
de.json // Namespace: guest (e.g., { "gallery": { "title": "Galerie" } })
en.json
main.tsx
@@ -146,7 +146,7 @@ State & Data
- TanStack Query for server data (events, photos); optimistic updates for likes.
- Zustand store for local-only state (profile, queue, banners).
- IndexedDB for upload queue; CacheStorage for shell/assets.
- i18n: react-i18next; load 'guest' namespace JSON from /lang/{locale}/guest.json; path-based detection for /de/e/:slug, /en/e/:slug; useTranslation('guest') in components.
- i18n: react-i18next; load 'guest' namespace JSON from /lang/\{locale\}/guest.json; path-based detection for /de/e/:slug, /en/e/:slug; useTranslation('guest') in components.
- Polling: focus-aware intervals (10s stats, 30s gallery); use document visibility to pause; backoff on failures.
Accessibility & Performance

View File

@@ -32,14 +32,14 @@ Core Features
- 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).
- Public share: host can hand out `https://app.domain/g/{token}`; guests see a themed, read-only gallery with per-photo downloads.
- Public share: host can hand out `https://app.domain/g/\{token\}`; guests see a themed, read-only gallery with per-photo downloads.
- Banner on gallery header highlights approaching expiry (D-7/D-1) and offers CTA to upload remaining shots before the deadline.
- Safety & abuse controls
- Rate limits per device and IP; content-length checks; mime/type sniffing.
- Upload moderation state: pending → approved/hidden; show local status.
- Notification Center
- Header bell opens a drawer that merges upload queue stats with server-driven notifications (photo highlights, major achievements, host broadcasts, upload failure hints, feedback reminders).
- Data fetched from `/api/v1/events/{token}/notifications` with `X-Device-Id` for per-device read receipts; guests can mark items as read/dismissed and follow CTAs (internal routes or external links).
- Data fetched from `/api/v1/events/\{token\}/notifications` with `X-Device-Id` for per-device read receipts; guests can mark items as read/dismissed and follow CTAs (internal routes or external links).
- Pull-to-refresh + background poll every 90s to keep single-day events reactive without WS infrastructure.
- When push is available (VAPID keys configured) the drawer surfaces a push toggle, persists subscriptions via `/push-subscriptions`, and the service worker refreshes notifications after every push message.
- Operations playbook: see `docs/ops/guest-notification-ops.md` for enabling push, required queues, and cron health checks.
@@ -71,7 +71,7 @@ Core Pages (Pflichtseiten)
- Behavior: Nicht verpflichtend, aber empfohlen; Name kann jederzeit im Settings Sheet angepasst oder geloescht werden.
- Startseite (Home/Feed)
- 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}!".
- 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
@@ -93,20 +93,20 @@ Technical Notes
- Storage: IndexedDB for queue + cache; `CacheStorage` for shell/assets.
- 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: react-i18next with JSON files (`public/lang/{locale}/guest.json`); default `de`, fallback `en`; path-based detection (/de/, /en/); RTL not in MVP. Strings for UI (e.g., gallery, upload, tasks) extracted via i18next-scanner; integrate with prefixed routing and middleware.
- i18n: react-i18next with JSON files (`public/lang/\{locale\}/guest.json`); default `de`, fallback `en`; path-based detection (/de/, /en/); RTL not in MVP. Strings for UI (e.g., gallery, upload, tasks) extracted via i18next-scanner; integrate with prefixed routing and middleware.
- 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/{token}` — public event metadata (when open) + theme.
- GET `/api/v1/events/{token}/photos` — paginated gallery (approved only).
- POST `/api/v1/events/{token}/photos` — signed upload initiation; returns URL + fields.
- GET `/api/v1/events/\{token\}` — public event metadata (when open) + theme.
- GET `/api/v1/events/\{token\}/photos` — paginated gallery (approved only).
- POST `/api/v1/events/\{token\}/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/{token}/notifications` — list guest notifications (requires `X-Device-Id`).
- POST `/api/v1/events/{token}/notifications/{notification}/read|dismiss` — mark/dismiss notification with device identity.
- POST `/api/v1/events/{token}/push-subscriptions` — register a browser push subscription (requires `X-Device-Id` + VAPID public key).
- DELETE `/api/v1/events/{token}/push-subscriptions` — revoke a stored push subscription by endpoint.
- POST `/api/v1/photos/\{id\}/like` — idempotent like with device token.
- GET `/api/v1/events/\{token\}/notifications` — list guest notifications (requires `X-Device-Id`).
- POST `/api/v1/events/\{token\}/notifications/\{notification\}/read|dismiss` — mark/dismiss notification with device identity.
- POST `/api/v1/events/\{token\}/push-subscriptions` — register a browser push subscription (requires `X-Device-Id` + VAPID public key).
- DELETE `/api/v1/events/\{token\}/push-subscriptions` — revoke a stored push subscription by endpoint.
Limits (MVP defaults)
- Max uploads per device per event: 50

View File

@@ -14,7 +14,7 @@
- **Guest Join Tokens***Owner: Guest Platform*
Hash stored join tokens, add anomaly metrics (usage spikes, stale tokens), and tighten gallery/photo rate limits with visibility in storage dashboards. Join-token access is now logged to `event_join_token_events` with summaries surfaced in the Event admin modal.
- **Public API Resilience***Owner: Core API*
Ensure gallery/download endpoints serve signed URLs, expand abuse throttles (token + IP), and document incident response runbooks in ops guides. See `docs/deployment/public-api-incident-playbook.md` for the response checklist.
Ensure gallery/download endpoints serve signed URLs, expand abuse throttles (token + IP), and document incident response runbooks in ops guides. See `docs/ops/deployment/public-api-incident-playbook.md` for the response checklist.
- **Media Pipeline & Storage***Owner: Media Services*
Introduce antivirus + EXIF scrubbing workers, stream uploads to disk to avoid buffering, and enforce checksum verification during hot→archive transfers with configurable alerts from `StorageHealthService`.
- Queue `media-security` (job: `ProcessPhotoSecurityScan`) performs antivirus + EXIF sanitisation per upload; configure via `config/security.php`.
@@ -23,4 +23,4 @@
- **Frontend & CSP***Owner: Marketing Frontend*
Replace unsafe-inline allowances (Stripe/Matomo) with nonce or hashed CSP rules, gate analytics injection behind consent, and localise cookie-banner copy that discloses data sharing.
Progress updates belong in `docs/changes/` and roadmap status in `docs/implementation-roadmap.md`.
Progress updates belong in `docs/process/changes/` and roadmap status in `docs/process/roadmap.md`.

View File

@@ -13,36 +13,36 @@
## Backend (Laravel/PHP)
- **Config**: `config/app.php` `locale => 'de'`, `fallback_locale => 'en'`, `available_locales => ['de', 'en']`.
- **Translation Files**:
- PHP arrays: `resources/lang/{locale}/{group}.php` (e.g., `marketing.php`, `auth.php`, `legal.php`) for Blade and API responses.
- JSON for PWAs: `public/lang/{locale}/{namespace}.json` (e.g., `public/lang/de/marketing.json`) migrated from PHP where possible; loaded via dedicated route.
- PHP arrays: `resources/lang/\{locale\}/\{group\}.php` (e.g., `marketing.php`, `auth.php`, `legal.php`) for Blade and API responses.
- JSON for PWAs: `public/lang/\{locale\}/\{namespace\}.json` (e.g., `public/lang/de/marketing.json`) migrated from PHP where possible; loaded via dedicated route.
- **Routing**:
- Prefixed groups: `Route::prefix('{locale?}')->where(['locale' => 'de|en'])->middleware('SetLocale')` in `routes/web.php`.
- Fallbacks: Non-prefixed routes redirect to `/de/{path}` (e.g., `/login``/de/login`).
- Prefixed groups: `Route::prefix('\{locale?\}')->where(['locale' => 'de|en'])->middleware('SetLocale')` in `routes/web.php`.
- Fallbacks: Non-prefixed routes redirect to `/de/\{path\}` (e.g., `/login``/de/login`).
- Auth routes (login, register, logout): Prefixed and named (e.g., `Route::get('/login', ...)->name('login')`).
- API routes: Locale from header/session; no URL prefix for `/api/v1`.
- **Middleware**: `SetLocale` Extracts locale from URL segment(1), sets `App::setLocale()`, stores in session; defaults to 'de'.
- **JSON Loader Route**: `Route::get('/lang/{locale}/{namespace}.json', ...)` Serves from `public_path('lang/{locale}/{namespace}.json')`; Vite proxy forwards requests.
- **JSON Loader Route**: `Route::get('/lang/\{locale\}/\{namespace\}.json', ...)` Serves from `public_path('lang/\{locale\}/\{namespace\}.json')`; Vite proxy forwards requests.
- **DB Translations**: Use JSON fields with spatie/laravel-translatable or native casts; admin UI (Filament) for editing per locale.
- **Legal Pages**: Dynamic via LegalPage model; rendered with `__($key)` or JSON for PWAs.
## Frontend (React/Vite PWAs)
- **Library**: react-i18next with i18next-http-backend for async JSON loads.
- **Setup** (`resources/js/i18n.js`):
- Init: `i18n.use(Backend).use(LanguageDetector).init({ lng: 'de', fallbackLng: 'en', ns: ['marketing', 'auth'], backend: { loadPath: '/lang/{{lng}}/{{ns}}.json' } })`.
- Init: `i18n.use(Backend).use(LanguageDetector).init({ lng: 'de', fallbackLng: 'en', ns: ['marketing', 'auth'], backend: { loadPath: '/lang/\{\{lng\}\}/\{\{ns\}\}.json' } })`.
- Detection: Path-based (`order: ['path']`, `lookupFromPathIndex: 0`) for prefixed URLs; cookie/session fallback.
- Provider: Wrap `<App>` in `<I18nextProvider i18n={i18n}>` in `app.tsx`.
- Provider: Wrap `&lt;App&gt;` in `&lt;I18nextProvider i18n=\{i18n\}&gt;` in `app.tsx`.
- **Usage**:
- Hook: `const { t } = useTranslation('namespace');` in components (e.g., `t('marketing.home.title')`).
- Interpolation: Placeholders `{count}`; pluralization via i18next rules.
- Interpolation: Placeholders `\{count\}`; pluralization via i18next rules.
- Dynamic Keys: Avoid; use namespaces for organization.
- **Inertia Integration**:
- Page Resolver: `resolvePageComponent(`./Pages/${name}.tsx`, import.meta.glob('./Pages/**/*.tsx'))` Matches capital 'Pages' directory.
- Page Resolver: `` resolvePageComponent(`./Pages/\${name}.tsx`, import.meta.glob('./Pages/**/*.tsx')) `` Matches capital 'Pages' directory.
- Props: Pass `locale` from middleware to pages.
- Links: `<Link href={`/${locale}/path`}>` for prefixed navigation (e.g., Header.tsx).
- Links: `&lt;Link href={`/${'{'}locale{'}'}/path`}&gt;` für prefixed navigation (e.g., Header.tsx).
- **Marketing Frontend**:
- Namespaces: `marketing` (Home, Packages, Blog, Features), `auth` (Login, Register).
- Components: All hard-coded strings replaced (e.g., Home.tsx: `t('marketing.hero.title')`); SEO meta via `Head` with `t()`.
- Header: Locale selector; dynamic links (e.g., `/${locale}/login` with `t('auth.header.login')`).
- Header: Locale selector; dynamic links (e.g., `/\{locale\}/login` with `t('auth.header.login')`).
- **Guest/Tenant PWAs**:
- Similar setup; load JSON on app init.
- Guest: Anonymous, locale from URL or default 'de'; strings for UI (e.g., gallery, upload).
@@ -53,15 +53,15 @@
## SEO & Accessibility
- **Multilingual URLs**: `/de/home`, `/en/home`; 301 redirects for non-prefixed.
- **Hreflang**: `<link rel="alternate" hreflang="de" href="/de/home">` in `<Head>`.
- **Canonical**: `<link rel="canonical" href={currentUrl}>` based on detected locale.
- **Hreflang**: `&lt;link rel="alternate" hreflang="de" href="/de/home"&gt;` in `&lt;Head&gt;`.
- **Canonical**: `&lt;link rel="canonical" href=\{currentUrl\}&gt;` based on detected locale.
- **Meta**: Translated via `t('seo.title')`; og:locale='de_DE'.
- **Sitemap**: Generate with `de/` and `en/` variants; update `public/sitemap.xml`.
- **Robots.txt**: Allow both locales; noindex for dev.
- **Accessibility**: ARIA labels with `t()`; screen reader support for language switches.
## Migration from PHP to JSON
- Extract keys from `resources/lang/{locale}/marketing.php` to `public/lang/{locale}/marketing.json`.
- Extract keys from `resources/lang/\{locale\}/marketing.php` to `public/lang/\{locale\}/marketing.json`.
- Consolidate: Remove duplicates; use nested objects (e.g., `{ "header": { "login": "Anmelden" } }`).
- Fallback: PHP arrays remain for backend; JSON for PWAs.

View File

@@ -14,7 +14,7 @@ This document details the Package-based business model for the Fotospiel tenant
- No freeloader problem
**Cons:**
- Lower acquisition (<5% download conversion)
- Lower acquisition (lower than 5% download conversion)
- High churn if first event doesn't impress
- Slower scaling, higher marketing costs per install (€2-5)
@@ -132,8 +132,8 @@ See 15-packages-design.md for updated schema: `packages`, `event_packages`, `ten
### Acquisition
- Downloads: 10k in first 3 months
- Cost per Install: <€1.50
- App Store Rating: >4.5 stars
- Cost per Install: less than €1.50
- App Store Rating: more than 4.5 stars
### Conversion
- Free → Paid: 5% within 30 days
@@ -141,13 +141,13 @@ See 15-packages-design.md for updated schema: `packages`, `event_packages`, `ten
- Average Order Value: €8-12
### Retention
- Day 1 Retention: >40%
- Day 7 Retention: >25%
- Day 1 Retention: more than 40%
- Day 7 Retention: more than 25%
- Monthly Active Users: 20% of downloads
### Revenue
- Month 1 Revenue: €1,000-2,000
- ARPU: €0.50-1.00 overall
- LTV >3x acquisition cost
- LTV more than 3x acquisition cost
This Freemium model balances user acquisition with sustainable revenue growth, leveraging the event-based nature of the app for recurring purchases while maintaining an accessible entry point.

View File

@@ -29,7 +29,7 @@ Ziel: Vollständige Migration zu Inertia.js für SPA-ähnliche Konsistenz, mit e
### 3. Page-Komponenten
- Alle Marketing-Seiten als React/TSX (resources/js/pages/marketing/*.tsx):
- z.B. Packages.tsx: Rendert Paket-Karten in Grid/Carousel (shadcn), mit Modal für Details/Upsell.
- Wrapper: In App.tsx oder router.tsx: if (route.startsWith('/marketing')) return <MarketingLayout><Page /></MarketingLayout>;
- Wrapper: In App.tsx oder router.tsx: if (route.startsWith('/marketing')) return `<MarketingLayout><Page /></MarketingLayout>`;
- Migration-Reihenfolge:
1. Statische Seiten (Home, Blog-Index): Von Blade zu Inertia.
2. Dynamische (Packages, Register): Props integrieren.

View File

@@ -21,21 +21,21 @@
- Dynamische Darstellung: Features map() aus JSON, partial Limits (max_photos, max_tenants).
- Hero-Section: Aurora-Gradient (bg-aurora-enhanced), Playfair Display Überschrift, Montserrat Text.
- Konsistentes Layout: MarketingLayout (Header mit Nav, Footer mit Legal-Links).
- CTA: Links zu /buy-packages/{id}, Hover-Transitions.
- CTA: Links zu /buy-packages/\{id\}, Hover-Transitions.
- Fonts: font-display (Playfair), font-sans-marketing (Montserrat).
### Offene/Mögliche Verbesserungen
1. **Multi-Step-Modal auf Card-Click**: Dialog (shadcn) mit Tabs (Step 1: Details + Social Proof/Testimonials (3 Cards mit Stars); Step 2: Upsell-Tabelle (shadcn Table, Spalten: Features/Limits, Zeilen: alle Packages, Highlight selected mit bg-[#FFB6C1]); Step 3: CTA (usePage().props.auth ? Link /buy-packages : /register?package_id, localStorage pre-fill für Name/Email)).
2. **Erweiterte Limits-Darstellung**: Vollständig in Cards (gallery_days, max_guests, max_tasks als <li>, watermark/branding als Badge/Check/X-Icons).
2. **Erweiterte Limits-Darstellung**: Vollständig in Cards (gallery_days, max_guests, max_tasks als &lt;li&gt;, watermark/branding als Badge/Check/X-Icons).
3. **UI-Enhancements**: Progress Bar (33/66/100% für Steps), Micro-Interactions (Card-Hover: scale-105/shadow-lg), FAQ-Section (Accordion mit 4 Fragen: Free-Paket, Upgrade, Reseller, Zahlung), Testimonials-Section (3 Cards mit Quotes/Ratings).
4. **Desktop Pricing Table**: Toggle-Button neben Grid (View: Table-Modus, Vergleichs-View mit Checkmarks für Features).
5. **Weitere**: A/B-Testing (CTAs), Accessibility (ARIA-Labels für Carousel/Modal, Keyboard-Nav), SEO (Head meta description pro Package), Performance (Lazy Testimonials), Integration (Track Clicks mit Analytics).
## Implementierungs-Plan (Code-Modus)
1. **Modal hinzufügen**: useState für open/selected/step; Dialog mit Tabs; Step 1: Details + Testimonials; Step 2: Table (alle Packages); Step 3: CTA (auth-check, pre-fill).
2. **Limits erweitern**: In Cards <li> für gallery_days/max_guests/max_tasks; Badges für watermark/branding.
2. **Limits erweitern**: In Cards &lt;li&gt; für gallery_days/max_guests/max_tasks; Badges für watermark/branding.
3. **UI-Verbesserungen**: Progress in Modal, Hover auf Cards, FAQ-Accordion, Testimonials-Section.
4. **Pricing Table**: useState für viewMode (Grid/Table); Table mit Check/X für Features.
5. **Test**: npm run build/dev; Browser: Card-Click → Modal-Steps, Tabelle-Vergleich, CTA-Redirect, Responsiveness.
Nach Umsetzung: Update PRP (docs/prp/15-packages-design.md mit UI-Details).
Nach Umsetzung: Update PRP (docs/prp/15-packages-design.md mit UI-Details).

View File

@@ -1,6 +1,6 @@
# 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>`.
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
@@ -30,7 +30,7 @@ Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Req
### Stats laden
- **GET /api/v1/tenant/dashboard**
- **Headers**: `Authorization: Bearer {token}`
- **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)
@@ -38,7 +38,7 @@ Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Req
### Events-Liste
- **GET /api/v1/tenant/events**
- **Headers**: `Authorization: Bearer {token}`
- **Headers**: `Authorization: Bearer \{token\}`
- **Params**:
- `page=1` (Pagination)
- `per_page=50` (max für Mobile)
@@ -49,32 +49,32 @@ Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Req
### Event erstellen
- **POST /api/v1/tenant/events**
- **Headers**: `Authorization: Bearer {token}`, `Content-Type: application/json`
- **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}`
- **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}`
- **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}`
- **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}`
- **GET /api/v1/tenant/events/\{event_id\}/photos**
- **Headers**: `Authorization: Bearer \{token\}`
- **Params**:
- `page=1`, `per_page=50`
- `status=pending|approved|rejected|featured`
@@ -84,34 +84,34 @@ Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Req
- **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`
- **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}`
- **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}`
- **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}`
- **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`
- **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
@@ -119,7 +119,7 @@ Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Req
### Tasks laden
- **GET /api/v1/tasks**
- **Headers**: `Authorization: Bearer {token}`
- **Headers**: `Authorization: Bearer \{token\}`
- **Params**:
- `global=true/false` (globale vs. tenant Tasks)
- `tenant_id=me` (nur eigene Tasks)
@@ -128,14 +128,14 @@ Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Req
- **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}`
- **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`
- **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
@@ -143,12 +143,12 @@ Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Req
### Settings laden
- **GET /api/v1/tenant/settings**
- **Headers**: `Authorization: Bearer {token}`
- **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`
- **Headers**: `Authorization: Bearer \{token\}`, `Content-Type: application/json`
- **Body**: Partial Settings-Daten
- **Response**: Updated Settings
@@ -156,33 +156,33 @@ Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Req
### Balance laden
- **GET /api/v1/tenant/credits/balance**
- **Headers**: `Authorization: Bearer {token}`
- **Headers**: `Authorization: Bearer \{token\}`
- **Response**: `{ balance: number }`
### Ledger-Verlauf
- **GET /api/v1/tenant/credits/ledger**
- **Headers**: `Authorization: Bearer {token}`
- **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`
- **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`
- **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`
- **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
@@ -190,9 +190,9 @@ Diese Dokumentation beschreibt alle API-Endpunkte der Tenant Admin App. Alle Req
## Allgemeine Headers
Alle API-Requests enthalten:
- **Authorization**: `Bearer {access_token}` (Sanctum PAT mit Fähigkeit `tenant:{id}`)
- **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)
- **If-Match**: `\{etag\}` (für Concurrency-Control bei Updates)
- **Accept**: `application/json`
## Error-Handling
@@ -227,12 +227,12 @@ Alle Listen-Endpunkte unterstützen:
## Headers für Concurrency
Mutierende Endpunkte (PATCH/DELETE) erfordern:
- **If-Match**: `{etag}` aus GET-Response
- **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
- **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
@@ -247,7 +247,7 @@ Mutierende Endpunkte (PATCH/DELETE) erfordern:
### Beispiel curl (mit Token)
```bash
curl -H "Authorization: Bearer {token}" \
curl -H "Authorization: Bearer \{token\}" \
-H "Content-Type: application/json" \
https://api.fotospiel.com/api/v1/tenant/events
```

4
docs/site/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Local node dependencies and build artifacts for the docs site
node_modules
build
.docusaurus

31
docs/site/README.md Normal file
View File

@@ -0,0 +1,31 @@
# Fotospiel Docs Site
This directory hosts a standalone [Docusaurus](https://docusaurus.io/) project that renders everything inside the main `/docs` tree as a browsable internal website. Keeping the static-site tooling here isolates all Node dependencies from the Laravel/Vite application.
## Structure
- `../` — existing Markdown sources (PRP, ops runbooks, etc.). These stay untouched.
- `./package.json` — dependencies and scripts for the docs site only.
- `./docusaurus.config.js` — points the docs plugin at `path: '../'` and excludes this `site/` directory.
- `./sidebars.js` — auto-generates the sidebar from the folder hierarchy.
- `./src/css/custom.css` — brand overrides for the default theme.
## Usage
```bash
cd docs/site
npm install
npm run start # Dev server at http://localhost:3100
npm run build # Outputs to docs/site/build
npm run serve # Serves built assets for preview
```
Because `routeBasePath` is `/`, the docs front page is the PRP index (or whichever document you place at `docs/README.md`). Update nav/footer links in `docusaurus.config.js` as needed.
## Deployment
1. `npm run build` creates the static site under `docs/site/build`.
2. Publish that directory to your static host (S3 + CloudFront, Dokploy static app, etc.).
3. Automate via CI by running installs/builds only inside this folder so the main app pipeline remains unchanged.
If you add new Markdown files anywhere under `/docs`, they automatically appear in the sidebar. To hide files, add ignore patterns to `include/exclude` in `docusaurus.config.js`.

View File

@@ -0,0 +1,97 @@
// @ts-check
const path = require('path');
const { themes } = require('prism-react-renderer');
const lightCodeTheme = themes.github;
const darkCodeTheme = themes.dracula;
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Fotospiel Ops & Product Docs',
tagline: 'Single source of truth for the platform',
url: 'https://docs.fotospiel.local',
baseUrl: '/',
favicon: 'img/favicon.ico',
organizationName: 'fotospiel',
projectName: 'fotospiel-docs-site',
onBrokenLinks: 'warn',
trailingSlash: false,
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
markdown: {
hooks: {
onBrokenMarkdownLinks: 'warn',
},
},
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
path: path.resolve(__dirname, '..'),
routeBasePath: '/',
sidebarPath: require.resolve('./sidebars.js'),
include: ['**/*.md', '**/*.mdx'],
exclude: ['site/**', 'archive/**', '**/_drafts/**'],
editUrl: undefined,
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
blog: false,
pages: false,
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
themes: [],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
navbar: {
title: 'Fotospiel Docs',
items: [
{
type: 'docSidebar',
sidebarId: 'docsSidebar',
position: 'left',
label: 'Documentation',
},
{
href: 'https://github.com/fotospiel',
label: 'Git',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Docs',
items: [
{
label: 'Architecture PRP',
to: '/',
},
{
label: 'Ops Playbooks',
to: '/ops',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} Fotospiel. Internal use only.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
};
module.exports = config;

18268
docs/site/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
docs/site/package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "fotospiel-docs-site",
"private": true,
"version": "0.1.0",
"description": "Docusaurus wrapper for the Fotospiel internal documentation.",
"scripts": {
"start": "docusaurus start",
"dev": "docusaurus start --host 0.0.0.0 --port 3100",
"build": "docusaurus build",
"serve": "docusaurus serve",
"clean": "rimraf build .docusaurus"
},
"dependencies": {
"@docusaurus/core": "^3.4.0",
"@docusaurus/preset-classic": "^3.4.0",
"clsx": "^2.1.1",
"prism-react-renderer": "^2.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"rimraf": "^5.0.7"
},
"engines": {
"node": ">=18.0.0"
}
}

10
docs/site/sidebars.js Normal file
View File

@@ -0,0 +1,10 @@
/**
* Sidebar configuration for the Fotospiel docs site.
*/
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
docsSidebar: [{ type: 'autogenerated', dirName: '.' }],
};
module.exports = sidebars;

View File

@@ -0,0 +1,18 @@
:root {
--ifm-color-primary: #ff5a5f;
--ifm-color-primary-dark: #e44f54;
--ifm-color-primary-darker: #cc4449;
--ifm-color-primary-darkest: #b23a3f;
--ifm-color-primary-light: #ff7376;
--ifm-color-primary-lighter: #ff8c8f;
--ifm-color-primary-lightest: #ffa5a8;
--ifm-code-font-size: 95%;
}
.navbar__brand {
font-weight: 600;
}
.footer--dark {
background-color: #1b1f23;
}