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,147 @@
# Photobooth FTP Ingestion
This guide explains how to operate the Photobooth FTP workflow endtoend: provisioning FTP users for tenants, running the ingest pipeline, and exposing photobooth photos inside the Guest PWA.
## Architecture Overview
1. **vsftpd container** (port `2121`) accepts uploads into a shared volume (default `/var/www/storage/app/photobooth`). Each event receives isolated credentials and a dedicated directory.
2. **Control Service** (REST) provisions FTP accounts. Laravel calls it during enable/rotate/disable actions.
3. **Photobooth settings** (Filament SuperAdmin) define global port, rate limit, expiry grace, and Control Service connection.
4. **Ingest command** copies uploaded files into the events storage disk, generates thumbnails, records `photos.ingest_source = photobooth`, and respects package quotas.
5. **Guest PWA filter** consumes `/api/v1/events/{token}/photos?filter=photobooth` to render the “Fotobox” tab. Sparkbooth uploads reuse this filter via `ingest_source = sparkbooth`.
```
Photobooth -> FTP (vsftpd) -> photobooth disk
photobooth:ingest (queue/scheduler)
-> Event media storage (public disk/S3)
-> packages_usage, thumbnails, security scan
Sparkbooth -> HTTP upload endpoint -> ingest (direct, no staging disk)
```
## Environment Variables
Add the following to `.env` (already scaffolded in `.env.example`):
```env
PHOTOBOOTH_CONTROL_BASE_URL=https://control.internal/api
PHOTOBOOTH_CONTROL_TOKEN=your-control-token
PHOTOBOOTH_CONTROL_TIMEOUT=5
PHOTOBOOTH_FTP_HOST=ftp.internal
PHOTOBOOTH_FTP_PORT=2121
PHOTOBOOTH_USERNAME_PREFIX=pb
PHOTOBOOTH_USERNAME_LENGTH=8
PHOTOBOOTH_PASSWORD_LENGTH=8
PHOTOBOOTH_RATE_LIMIT_PER_MINUTE=20
PHOTOBOOTH_EXPIRY_GRACE_DAYS=1
PHOTOBOOTH_IMPORT_DISK=photobooth
PHOTOBOOTH_IMPORT_ROOT=/var/www/storage/app/photobooth
PHOTOBOOTH_IMPORT_MAX_FILES=50
PHOTOBOOTH_ALLOWED_EXTENSIONS=jpg,jpeg,png,webp
# Sparkbooth defaults (optional overrides)
SPARKBOOTH_ALLOWED_EXTENSIONS=jpg,jpeg,png,webp
SPARKBOOTH_MAX_SIZE_KB=8192
SPARKBOOTH_RATE_LIMIT_PER_MINUTE=20
SPARKBOOTH_RESPONSE_FORMAT=json
```
### Filesystem Disk
`config/filesystems.php` registers a `photobooth` disk that must point to the shared volume where vsftpd writes files. Mount the same directory inside both the FTP container and the Laravel app container.
## Control Service Contract
Laravel expects the Control Service to expose:
```
POST /users { username, password, path, rate_limit_per_minute, expires_at, ftp_port }
POST /users/{username}/rotate { password, rate_limit_per_minute, expires_at }
DELETE /users/{username}
POST /config { ftp_port, rate_limit_per_minute, expiry_grace_days }
```
Authentication is provided via `PHOTOBOOTH_CONTROL_TOKEN` (Bearer token).
## Scheduler & Commands
| Command | Purpose | Default schedule |
|---------|---------|------------------|
| `photobooth:ingest [--event=ID] [--max-files=N]` | Pulls files from the Photobooth disk and imports them into the event storage. | every 5 minutes |
| `photobooth:cleanup-expired` | De-provisions FTP accounts after their expiry. | hourly |
You can run the ingest job manually for a specific event:
```bash
php artisan photobooth:ingest --event=123 --max-files=20
```
## Sparkbooth HTTP Uploads (Custom Upload)
Use this when Sparkbooth runs in “Custom Upload” mode instead of FTP.
- Endpoint: `POST /api/v1/photobooth/upload`
- Auth: per-event username/password (set in Event Admin → Fotobox-Uploads; switch mode to “Sparkbooth”).
- Body (multipart/form-data): `media` (file or base64), `username`, `password`, optionally `name`, `email`, `message`.
- Response:
- JSON success: `{"status":true,"error":null,"url":null}`
- JSON failure: `{"status":false,"error":"Invalid credentials"}`
- XML (if `format=xml` or event preference is XML):
- Success: `<rsp status="ok" url="..."/>`
- Failure: `<rsp status="fail"><err msg="Invalid credentials" /></rsp>`
- Limits: allowed extensions reuse photobooth defaults; max size `SPARKBOOTH_MAX_SIZE_KB` (default 8 MB); per-event rate limit `SPARKBOOTH_RATE_LIMIT_PER_MINUTE` (fallback to photobooth rate limit).
- Ingest: writes straight to the events hot storage, applies thumbnail/watermark/security scan, sets `photos.ingest_source = sparkbooth`.
Example cURL (JSON response):
```bash
curl -X POST https://app.example.com/api/v1/photobooth/upload \
-F "media=@/path/to/photo.jpg" \
-F "username=PB123" \
-F "password=SECRET" \
-F "message=Wedding booth"
```
Example cURL (request XML response):
```bash
curl -X POST https://app.example.com/api/v1/photobooth/upload \
-F "media=@/path/to/photo.jpg" \
-F "username=PB123" \
-F "password=SECRET" \
-F "format=xml"
```
## Tenant Admin UX
Inside the Event Admin PWA, go to **Event → Fotobox-Uploads** to:
1. Enable/disable the Photobooth link.
2. Rotate credentials (max 10-char usernames, 8-char passwords).
3. Switch mode (FTP or Sparkbooth), view rate limit + expiry info, copy ftp:// or POST URL + creds.
## Guest PWA Filter
The Guest gallery now exposes a “Fotobox” tab (both preview card and full gallery). API usage:
```
GET /api/v1/events/{token}/photos?filter=photobooth
Headers: X-Device-Id (optional)
```
Response items contain `ingest_source`, allowing the frontend to toggle photobooth-only views.
## Operational Checklist
1. **Set env vars** from above and restart the app.
2. **Ensure vsftpd + Control Service** are deployed; verify port 2121 and REST endpoint connectivity.
3. **Mount shared volume** to `/var/www/storage/app/photobooth` (or update `PHOTOBOOTH_IMPORT_ROOT` + `filesystems.disks.photobooth.root`).
4. **Run migrations** (`php artisan migrate`) to create settings/event columns.
5. **Seed default storage target** (e.g., `MediaStorageTarget::create([... 'key' => 'public', ...])`) in non-test environments if not present.
6. **Verify scheduler** (Horizon or cron) is running commands `photobooth:ingest` and `photobooth:cleanup-expired`.
7. **Test end-to-end**: enable Photobooth on a staging event, upload a file via FTP, wait for ingest, and confirm it appears under the Fotobox filter in the PWA.
8. **Test Sparkbooth**: switch event mode to Sparkbooth, copy Upload URL/user/pass, send a sample POST (or real Sparkbooth upload), verify it appears under the Fotobox filter.

