app->environment('local')) { $this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class); $this->app->register(TelescopeServiceProvider::class); } } /** * Bootstrap any application services. */ public function boot(): void { $this->configureUrlScheme(); // Cashier configuration Cashier::useCustomerModel(Workspace::class); // Analytics $this->configurePostHog(); $this->configureRateLimiting(); // Vite configuration Vite::prefetch(concurrency: 3); // Gate policies Gate::policy(Workspace::class, WorkspacePolicy::class); // Custom email verification template VerifyEmail::toMailUsing(function (User $user, string $url) { return (new MailMessage) ->subject('Verify Email Address') ->view('mail.email-verification', [ 'title' => 'Confirm your email address', 'previewText' => 'Please confirm your email address.', 'user' => $user, 'url' => $url, ]); }); // Morph map for polymorphic relationships Relation::enforceMorphMap([ 'domain' => Domain::class, 'invite' => Invite::class, 'link' => Link::class, 'linkStat' => LinkStat::class, 'media' => Media::class, 'plan' => Plan::class, 'tag' => Tag::class, 'user' => User::class, 'workspace' => Workspace::class, ]); } /** * The SDK is only initialised when a key is actually configured; every * call site is already gated on PostHogService, so a missing key means * nothing is sent rather than anything failing. */ /** * Laravel omits the scheme on an absolute URL unless one is forced, * leaving `//lua.sh/...` — Inertia then resolves that against * http://localhost on the server and window.location in the browser, so * the same link hydrates mismatched between the two. This now matters * for the domain-scoped redirect routes in `web.php` rather than the * marketing routes that used to carry the justification. */ protected function configureUrlScheme(): void { URL::forceScheme(parse_url((string) config('app.url'), PHP_URL_SCHEME)); } protected function configurePostHog(): void { if (! PostHogService::isEnabled()) { return; } PostHog::init(config('services.posthog.api_key'), [ 'host' => config('services.posthog.host'), ]); } /** * Dynamic client registration is unauthenticated by design, so it gets a * tight limit of its own rather than sharing the global API budget. */ protected function configureRateLimiting(): void { // Keyed by the token's workspace so one tenant cannot exhaust another's // budget, falling back to the IP for the unauthenticated routes. RateLimiter::for('api', function (Request $request) { if ($this->app->environment('local')) { return Limit::none(); } return Limit::perMinute(60)->by($request->workspace?->id ?: $request->ip()); }); RateLimiter::for('mcp', function (Request $request) { if ($this->app->environment('local')) { return Limit::none(); } return Limit::perMinute(120)->by($request->user()?->id ?: $request->ip()); }); RateLimiter::for( 'mcp-oauth-registration', fn (Request $request): Limit => Limit::perMinute(30)->by($request->ip()), ); } }