app->singleton(CheckoutSessionService::class); $this->app->singleton(CheckoutAssignmentService::class); $this->app->singleton(EventStorageManager::class); $this->app->singleton(StorageHealthService::class); $this->app->singleton(PhotoSecurityScanner::class); $heroiconsPrefix = config('blade-heroicons.prefix'); if (! is_string($heroiconsPrefix) || $heroiconsPrefix === '') { config()->set('blade-heroicons.prefix', 'heroicon'); } } /** * Bootstrap any application services. */ public function boot(): void { if (str_starts_with((string) config('app.url'), 'https://')) { URL::forceScheme('https'); } Route::aliasMiddleware('signed', \App\Http\Middleware\ValidateSignature::class); $this->app->make(EventStorageManager::class)->registerDynamicDisks(); EventFacade::listen( EventPackagePhotoThresholdReached::class, [QueuePhotoThresholdNotification::class, 'handle'] ); EventFacade::listen( EventPackagePhotoLimitReached::class, [QueuePhotoLimitNotification::class, 'handle'] ); EventFacade::listen( EventPackageGuestThresholdReached::class, [QueueGuestThresholdNotification::class, 'handle'] ); EventFacade::listen( EventPackageGuestLimitReached::class, [QueueGuestLimitNotification::class, 'handle'] ); EventFacade::listen( EventPackageGalleryExpiring::class, [QueueGalleryWarningNotification::class, 'handle'] ); EventFacade::listen( EventPackageGalleryExpired::class, [QueueGalleryExpiredNotification::class, 'handle'] ); EventFacade::listen( TenantPackageEventThresholdReached::class, [QueueTenantEventThresholdNotification::class, 'handle'] ); EventFacade::listen( TenantPackageEventLimitReached::class, [QueueTenantEventLimitNotification::class, 'handle'] ); EventFacade::listen( TenantPackageExpiring::class, [QueueTenantPackageExpiringNotification::class, 'handle'] ); EventFacade::listen( TenantPackageExpired::class, [QueueTenantPackageExpiredNotification::class, 'handle'] ); EventFacade::listen( GuestPhotoUploaded::class, [SendPhotoUploadedNotification::class, 'handle'] ); EventFacade::listen( GuestNotificationCreated::class, [DispatchGuestNotificationPush::class, 'handle'] ); if (config('e2e.testing_enabled')) { EventFacade::listen( MessageSent::class, [Mailbox::class, 'record'] ); } RateLimiter::for('tenant-api', function (Request $request) { $tenantId = $request->attributes->get('tenant_id') ?? $request->user()?->tenant_id ?? $request->user()?->tenant?->id; $key = $tenantId ? 'tenant:'.$tenantId : ('ip:'.($request->ip() ?? 'unknown')); return Limit::perMinute(600)->by($key); }); RateLimiter::for('guest-api', function (Request $request) { return Limit::perMinute(300)->by('guest-api:'.($request->ip() ?? 'unknown')); }); RateLimiter::for('photobooth-connect', function (Request $request) { return Limit::perMinute(30)->by('photobooth-connect:'.($request->ip() ?? 'unknown')); }); RateLimiter::for('tenant-auth', function (Request $request) { return Limit::perMinute(20)->by('tenant-auth:'.($request->ip() ?? 'unknown')); }); RateLimiter::for('coupon-preview', function (Request $request) { $code = strtoupper((string) $request->input('code')); $identifier = ($request->ip() ?? 'unknown').($code ? ':'.$code : ''); return Limit::perMinute(10)->by('coupon-preview:'.$identifier); }); RateLimiter::for('contact-form', function (Request $request) { $ip = $request->ip() ?? 'unknown'; return [ Limit::perMinute(5)->by('contact:ip:'.$ip), Limit::perHour(30)->by('contact:hour:'.$ip), ]; }); RateLimiter::for('paddle-webhook', function (Request $request) { return Limit::perMinute(30)->by('paddle:'.$request->ip()); }); RateLimiter::for('gift-lookup', function (Request $request) { $code = strtoupper((string) $request->query('code')); $ip = $request->ip() ?? 'unknown'; return [ Limit::perMinute(30)->by('gift-lookup:ip:'.$ip), Limit::perMinute(10)->by('gift-lookup:code:'.($code ?: $ip)), ]; }); Livewire::component( 'support-api-token-manager', \App\Filament\SuperAdmin\Widgets\SupportApiTokenManager::class ); RateLimiter::for('gift-resend', function (Request $request) { $code = strtoupper((string) $request->input('code')); $ip = $request->ip() ?? 'unknown'; return [ Limit::perMinute(10)->by('gift-resend:ip:'.$ip), Limit::perHour(5)->by('gift-resend:code:'.($code ?: $ip)), ]; }); Inertia::share('locale', fn () => app()->getLocale()); Inertia::share('analytics', static function () { $config = config('services.matomo'); if (! ($config['enabled'] ?? false) || empty($config['url']) || empty($config['site_id_marketing'])) { return [ 'matomo' => [ 'enabled' => false, ], ]; } return [ 'matomo' => [ 'enabled' => true, 'url' => rtrim((string) ($config['url'] ?? ''), '/'), 'siteId' => (string) ($config['site_id_marketing'] ?? ''), ], ]; }); Inertia::share('security', static function () { $request = request(); if (! $request) { return [ 'csp' => [ 'scriptNonce' => null, 'styleNonce' => null, ], ]; } return [ 'csp' => [ 'scriptNonce' => $request->attributes->get('csp_script_nonce'), 'styleNonce' => $request->attributes->get('csp_style_nonce'), ], ]; }); Queue::failing(function (JobFailed $event) { $context = [ 'queue' => $event->job->getQueue(), 'job' => $event->job->resolveName(), 'exception' => $event->exception->getMessage(), ]; $command = data_get($event->job->payload(), 'data.command'); if (is_string($command)) { try { $instance = @unserialize($command, ['allowed_classes' => true]); if (is_object($instance)) { foreach (['eventId' => 'event_id', 'photoId' => 'photo_id'] as $property => $label) { if (isset($instance->{$property})) { $context[$label] = $instance->{$property}; } } } } catch (\Throwable $e) { $context['unserialize_error'] = $e->getMessage(); } } SentryReporter::captureException($event->exception, [ 'tags' => [ 'queue' => $context['queue'], 'job' => $context['job'], 'connection' => $event->connectionName, ], 'extra' => [ 'event_id' => $context['event_id'] ?? null, 'photo_id' => $context['photo_id'] ?? null, ], ]); if (config('storage-monitor.queue_failure_alerts')) { if ($mail = config('storage-monitor.alert_recipients.mail')) { Notification::route('mail', $mail)->notify(new UploadPipelineFailed($context)); } } }); EventFacade::listen(ScheduledTaskFailed::class, function (ScheduledTaskFailed $event): void { $task = $event->task; SentryReporter::captureException($event->exception, [ 'tags' => [ 'schedule_command' => $task->command ?? 'unknown', 'schedule_expression' => $task->expression ?? null, ], 'extra' => [ 'schedule_description' => $task->description ?? null, 'schedule_timezone' => $task->timezone ?? null, ], ]); }); } }