View File

@@ -0,0 +1,100 @@
# Photobooth Control Service API
The control service is a lightweight sidecar responsible for provisioning vsftpd accounts. Laravel talks to it via REST whenever an Event Admin enables, rotates, or disables the Photobooth feature.
## Authentication
- **Scheme:** Bearer token.
- **Header:** `Authorization: Bearer ${PHOTOBOOTH_CONTROL_TOKEN}`.
- **Timeout:** Configurable via `PHOTOBOOTH_CONTROL_TIMEOUT` (default 5s).
- **Token generation:** `openssl rand -hex 32` (or `php -r "echo bin2hex(random_bytes(32)), "`); store in `.env`/Dokploy secrets as `PHOTOBOOTH_CONTROL_TOKEN`.
## Endpoints
| Method & Path | Description |
|---------------|-------------|
| `POST /users` | Create a new FTP account for an event. |
| `POST /users/{username}/rotate` | Rotate credentials / extend expiry for an existing user. |
| `DELETE /users/{username}` | Remove an FTP account (called when an event disables Photobooth or expires). |
| `POST /config` | Optionally push global config changes (port, rate-limit, expiry grace) to the control service. |
### `POST /users`
```json
{
"username": "pbA12345",
"password": "F4P9K2QX",
"path": "tenant-slug/123",
"rate_limit_per_minute": 20,
"expires_at": "2025-06-15T22:59:59Z",
"ftp_port": 2121,
"allowed_ip_ranges": ["1.2.3.4/32"],
"metadata": {
"event_id": 123,
"tenant_id": 5
}
}
```
**Response:** `201 Created` with `{ "ok": true }`. On failure return 4xx/5xx JSON with `error.code` + `message`.
Implementation tips:
- Ensure the system user or virtual users home directory is set to the provided `path` (prefixed with the shared Photobooth root).
- Apply the rate limit token-bucket before writing to disk (or integrate with HAProxy).
- Store `expires_at` and automatically disable the account when reached (in addition to Laravels scheduled cleanup).
Reference implementation (current stack): `photobooth-ftp` builds from `docker/photobooth-control`, starts pure-ftpd and a Node-based control API on port 8080. Healthcheck: `GET /health` on 8080 (wired in docker-compose.dokploy.yml). Rate-limit/expiry enforcement inside the FTP tier is still to be implemented.
### `POST /users/{username}/rotate`
```json
{
"password": "K9M4T6QZ",
"rate_limit_per_minute": 20,
"expires_at": "2025-06-16T22:59:59Z"
}
```
- Rotate the password atomically and respond with `{ "ok": true }`.
- If username does not exist return `404` with a descriptive message so Laravel can re-provision.
### `DELETE /users/{username}`
No request body. Delete or disable the FTP account, removing access to the assigned directory.
### `POST /config`
Optional hook used when SuperAdmins change defaults:
```json
{
"ftp_port": 2121,
"rate_limit_per_minute": 20,
"expiry_grace_days": 1
}
```
Use this to reload vsftpd or adjust proxy rules without redeploying the control service.
## Error Contract
Return JSON structured as:
```json
{
"error": {
"code": "user_exists",
"message": "Username already provisioned",
"context": { "username": "pbA12345" }
}
}
```
Laravel treats any non-2xx as fatal and logs the payload (sans password). Prefer descriptive `code` values: `user_exists`, `user_not_found`, `rate_limit_violation`, `invalid_payload`, etc.
## Observability
- Emit structured logs for every create/rotate/delete with event + tenant IDs.
- Expose `/health` so Laravel (or uptime monitors) can verify connectivity.
- Consider metrics (e.g., Prometheus) for active accounts, rotations, and failures.

