Aufgabenkarten in der Gäste-pwa als swipe-barer Stapel umgesetzt. Sofortiges Freigeben von Foto-Uploads als Event-Einstellung implementiert.

This commit is contained in:
Codex Agent
2025-12-16 15:30:52 +01:00
parent f2473c6f6d
commit 9e4e9a0d87
19 changed files with 22590 additions and 21687 deletions

View File

@@ -490,7 +490,7 @@ class EventPublicController extends BaseController
])
->orderByRaw('COALESCE(event_task_collection.sort_order, event_task.sort_order, tasks.sort_order, 0)')
->orderBy('tasks.sort_order')
->limit(20)
->distinct('tasks.id')
->get();
$tasks = $rows->map(function ($row) use ($fallbacks) {
@@ -659,6 +659,22 @@ class EventPublicController extends BaseController
return $trimmed;
}
private function buildDeterministicSeed(?string $identifier): int
{
if ($identifier === null || trim($identifier) === '') {
return random_int(1, PHP_INT_MAX);
}
$hash = substr(sha1($identifier), 0, 8);
$seed = hexdec($hash);
if (! is_numeric($seed) || $seed <= 0) {
$seed = abs(crc32($identifier));
}
return max(1, (int) $seed);
}
private function buildAchievementsPayload(int $eventId, ?string $guestIdentifier, array $fallbacks): array
{
$totalPhotos = (int) DB::table('photos')->where('event_id', $eventId)->count();
@@ -1757,6 +1773,7 @@ class EventPublicController extends BaseController
'join_token' => $joinToken?->token,
'photobooth_enabled' => (bool) $event->photobooth_enabled,
'branding' => $branding,
'guest_upload_visibility' => Arr::get($event->settings ?? [], 'guest_upload_visibility', 'review'),
])->header('Cache-Control', 'no-store');
}
@@ -2443,7 +2460,27 @@ class EventPublicController extends BaseController
});
$tasks = $cached['tasks'];
$etag = $cached['hash'] ?? sha1(json_encode($tasks));
$baseHash = $cached['hash'] ?? sha1(json_encode($tasks));
$page = max(1, (int) $request->query('page', 1));
$perPage = max(1, min(100, (int) $request->query('per_page', 20)));
// Shuffle per request for unpredictability; stable when seeded by guest/device or explicit seed.
$seedParam = $request->query('seed');
$guestIdentifier = $this->determineGuestIdentifier($request);
$seedValue = is_numeric($seedParam)
? (int) $seedParam
: $this->buildDeterministicSeed(($guestIdentifier ? $guestIdentifier.'|' : '').(string) $event->id);
$randomizer = new \Random\Randomizer(new \Random\Engine\Mt19937($seedValue));
$shuffled = $randomizer->shuffleArray($tasks);
$total = count($shuffled);
$offset = ($page - 1) * $perPage;
$data = array_slice($shuffled, $offset, $perPage);
$lastPage = (int) ceil($total / $perPage);
$hasMore = $page < $lastPage;
$etag = sha1($baseHash . ':' . $page . ':' . $perPage . ':' . $seedValue);
$reqEtag = $request->headers->get('If-None-Match');
if ($reqEtag && $reqEtag === $etag) {
@@ -2454,7 +2491,19 @@ class EventPublicController extends BaseController
->header('ETag', $etag);
}
return response()->json($tasks)
$payload = [
'data' => $data,
'meta' => [
'total' => $total,
'per_page' => $perPage,
'current_page' => $page,
'last_page' => $lastPage,
'has_more' => $hasMore,
'seed' => $seedValue,
],
];
return response()->json($payload)
->header('Cache-Control', 'public, max-age=120')
->header('ETag', $etag)
->header('Vary', 'Accept-Language, X-Locale')
@@ -2629,6 +2678,8 @@ class EventPublicController extends BaseController
'eventPackages.package',
'storageAssignments.storageTarget',
])->findOrFail($eventId);
$uploadVisibility = Arr::get($eventModel->settings ?? [], 'guest_upload_visibility', 'review');
$autoApproveUploads = $uploadVisibility === 'immediate';
$tenantModel = $eventModel->tenant;
@@ -2744,7 +2795,7 @@ class EventPublicController extends BaseController
'thumbnail_path' => $thumbUrl,
'likes_count' => 0,
'ingest_source' => Photo::SOURCE_GUEST_PWA,
'status' => 'pending',
'status' => $autoApproveUploads ? 'approved' : 'pending',
// Handle emotion_id: prefer explicit ID, fallback to slug lookup, then default
'emotion_id' => $this->resolveEmotionId($validated, $eventId),
@@ -2827,10 +2878,14 @@ class EventPublicController extends BaseController
$this->packageUsageTracker->recordPhotoUsage($eventPackage, $previousUsed, 1);
}
$message = $autoApproveUploads
? 'Photo uploaded and visible.'
: 'Photo uploaded and pending review.';
$response = response()->json([
'id' => $photoId,
'status' => 'pending',
'message' => 'Photo uploaded and pending review.',
'status' => $autoApproveUploads ? 'approved' : 'pending',
'message' => $message,
], 201);
$this->recordTokenEvent(