Migrate backend to Porto architecture and fix minor bugs

- Fully transitioned the backend to the Porto architectural pattern
- Improved code organization and maintainability
- Fixed minor bugs and inconsistencies
This commit is contained in:
F4ilji
2025-05-12 08:47:43 +05:00
parent 76deaf75f3
commit c01016a154
620 changed files with 16218 additions and 6073 deletions
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Ship\Middleware;
use App\Containers\AppStructure\Models\Page;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AccessCheck
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// Проверяем, существует ли запись для текущего маршрута
$registeredRoute = Page::where('path', '=', $request->route()->uri)
->where('is_registered', '=', true)
->first();
// Если запись не найдена, пропускаем запрос
if (!$registeredRoute) {
return $next($request);
}
// Если код не 200, возвращаем соответствующий код ошибки
if ($registeredRoute->code != 200) {
abort($registeredRoute->code);
}
$request->attributes->set('settings_page', $registeredRoute->settings);
// Если все проверки пройдены, продолжаем выполнение запроса
return $next($request);
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Abstracts\Middleware\Authenticate as AbstractMiddleware;
class Authenticate extends AbstractMiddleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return '/';
}
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Abstracts\Middleware\EncryptCookies as AbstractMiddleware;
class EncryptCookies extends AbstractMiddleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [
//
];
}
@@ -0,0 +1,28 @@
<?php
namespace App\Ship\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserIsSuperadmin
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next)
{
if (!Auth::check() || !Auth::user()->hasRole('super_admin')) {
return Inertia::render('Error', ['status' => 403])
->toResponse($request)
->setStatusCode(403);
}
return $next($request);
}
}
@@ -0,0 +1,48 @@
<?php
namespace App\Ship\Middleware;
use App\Containers\Widget\Models\CustomForm;
use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class FormTimePeriodMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$form = CustomForm::select('settings')->find($request->route('id'));
if (!$form) {
abort(Response::HTTP_NOT_FOUND, 'Form not found');
}
if (!isset($form->settings['period'])) {
return $next($request);
}
$period = $form->settings['period'];
try {
$start_time = Carbon::parse($period['start_time']);
$end_time = Carbon::parse($period['end_time']);
} catch (\Exception $e) {
abort(Response::HTTP_BAD_REQUEST, 'Invalid time format');
}
$now = Carbon::now();
if ($now >= $start_time && $now <= $end_time) {
return $next($request);
}
abort(Response::HTTP_FORBIDDEN, 'Form is not available at this time');
}
}
@@ -0,0 +1,35 @@
<?php
namespace App\Ship\Middleware;
use App\Containers\AppStructure\Models\Page;
use App\Ship\Resources\Breadcrumb\ClientBreadcrumbPage;
use App\Ship\Resources\Breadcrumb\ClientBreadcrumbSection;
use App\Ship\Resources\Breadcrumb\ClientBreadcrumbSubSection;
use Closure;
use Illuminate\Http\Request;
class GenerateBreadcrumbs
{
public function handle(Request $request, Closure $next)
{
$path = $request->path();
$page = Page::where('path', '=', $path)
->with('section.pages.section', 'section.mainSection')
->first();
if ($page && isset($page->section)) {
$breadcrumbs = [
'mainSection' => new ClientBreadcrumbSection($page->section->mainSection),
'subSection' => new ClientBreadcrumbSubSection($page->section),
'page' => new ClientBreadcrumbPage($page),
];
} else {
$breadcrumbs = null;
}
$request->merge(['breadcrumbs' => $breadcrumbs]);
return $next($request);
}
}
@@ -0,0 +1,55 @@
<?php
namespace App\Ship\Middleware;
use App\Containers\AppStructure\Models\MainSection;
use App\Containers\AppStructure\UI\API\Transformers\NavigationResource;
use App\Services\App\Breadcrumb\BreadcrumbService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Inertia\Middleware;
use Tightenco\Ziggy\Ziggy;
class HandleInertiaRequests extends Middleware
{
protected $rootView = 'app';
public function version(Request $request): ?string
{
return parent::version($request);
}
public function share(Request $request): array
{
// Навигация (кешированная)
$navigation = Cache::remember('navigation', now()->addHours(1), function () {
return NavigationResource::collection(
MainSection::with('subSections.pages.section')
->orderBy('sort', 'asc')
->get()
);
});
// Хлебные крошки (автоматически по текущему URL)
$breadcrumbs = app(BreadcrumbService::class)->generateBreadcrumbs();
return [
...parent::share($request),
'auth' => [
'user' => $request->user() ? $request->user()->only('id', 'name', 'email', 'created_at') : null,
],
'ziggy' => fn () => [
...(new Ziggy)->toArray(),
'location' => $request->url(),
],
'navigation' => $navigation,
'breadcrumbs' => $breadcrumbs, // Добавляем хлебные крошки
'urlPrev' => function () {
if (url()->previous() !== url()->current()) {
return url()->previous();
}
return 'empty';
},
];
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Ship\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class InternalRequestOnly
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next)
{
$userAgent = $request->header('User-Agent');
// Проверяем, что User-Agent содержит ключевые слова, характерные для браузеров
if (!preg_match('/Mozilla|Chrome|Safari|Firefox|Edge/i', $userAgent)) {
return response('Access denied. This route is available only from a web browser.', 403);
}
return $next($request);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Ship\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
class LimitPost
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (auth()->user()->receivedInvitation === null) {
return $next($request);
}
if (auth()->user()->receivedInvitation->post_limit > 0) {
return $next($request);
} else {
throw new HttpException(403, 'Лимит постов исчерпан');
}
}
}
@@ -0,0 +1,17 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Abstracts\Middleware\PreventRequestsDuringMaintenance as AbstractMiddleware;
class PreventRequestsDuringMaintenance extends AbstractMiddleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [
//
];
}
@@ -0,0 +1,27 @@
<?php
namespace App\Ship\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class RateLimitCheckMiddleware
{
public function handle(Request $request, Closure $next)
{
$ip = $request->ip();
$key = 'rate_limit:' . $ip;
// Получаем текущее количество попыток
$attempts = Cache::get($key, 0);
if ($attempts >= 5) {
return response()->json(['message' => 'Слишком много запросов, пожалуйста, попробуйте позже.'], 429);
}
// Увеличиваем количество попыток
return $next($request);
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Ship\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class RateLimitCounterMiddleware
{
public function handle(Request $request, Closure $next)
{
$ip = $request->ip();
$key = 'rate_limit:' . $ip;
$maxAttempts = 5; // Максимальное количество попыток
$decayMinutes = 1; // Время блокировки в минутах
// Получаем текущее количество попыток
$attempts = Cache::get($key, 0);
if ($attempts >= $maxAttempts) {
return response()->json(['message' => 'Слишком много запросов, пожалуйста, попробуйте позже.'], 429);
}
// Увеличиваем количество попыток
Cache::put($key, $attempts + 1, $decayMinutes * 60);
return $next($request);
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @param string|null ...$guards
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Requests\Request;
use Closure;
use Illuminate\Http\Request as LaravelRequest;
class TransformRequestMiddleware
{
public function handle(LaravelRequest $request, Closure $next)
{
// Преобразуем стандартный Request в ваш кастомный
$customRequest = Request::createFrom($request);
return $next($customRequest);
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Abstracts\Middleware\TrimStrings as AbstractMiddleware;
class TrimStrings extends AbstractMiddleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Abstracts\Middleware\TrustHosts as AbstractMiddleware;
class TrustHosts extends AbstractMiddleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts()
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Abstracts\Middleware\TrustProxies as AbstractMiddleware;
use Illuminate\Http\Request;
class TrustProxies extends AbstractMiddleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Abstracts\Middleware\ValidateSignature as AbstractMiddleware;
class ValidateSignature extends AbstractMiddleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [
// 'fbclid',
// 'utm_campaign',
// 'utm_content',
// 'utm_medium',
// 'utm_source',
// 'utm_term',
];
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Ship\Middleware;
use App\Ship\Abstracts\Middleware\VerifyCsrfToken as AbstractMiddleware;
class VerifyCsrfToken extends AbstractMiddleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
//
];
}