Reworked maintenance system. Also had to rework a few HTTP error related things because the 404 page wasn't being run through middleware.

This commit is contained in:
Graphictoria 2022-04-21 22:37:18 -04:00
parent 4a06844a53
commit 0e4cec86cc
11 changed files with 185 additions and 209 deletions

View File

@ -50,12 +50,15 @@ class Handler extends ExceptionHandler
return response()->view('errors.403', [], 403); return response()->view('errors.403', [], 403);
}); });
/*
// Moved to route fallback
$this->renderable(function (NotFoundHttpException $e, $request) { $this->renderable(function (NotFoundHttpException $e, $request) {
return response()->view('errors.404', [], 404); return response()->view('errors.404', [], 404);
}); });
// Moved to middleware
$this->renderable(function (\ErrorException $e, $request) { $this->renderable(function (\ErrorException $e, $request) {
return response()->view('errors.500', ['stack' => $e->getTraceAsString()], 500); return response()->view('errors.500', ['stack' => $e->getTraceAsString()], 500);
}); });*/
} }
} }

View File

@ -20,7 +20,7 @@ class Kernel extends HttpKernel
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class, \App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\Illuminate\Session\Middleware\StartSession::class \Illuminate\Session\Middleware\StartSession::class,
]; ];
/** /**
@ -30,6 +30,7 @@ class Kernel extends HttpKernel
*/ */
protected $middlewareGroups = [ protected $middlewareGroups = [
'web' => [ 'web' => [
\App\Http\Middleware\Exception\WebException::class,
\App\Http\Middleware\EncryptCookies::class, \App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class, \Illuminate\Session\Middleware\StartSession::class,
@ -39,22 +40,12 @@ class Kernel extends HttpKernel
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
], ],
'admin' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [ 'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
'throttle:api', 'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
], ],
]; ];

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware\Exception;
use Closure;
class WebException
{
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->exception) {
return response()->view('errors.500', ['stack' => $response->exception->getTraceAsString()], 500);
}
return $response;
}
}

View File

@ -3,139 +3,33 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Support\Facades\Route;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Http\MaintenanceModeBypassCookie;
use Symfony\Component\HttpKernel\Exception\HttpException;
class PreventRequestsDuringMaintenance class PreventRequestsDuringMaintenance
{ {
/** protected $app;
* The application implementation. public function __construct(Application $app)
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* The URIs that should be accessible while maintenance mode is enabled.
*
* @var array
*/
protected $except = ['maintenance', 'maintenance/bypass'];
/**
* Create a new middleware instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct(Application $app)
{ {
$this->app = $app; $this->app = $app;
} }
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function handle($request, Closure $next)
{
if ($this->app->isDownForMaintenance()) {
$data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true);
if ($this->hasValidBypassCookie($request, $data) ||
$this->inExceptArray($request)) {
return $next($request);
}
return response('{"errors":[{"code":503,"message":"ServiceUnavailable"}]}', 503) public function handle($request, Closure $next)
->header('Cache-Control', 'private') {
->header('Content-Type', 'application/json; charset=utf-8'); if($this->app->isDownForMaintenance()) {
} if(in_array('web', $request->route()->middleware()))
{
return $next($request); if($request->route()->uri() != 'maintenance')
} return redirect('/maintenance?ReturnUrl=' . urlencode(url()->full()));
}
/** else
* Determine if the incoming request has a maintenance mode bypass cookie. {
* return response(['errors' => [['code' => 503, 'message' => 'ServiceUnavailable']]], 503)
* @param \Illuminate\Http\Request $request ->header('Cache-Control', 'private')
* @param array $data ->header('Content-Type', 'application/json; charset=utf-8');
* @return bool }
*/ }
protected function hasValidBypassCookie($request, array $data)
{ return $next($request);
return isset($data['secret']) && }
$request->cookie('gt_constraint') &&
MaintenanceModeBypassCookie::isValid(
$request->cookie('gt_constraint'),
$data['secret']
);
}
/**
* Determine if the request has a URI that should be accessible in maintenance mode.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function inExceptArray($request)
{
foreach ($this->except as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}
if ($request->fullUrlIs($except) || $request->is($except)) {
return true;
}
}
return false;
}
/**
* Redirect the user back to the root of the application with a maintenance mode bypass cookie.
*
* @param string $secret
* @return \Illuminate\Http\RedirectResponse
*/
protected function bypassResponse(string $secret)
{
return redirect('/')->withCookie(
MaintenanceModeBypassCookie::create($secret)
);
}
/**
* Get the headers that should be sent with the response.
*
* @param array $data
* @return array
*/
protected function getHeaders($data)
{
$headers = isset($data['retry']) ? ['Retry-After' => $data['retry']] : [];
if (isset($data['refresh'])) {
$headers['Refresh'] = $data['refresh'];
}
return $headers;
}
/**
* Get the URIs that should be accessible even when maintenance mode is enabled.
*
* @return array
*/
public function getExcludedPaths()
{
return $this->except;
}
} }