View File

@@ -0,0 +1,53 @@
# Photobooth Operations Playbook
Use this checklist when bringing Photobooth FTP online for a tenant or debugging ingest issues.
## 1. Provisioning Flow
1. **SuperAdmin config** set defaults in Filament → Platform Management → Photobooth Settings.
2. **Tenant enablement** Event Admin opens the event → Fotobox-Uploads → “Photobooth aktivieren”.
3. Laravel generates credentials and calls the control service (`POST /users`).
4. vsftpd accepts uploads at `ftp://username:password@HOST:PORT/`.
5. `photobooth:ingest` copies files into the hot storage disk and applies moderation/security pipelines.
## 2. Troubleshooting
| Symptom | Action |
|---------|--------|
| Tenants Photobooth page shows “Deaktiviert” immediately | Check `storage/logs/laravel.log` for control-service errors; re-run `photobooth:ingest --event=ID -vv`. |
| Files remain under `/storage/app/photobooth/<tenant>/<event>` | Ensure scheduler (Horizon/cron) runs `photobooth:ingest`; run manual command to force ingestion. |
| Photos missing from guest “Fotobox” tab | Confirm `photos.ingest_source = photobooth` and that `/api/v1/events/{token}/photos?filter=photobooth` returns data. |
| Rate-limit complaints | Inspect control service logs; adjust `PHOTOBOOTH_RATE_LIMIT_PER_MINUTE` and re-save settings (fires `/config`). |
| Credentials leaked/compromised | Click “Zugang neu generieren” in Event Admin; optional `php artisan photobooth:cleanup-expired --event=ID` to force deletion before expiry. |
## 3. Command Reference
```bash
# Manually ingest pending files for a single event
php artisan photobooth:ingest --event=123 --max-files=100
# Check ingest for all active events (dry run)
php artisan photobooth:ingest --max-files=10
# Remove expired accounts (safe to run ad hoc)
php artisan photobooth:cleanup-expired
```
## 4. Pre-flight Checklist for New Deployments
1. `php artisan migrate`
2. Configure `.env` Photobooth variables.
3. Mount shared Photobooth volume in all containers (FTP + Laravel).
4. Verify `MediaStorageTarget` records exist (hot target pointing at the hot disk).
5. Seed baseline emotions (Photobooth ingest assigns `emotion_id` from existing rows).
6. Confirm scheduler runs (Horizon supervisor or system cron).
## 5. Incident Response
1. **Identify scope** which events/tenants are affected? Check ingestion logs for specific usernames/path.
2. **Quarantine** disable the Photobooth toggle for impacted events via Admin UI.
3. **Remediate** fix FTP/control issues, rotate credentials, run `photobooth:ingest`.
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/ops/photobooth` welcome.