From 8e41f6fdf8538f805a1c54c29783cbf0bc3b7adb Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 26 Aug 2026 23:18:39 +0930 Subject: [PATCH] refactor: migrate to modern bootstrap/app.php architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate the legacy Laravel 5-era kernel/handler architecture into the modern Application::configure() pattern introduced in Laravel 11: - HTTP middleware stack → bootstrap/app.php withMiddleware() - Console schedule → bootstrap/app.php withSchedule() - Exception handling → bootstrap/app.php withExceptions() - Route registration → bootstrap/app.php withRouting() - Service providers → bootstrap/providers.php Deleted files: - app/Http/Kernel.php - app/Console/Kernel.php - app/Exceptions/Handler.php - app/Providers/RouteServiceProvider.php - app/Providers/BroadcastServiceProvider.php Removed framework providers from config/app.php (auto-registered by Application::configure). Package providers use auto-discovery. All 107 tests pass. Schedule, routes, and middleware verified working. --- app/Console/Kernel.php | 70 ------ app/Exceptions/Handler.php | 97 -------- app/Http/Kernel.php | 118 ---------- app/Providers/BroadcastServiceProvider.php | 21 -- app/Providers/RouteServiceProvider.php | 65 ------ bootstrap/app.php | 254 ++++++++++++++++----- bootstrap/providers.php | 15 ++ config/app.php | 161 ++++++------- routes/channels.php | 2 + 9 files changed, 291 insertions(+), 512 deletions(-) delete mode 100644 app/Console/Kernel.php delete mode 100644 app/Exceptions/Handler.php delete mode 100644 app/Http/Kernel.php delete mode 100644 app/Providers/BroadcastServiceProvider.php delete mode 100644 app/Providers/RouteServiceProvider.php create mode 100644 bootstrap/providers.php diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php deleted file mode 100644 index c3dff4a46..000000000 --- a/app/Console/Kernel.php +++ /dev/null @@ -1,70 +0,0 @@ -command('media:optimize')->hourlyAt(40)->onOneServer(); - $schedule->command('media:gc')->hourlyAt(5)->onOneServer(); - $schedule->command('horizon:snapshot')->everyFiveMinutes()->onOneServer(); - $schedule->command('story:gc')->everyFiveMinutes()->onOneServer(); - $schedule->command('gc:failedjobs')->dailyAt(3)->onOneServer(); - $schedule->command('gc:passwordreset')->dailyAt('09:41')->onOneServer(); - $schedule->command('gc:sessions')->twiceDaily(13, 23)->onOneServer(); - $schedule->command('app:weekly-instance-scan')->weeklyOn(2, '4:20')->onOneServer(); - $schedule->command('app:cleanup-expired-app-registrations')->dailyAt(1)->onOneServer(); - $schedule->command('passport:purge')->everyFourHours(20)->onOneServer(); - - if ((bool) config_cache('pixelfed.cloud_storage') && (bool) config_cache('media.delete_local_after_cloud')) { - $schedule->command('media:s3gc')->hourlyAt(15); - } - - if (config('import.instagram.enabled')) { - $schedule->command('app:transform-imports')->twiceDaily(13, 22)->onOneServer(); - $schedule->command('app:import-upload-garbage-collection')->hourlyAt(51)->onOneServer(); - $schedule->command('app:import-remove-deleted-accounts')->hourlyAt(37)->onOneServer(); - $schedule->command('app:import-upload-clean-storage')->twiceDailyAt(1, 13, 32)->onOneServer(); - - if (config('import.instagram.storage.cloud.enabled') && (bool) config_cache('pixelfed.cloud_storage')) { - $schedule->command('app:import-upload-media-to-cloud-storage')->hourlyAt(39)->onOneServer(); - } - } - - $schedule->command('app:notification-epoch-update')->weeklyOn(1, '2:21')->onOneServer(); - $schedule->command('app:hashtag-cached-count-update')->hourlyAt(25)->onOneServer(); - $schedule->command('app:account-post-count-stat-update')->everySixHours(25)->onOneServer(); - $schedule->command('app:instance-update-total-local-posts')->twiceDailyAt(1, 13, 45)->onOneServer(); - } - - /** - * Register the commands for the application. - * - * @return void - */ - protected function commands() - { - $this->load(__DIR__.'/Commands'); - - require base_path('routes/console.php'); - } -} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php deleted file mode 100644 index e970f5c24..000000000 --- a/app/Exceptions/Handler.php +++ /dev/null @@ -1,97 +0,0 @@ -reportable(function (\BadMethodCallException $e) { - return app()->environment() !== 'production'; - }); - - $this->reportable(function (ConnectionException $e) { - return app()->environment() !== 'production'; - }); - } - - /** - * Render an exception into an HTTP response. - * - * @param Request $request - * @param \Exception $exception - * @return Response - */ - public function render($request, Throwable $exception) - { - if ($request->wantsJson()) { - if ($exception instanceof HttpResponseException) { - return $exception->getResponse(); - } - - if ($exception instanceof ValidationException) { - return response()->json([ - 'message' => $exception->getMessage(), - 'errors' => $exception->validator->getMessageBag(), - ], $exception->status); - } - - $isHttp = $this->isHttpException($exception); - - return response()->json( - ['error' => $exception->getMessage()], - $isHttp ? $exception->getStatusCode() : 500, - $isHttp ? $exception->getHeaders() : [], - ); - } - - return parent::render($request, $exception); - } -} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php deleted file mode 100644 index 2e2a5fa15..000000000 --- a/app/Http/Kernel.php +++ /dev/null @@ -1,118 +0,0 @@ - [ - EncryptCookies::class, - FrameGuard::class, - AddQueuedCookiesToResponse::class, - StartSession::class, - AuthenticateSession::class, - ShareErrorsFromSession::class, - VerifyCsrfToken::class, - SubstituteBindings::class, - CreateFreshApiToken::class, - 'restricted', - ], - - 'oauth-web' => [ - EncryptCookies::class, - FrameGuard::class, - AddQueuedCookiesToResponse::class, - StartSession::class, - ShareErrorsFromSession::class, - VerifyCsrfToken::class, - SubstituteBindings::class, - CreateFreshApiToken::class, - ], - - 'api' => [ - 'bindings', - ], - ]; - - /** - * The application's middleware aliases. - * - * Aliases may be used instead of class names to conveniently assign middleware to routes and groups. - * - * @var array - */ - protected $middlewareAliases = [ - 'api.admin' => Middleware\Api\Admin::class, - 'admin' => Admin::class, - 'auth' => Authenticate::class, - 'auth.basic' => AuthenticateWithBasicAuth::class, - 'bindings' => SubstituteBindings::class, - 'cache.headers' => SetCacheHeaders::class, - 'can' => Authorize::class, - 'dangerzone' => DangerZone::class, - 'localization' => Localization::class, - 'guest' => RedirectIfAuthenticated::class, - 'signed' => ValidateSignature::class, - 'throttle' => ThrottleRequests::class, - 'twofactor' => TwoFactorAuth::class, - 'validemail' => EmailVerificationCheck::class, - 'interstitial' => AccountInterstitial::class, - 'scopes' => CheckScopes::class, - 'scope' => CheckForAnyScope::class, - 'restricted' => RestrictedAccess::class, - ]; -} diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php deleted file mode 100644 index 395c518bc..000000000 --- a/app/Providers/BroadcastServiceProvider.php +++ /dev/null @@ -1,21 +0,0 @@ -mapApiRoutes(); - $this->mapWebRoutes(); - } - - /** - * Define the "web" routes for the application. - * - * These routes all receive session state, CSRF protection, etc. - * - * @return void - */ - protected function mapWebRoutes() - { - Route::middleware('web') - ->group(base_path('routes/web-admin.php')); - - Route::middleware('web') - ->group(base_path('routes/web-portfolio.php')); - - Route::middleware('web') - ->group(base_path('routes/web-api.php')); - - Route::middleware('web') - ->group(base_path('routes/web.php')); - } - - /** - * Define the "api" routes for the application. - * - * These routes are typically stateless. - * - * @return void - */ - protected function mapApiRoutes() - { - Route::middleware('api') - ->group(base_path('routes/api.php')); - } -} diff --git a/bootstrap/app.php b/bootstrap/app.php index 057c8a84e..54a70c423 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,60 +1,202 @@ singleton( - Illuminate\Contracts\Http\Kernel::class, - Kernel::class -); - -$app->singleton( - Illuminate\Contracts\Console\Kernel::class, - App\Console\Kernel::class -); - -$app->singleton( - ExceptionHandler::class, - Handler::class -); - -/* -|-------------------------------------------------------------------------- -| Return The Application -|-------------------------------------------------------------------------- -| -| This script returns the application instance. The instance is given to -| the calling script so we can separate the building of the instances -| from the actual running of the application and sending responses. -| -*/ - -return $app; +return Application::configure(basePath: dirname(__DIR__)) + ->withRouting( + using: function () { + Route::middleware('web') + ->group(base_path('routes/web-admin.php')); + + Route::middleware('web') + ->group(base_path('routes/web-portfolio.php')); + + Route::middleware('web') + ->group(base_path('routes/web-api.php')); + + Route::middleware('web') + ->group(base_path('routes/web.php')); + + Route::middleware('api') + ->group(base_path('routes/api.php')); + }, + channels: __DIR__.'/../routes/channels.php', + commands: __DIR__.'/../routes/console.php', + ) + ->withMiddleware(function (Middleware $middleware) { + $middleware->use([ + HandleCors::class, + PreventRequestsDuringMaintenance::class, + ValidatePostSize::class, + TrustProxies::class, + TrimStrings::class, + ConvertEmptyStringsToNull::class, + ]); + + $middleware->group('web', [ + EncryptCookies::class, + FrameGuard::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + AuthenticateSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + CreateFreshApiToken::class, + 'restricted', + ]); + + $middleware->group('oauth-web', [ + EncryptCookies::class, + FrameGuard::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + CreateFreshApiToken::class, + ]); + + $middleware->group('api', [ + 'bindings', + ]); + + $middleware->alias([ + 'api.admin' => ApiAdmin::class, + 'admin' => Admin::class, + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'bindings' => SubstituteBindings::class, + 'cache.headers' => SetCacheHeaders::class, + 'can' => Authorize::class, + 'dangerzone' => DangerZone::class, + 'localization' => Localization::class, + 'guest' => RedirectIfAuthenticated::class, + 'signed' => ValidateSignature::class, + 'throttle' => ThrottleRequests::class, + 'twofactor' => TwoFactorAuth::class, + 'validemail' => EmailVerificationCheck::class, + 'interstitial' => AccountInterstitial::class, + 'scopes' => CheckScopes::class, + 'scope' => CheckForAnyScope::class, + 'restricted' => RestrictedAccess::class, + ]); + }) + ->withSchedule(function (Schedule $schedule) { + $schedule->command('media:optimize')->hourlyAt(40)->onOneServer(); + $schedule->command('media:gc')->hourlyAt(5)->onOneServer(); + $schedule->command('horizon:snapshot')->everyFiveMinutes()->onOneServer(); + $schedule->command('story:gc')->everyFiveMinutes()->onOneServer(); + $schedule->command('gc:failedjobs')->dailyAt(3)->onOneServer(); + $schedule->command('gc:passwordreset')->dailyAt('09:41')->onOneServer(); + $schedule->command('gc:sessions')->twiceDaily(13, 23)->onOneServer(); + $schedule->command('app:weekly-instance-scan')->weeklyOn(2, '4:20')->onOneServer(); + $schedule->command('app:cleanup-expired-app-registrations')->dailyAt(1)->onOneServer(); + $schedule->command('passport:purge')->everyFourHours(20)->onOneServer(); + + if ((bool) config_cache('pixelfed.cloud_storage') && (bool) config_cache('media.delete_local_after_cloud')) { + $schedule->command('media:s3gc')->hourlyAt(15); + } + + if (config('import.instagram.enabled')) { + $schedule->command('app:transform-imports')->twiceDaily(13, 22)->onOneServer(); + $schedule->command('app:import-upload-garbage-collection')->hourlyAt(51)->onOneServer(); + $schedule->command('app:import-remove-deleted-accounts')->hourlyAt(37)->onOneServer(); + $schedule->command('app:import-upload-clean-storage')->twiceDailyAt(1, 13, 32)->onOneServer(); + + if (config('import.instagram.storage.cloud.enabled') && (bool) config_cache('pixelfed.cloud_storage')) { + $schedule->command('app:import-upload-media-to-cloud-storage')->hourlyAt(39)->onOneServer(); + } + } + + $schedule->command('app:notification-epoch-update')->weeklyOn(1, '2:21')->onOneServer(); + $schedule->command('app:hashtag-cached-count-update')->hourlyAt(25)->onOneServer(); + $schedule->command('app:account-post-count-stat-update')->everySixHours(25)->onOneServer(); + $schedule->command('app:instance-update-total-local-posts')->twiceDailyAt(1, 13, 45)->onOneServer(); + }) + ->withExceptions(function (Exceptions $exceptions) { + $exceptions->dontReport([ + OAuthServerException::class, + ConnectException::class, + ConnectionException::class, + ]); + + $exceptions->dontFlash([ + 'password', + 'password_confirmation', + ]); + + $exceptions->reportable(function (BadMethodCallException $e) { + return app()->environment() !== 'production'; + }); + + $exceptions->reportable(function (ConnectionException $e) { + return app()->environment() !== 'production'; + }); + + $exceptions->render(function (Throwable $e, $request) { + if ($request->wantsJson()) { + if ($e instanceof HttpResponseException) { + return $e->getResponse(); + } + + if ($e instanceof ValidationException) { + return response()->json([ + 'message' => $e->getMessage(), + 'errors' => $e->validator->getMessageBag(), + ], $e->status); + } + + $isHttp = $e instanceof HttpExceptionInterface; + + return response()->json( + ['error' => $e->getMessage()], + $isHttp ? $e->getStatusCode() : 500, + $isHttp ? $e->getHeaders() : [], + ); + } + }); + }) + ->create(); diff --git a/bootstrap/providers.php b/bootstrap/providers.php new file mode 100644 index 000000000..a1422ea4c --- /dev/null +++ b/bootstrap/providers.php @@ -0,0 +1,15 @@ + [ - - /* - * Laravel Framework Service Providers... - */ - Illuminate\Auth\AuthServiceProvider::class, - Illuminate\Broadcasting\BroadcastServiceProvider::class, - Illuminate\Bus\BusServiceProvider::class, - Illuminate\Cache\CacheServiceProvider::class, - Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, - Illuminate\Cookie\CookieServiceProvider::class, - Illuminate\Database\DatabaseServiceProvider::class, - Illuminate\Encryption\EncryptionServiceProvider::class, - Illuminate\Filesystem\FilesystemServiceProvider::class, - Illuminate\Foundation\Providers\FoundationServiceProvider::class, - Illuminate\Hashing\HashServiceProvider::class, - Illuminate\Mail\MailServiceProvider::class, - Illuminate\Notifications\NotificationServiceProvider::class, - Illuminate\Pagination\PaginationServiceProvider::class, - Illuminate\Pipeline\PipelineServiceProvider::class, - Illuminate\Queue\QueueServiceProvider::class, - Illuminate\Redis\RedisServiceProvider::class, - Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, - Illuminate\Session\SessionServiceProvider::class, - Illuminate\Translation\TranslationServiceProvider::class, - Illuminate\Validation\ValidationServiceProvider::class, - Illuminate\View\ViewServiceProvider::class, - - /* - * Package Service Providers... - */ - ProtoneMedia\LaravelFFMpeg\Support\ServiceProvider::class, - - /* - * Application Service Providers... - */ - App\Providers\AppServiceProvider::class, - App\Providers\AuthServiceProvider::class, - App\Providers\BroadcastServiceProvider::class, - App\Providers\HorizonServiceProvider::class, - App\Providers\EventServiceProvider::class, - App\Providers\RouteServiceProvider::class, - // App\Providers\TelescopeServiceProvider::class, - App\Providers\PassportServiceProvider::class, - - ], - /* |-------------------------------------------------------------------------- | Class Aliases @@ -180,44 +171,44 @@ return [ 'aliases' => [ 'App' => Illuminate\Support\Facades\App::class, - 'Artisan' => Illuminate\Support\Facades\Artisan::class, - 'Auth' => Illuminate\Support\Facades\Auth::class, - 'Blade' => Illuminate\Support\Facades\Blade::class, - 'Broadcast' => Illuminate\Support\Facades\Broadcast::class, - 'Bus' => Illuminate\Support\Facades\Bus::class, - 'Cache' => Illuminate\Support\Facades\Cache::class, - 'Config' => Illuminate\Support\Facades\Config::class, - 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\Facades\Crypt::class, - 'DB' => Illuminate\Support\Facades\DB::class, - 'Eloquent' => Illuminate\Database\Eloquent\Model::class, - 'Event' => Illuminate\Support\Facades\Event::class, - 'File' => Illuminate\Support\Facades\File::class, - 'Gate' => Illuminate\Support\Facades\Gate::class, - 'Hash' => Illuminate\Support\Facades\Hash::class, - 'Lang' => Illuminate\Support\Facades\Lang::class, - 'Log' => Illuminate\Support\Facades\Log::class, - 'Mail' => Illuminate\Support\Facades\Mail::class, - 'Notification' => Illuminate\Support\Facades\Notification::class, - 'Password' => Illuminate\Support\Facades\Password::class, - 'Queue' => Illuminate\Support\Facades\Queue::class, - 'Redirect' => Illuminate\Support\Facades\Redirect::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, - 'Request' => Illuminate\Support\Facades\Request::class, - 'Response' => Illuminate\Support\Facades\Response::class, - 'Route' => Illuminate\Support\Facades\Route::class, - 'Schema' => Illuminate\Support\Facades\Schema::class, - 'Session' => Illuminate\Support\Facades\Session::class, - 'Storage' => Illuminate\Support\Facades\Storage::class, - 'URL' => Illuminate\Support\Facades\URL::class, - 'Validator' => Illuminate\Support\Facades\Validator::class, - 'View' => Illuminate\Support\Facades\View::class, - 'Str' => Illuminate\Support\Str::class, - - 'PrettyNumber' => App\Util\Lexer\PrettyNumber::class, - 'Purify' => Stevebauman\Purify\Facades\Purify::class, - 'FFMpeg' => ProtoneMedia\LaravelFFMpeg\Support\FFMpeg::class, - 'Captcha' => Buzz\LaravelHCaptcha\CaptchaFacade::class, + 'Artisan' => Artisan::class, + 'Auth' => Auth::class, + 'Blade' => Blade::class, + 'Broadcast' => Broadcast::class, + 'Bus' => Bus::class, + 'Cache' => Cache::class, + 'Config' => Config::class, + 'Cookie' => Cookie::class, + 'Crypt' => Crypt::class, + 'DB' => DB::class, + 'Eloquent' => Model::class, + 'Event' => Event::class, + 'File' => File::class, + 'Gate' => Gate::class, + 'Hash' => Hash::class, + 'Lang' => Lang::class, + 'Log' => Log::class, + 'Mail' => Mail::class, + 'Notification' => Notification::class, + 'Password' => Password::class, + 'Queue' => Queue::class, + 'Redirect' => Redirect::class, + 'Redis' => Redis::class, + 'Request' => Request::class, + 'Response' => Response::class, + 'Route' => Route::class, + 'Schema' => Schema::class, + 'Session' => Session::class, + 'Storage' => Storage::class, + 'URL' => URL::class, + 'Validator' => Validator::class, + 'View' => View::class, + 'Str' => Str::class, + + 'PrettyNumber' => PrettyNumber::class, + 'Purify' => Purify::class, + 'FFMpeg' => FFMpeg::class, + 'Captcha' => CaptchaFacade::class, ], ]; diff --git a/routes/channels.php b/routes/channels.php index d38ae3ec5..7c1ff64fe 100644 --- a/routes/channels.php +++ b/routes/channels.php @@ -1,5 +1,7 @@