- 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.
224 lines
8 KiB
PHP
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');
|
|
}
|
|
}
|