From 82bfe68ce0c20e05c2a9ac2a2c010f39665066c7 Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Thu, 4 Dec 2025 21:03:03 +0100 Subject: [PATCH] added ftp controlservice health check and fixed gallery js error --- docker-compose.dokploy.yml | 6 ++++ docs/ops/photobooth/control_service.md | 3 ++ resources/js/guest/services/eventApi.ts | 45 ++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/docker-compose.dokploy.yml b/docker-compose.dokploy.yml index 61d160e..b32db91 100644 --- a/docker-compose.dokploy.yml +++ b/docker-compose.dokploy.yml @@ -147,6 +147,12 @@ services: networks: - dokploy-network - photobooth-network + healthcheck: + test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/health"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 10s healthcheck: test: ["CMD", "nc", "-z", "localhost", "21"] interval: 30s diff --git a/docs/ops/photobooth/control_service.md b/docs/ops/photobooth/control_service.md index 2b34a4f..9326b07 100644 --- a/docs/ops/photobooth/control_service.md +++ b/docs/ops/photobooth/control_service.md @@ -7,6 +7,7 @@ The control service is a lightweight sidecar responsible for provisioning vsftpd - **Scheme:** Bearer token. - **Header:** `Authorization: Bearer ${PHOTOBOOTH_CONTROL_TOKEN}`. - **Timeout:** Configurable via `PHOTOBOOTH_CONTROL_TIMEOUT` (default 5 s). +- **Token generation:** `openssl rand -hex 32` (or `php -r "echo bin2hex(random_bytes(32)), "`); store in `.env`/Dokploy secrets as `PHOTOBOOTH_CONTROL_TOKEN`. ## Endpoints @@ -43,6 +44,8 @@ Implementation tips: - 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 Laravel’s 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 diff --git a/resources/js/guest/services/eventApi.ts b/resources/js/guest/services/eventApi.ts index 6aab1a0..b333b40 100644 --- a/resources/js/guest/services/eventApi.ts +++ b/resources/js/guest/services/eventApi.ts @@ -1,4 +1,5 @@ import { getDeviceId } from '../lib/device'; +import { DEFAULT_LOCALE } from '../i18n/messages'; export interface EventBrandingPayload { primary_color?: string | null; @@ -149,6 +150,31 @@ export class FetchEventError extends Error { } } +function coerceLocalized(value: unknown, fallback: string): string { + if (typeof value === 'string' && value.trim() !== '') { + return value; + } + + if (value && typeof value === 'object') { + const obj = value as Record; + const preferredKeys = ['de', 'en']; + + for (const key of preferredKeys) { + const candidate = obj[key]; + if (typeof candidate === 'string' && candidate.trim() !== '') { + return candidate; + } + } + + const firstString = Object.values(obj).find((candidate) => typeof candidate === 'string' && candidate.trim() !== ''); + if (typeof firstString === 'string') { + return firstString; + } + } + + return fallback; +} + const API_ERROR_CODES: FetchEventErrorCode[] = [ 'invalid_token', 'token_expired', @@ -231,7 +257,24 @@ export async function fetchEvent(eventKey: string): Promise { }); } - return await res.json(); + const json = await res.json(); + + const normalized: EventData = { + ...json, + name: coerceLocalized(json?.name, 'Fotospiel Event'), + default_locale: typeof json?.default_locale === 'string' && json.default_locale.trim() !== '' + ? json.default_locale + : DEFAULT_LOCALE, + }; + + if (json?.type) { + normalized.type = { + ...json.type, + name: coerceLocalized(json.type?.name, 'Event'), + }; + } + + return normalized; } catch (error) { if (error instanceof FetchEventError) { throw error;