Add join token TTL policy and Live Show link sharing
This commit is contained in:
@@ -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();
|
||||
|
||||
135
app/Http/Controllers/Api/Tenant/LiveShowLinkController.php
Normal file
135
app/Http/Controllers/Api/Tenant/LiveShowLinkController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
Reference in New Issue
Block a user