feat(superadmin): migrate internal docs from docusaurus to guava kb
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-02-07 09:58:39 +01:00
parent 1d2242fb4d
commit fb45d1f6ab
77 changed files with 3813 additions and 18636 deletions

View File

@@ -0,0 +1,101 @@
# 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 `/scripts/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. |
| Checksum alerts | `config/storage-monitor.php['checksum_validation']` | Enables checksum verification alerts and thresholding window. |
| 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.
- Checksum mismatches (hot→archive) are flagged by `storage:monitor` using `checksum_validation` thresholds.
- 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 live in `/scripts/`).
- `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

@@ -0,0 +1,67 @@
## Guest Notification & Push Ops Guide
This runbook explains how to keep the guest notification centre healthy, roll out web push, and operate the new upload health alerts.
### 1. Database & config prerequisites
1. Run the latest migrations so the `push_subscriptions` table exists:
```bash
php artisan migrate --force
```
2. Generate VAPID keys (using `web-push` or any Web Push helper) and store them in the environment:
```
PUSH_ENABLED=true
PUSH_VAPID_PUBLIC_KEY=<base64-url-key>
PUSH_VAPID_PRIVATE_KEY=<base64-url-key>
PUSH_VAPID_SUBJECT="mailto:ops@example.com"
```
3. Redeploy the guest PWA (Vite build) so the runtime config exposes the new keys to the service worker.
### 2. Queue workers
Push deliveries are dispatched on the dedicated `notifications` queue. Ensure one of the queue workers listens to it:
```bash
/var/www/html/scripts/queue-worker.sh default,notifications
```
If Horizon is in use just add `notifications` to the list of queues for at least one supervisor. Monitor `storage/logs/notifications.log` (channel `notifications`) for transport failures.
### 3. Upload health alerts
The `storage:check-upload-queues` command now emits guest-facing alerts when uploads stall or fail repeatedly. Schedule it every 5 minutes via cron (see `cron/upload_queue_health.sh`) or the Laravel scheduler:
```
*/5 * * * * /var/www/html/cron/upload_queue_health.sh
```
Tune thresholds with the `STORAGE_QUEUE_*` variables in `.env` (see `.env.example` for defaults). When an alert fires, the tenant admin toolkit also surfaces the same issues.
### 4. Manual API interactions
- Register push subscription (from browser dev-tools):
```
POST /api/v1/events/{token}/push-subscriptions
Headers: X-Device-Id
Body: { endpoint, keys:{p256dh, auth}, content_encoding }
```
- Revoke subscription:
```
DELETE /api/v1/events/{token}/push-subscriptions
Body: { endpoint }
```
- Inspect per-guest state:
```bash
php artisan tinker
>>> App\Models\PushSubscription::where('event_id', 123)->get();
```
### 5. Smoke tests
After enabling push:
1. Join a published event, open the notification centre, and enable push (browser prompt must appear).
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 a bd issue note for future regressions.

View File

@@ -0,0 +1,131 @@
## 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. Queue entrypoints now live in `/scripts/` inside the container so every service can execute the same shell scripts.
### 1. Prepare the application image
Make sure the worker scripts are copied into the image and marked as executable:
```dockerfile
# Dockerfile (excerpt)
COPY scripts /var/www/html/scripts
RUN chmod +x /var/www/html/scripts/*.sh
```
If you keep the project root mounted as a volume during development the `chmod` step can be skipped because the files will inherit host permissions.
### 2. Queue worker containers
Add one or more worker services to `docker-compose.yml`. The production compose file in the repo already defines `queue` and `media-storage-worker` services that call these scripts; the snippet below shows the essential pattern if you need to tweak scaling.
```yaml
services:
queue-worker:
image: fotospiel-app # reuse the main app image
restart: unless-stopped
depends_on:
- redis # or your queue backend
environment:
APP_ENV: ${APP_ENV:-production}
QUEUE_CONNECTION: redis
QUEUE_TRIES: 3 # optional overrides
QUEUE_SLEEP: 3
command: >
/var/www/html/scripts/queue-worker.sh default
media-storage-worker:
image: fotospiel-app
restart: unless-stopped
depends_on:
- redis
environment:
APP_ENV: ${APP_ENV:-production}
QUEUE_CONNECTION: redis
QUEUE_TRIES: 5
QUEUE_SLEEP: 5
command: >
/var/www/html/scripts/queue-worker.sh media-storage
media-security-worker:
image: fotospiel-app
restart: unless-stopped
depends_on:
- redis
environment:
APP_ENV: ${APP_ENV:-production}
QUEUE_CONNECTION: redis
QUEUE_TRIES: 3
QUEUE_SLEEP: 5
command: >
/var/www/html/scripts/queue-worker.sh media-security
```
Scale workers by increasing `deploy.replicas` (Swarm) or adding `scale` counts (Compose v2).
> **Heads-up:** Guest push notifications are dispatched on the `notifications` queue. Either add that queue to the default worker (`queue-worker.sh default,notifications`) or create a dedicated worker so push jobs are consumed even when other queues are busy.
### 3. Optional: Horizon container
If you prefer Horizons dashboard and auto-balancing, add another service:
```yaml
services:
horizon:
image: fotospiel-app
restart: unless-stopped
depends_on:
- redis
environment:
APP_ENV: ${APP_ENV:-production}
QUEUE_CONNECTION: redis
command: >
/var/www/html/scripts/horizon.sh
```
Expose Horizon via your web proxy and protect it with authentication (the app already guards `/horizon` behind the super admin panel login if configured).
### 4. Environment variables
- `QUEUE_CONNECTION` — should match the driver configured in `.env` (`redis` recommended).
- `QUEUE_TRIES`, `QUEUE_SLEEP`, `QUEUE_TIMEOUT`, `QUEUE_MAX_TIME` — optional tuning knobs consumed by `queue-worker.sh`.
- `STORAGE_ALERT_EMAIL` — enables upload failure notifications introduced in the new storage pipeline.
- `SECURITY_SCAN_QUEUE` — overrides the queue name for the photo antivirus/EXIF worker (`media-security` by default).
- Redis / database credentials must be available in the worker containers exactly like the web container.
### 5. Bootstrapping reminder
Before starting workers on a new environment:
```bash
php artisan migrate
php artisan db:seed --class=MediaStorageTargetSeeder
```
Existing assets should be backfilled into `event_media_assets` with a one-off artisan command before enabling automatic archival jobs.
### 6. Monitoring & logs
- Containers log to STDOUT; aggregate via `docker logs` or a centralized stack.
- Horizon users can inspect `/horizon` for queue lengths and failed jobs.
- With plain workers run `php artisan queue:failed` (inside the container) to inspect failures and `php artisan queue:retry all` after resolving issues.
### 7. Rolling updates
When deploying new code:
1. Build and push updated app image.
2. Run migrations & seeders.
3. Recreate worker/horizon containers: `docker compose up -d --force-recreate queue-worker media-storage-worker horizon`.
4. Tail logs to confirm workers boot cleanly and start consuming jobs.
### 8. Running inside Dokploy
If you host Fotospiel on Dokploy:
- Create separate Dokploy applications for each worker type using the same image and command snippets above (`queue-worker.sh default`, `media-storage`, etc.).
- Attach the same environment variables and storage volumes defined for the main app.
- Use Dokploys one-off command feature to run migrations or `queue:retry`.
- Expose the Horizon service through the Dokploy HTTP proxy (or keep it internal and access via SSH tunnel).
- Enable health checks so Dokploy restarts workers automatically if they exit unexpectedly.
These services can be observed, redeployed, or reloaded from Dokploys dashboard and from the SuperAdmin integration powered by the Dokploy API.

View File

@@ -0,0 +1,81 @@
---
title: How-to Gäste können nicht hochladen
---
Dieses Howto beschreibt, wie du vorgehst, wenn Gäste melden, dass sie keine Fotos mehr hochladen können (Fehler im UploadFlow oder „hängenbleibende“ Uploads).
## 1. Problem eingrenzen
Fragen an den Tenant/Support:
- Betrifft es **alle** Gäste oder nur einzelne?
- Betrifft es **alle** Events oder nur ein bestimmtes Event?
- Welche Fehlermeldung erscheint im GuestFrontend (so genau wie möglich, gerne mit Screenshot)?
- Seit wann tritt das Problem auf? (Zeitfenster)
Diese Informationen bestimmen, ob du in Richtung API/RateLimit, Storage/Queues oder EventKonfiguration schauen musst.
## 2. Basischecks API & App
1. **Public-API Status**
- Teste manuell einen Upload gegen ein TestEvent oder reproduziere das Problem mit dem betroffenen JoinToken.
- Achte auf HTTPStatuscodes im BrowserNetworkTab (4xx vs. 5xx).
2. **App- / Deployment-Status**
- Prüfe in Docker/Dokploy, ob App/Queue/Redis/DBContainer gesund sind.
- Schaue in `storage/logs/laravel.log` nach offensichtlichen Exceptions rund um das gemeldete Zeitfenster.
Wenn die PublicAPI generell 5xx liefert, greift eher das PublicAPIIncidentPlaybook (`docs/ops/deployment/public-api-incident-playbook.md`).
## 3. Queues & Upload-Health
Wenn das Problem hauptsächlich Uploads betrifft (andere Funktionen laufen):
1. **Queue-Längen prüfen**
- In Horizon:
- `media-storage`, `media-security` und ggf. `notifications` QueueLängen ansehen.
- In Logs:
- Warnungen aus `storage:check-upload-queues` oder `storage-jobs` suchen.
2. **Upload-Health-Command**
- Sicherstellen, dass `storage:check-upload-queues` regelmäßig läuft (Cron / Scheduler).
- Manuell ausführen (in der AppContainerShell):
```bash
php artisan storage:check-upload-queues
```
- Ausgaben/Logs prüfen:
- Meldungen zu „stalled“ Uploads, Events mit dauerhaft vielen PendingAssets.
## 4. Storage & Limit-Probleme
1. **Hot-Storage-Füllstand**
- Prüfen, ob das StorageVolume/Bucket nahe an 100 % ist (siehe `docs/ops/dr-storage-issues.md`).
- Wenn ja:
- Archivierung beschleunigen (`storage:archive-pending` verifizieren).
- Kurzfristig Speicher vergrößern oder Caches aufräumen.
2. **Paket-/Limit-Prüfungen**
- Wenn nur bestimmte Events betroffen sind:
- PaketLimits des Events prüfen (z.B. max_photos/max_guests).
- EventStatus (abgelaufen/archiviert?) prüfen.
- Logs können Fehlercodes liefern wie „photo_limit_exceeded“ diese deuten auf bewusst ausgelöste LimitSperren hin, nicht auf technische Fehler.
## 5. Typische Muster & Gegenmaßnahmen
- **Hohe Fehlerrate beim Upload (5xx)**
- Hinweis auf API/BackendProblem:
- Siehe PublicAPIRunbook und AppLogs (Datenbank/RedisFehler, Timeouts).
- **Uploads bleiben „ewig“ auf „wird verarbeitet“**
- Queues laufen nicht oder `media-storage`/`media-security` steckt fest:
- Horizon prüfen, ob WorkerContainer laufen.
- Ggf. Worker neu starten und Failed Jobs analysieren.
- **Nur ein Event betroffen, andere funktionieren**
- Meist Limit oder KonfigThema (Paket voll, Galerie abgelaufen, Event deaktiviert).
- TenantAdminUI prüfen: EventStatus, PaketStatus, Data Lifecycle Einstellungen.
## 6. Kommunikation
- **An Tenant/Support zurückmelden**:
- Was war die Ursache? (z.B. PaketLimit, temporäre Überlastung, StorageKnappheit).
- Was wurde getan? (z.B. Paket angepasst, Queues neu gestartet, Storage erweitert).
- Ob und wie der Tenant/gäste weiteres tun müssen (z.B. Seite neu laden, später erneut probieren).
Für tiefere Ursachen rund um Storage siehe `docs/ops/media-storage-spec.md` und `docs/ops/dr-storage-issues.md`.

View File

@@ -0,0 +1,85 @@
---
title: How-to Photobooth lädt keine Fotos
---
Dieses Howto beschreibt, wie du vorgehst, wenn ein Tenant meldet, dass von der Photobooth keine Fotos im Event ankommen.
## 1. Problem eingrenzen
Fragen an den Tenant:
- Welcher Event ist betroffen? (EventID oder Titel).
- Wird im TenantAdmin unter „Fotobox-Uploads“ angezeigt, dass die Photobooth aktiviert ist?
- Sieht der PhotoboothOperator offensichtliche Fehler am Gerät (z.B. FTPFehler, Timeout)?
- Seit wann kommt nichts mehr an? (Zeitfenster)
Diese Infos helfen dir, zwischen Konfigurations, FTP oder IngestProblem zu unterscheiden.
## 2. Konfiguration im Admin prüfen
1. Im Tenant-Admin:
- Den betroffenen Event öffnen.
- Prüfen, ob die PhotoboothFunktion für diesen Event aktiviert ist.
2. Wenn Photobooth deaktiviert ist:
- Tenant bitten, sie im UI zu aktivieren (dies triggert die Provisionierung und Credentials).
- Danach erneut testen, ob Uploads ankommen.
## 3. FTP-/Control-Service überprüfen
Siehe auch `docs/ops/photobooth/control_service.md` und `docs/ops/photobooth/ops_playbook.md`.
1. **FTP-Erreichbarkeit**
- Host/Port aus den PhotoboothEinstellungen entnehmen.
- Testverbindung (z.B. über lokales FTPTool oder `nc`/`telnet`) herstellen:
- Port (z.B. 2121) erreichbar?
2. **Credentials validieren**
- Prüfen, ob Username/Passwort im TenantAdmin zu den ControlServiceDaten passen.
- Bei Verdacht auf Fehler:
- Im Admin die Zugangsdaten neu generieren lassen.
- Tenant/PhotoboothTeam informieren, dass sie die neuen Credentials konfigurieren müssen.
## 4. Ingest-Service & Scheduler prüfen
Die Photobooth legt Dateien zunächst in einem ImportPfad ab, der dann vom IngestService verarbeitet wird.
1. **Import-Verzeichnis prüfen**
- Pfad: üblicherweise `storage/app/photobooth/{tenant}/{event}` (siehe `docs/ops/photobooth/README.md`).
- In den Logs kontrollieren, ob neue Dateien dort landen.
2. **Ingest-Command**
- Sicherstellen, dass `photobooth:ingest` regelmäßig läuft (Scheduler/Cron):
```bash
php artisan photobooth:ingest --max-files=100
```
- Optional: für einen konkreten Event:
```bash
php artisan photobooth:ingest --event=EVENT_ID --max-files=50 -vv
```
- Logs auf Hinweise prüfen:
- Fehler beim Lesen der FTPDateien.
- Probleme beim Schreiben in den HotStorage.
3. **Queues**
- Verifizieren, dass relevante Queues laufen (falls Ingest Jobs dispatcht).
## 5. Typische Fehlerbilder & Lösungen
- **FTP erreicht, aber Import-Verzeichnis bleibt leer**
- PhotoboothSoftware schreibt nicht an den erwarteten Pfad → Pfad in der PhotoboothKonfiguration mit den Angaben aus `PHOTOBOOTH_IMPORT_ROOT` abgleichen.
- Evtl. Berechtigungsproblem im FTPContainer (Perms/Ownership).
- **Import-Verzeichnis gefüllt, aber nichts im Event**
- `photobooth:ingest` läuft nicht oder bricht ab:
- Scheduler prüfen (`scheduler`Service in Docker/Dokploy).
- Kommando manuell ausführen und Fehler analysieren.
- **Fotos tauchen mit großer Verzögerung auf**
- Ingest läuft zu selten (Cron/Intervalle zu groß).
- Events haben hohe Medienlast → `--max-files` erhöhen oder Ingest häufiger anstoßen.
## 6. Kommunikation mit dem Tenant
- Sobald Ursache und Fix klar sind:
- Tenant informieren, ob es ein Konfig, Netzwerk oder IngestProblem war.
- Falls nötig, dem PhotoboothTeam neue Credentials/Anweisungen zukommen lassen.
- Falls einige Dateien irreversibel verloren gegangen sind:
- Transparent kommunizieren und ggf. Kulanzlösungen (z.B. Gutschrift) über Finance/Success abstimmen.
Nutze für tiefere Analysen die ausführlicheren Playbooks in `docs/ops/photobooth/ops_playbook.md`.