ldc-boutique-cnc-sable/app/Providers/RouteServiceProvider.php
Jérémy Munsch e857a58d00
test(Product): add comprehensive tests for Product entity
- Implement tests for product publication status.
- Add tests for product orderability based on various conditions.
- Enhance pricing calculation tests for different discount scenarios.
- Remove outdated PricingServiceTest and refactor ProductAvailabilityServiceTest.
2026-10-29 19:48:38 +01:00

224 lines
8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Providers;
use App\Dto\ProductWithPricingDto;
use App\Lib\Helpers\RequestHelper;
use App\Models\Discount;
use App\Models\Faq;
use App\Models\Product;
use App\Models\ProductInco;
use App\Models\Range;
use App\Models\Slide;
use App\Models\Tag;
use Domain\Ports\Repositories\ProductRepository;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Shared\Pagination\Pagination;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class RouteServiceProvider extends ServiceProvider
{
public const string ROUTE_GROUP_WEB = 'web';
public const string ROUTE_GROUP_WEB_CREDENTIALS = 'credentials';
public const string ROUTE_GROUP_WEB_AJAX = 'web-ajax';
public const string ROUTE_GROUP_API = 'api';
private const int RATE_LIMIT_PER_MINUTE_FOR_CREDENTIALS = 6;
private const int RATE_LIMIT_PER_MINUTE_FOR_WEB = 60;
private const int RATE_LIMIT_PER_MINUTE_FOR_AJAX = 60;
private const int RATE_LIMIT_PER_MINUTE_FOR_API = 60;
/**
* The path to the "home" route for your application.
*
* This is used by Laravel authentication to redirect users after login.
*
* @var string
*/
public const HOME = '/';
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
#[\Override]
public function boot()
{
$this->configureRateLimiting();
Request::macro('pagination', function (int $defaultPageSize = 25): Pagination {
/** @var \Illuminate\Http\Request $this */
return RequestHelper::pagination($this, $defaultPageSize);
});
$this->routes(function (): void {
Route::middleware([
self::ROUTE_GROUP_WEB,
'admin-secure',
'throttle:' . self::ROUTE_GROUP_WEB,
])->name('bo.')->prefix('bo')
->group(base_path('routes/web-bo.php'));
Route::middleware([
self::ROUTE_GROUP_WEB,
'throttle:' . self::ROUTE_GROUP_WEB,
])->name('fo.')
->group(base_path('routes/web-fo.php'));
Route::middleware([
self::ROUTE_GROUP_WEB_AJAX,
'throttle:' . self::ROUTE_GROUP_WEB_AJAX,
])->name('ajax.')->prefix('ajax')
->group(base_path('routes/ajax-fo.php'));
Route::middleware([
self::ROUTE_GROUP_API,
'throttle:' . self::ROUTE_GROUP_API,
])->name('api.')->prefix('api')
->group(base_path('routes/api.php'));
});
Route::model('inco', ProductInco::class);
Route::model('faq', Faq::class);
Route::model('tag', Tag::class);
Route::model('slide', Slide::class);
Route::model('range', Range::class);
Route::model('discount', Discount::class);
Route::model('product', Product::class);
Route::bind(
'productWithPricingPublished',
fn (string $value) => $this->resolvePublishedProductWithPricing($value)
);
$this->cspReportRoute();
}
/**
* Configure the rate limiters for the application.
*/
protected function configureRateLimiting(): void
{
RateLimiter::for(
self::ROUTE_GROUP_WEB_CREDENTIALS,
fn (Request $request) => Limit::perMinute(self::RATE_LIMIT_PER_MINUTE_FOR_CREDENTIALS)
->by($request->user()?->id ?: resolve('clientIp'))
);
RateLimiter::for(
self::ROUTE_GROUP_WEB,
fn (Request $request) => Limit::perMinute(self::RATE_LIMIT_PER_MINUTE_FOR_WEB)
->by($request->user()?->id ?: resolve('clientIp'))
);
RateLimiter::for(
self::ROUTE_GROUP_WEB_AJAX,
fn (Request $request) => Limit::perMinute(self::RATE_LIMIT_PER_MINUTE_FOR_AJAX)
->by($request->user()?->id ?: resolve('clientIp'))
);
RateLimiter::for(
self::ROUTE_GROUP_API,
fn (Request $request) => Limit::perMinute(self::RATE_LIMIT_PER_MINUTE_FOR_API)
->by($request->user()?->id ?: resolve('clientIp'))
);
}
private function resolvePublishedProductWithPricing(string $value): ProductWithPricingDto
{
$domainProductRepository = resolve(ProductRepository::class);
$product = Product::query()
->where('slug', $value)
->published()
->firstOrFail();
if (Gate::denies('viewPublished', $product)) {
throw new NotFoundHttpException();
}
$product->load(['inco', 'validDiscount', 'products']);
$domainProduct = $domainProductRepository->find($product->getId());
$pricing = $domainProduct?->pricing();
if (! $pricing) {
throw new NotFoundHttpException();
}
return new ProductWithPricingDto($product, $pricing);
}
/**
* * Register CSP report URI
*
* Put this function inside RouteServiceProvider,
* and call it in RouteServiceProvider::boot
*/
private function cspReportRoute(): void
{
// * CSP REPORT URI, Please add /csp-report to VerifyCsrfToken $except middleware
Route::post('/csp-report', function (Request $request): void {
$isCspRequest = in_array(
request()->header('Content-Type'),
[
'application/reports+json',
'application/csp-report',
]
);
abort_if(! $isCspRequest, 401);
$cspReport = (function (array $data): array {
// Format Reporting API moderne
if (isset($data[0]['type']) && $data[0]['type'] === 'csp-violation') {
return (array) ($data[0]['body'] ?? []);
}
// Format CSP classique
if (isset($data['csp-report'])) {
return (array) $data['csp-report'];
}
return [];
})((array) $request->json()->all());
// * Filter array keys
$cspReport = Collection::make([
'document-uri' => Arr::string($cspReport, 'documentURL', Arr::string($cspReport, 'document-uri', 'unknown')),
'referrer' => Arr::string($cspReport, 'referrer', ''),
'blocked-uri' => Arr::string($cspReport, 'blockedURL', Arr::string($cspReport, 'blocked-uri', '')),
'effective-directive' => Arr::string($cspReport, 'effectiveDirective', Arr::string($cspReport, 'effective-directive', 'unknown')),
'original-policy' => Arr::string($cspReport, 'originalPolicy', Arr::string($cspReport, 'original-policy', 'unknown')),
'disposition' => Arr::string($cspReport, 'disposition', 'enforce'),
'status-code' => Arr::integer(
$cspReport,
'statusCode',
Arr::integer($cspReport, 'status-code', 200)
),
'line-number' => Arr::integer($cspReport, 'lineNumber'),
'column-number' => Arr::integer($cspReport, 'columnNumber'),
'source-file' => Arr::string($cspReport, 'sourceFile'),
]);
Log::warning(sprintf(
"CSP REPORT : \n %s",
$cspReport->transform(fn ($value, $key) => "\t{$key} -> {$value}")->join("\n")
));
})->middleware('throttle:5,2')
->withoutMiddleware(PreventRequestForgery::class)
->name('cspReportUri');
}
}