Add join token TTL policy and Live Show link sharing
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-01-05 21:11:36 +01:00
parent 3f3061a899
commit 88012c35bd
18 changed files with 636 additions and 9 deletions

View File

@@ -53,6 +53,8 @@ class GuestPolicySettingsPage extends Page
public int $join_token_download_decay_minutes = 1;
public int $join_token_ttl_hours = 168;
public int $share_link_ttl_hours = 48;
public ?int $guest_notification_ttl_hours = null;
@@ -71,6 +73,7 @@ class GuestPolicySettingsPage extends Page
$this->join_token_access_decay_minutes = (int) ($settings->join_token_access_decay_minutes ?? 1);
$this->join_token_download_limit = (int) ($settings->join_token_download_limit ?? 60);
$this->join_token_download_decay_minutes = (int) ($settings->join_token_download_decay_minutes ?? 1);
$this->join_token_ttl_hours = (int) ($settings->join_token_ttl_hours ?? 168);
$this->share_link_ttl_hours = (int) ($settings->share_link_ttl_hours ?? 48);
$this->guest_notification_ttl_hours = $settings->guest_notification_ttl_hours;
}
@@ -130,6 +133,11 @@ class GuestPolicySettingsPage extends Page
->columns(2),
Section::make(__('admin.guest_policy.sections.retention'))
->schema([
Forms\Components\TextInput::make('join_token_ttl_hours')
->label(__('admin.guest_policy.fields.join_token_ttl_hours'))
->numeric()
->minValue(0)
->helperText(__('admin.guest_policy.help.join_token_ttl')),
Forms\Components\TextInput::make('share_link_ttl_hours')
->label(__('admin.guest_policy.fields.share_link_ttl_hours'))
->numeric()
@@ -160,6 +168,7 @@ class GuestPolicySettingsPage extends Page
$settings->join_token_access_decay_minutes = (int) $this->join_token_access_decay_minutes;
$settings->join_token_download_limit = (int) $this->join_token_download_limit;
$settings->join_token_download_decay_minutes = (int) $this->join_token_download_decay_minutes;
$settings->join_token_ttl_hours = (int) $this->join_token_ttl_hours;
$settings->share_link_ttl_hours = (int) $this->share_link_ttl_hours;
$settings->guest_notification_ttl_hours = $this->guest_notification_ttl_hours;
$settings->save();

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers\Api\Tenant;
use App\Http\Controllers\Controller;
use App\Models\Event;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
class LiveShowLinkController extends Controller
{
public function show(Request $request, Event $event): JsonResponse
{
$this->authorizeEvent($request, $event);
$token = $event->ensureLiveShowToken();
return response()->json([
'data' => $this->buildPayload($event, $token),
]);
}
public function rotate(Request $request, Event $event): JsonResponse
{
$this->authorizeEvent($request, $event);
$token = $event->rotateLiveShowToken();
return response()->json([
'data' => $this->buildPayload($event, $token),
]);
}
private function authorizeEvent(Request $request, Event $event): void
{
$tenantId = $request->attributes->get('tenant_id');
if ($event->tenant_id !== $tenantId) {
abort(404, 'Event not found');
}
}
private function buildPayload(Event $event, string $token): array
{
$url = $this->buildLiveShowUrl($event, $token);
return [
'token' => $token,
'url' => $url,
'qr_code_data_url' => $this->buildQrCodeDataUrl($url),
'rotated_at' => $event->live_show_token_rotated_at?->toIso8601String(),
];
}
private function buildLiveShowUrl(Event $event, string $token): string
{
$baseUrl = $this->resolveBaseUrl($event);
return rtrim($baseUrl, '/').'/show/'.$token;
}
private function resolveBaseUrl(Event $event): string
{
$settings = is_array($event->settings) ? $event->settings : [];
$customDomain = $settings['custom_domain'] ?? null;
if (is_string($customDomain) && $customDomain !== '') {
return sprintf('%s://%s', $this->resolveScheme(), $customDomain);
}
$publicUrl = $settings['public_url'] ?? null;
if (is_string($publicUrl) && $publicUrl !== '') {
$parsed = parse_url($publicUrl);
$host = is_array($parsed) ? ($parsed['host'] ?? null) : null;
if (is_string($host) && $host !== '') {
$scheme = $parsed['scheme'] ?? $this->resolveScheme();
$port = $parsed['port'] ?? null;
$base = $scheme.'://'.$host;
if ($port) {
$base .= ':'.$port;
}
return $base;
}
}
return (string) config('app.url');
}
private function resolveScheme(): string
{
$appUrl = config('app.url');
if (is_string($appUrl)) {
$scheme = parse_url($appUrl, PHP_URL_SCHEME);
if (is_string($scheme) && $scheme !== '') {
return $scheme;
}
}
return 'https';
}
private function buildQrCodeDataUrl(string $url): ?string
{
if ($url === '') {
return null;
}
try {
$png = QrCode::format('png')
->size(360)
->margin(1)
->errorCorrection('M')
->generate($url);
$pngBinary = (string) $png;
if ($pngBinary === '') {
return null;
}
return 'data:image/png;base64,'.base64_encode($pngBinary);
} catch (\Throwable $exception) {
report($exception);
}
return null;
}
}

View File

@@ -21,6 +21,7 @@ class GuestPolicySetting extends Model
'join_token_access_decay_minutes' => 'integer',
'join_token_download_limit' => 'integer',
'join_token_download_decay_minutes' => 'integer',
'join_token_ttl_hours' => 'integer',
'share_link_ttl_hours' => 'integer',
];
@@ -44,6 +45,7 @@ class GuestPolicySetting extends Model
'join_token_access_decay_minutes' => (int) config('join_tokens.access_decay_minutes', 1),
'join_token_download_limit' => (int) config('join_tokens.download_limit', 60),
'join_token_download_decay_minutes' => (int) config('join_tokens.download_decay_minutes', 1),
'join_token_ttl_hours' => 168,
'share_link_ttl_hours' => (int) config('share-links.ttl_hours', 48),
'guest_notification_ttl_hours' => null,
];

View File

@@ -4,6 +4,7 @@ namespace App\Services;
use App\Models\Event;
use App\Models\EventJoinToken;
use App\Models\GuestPolicySetting;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
@@ -28,6 +29,12 @@ class EventJoinTokenService
$payload['expires_at'] = $expiresAt instanceof Carbon
? $expiresAt
: Carbon::parse($expiresAt);
} else {
$ttlHours = (int) (GuestPolicySetting::current()->join_token_ttl_hours ?? 0);
if ($ttlHours > 0) {
$payload['expires_at'] = now()->addHours($ttlHours);
}
}
if ($createdBy = Arr::get($attributes, 'created_by')) {