View File

@ -0,0 +1,42 @@
<?php
namespace App\Providers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class MaintenanceServiceProvider extends ServiceProvider
{
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Blade::directive('live', function() {
return '<?php if(!app()->isDownForMaintenance()): ?>';
});
Blade::directive('endlive', function() {
return '<?php endif; ?>';
});
}
}

View File

@ -37,7 +37,7 @@ class RouteServiceProvider extends ServiceProvider
*/ */
public function boot() public function boot()
{ {
$this->configureRateLimiting(); //$this->configureRateLimiting();
$this->routes(function () { $this->routes(function () {
Route::domain('apis.' . env('APP_URL')) Route::domain('apis.' . env('APP_URL'))
@ -66,7 +66,7 @@ class RouteServiceProvider extends ServiceProvider
->group(base_path('routes/versioncompatibility.php')); ->group(base_path('routes/versioncompatibility.php'));
Route::domain('impulse.' . env('APP_URL')) Route::domain('impulse.' . env('APP_URL'))
->middleware('admin') ->middleware('web')
->namespace($this->namespace) ->namespace($this->namespace)
->group(base_path('routes/admin.php')); ->group(base_path('routes/admin.php'));
@ -82,15 +82,15 @@ class RouteServiceProvider extends ServiceProvider
}); });
} }
/** // /**
* Configure the rate limiters for the application. // * Configure the rate limiters for the application.
* // *
* @return void // * @return void
*/ // */
protected function configureRateLimiting() // protected function configureRateLimiting()
{ // {
RateLimiter::for('api', function (Request $request) { // RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); // return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
}); // });
} // }
} }

View File

@ -139,7 +139,7 @@ return [
/* /*
* Laravel Framework Service Providers... * Laravel Framework Service Providers...
*/ */
Illuminate\Auth\AuthServiceProvider::class, App\Providers\MaintenanceServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class, Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class, Illuminate\Cache\CacheServiceProvider::class,

View File

@ -4,10 +4,13 @@
@section('content') @section('content')
<div class="container graphictoria-center-vh"> <div class="container graphictoria-center-vh">
@env(['staging', 'local'])
<br />
@endenv
<x-card title="INTERNAL SERVER ERROR"> <x-card title="INTERNAL SERVER ERROR">
<x-slot name="body"> <x-slot name="body">
Oops, we ran into an issue while trying to process your request, please try again later in a few minutes. If the issue persists after a few minutes, please contact us at <a href="mailto:support@gtoria.net" class="fw-bold text-decoration-none">support@gtoria.net</a>. Oops, we ran into an issue while trying to process your request, please try again later in a few minutes. If the issue persists after a few minutes, please contact us at <a href="mailto:support@gtoria.net" class="fw-bold text-decoration-none">support@gtoria.net</a>.
@env(['production', 'staging']) @env(['staging', 'local'])
@if(isset($stack)) @if(isset($stack))
<div class="border border-primary bg-dark p-3 m-4"> <div class="border border-primary bg-dark p-3 m-4">
<code> <code>
@ -24,5 +27,8 @@
</div> </div>
</x-slot> </x-slot>
</x-card> </x-card>
@env(['staging', 'local'])
<br />
@endenv
</div> </div>
@endsection @endsection

View File

@ -31,18 +31,20 @@
<div class="footer mt-auto pt-3 text-center shadow-lg"> <div class="footer mt-auto pt-3 text-center shadow-lg">
<div class="container"> <div class="container">
<h4 class="fw-bold mb-0">Graphictoria</h4> <h4 class="fw-bold mb-0">Graphictoria</h4>
<p class="text-muted fw-bold mb-0 mt-1"> @live
@foreach($routes as $index => $route) <p class="text-muted fw-bold mb-0 mt-1">
@php @foreach($routes as $index => $route)
// HACK @php
$route = (object)$route; // HACK
@endphp $route = (object)$route;
<a class="text-decoration-none fw-normal" href="{{ url($route->location) }}"{{ $route->label == 'Blog' ? ' target="_blank"' : '' }}>{{ $route->label }}</a> @endphp
@if($index != array_key_last($routes)) <a class="text-decoration-none fw-normal" href="{{ url($route->location) }}"{{ $route->label == 'Blog' ? ' target="_blank"' : '' }}>{{ $route->label }}</a>
{{ ' | ' }} @if($index != array_key_last($routes))
@endif {{ ' | ' }}
@endforeach @endif
</p> @endforeach
</p>
@endlive
<hr class="mx-auto my-2 w-25"/> <hr class="mx-auto my-2 w-25"/>
<p class="text-muted fw-light m-0">Copyright © {{ \Carbon\Carbon::now()->format('Y') }} Graphictoria. All rights reserved.</p> <p class="text-muted fw-light m-0">Copyright © {{ \Carbon\Carbon::now()->format('Y') }} Graphictoria. All rights reserved.</p>
<p class="text-muted fw-light m-0">Graphictoria is not affiliated with, endorsed by, or sponsored by Roblox Corporation. The usage of this website signifies your acceptance of the <a class="text-decoration-none fw-normal" href="{{ url('/legal/terms-of-use') }}">Terms of Use</a> and our <a class="text-decoration-none fw-normal" href="{{ url('/legal/privacy-policy') }}">Privacy Policy</a>.</p> <p class="text-muted fw-light m-0">Graphictoria is not affiliated with, endorsed by, or sponsored by Roblox Corporation. The usage of this website signifies your acceptance of the <a class="text-decoration-none fw-normal" href="{{ url('/legal/terms-of-use') }}">Terms of Use</a> and our <a class="text-decoration-none fw-normal" href="{{ url('/legal/privacy-policy') }}">Privacy Policy</a>.</p>

View File

@ -18,55 +18,69 @@
<div class="navbar graphictoria-navbar fixed-top navbar-expand-md shadow-sm"> <div class="navbar graphictoria-navbar fixed-top navbar-expand-md shadow-sm">
<div class="container-md"> <div class="container-md">
<a class="navbar-brand" href="/"> @live
<img src="{{ asset('/images/logo.png') }}" alt="Graphictoria" width="43" height="43" draggable="false"/> <a class="navbar-brand" href="/">
</a> <img src="{{ asset('/images/logo.png') }}" alt="Graphictoria" width="43" height="43" draggable="false"/>
</a>
@else
<i class="navbar-brand">
<img src="{{ asset('/images/logo.png') }}" alt="Graphictoria" width="43" height="43" draggable="false"/>
</i>
@endlive
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#graphictoria-nav" aria-controls="graphictoria-nav" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#graphictoria-nav" aria-controls="graphictoria-nav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="graphictoria-nav"> <div class="collapse navbar-collapse" id="graphictoria-nav">
<ul class="navbar-nav me-auto"> <ul class="navbar-nav me-auto">
@foreach($routes as $route) @live
@php @foreach($routes as $route)
// HACK @php
$route = (object)$route; // HACK
@endphp $route = (object)$route;
<li class="nav-item"> @endphp
<a @class(['nav-link', 'active'=>str_starts_with(Request::path(), $route->location)]) href="{{ url('/' . $route->location) }}">{{ $route->label }}</a> <li class="nav-item">
</li> <a @class(['nav-link', 'active'=>str_starts_with(Request::path(), $route->location)]) href="{{ url('/' . $route->location) }}">{{ $route->label }}</a>
@endforeach </li>
<li class="nav-item dropdown"> @endforeach
<a class="nav-link dropdown-toggle" href="#" id="graphictoria-nav-dropdown" role="button" data-bs-toggle="dropdown" area-expanded="false">More</a> <li class="nav-item dropdown">
<ul class="dropdown-menu graphictoria-nav-dropdown" area-labelledby="graphictoria-nav-dropdown"> <a class="nav-link dropdown-toggle" href="#" id="graphictoria-nav-dropdown" role="button" data-bs-toggle="dropdown" area-expanded="false">More</a>
<li><a @class(['dropdown-item', 'active'=>str_starts_with(Request::path(), 'users')]) href="{{ url('/users') }}">Users</a></li> <ul class="dropdown-menu graphictoria-nav-dropdown" area-labelledby="graphictoria-nav-dropdown">
<li><a class="dropdown-item" href="https://discord.gg/q666a2sF6d" target="_blank" rel="noreferrer">Discord</a></li> <li><a @class(['dropdown-item', 'active'=>str_starts_with(Request::path(), 'users')]) href="{{ url('/users') }}">Users</a></li>
</ul> <li><a class="dropdown-item" href="https://discord.gg/q666a2sF6d" target="_blank" rel="noreferrer">Discord</a></li>
</li>
</ul>
@if($authenticated)
<div class="flex">
<p class="my-auto me-2 text-muted" style="color:#e59800!important;font-weight:bold">
<span data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tokens are Graphictoria's currency.">
<img src="{{ asset('images/symbols/token.svg') }}" height="20" width="20" class="img-fluid me-1" style="margin-top:-1px" />
123
</span>
</p>
<div class="dropdown">
<a class="nav-link dropdown-toggle graphictoria-user-dropdown" href="#" id="graphictoria-user-dropdown" role="button" data-bs-toggle="dropdown" area-expanded="false">
<span class="d-flex align-items-center">
<img src="{{ asset('images/testing/headshot.png') }}" class="img-fluid border me-1 graphictora-user-circle" width="37" height="37">
<p>Username</p>
</span>
</a>
<ul class="dropdown-menu graphictoria-user-dropdown" area-labelledby="graphictoria-user-dropdown">
<li><a class="dropdown-item" href="{{ url('/my/settings') }}">Settings</a></li>
<li><a class="dropdown-item" href="{{ url('/my/logout') }}">Logout</a></li>
</ul> </ul>
</li>
@else
<li class="nav-item">
<a class="nav-link" href="https://discord.gg/q666a2sF6d" target="_blank" rel="noreferrer">Discord</a>
</li>
@endlive
</ul>
@live
@if($authenticated)
<div class="flex">
<p class="my-auto me-2 text-muted" style="color:#e59800!important;font-weight:bold">
<span data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tokens are Graphictoria's currency.">
<img src="{{ asset('images/symbols/token.svg') }}" height="20" width="20" class="img-fluid me-1" style="margin-top:-1px" />
123
</span>
</p>
<div class="dropdown">
<a class="nav-link dropdown-toggle graphictoria-user-dropdown" href="#" id="graphictoria-user-dropdown" role="button" data-bs-toggle="dropdown" area-expanded="false">
<span class="d-flex align-items-center">
<img src="{{ asset('images/testing/headshot.png') }}" class="img-fluid border me-1 graphictora-user-circle" width="37" height="37">
<p>Username</p>
</span>
</a>
<ul class="dropdown-menu graphictoria-user-dropdown" area-labelledby="graphictoria-user-dropdown">
<li><a class="dropdown-item" href="{{ url('/my/settings') }}">Settings</a></li>
<li><a class="dropdown-item" href="{{ url('/my/logout') }}">Logout</a></li>
</ul>
</div>
</div> </div>
</div> @else
@else <a class="btn btn-success" href="/login">Login / Sign up</a>
<a class="btn btn-success" href="/login">Login / Sign up</a> @endif
@endif @endlive
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,3 +22,8 @@ Route::view('/javascript', 'javascript');
// client // client
Route::get('/asset', 'ContentController@fetchAsset'); Route::get('/asset', 'ContentController@fetchAsset');
// fallback
Route::fallback(function(){
return response()->view('errors.404', [], 404);
});