Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Jérémy Munsch
656120f042
TMP
- Arcadia Xml Jobs WIP
- Api WIP
2022-04-11 12:41:44 +02:00
Jérémy Munsch
e3318d652c
Add missing composer and NPM dependencies + gitignore commands 2022-04-05 09:45:12 +02:00
109 changed files with 21748 additions and 1708 deletions

View file

@ -1,8 +1,8 @@
APP_NAME=Laravel
APP_NAME=Incollector
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_URL=https://incollector.s-web.test
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null

47
.env.testing.example Normal file
View file

@ -0,0 +1,47 @@
APP_NAME=Incollector
APP_ENV=testing
APP_KEY=base64:oRXNjgvWgIcboJN2O0NgaBt3kMUUfgyQ1Wv0S9/7sAA=
APP_DEBUG=true
APP_URL="http://127.0.0.1:8888"
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=dev-mariadb
DB_PORT=3306
DB_DATABASE=testing-incollector
DB_USERNAME=
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=incollector@ldc.fr
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

13
.gitignore vendored
View file

@ -1,9 +1,22 @@
/node_modules
/public/hot
/public/storage
/public/css
/public/js
/public/images
/public/fonts
/public/medias
/public/uploads
/public/assets
/public/mix-manifest.json
.vscode
/storage/*.key
/storage/inco/*
/storage/marque/*
/vendor
.env
.env.testing
.env.backup
.phpunit.result.cache
docker-compose.override.yml

24
.vscode/settings.json vendored
View file

@ -1,23 +1,23 @@
{
"phpcs.executablePath": "vendor/bin/phpcs",
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#a714ac",
"activityBar.activeBorder": "#eb549a",
"activityBar.background": "#a714ac",
"activityBar.activeBackground": "#834329",
"activityBar.activeBorder": "#08180c",
"activityBar.background": "#834329",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#eb549a",
"activityBarBadge.foreground": "#15202b",
"sash.hoverBorder": "#a714ac",
"statusBar.background": "#0f437e",
"activityBarBadge.background": "#08180c",
"activityBarBadge.foreground": "#e7e7e7",
"sash.hoverBorder": "#834329",
"statusBar.background": "#5c2f1d",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#a714ac",
"statusBarItem.remoteBackground": "#0f437e",
"statusBarItem.hoverBackground": "#834329",
"statusBarItem.remoteBackground": "#5c2f1d",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#0f437e",
"titleBar.activeBackground": "#5c2f1d",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#0f437e99",
"titleBar.inactiveBackground": "#5c2f1d99",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.remoteColor": "#0f437e"
"peacock.remoteColor": "#5c2f1d"
}

View file

@ -0,0 +1,78 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
/**
* Command Name: LDC Web Status Checker
* Description: Envoi des infos Serveur/Laravel vers LDC Web
* Version: 1.1
* Author: Vincent BRIDIER
* Author URI: vincent.bridier@ldc.fr
*
* @codeCoverageIgnore
*/
class LdcWebStatusChecker extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ldcweb:statuschecker';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Envoi des infos Serveur/Laravel vers LDC Web';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$data = [];
$data['uuid'] = config('ldcweb.project_uuid');
// Sitemap.xml ?
if (file_exists(public_path() . '/sitemap.xml')) {
$data['sitemap'] = 'true';
} else {
$data['sitemap'] = 'false';
}
// Robots.txt ?
if (file_exists(public_path() . '/robots.txt')) {
$data['robots'] = 'true';
} else {
$data['robots'] = 'false';
}
// Check logs.
$data['logs'] = 0;
$patherror = storage_path('/logs');
$files = glob($patherror . '/*.log');
if (count($files) > 0) {
foreach ($files as $file) {
$data['logs'] = $data['logs'] + filesize($file);
}
}
$data['code']['version'] = app()->version();
// Send by cURL.
$url = 'https://ldc-web.ldc.fr/api/status-checker';
$ch = curl_init($url);
$payload = ['token' => 'CE8S52Qq7stf5k8frF4T', 'datas' => json_encode($data)];
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
return 0;
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
/**
* @codeCoverageIgnore
*/
class UserCreate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'user:create';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*
* @return integer
*/
public function handle()
{
$firstname = $this->ask('What is the firstname?');
$lastname = $this->ask('What is the lastname?');
$email = $this->ask('What is the email?');
$pwd = $this->secret('What is the password?');
$user = new User();
$user->firstname = $firstname;
$user->lastname = $lastname;
$user->email = $email;
$user->password = $pwd;
$user->saveOrFail();
$this->info('User created.');
return 0;
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace App\Console\Commands;
use App\Jobs\Arcadia\XmlBrandReaderJob;
use Illuminate\Console\Command;
class XmlBrandReader extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'xml:brand';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the XmlBrandReaderJob manually';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->info('Running the Job, this could take some time.');
$job = new XmlBrandReaderJob();
$job->console = $this;
$job->handle();
$this->info('Job completed');
}
}

View file

@ -2,12 +2,11 @@
namespace App\Console\Commands;
use App\Jobs\XmlIncoReader as JobsXmlIncoReader;
use App\Jobs\Arcadia\XmlIncoReaderJob;
use Illuminate\Console\Command;
class XmlIncoReader extends Command
{
/**
* The name and signature of the console command.
*
@ -30,7 +29,7 @@ class XmlIncoReader extends Command
public function handle()
{
$this->info('Running the Job, this could take some time.');
$job = new JobsXmlIncoReader();
$job = new XmlIncoReaderJob();
$job->console = $this;
$job->handle();
$this->info('Job completed');

View file

@ -2,6 +2,7 @@
namespace App\Console;
use App\Console\Commands\LdcWebStatusChecker;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -12,20 +13,17 @@ class Kernel extends ConsoleKernel
*
* @var array
*/
protected $commands = [
//
];
protected $commands = [];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')->hourly();
//$schedule->job(new XmlIncoReader)->weeklyOn(1, '8:00');
$schedule->command(LdcWebStatusChecker::class)->everyFifteenMinutes();
}
/**
@ -35,8 +33,6 @@ class Kernel extends ConsoleKernel
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
$this->load(__DIR__ . '/Commands');
}
}

84
app/Enums/ApiError.php Normal file
View file

@ -0,0 +1,84 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* This describes an API error.
*
* @method static self modelNotFound()
* The request model was not found (typically the customer)
*
* @method static self validationFailed()
* This means some data could not be validated,
* you may have to look into message to get the details
*
* @method static self apiAuthFailed()
* The authentification to the api failed. You may have to
* check into config or .env file for your service credentials
*
* @method static self methodNotAllowed()
* This http method is not allowed
*
* @method static self wrongTimeStamp()
* The timestamp given is wrong. This would happen in case if
* a replay attack scenario. Timestamps are provided in headers
* and in the http body
*
* @method static self missingParameter()
* The request is missing some parameters
*
* @method static self throttleRequestsException()
* Too many requests have been attempted. The is a throttle security.
* Normally you should not trigger this.
* If this happens fill a ticket on https://supportweb.ldc.fr
* with some technical details
*
* @method static self authorizationException()
* You are maybe trying to access data that is out of your range.
* This should not happen normally, it is here to allow you to handle this case.
* If this happens fill a ticket on https://supportweb.ldc.fr
* with some technical details
*
*/
class ApiError extends BaseEnumRoutable
{
/**
* Valeurs des possibilités de cet énumération (value)
*
* @return array
*/
public static function values(): array
{
return [
'modelNotFound' => 101,
'validationFailed' => 102,
'apiAuthFailed' => 119,
'methodNotAllowed' => 150,
'wrongTimeStamp' => 151,
'missingParameter' => 400,
'authorizationException' => 403,
'throttleRequestsException' => 429
];
}
/**
* Libellés des possibilités de cet énumération (label)
*
* @return array
*/
public static function labels(): array
{
return [
'modelNotFound' => '%s introuvable.',
'validationFailed' => 'Impossible de valider les données',
'apiAuthFailed' => 'Echec de l\'authentification client à l\'Api LDC-CRM pour ce service',
'methodNotAllowed' => 'This method is not allowed for this request',
'wrongTimeStamp' => 'Your request is malformed, please check timestamp header and parameter',
'missingParameter' => 'The request is missing some parameters',
'authorizationException' => 'You are not autorized to access this',
'throttleRequestsException' => 'Throttle: too many request have been attempted, please retry later'
];
}
}

16
app/Enums/ApiStatus.php Normal file
View file

@ -0,0 +1,16 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self OK()
* @method static self KO()
*
* @package LDCCrmClient
* @subpackage enums
*/
class ApiStatus extends BaseEnumRoutable
{
}

View file

@ -2,8 +2,8 @@
namespace App\Exceptions;
use Exception;
use ErrorException;
class AbortXmlImportException extends Exception
class AbortXmlImportException extends ErrorException
{
}

View file

@ -0,0 +1,101 @@
<?php
namespace App\Exceptions\Api;
use App\Enums\ApiError;
use Exception;
use Illuminate\Support\Facades\Log;
use Throwable;
/**
* This would be thrown if any of the case
* referenced in \App\Enums\ApiError would
* occur within the crm
*
* On the client an ApiErrorException would
* also be throw with the ApiError corresponding to
* the situation
*/
class ApiErrorException extends Exception
{
/**
* Api Error
* This value makes the error more explicit.
* Handling these errors are normal as inputs or
* request may be undesired or refused by the crm.
*
* @var \App\Enums\ApiError
*/
protected $error;
/**
* This is an explicit error message,
* ou may print this to the front or not depending
* on $error value
*
* @var string
*/
protected $message;
/**
* This is the http code returned by the crm.
* This value could be used to handle errors.
* But please take in consideration $error first
*
* @var integer
*/
protected $httpCode;
/**
* Create a new exception base on ApiError enum.
*
* @param ApiError $error
* @param integer $httpCode
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(ApiError $error, int $httpCode = 400, string $message = '', Throwable $previous = null)
{
$this->error = $error;
$this->message = $message;
$this->httpCode = $httpCode;
parent::__construct(
strlen($this->message) ? $this->message : $error->label,
$error->value,
$previous
);
}
/**
* Get the Api Error
*
* @return ApiError
*/
public function getApiError(): ApiError
{
return $this->error;
}
/**
* Get the http code
*
* @return integer
*/
public function getStatusCode(): int
{
return $this->httpCode;
}
/**
* This is used by laravel only.
* It uses Log class to print the error.
*
* @return void
*/
public function report()
{
// @codeCoverageIgnoreStart
Log::debug(sprintf('CRM API: %s', $this->getMessage()));
// @codeCoverageIgnoreEnd
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Exceptions\Api;
use Exception;
use Illuminate\Support\Facades\Log;
/**
* This is a critical error.
* The request could not be handled. You may dump any value you can
* get before submitting a ticket to https://supportweb.ldc.fr
*/
class ApplicationException extends Exception
{
/**
* This is used by laravel only.
* It uses Log class to print the error.
*
* @return void
*/
public function report()
{
Log::critical(sprintf('API: %s', $this->getMessage()));
}
}

View file

@ -2,7 +2,20 @@
namespace App\Exceptions;
use App\Enums\ApiError;
use App\Enums\ApiStatus;
use App\Exceptions\Api\ApiErrorException;
use App\Http\Resources\Api\V1\ApiResponseResource;
use App\Lib\Helper;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Kwaadpepper\ExceptionHandler\Exceptions\ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Throwable;
class Handler extends ExceptionHandler
@ -10,19 +23,18 @@ class Handler extends ExceptionHandler
/**
* A list of the exception types that are not reported.
*
* @var string[]
* @var array
*/
protected $dontReport = [
//
ApiErrorException::class
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var string[]
* @var array
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
@ -34,8 +46,125 @@ class Handler extends ExceptionHandler
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
}
/**
* Render an exception into an HTTP response.
*
* @param mixed $request
* @param \Throwable $e
* @return \Symfony\Component\HttpFoundation\Response
*/
public function render($request, Throwable $e)
{
if (Helper::isRequestForApiV1($request) and $request->isJson()) {
return $this->handleApiV1Exception($request, $e);
} //end if
// @codeCoverageIgnoreStart
return parent::render($request, $e);
// @codeCoverageIgnoreEnd
}
/**
* Generate a json response for any apiV2 error case
*
* @param Request $request
* @param \Throwable $e
* @return JsonResponse
*/
private function handleApiV1Exception(Request $request, \Throwable $e): JsonResponse
{
switch (get_class($e)) {
case AuthenticationException::class:
/** @var AuthenticationException $e */
return $this->getReponse(
$request,
401,
ApiError::apiAuthFailed()->value,
sprintf('%s, %s', ApiError::apiAuthFailed()->label, $e->getMessage())
);
case MethodNotAllowedHttpException::class:
/** @var MethodNotAllowedHttpException $e */
return $this->getReponse(
$request,
$e->getStatusCode(),
ApiError::methodNotAllowed()->value,
sprintf('%s, %s', ApiError::methodNotAllowed()->label, $e->getMessage())
);
case ValidationException::class:
/** @var ValidationException $e */
return $this->getReponse(
$request,
$e->status,
ApiError::validationFailed()->value,
sprintf(ApiError::validationFailed()->label, $e->getMessage()),
['errors' => $e->errors()]
);
case ModelNotFoundException::class:
/** @var ModelNotFoundException $e */
return $this->getReponse(
$request,
404,
ApiError::modelNotFound()->value,
sprintf(
ApiError::modelNotFound()->label,
\array_slice(explode('\\', $e->getModel()), -1)[0]
)
);
case ApiErrorException::class:
/** @var ApiErrorException $e */
return $this->getReponse(
$request,
$e->getStatusCode(),
$e->getCode(),
$e->getMessage()
);
case ThrottleRequestsException::class:
/** @var ThrottleRequestsException $e */
return $this->getReponse(
$request,
429,
ApiError::throttleRequestsException()->value,
ApiError::throttleRequestsException()->label
);
case AuthorizationException::class:
/** @var AuthorizationException $e */
return $this->getReponse(
$request,
403,
ApiError::authorizationException()->value,
ApiError::authorizationException()->label
);
} //end switch
// @codeCoverageIgnoreStart
if (config('app.env') === 'testing') {
// phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found
print_r((string)$e);
}
return $this->getReponse($request, 500, $e->getCode(), $e->getMessage());
// @codeCoverageIgnoreEnd
}
/**
* Generate an ApiV2 json response
*
* @param Request $request
* @param integer $httpCode
* @param integer $apiCode
* @param string $message
* @param array $data
* @return JsonResponse
*/
private static function getReponse(
Request $request,
int $httpCode,
int $apiCode,
string $message,
array $data = []
): JsonResponse {
return (new ApiResponseResource(
(object)array_merge($data, ['message' => $message, 'code' => $apiCode]),
ApiStatus::KO()
))->toResponse($request)->setStatusCode($httpCode);
}
}

View file

@ -2,8 +2,8 @@
namespace App\Exceptions;
use Exception;
use ErrorException;
class InvalidXmlException extends Exception
class InvalidXmlException extends ErrorException
{
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
class ApiController extends Controller
{
/**
* Just returns HTTP 200 response
*
* @return JsonResponse
*/
public function ping(): JsonResponse
{
return response()->json();
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\XmlIncosIndexRequest;
use App\Http\Resources\Api\V1\XmlIncoResource;
use App\Models\XmlInco;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
class XmlIncoController extends Controller
{
/**
* Returns xmlIncos.
*
* @param XmlIncosIndexRequest $request
* - autorised is 'brand' with id
* @return JsonResource
*/
public function index(XmlIncosIndexRequest $request): ResourceCollection
{
/** @var \App\Models\ArcadiaBrand|null $arcadiaBrand */
$arcadiaBrand = $request->arcadiaBrand;
$xmlIncos = XmlInco::query();
if ($arcadiaBrand) {
// * Make sure we are accessing xmlIncos that belongs to our service.
$this->authorize('arcadiaBrandBelongsToService', $arcadiaBrand);
$xmlIncos->whereHas('arcadiaBrands', function ($q) use ($arcadiaBrand) {
$q->whereId($arcadiaBrand->id);
});
}
return XmlIncoResource::collection($xmlIncos->simplePaginate(
$request->per_page,
['*'],
'page',
$request->page
)->items());
}
/**
* Return a specific xmlInco.
*
* @param XmlInco $xmlInco
* @return JsonResource
*/
public function show(XmlInco $xmlInco): JsonResource
{
// * Make sure we are accessing a xmlInco that belongs to our service.
$this->authorize('arcadiaBrandBelongsToService', $xmlInco->arcadiaBrand);
return new XmlIncoResource($xmlInco);
}
}

View file

@ -9,5 +9,7 @@ use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
}

View file

@ -14,12 +14,12 @@ class Kernel extends HttpKernel
* @var array
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\App\Http\Middleware\XssSanitizer::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
@ -33,16 +33,18 @@ class Kernel extends HttpKernel
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\JsonOnly::class,
\App\Http\Middleware\ApiResponse::class,
\App\Http\Middleware\ApiCheckTimeStamps::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class
],
];

View file

@ -0,0 +1,38 @@
<?php
namespace App\Http\Middleware;
use App\Enums\ApiError;
use App\Exceptions\Api\ApiErrorException;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
/**
* Check Http Header Timestamp with request timestamp
* to prevent replay attacks
* https://restfulapi.net/security-essentials/
*/
class ApiCheckTimeStamps
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
* @throws ApiErrorException With ApiError::wrongTimeStamp if timestamp has an incorrect format or is timed out.
*/
public function handle(Request $request, \Closure $next)
{
if (!\is_string($request->timestamp) or !Carbon::hasFormat($request->timestamp, 'Y-m-d H:i:s')) {
throw new ApiErrorException(ApiError::wrongTimeStamp());
}
if (
Carbon::parse($request->timestamp, 'utc')
->isBefore(Carbon::now('UTC')->subSeconds(config('api.timestampTimeout', 10)))
) {
throw new ApiErrorException(ApiError::wrongTimeStamp());
}
return $next($request);
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace App\Http\Middleware;
use App\Enums\ApiStatus;
use App\Http\Resources\Api\V1\ApiResponseResource;
use App\Models\Service;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ApiResponse
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
* @throws \ErrorException If the received response is not a JsonResponse.
*/
public function handle(Request $request, \Closure $next)
{
/** @var \Illuminate\Http\JsonResponse $response */
$response = $next($request);
if (\get_class($response) !== JsonResponse::class) {
// @codeCoverageIgnoreStart
throw new \ErrorException('All api responses should be json.');
// @codeCoverageIgnoreEnd
}
// Set no cache header.
$response->header('Cache-Control', 'No-Store');
/** @var \App\Models\Service|null */
if (\optional($service = app(Service::class))->id) {
$service->incrementStat();
}
/** @var object $data */
$data = $response->getData();
// If reponse is already a V2ApiResponse then just return it. This happens if error handler comes to action.
if (
isset($data->status) and
isset($data->data) and
isset($data->version) and
in_array($data->status, [ApiStatus::OK()->value, ApiStatus::KO()->value])
) {
return $response;
}
/** @var \App\Enums\ApiStatus $status */
$status = intval($response->getStatusCode() / 100) == 2 ? ApiStatus::OK() : ApiStatus::KO();
return $response->setData(new ApiResponseResource((object)$response->getData(), $status));
}
}

View file

@ -9,7 +9,7 @@ class Authenticate extends Middleware
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)

View file

@ -12,6 +12,5 @@ class EncryptCookies extends Middleware
* @var array
*/
protected $except = [
//
];
];
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
class JsonOnly
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, \Closure $next)
{
if (!$request->isJson() or !$request->expectsJson() or is_null(json_decode($request->getContent()))) {
abort(400, 'Invalid JSON');
}
return $next($request);
}
}

View file

@ -12,6 +12,5 @@ class PreventRequestsDuringMaintenance extends Middleware
* @var array
*/
protected $except = [
//
];
];
}

View file

@ -12,9 +12,9 @@ class RedirectIfAuthenticated
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null ...$guards
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null ...$guards
* @return mixed
*/
public function handle(Request $request, Closure $next, ...$guards)

View file

@ -17,7 +17,7 @@ class TrustProxies extends Middleware
/**
* The headers that should be used to detect proxies.
*
* @var int
* @var integer
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |

View file

@ -12,6 +12,5 @@ class VerifyCsrfToken extends Middleware
* @var array
*/
protected $except = [
//
];
];
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
class XssSanitizer
{
/**
* Handle an incoming request.
*
* @param mixed $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$input = $request->all();
array_walk_recursive($input, function (&$input) {
$input = strip_tags($input);
});
$request->merge($input);
return $next($request);
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Api\V1;
use Illuminate\Foundation\Http\FormRequest;
class PaginatorRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'page' => 'sometimes|numeric|min:1',
'per_page' => 'sometimes|numeric|min:1|max:100',
];
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace App\Http\Requests\Api\V1;
use App\Models\ArcadiaBrand;
class XmlIncosIndexRequest extends PaginatorRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
$this->merge([
'brand' => $this->brand ? ArcadiaBrand::whereId($this->brand)->firstOrFail() : null
]);
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return \array_merge(
parent::rules(),
[
'brand' => 'nullable'
]
);
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Resources\Api\V1;
use App\Enums\ApiStatus;
use Illuminate\Http\Resources\Json\JsonResource;
use stdClass;
class ApiResponseResource extends JsonResource
{
/** @var stdClass */
protected $data;
/** @var \App\Enums\ApiStatus */
protected $status;
/**
* @param stdClass $data
* @param ApiStatus $status
*/
public function __construct(stdClass $data = null, ApiStatus $status)
{
$this->status = $status;
$this->data = $data;
}
/**
* Transform the resource into an array.
*
* @param mixed $request
* @return array
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing
public function toArray($request): array
{
return [
'status' => $this->status->value,
'data' => (array)$this->data,
'version' => config('api.version')
];
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace App\Http\Resources\Api\V1;
use App\Models\XmlInco;
use Illuminate\Http\Resources\Json\JsonResource;
class XmlIncoResource extends JsonResource
{
/** @var array */
private static $skeleton = [
'id' => '',
'no_art' => '',
'prod_saison' => '',
'allegations' => '',
'conseil_prepa' => '',
'astuces' => '',
'descriptif' => '',
'arg_vente' => '',
'nb_portion' => '',
'typ_portion' => '',
'pds_moy_theo' => '',
'lib_conso' => '',
'lst_ing' => '',
'lib_allergene' => '',
'nutritional_values' => '',
'pictures' => '',
];
/**
* XmlIncoResource
*
* @param XmlInco $xmlInco
*/
public function __construct(XmlInco $xmlInco)
{
$this->xmlInco = $xmlInco;
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request|null $request
* @return array
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray($request = null)
{
$data = array_intersect_key(
$this->xmlInco->toArray(),
$this->getSkeleton()
);
return $data;
}
/**
* Returns the resource skeletton
*
* @return array
*/
public static function getSkeleton(): array
{
return self::$skeleton;
}
}

View file

@ -1,9 +1,8 @@
<?php
namespace App\Jobs;
namespace App\Jobs\Arcadia;
use App\Enums\LogType;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -30,9 +29,23 @@ abstract class XArcadiaFlux implements ShouldQueue
/**
* This is for Laravel Console usage
* @var \Illuminate\Console\Command|null
*/
public $console = null;
/**
* This is for Laravel Console usage
* @var \Symfony\Component\Console\Helper\ProgressBar|null
*/
public $progressBar = null;
/**
* invalid xml folder
*
* @var string
*/
protected $invalidFolderName = 'invalid';
/**
* The relative folder where to archive data
*
@ -53,6 +66,7 @@ abstract class XArcadiaFlux implements ShouldQueue
* Check if the file extension
*
* @param string $filename
* @param string $extension
* @return boolean
*/
protected function checkExtension(string $filename, string $extension): bool
@ -69,6 +83,81 @@ abstract class XArcadiaFlux implements ShouldQueue
return $isExtension;
}
/**
* Move the file to invalid folder
*
* @param string $filename
* @return void
*/
protected function moveToInvalid(string $filename): void
{
$newFileName = $filename;
if (!$this->getDisk()->exists($filename)) {
$this->log(
sprintf(
'File %s could not be moved to invalid (not found)',
$filename
),
LogType::warning()
);
return;
}
if ($this->getDisk()->exists(sprintf('%s/%s', $this->invalidFolderName, $filename))) {
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
$newFileName = \sprintf(
'%s_%d.%s',
\substr($filename, 0, 0 - (strlen($ext) + 1)),
\time(),
$ext
);
}
try {
if (!$this->getDisk()->move($filename, sprintf('%s/%s', $this->invalidFolderName, $newFileName))) {
$this->log(
sprintf(
'File %s could not be moved to invalid',
$filename
),
LogType::warning()
);
}
} catch (\Exception $e) {
$this->log(
sprintf(
'File %s could not be moved to invalid',
$filename
),
LogType::warning()
);
}
}
/**
* Suppression des fichiers dans
* invalid qui ont plus de 6 mois
*
* @return void
*/
protected function clearOldInvalids(): void
{
$disk = $this->getDisk();
$invalids = $disk->files($this->invalidFolderName);
// /** @var File */
foreach ($invalids as $invalid) {
$pi = \pathinfo($invalid, \PATHINFO_BASENAME);
if (in_array($pi, $this->ignoreFiles)) {
continue;
}
if ($disk->lastModified($invalid) < strtotime(Carbon::now()->subMonth(6))) {
if (!$disk->delete($invalid)) {
Log::critical(sprintf('%s : Could not delete file %s', get_class($this), $invalid));
};
}
}
}
/**
* Move the file to archives
*
@ -107,7 +196,7 @@ abstract class XArcadiaFlux implements ShouldQueue
LogType::warning()
);
}
} catch (Exception $e) {
} catch (\Exception $e) {
$this->log(
sprintf(
'File %s could not be moved to archives',
@ -160,6 +249,7 @@ abstract class XArcadiaFlux implements ShouldQueue
* @param string $message
* @param LogType $logtype
* @return void
* @throws \ErrorException If logtype is not handled.
*/
protected function log(string $message, LogType $logtype = null): void
{
@ -187,8 +277,8 @@ abstract class XArcadiaFlux implements ShouldQueue
Log::critical($message);
break;
default:
throw new Exception(sprintf('%s : unhandled log type %s', get_class($this), $logtype->label));
}//end switch
throw new \ErrorException(sprintf('%s : unhandled log type %s', get_class($this), $logtype->label));
} //end switch
$this->consolePrint($message, $logtype);
}
@ -198,9 +288,11 @@ abstract class XArcadiaFlux implements ShouldQueue
* @param string $message
* @param LogType $logtype
* @return void
* @throws \ErrorException If logtype is not handled.
*/
protected function consolePrint(string $message, LogType $logtype): void
{
$this->clearProgressBarFromLine();
if ($this->console) {
switch ($logtype->value) {
case LogType::debug()->value:
@ -216,8 +308,59 @@ abstract class XArcadiaFlux implements ShouldQueue
$this->console->error($message);
break;
default:
throw new Exception(sprintf('%s : unhandled log type %s', get_class($this), $logtype->label));
throw new \ErrorException(sprintf('%s : unhandled log type %s', get_class($this), $logtype->label));
}
}
}
/**
* Start ProgressBar
*
* @param integer $total
* @return void
*/
protected function startProgressBar(int $total)
{
if ($this->console) {
$this->progressBar = $this->console->getOutput()
->createProgressBar($total - 1);
$this->progressBar->start();
}
}
/**
* Clear the line to write someting
*
* @return void
*/
protected function clearProgressBarFromLine()
{
if ($this->progressBar) {
$this->progressBar->clear();
}
}
/**
* Increment 1 on progress bar
*
* @return void
*/
protected function incrementProgressBar()
{
if ($this->progressBar) {
$this->progressBar->advance();
}
}
/**
* End the progress bar
*
* @return void
*/
protected function endProgressBar()
{
if ($this->progressBar) {
$this->progressBar->finish();
}
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace App\Jobs\Arcadia;
use App\Models\ArcadiaBrand;
class XmlBrandReaderJob extends XmlJobs
{
/**
* The xsd file path used to validate files
*
* @var string|null
*/
protected $xsdSchemaPath = 'resources/documents/arcadia/marque.xsd';
/**
* Disk name from config/filesystems.php
*
* @var string
*/
protected $disk = 'arcadia_brand';
/**
* @var array Columns to take from the xml.
*/
protected $takeColumns = [
'codMarq' => 'arcadia_id',
'libMarq' => 'libelle',
];
/**
* Fields that are allowed to be null
*
* @var array
*/
protected $nullableFields = [];
/**
* Unique keys for updating rows
*
* @var array
*/
protected $uniqueKeyField = ['arcadia_id'];
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->parseData('dam', ArcadiaBrand::class);
}
}

View file

@ -1,20 +1,36 @@
<?php
namespace App\Jobs;
namespace App\Jobs\Arcadia;
use App\Exceptions\AbortXmlImportException;
use App\Models\XmlInco;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use SimpleXMLElement;
class XmlIncoReader extends XmlJobs
class XmlIncoReaderJob extends XmlJobs
{
/**
* The xsd file path used to validate files
*
* @var string|null
*/
protected $xsdSchemaPath = 'resources/documents/inco/incollector.xsd';
/**
* Disk name from config/filesystems.php
*
* @var string
*/
protected $disk = 'inco';
/**
* Storage path
*
* @var string
*/
protected $imagesPath = 'images';
protected $xsdFile = 'xsd/incollector.xsd';
/**
* éléments à la racine : nom du noeud
* sous éléments d'un noeud PARENT :
@ -29,31 +45,35 @@ class XmlIncoReader extends XmlJobs
* 'EXPLOITANT' => 'exploitant', // Consider this sub array
* 'EXPLOITANT.ID_SOCIETE' => 'id_societe',
* 'EXPLOITANT.NOM_SOCIETE' => 'nom_societe',
* @var array Columns to take from the xml.
*/
protected $takeColumns = [
'NO_ART' => 'id', // primary key on model
//this wil not return anything cf doc above
'NO_ART' => 'id',
// primary key on model
// this wil not return anything cf doc above
'ID_SOCIETE' => 'id_societe',
'NOM_SOCIETE' => 'nom_societe',
'PROD_SAISON' => 'prod_saison',
'NOM' => 'nom',
'NUMERO' => 'numero',
'TAILLE_REF' => 'taille_ref', //$poidsRef censé être dans INCO_COMPO (et généralement 100g)
'TAILLE_REF' => 'taille_ref',
// $poidsRef censé être dans INCO_COMPO (et généralement 100g)
'ALLEGATIONS' => 'allegations',
'CONSEIL_PREPA' => 'conseil_prepa',
'ASTUCES' => 'astuces',
'DESCRIPTIF' => 'descriptif', //$descriptif
'DESCRIPTIF' => 'descriptif',
// $descriptif
'ARG_VENTE' => 'arg_vente',
'NB_PORTION' => 'nb_portion',
'TYP_PORTION' => 'typ_portion',
'PDS_MOY_THEO' => 'pds_moy_theo',
'LIB_CONSO' => 'lib_conso',
'LST_ING' => 'lst_ing', //$ingredients
'LST_ING' => 'lst_ing',
// $ingredients
// list alergene
'LST_ALLERGENE' => 'lst_allergene', // Consider this sub array
'LST_ALLERGENE' => 'lst_allergene',
// Consider this sub array
'LST_ALLERGENE.LIB_ALLERGENE' => 'lib_allergene',
// Allegations
@ -62,11 +82,13 @@ class XmlIncoReader extends XmlJobs
'ALLEGAT_AUTRE' => 'allegat_autre',
'INCO_COMPO' => 'inco_compo', // Consider this sub array
'INCO_COMPO' => 'inco_compo',
// Consider this sub array
'INCO_COMPO.NOM' => 'nom',
'INCO_COMPO.TAILLE_REF' => 'taille_ref',
'INCO_COMPO.VAL_ENERG_KJ_NUT' => 'val_energ_kj_nut',
'INCO_COMPO.VAL_ENERG_KJ_R' => 'val_energ_kj_r', //_R apports de la taille_ref
'INCO_COMPO.VAL_ENERG_KJ_R' => 'val_energ_kj_r',
// _R apports de la taille_ref
'INCO_COMPO.VAL_ENERG_KCAL_R' => 'val_energ_kcal_r',
'INCO_COMPO.VAL_ENERG_KCAL_NUT' => 'val_energ_kcal_nut',
'INCO_COMPO.MAT_GRASS_R' => 'mat_grass_r',
@ -87,7 +109,8 @@ class XmlIncoReader extends XmlJobs
'INCO_COMPO.ENER_E14_R' => 'ener_e14_r',
'INCO_COMPO.FRUITS_R' => 'fruits_r',
'INCO_COMPO.FRUITS_COQUE_SECH_R' => 'fruits_coque_sech_r',
'INCO_COMPO.POURCENT_APPORT_REF_E14_R' => 'pourcent_apport_ref_e14_r', //POURCENT_*_P apports journaliers par portion
'INCO_COMPO.POURCENT_APPORT_REF_E14_R' => 'pourcent_apport_ref_e14_r',
// POURCENT_*_P apports journaliers par portion
'INCO_COMPO.ENER_KJO_R' => 'ener_kjo_r',
'INCO_COMPO.POURCENT_APPORT_REF_KJO_R' => 'pourcent_apport_ref_kjo_r',
'INCO_COMPO.POURCENT_APPORT_REF_FAT_R' => 'pourcent_apport_ref_fat_r',
@ -104,8 +127,10 @@ class XmlIncoReader extends XmlJobs
'INCO_COMPO.SALTEQ_R' => 'salteq_r',
'INCO_COMPO.POURCENT_APPORT_REF_SALTEQ_R' => 'pourcent_apport_ref_salteq_r',
'INCO_COMPO.APPORT_REF' => 'apport_ref',
'INCO_COMPO.TAILLE_PORT' => 'taille_port', //$poidsPortion
'INCO_COMPO.VAL_ENERG_KJ_P' => 'val_energ_kj_p', //_P apports par portion
'INCO_COMPO.TAILLE_PORT' => 'taille_port',
// $poidsPortion
'INCO_COMPO.VAL_ENERG_KJ_P' => 'val_energ_kj_p',
// _P apports par portion
'INCO_COMPO.VAL_ENERG_KCAL_P' => 'val_energ_kcal_p',
'INCO_COMPO.MAT_GRASS_P' => 'mat_grass_p',
'INCO_COMPO.AGS_P' => 'ags_p',
@ -147,8 +172,10 @@ class XmlIncoReader extends XmlJobs
'INCO_COMPO.COMPOSANTE_N' => 'composante_n',
'INCO_COMPO.COMPOSANTE_P' => 'composante_p',
'INCO_COMPO.POURCENT_FRUITS' => 'pourcent_fruits',
'INCO_COMPO.SCORE_NUTRI' => 'score_nutri', //du coup nutriscore ?
'INCO_COMPO.CLASSEMENT' => 'classement', //$nutriscore mais pas censé être dans INCO_COMPO
'INCO_COMPO.SCORE_NUTRI' => 'score_nutri',
// du coup nutriscore ?
'INCO_COMPO.CLASSEMENT' => 'classement',
// $nutriscore mais pas censé être dans INCO_COMPO
'INCO_COMPO.MAT_GRASS_R_PREC' => 'mat_grass_r_prec',
'INCO_COMPO.AGS_R_PREC' => 'ags_r_prec',
'INCO_COMPO.GLUCIDES_R_PREC' => 'glucides_r_prec',
@ -161,6 +188,11 @@ class XmlIncoReader extends XmlJobs
'INCO_COMPO.FRUITS_COQUE_SECH_R_PREC' => 'fruits_coque_sech_r_prec',
];
/**
* Fields that are allowed to be null
*
* @var array
*/
protected $nullableFields = [
'taille_ref',
'conseil_prepa',
@ -274,8 +306,18 @@ class XmlIncoReader extends XmlJobs
'inco_compo.fruits_coque_sech_r_prec'
];
/**
* Unique keys for updating rows
*
* @var array
*/
protected $uniqueKeyField = ['id'];
/**
* Columns to merge in
*
* @var array
*/
protected $mergeColumns = [
'allegations' => [
'allegat_sante',
@ -381,15 +423,20 @@ class XmlIncoReader extends XmlJobs
]
];
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$this->parseData('INCO', XmlInco::class, function (SimpleXMLElement $xmlData, array &$line) {
// merge columns
// * merge columns
foreach ($this->mergeColumns as $key => $values) {
$line[$key] = [];
// merge columns and remove merged columns from line
// * merge columns and remove merged columns from line
array_map(function ($kValue) use (&$line, $key) {
if (array_key_exists($kValue, $line)) {
$line[$key][$kValue] = $line[$kValue];
@ -400,22 +447,33 @@ class XmlIncoReader extends XmlJobs
$line[$key] = implode(',', array_filter($line[$key]));
$line[$key] = strlen($line[$key]) ? $line[$key] : null;
}
//json column cast into array in model => no need for encode
// json column cast into array in model => no need for encode
// if ($key == 'nutritional_values') {
// $line[$key] = json_encode($line[$key], \JSON_FORCE_OBJECT);
// $line[$key] = json_encode($line[$key], \JSON_FORCE_OBJECT);
// }
}
// Boolean cast prod_saison
// * Boolean cast prod_saison
$line['prod_saison'] = isset($line['prod_saison']) ?
(strtolower($line['prod_saison']) == 'non' ? false : true) : false;
(strtolower($line['prod_saison']) == 'non' ? false : true) : false;
$arImgPaths = $this->parseImage($xmlData);
$line['picture'] = $arImgPaths;
/** @var \Illuminate\Database\Eloquent\Model|null $model */
$model = XmlInco::where($this->getUniqueValues($line))->first();
$line['pictures'] = $this->updateOrCreateImagesFiles(
$this->parseImages($xmlData),
$model
);
});
}
private function parseImage(SimpleXMLElement $xmlData)
/**
* Parse images
*
* @param SimpleXMLElement $xmlData
* @return array List of arrays [imgNamePath, imageData]
*/
private function parseImages(SimpleXMLElement $xmlData): array
{
$images = $xmlData->INCO->MEDIA_VISUEL_EAI->MEDIA_VISUEL_EAI;
$arImgPaths = array();
@ -423,51 +481,138 @@ class XmlIncoReader extends XmlJobs
foreach ($images as $image) {
$imageData = trim((string)$image);
if (strlen($imageData)) {
$imageData = base64_decode($imageData);
if (strlen($imageData) and $imageData = base64_decode($imageData)) {
$imgName = strval($xmlData->INCO->INCO_COMPO->TBL_INCO_COMPO->ID_INCO);
if(empty($imgName)) {
if (empty($imgName)) {
$imgName = strval($xmlData->INCO->NO_ART);
}
$imgName = $imgName."_".$index;
$imgName = $imgName . "_" . $index;
$imgNamePath = sprintf('%s/%s', rtrim($this->imagesPath, '/'), $imgName);
$disk = $this->getDisk();
$info = getimagesizefromstring($imageData);
if($info !== false) {
$mime = $info['mime'];
switch ($mime) {
case 'image/jpeg':
$imgExtension = 'jpg';
break;
case 'image/png':
$imgExtension = 'png';
break;
case 'image/gif':
$imgExtension = 'gif';
break;
default:
throw new AbortXmlImportException(sprintf('Unknown image type %s', $imgName));
}
$imgNamePath = sprintf('%s.%s', $imgNamePath, $imgExtension);
if (!$disk->put($imgNamePath, $imageData)) {
throw new AbortXmlImportException(sprintf('The image %s could not be saved', $imgNamePath));
}
$arImgPaths[$index] = sprintf('%s/%s', $this->disk, $imgNamePath);
$index++;
} else {
//probably not an image could be a PDF, PPT, AI anything is possible
if ($info === false) {
Log::debug("Could not get file info from $imgName");
continue;
}
}
}
switch ($info['mime'] ?? '') {
case 'image/jpeg':
$imgExtension = 'jpg';
break;
case 'image/png':
$imgExtension = 'png';
break;
case 'image/gif':
$imgExtension = 'gif';
break;
default:
Log::debug("Unknown image type from $imgName");
}
$imgNamePath = sprintf('%s.%s', $imgNamePath, $imgExtension);
$arImgPaths[$index] = compact('imgNamePath', 'imageData');
$index++;
} //end if
} //end foreach
return $arImgPaths;
}
/**
* Update or create images
*
* @param array $images
* @param \Illuminate\Database\Eloquent\Model|null $model
* @return array Pictures pathes with checksums as keys that will be save into model.
*/
private function updateOrCreateImagesFiles(array $images, Model $model = null): array
{
$disk = $this->getDisk();
$oldChecksums = [];
$oldPicturesPaths = [];
if ($model) {
/** @var array $oldChecksums */
$oldChecksums = \array_keys($model->pictures ?? []);
/** @var array $oldPicturesPaths */
$oldPicturesPaths = $model->pictures ?? [];
}
/** @var array $oldPictures old pictures pathes with checksums as keys */
$oldPictures = \array_combine($oldChecksums, $oldPicturesPaths);
/** @var array $newPictures new pictures pathes with checksums as keys */
$newPictures = collect($images)->mapWithKeys(function (array $imageProps) {
/** @var string $imgNamePath */
/** @var string $imageData */
extract($imageProps);
$imageFinalPath = sprintf('%s/%s', $this->disk, $imgNamePath);
// ? Sucessfully calculate checksum ?
if (!($newFileChk = hash('crc32b', $imageFinalPath . $imageData))) {
throw new \ErrorException('crc32b doesnt seems to be supported.');
}
return [$newFileChk => $imageFinalPath];
});
/** @var array $modelPictures pictures that wont change */
$modelPictures = \array_intersect_key($oldPictures, $newPictures);
/** @var \Illuminate\Support\Collection $removePictures pictures that we will remove (delete from disk) */
$removePictures = collect(\array_diff_key($oldPictures, $newPictures));
/** @var \Illuminate\Support\Collection $addPictures pictures that we will create (save on disk) */
$addPictures = collect(\array_diff_key($newPictures, $oldPictures));
// * Save or remove pictures from storage as needed
collect($images)->mapWithKeys(function (
array $imageProps,
string $checksum
) use (
$disk,
$removePictures,
$addPictures
) {
/** @var string $imgNamePath */
/** @var string $imageData */
extract($imageProps);
if ($addPictures->has($checksum)) {
// * Save new picture
if (!$disk->put($imgNamePath, $imageData)) {
throw new AbortXmlImportException(sprintf('The image %s could not be saved', $imgNamePath));
}
}
if ($removePictures->has($checksum)) {
// * Remove old picture (if exists)
if ($disk->exists($imgNamePath) and !$disk->delete($imgNamePath)) {
throw new AbortXmlImportException(sprintf('The image %s could not be delete', $imgNamePath));
}
}
});
return $newPictures;
}
/**
* Get Unique Values for a specific file
*
* @param array $line
* @return array
*/
private function getUniqueValues(array $line): array
{
/** @var array Unique Key columns */
$uniqueColumnsKey = [];
foreach ($this->uniqueKeyField as $uField) {
$uniqueColumnsKey[$uField] = $line[$uField];
}
return $uniqueColumnsKey;
}
/**
* Recursive extract to array
* introduce sort of Hook to transform
@ -500,15 +645,18 @@ class XmlIncoReader extends XmlJobs
if ($value->count()) {
$extraction = $this->extract($value);
if(count($extraction)
&& array_key_exists("code",$extraction)
&& array_key_exists("libelle",$extraction)) {
$val = strval($extraction['libelle']);
if (
count($extraction)
&& array_key_exists("code", $extraction)
&& array_key_exists("libelle", $extraction)
) {
$val = strval($extraction['libelle']);
$extract[$key] = strlen($val) ? $val : null;
continue;
}
$extract[$key][] = $extraction; //$this->extract($value);
$extract[$key][] = $extraction;
// $this->extract($value);
continue;
}
$value = strval($value);
@ -517,7 +665,7 @@ class XmlIncoReader extends XmlJobs
} else {
$extract[$key] = strlen($value) ? $value : null;
}
}
} //end foreach
return $extract;
}
}

View file

@ -1,38 +1,32 @@
<?php
namespace App\Jobs;
namespace App\Jobs\Arcadia;
use App\Enums\LogType;
use App\Exceptions\AbortXmlImportException;
use App\Exceptions\InvalidXmlException;
use App\Lib\Utils;
use ErrorException;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use RecursiveArrayIterator;
use RecursiveIteratorIterator;
use SimpleXMLElement;
use Throwable;
use TypeError;
abstract class XmlJobs extends XArcadiaFlux
{
/**
* invalid xml folder
* The xsd file path used to validate files
*
* @var string
* @var string|null
*/
protected $invalidFolderName = 'invalid';
protected $xsdSchemaPath = null;
/**
* xsd file location
* The xsd file content used to validate files
*
* @var string
* @var string|null
*/
protected $xsdFile = '';
protected $xsdSchema = null;
/**
* The columns to take from the XML
@ -60,80 +54,113 @@ abstract class XmlJobs extends XArcadiaFlux
/**
* Parse Data from the XML and gives it to the callback
*
* @param string $entityName
* @param string $modelClass
* @param string $entityName
* @param string $modelClass
* @param callable $afterParseCallback After parse is done.
* @param callable $saveCallback Instead of updateOrCreate, execute $saveCallback.
* @param callable $afterSaveCallback After save is done.
* @return void
* @throws InvalidXmlException If xml file is not valid.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded
*/
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
protected function parseData(string $entityName, string $modelClass, callable $callback = null): void
{
protected function parseData(
string $entityName,
string $modelClass,
callable $afterParseCallback = null,
callable $saveCallback = null,
callable $afterSaveCallback = null
): void {
/** @var Filesystem */
$disk = $this->getDisk();
$this->log('Démarrage du traitement');
$this->assertXsdIsHereIfAny();
$files = $disk->files();
$progress = 1;
$total = count(array_filter($files, function ($filename) {
$total = count(array_filter($files, function ($filename) {
if ($this->fileHasToBeIgnored($filename)) {
return false;
}
return true;
}));
$this->startProgressBar($total);
foreach ($files as $filename) {
if ($this->fileHasToBeIgnored($filename)) {
continue;
}
$this->log(sprintf(' traitement %d sur %s ', $progress++, $total));
$this->incrementProgressBar();
try {
/** @var string $data */
$data = $disk->get($filename);
$this->validateXmlFile($filename, $data);
try {
$this->validateSchema($data);
if ($this->encapsulate) {
$data = sprintf('<Root>%s</Root>', $data);
/** @var SimpleXMLElement|false */
/** @var \SimpleXMLElement|false */
$xmlContent = simplexml_load_string($data);
} else {
/** @var SimpleXMLElement|false */
/** @var \SimpleXMLElement|false */
$xmlContent = simplexml_load_string($data, null, LIBXML_NOCDATA | LIBXML_PARSEHUGE);
}
if ($xmlContent === false) {
throw new InvalidXmlException('XML is Invalid (simplexml_load_string returned false)');
}
} catch (ErrorException $e) {
} catch (\ErrorException $e) {
throw new InvalidXmlException('XML is Invalid', 0, $e);
}
$data = $this->extract($xmlContent);
$data = $this->extract($xmlContent);
$lines = [];
foreach ($data[$entityName] as $traca) {
$line = $this->processFields($traca);
if ($callback) {
$callback($xmlContent, $line);
// * Multiple Entities within file
if (isset($data[$entityName])) {
foreach ($data[$entityName] as $traca) {
$line = $this->processFields($traca);
if ($afterParseCallback) {
$afterParseCallback($xmlContent, $line);
}
$lines[] = $line;
}
} else {
// * Only one entity within file
$line = $this->processFields($data);
if ($afterParseCallback) {
$afterParseCallback($xmlContent, $line);
}
$lines[] = $line;
}
// Mise en base des données
// * Mise en base des données
foreach ($lines as $line) {
/** @var array Unique Key columns */
$uniqueColumnsKey = [];
foreach ($this->uniqueKeyField as $uField) {
$uniqueColumnsKey[$uField] = $line[$uField];
}
forward_static_call_array(
[$modelClass, 'updateOrCreate'],
[$uniqueColumnsKey, $line]
);
}
if (count($uniqueColumnsKey)) {
if ($saveCallback) {
$saveCallback($uniqueColumnsKey, $line);
} else {
forward_static_call_array(
[$modelClass, 'updateOrCreate'],
[$uniqueColumnsKey, $line]
);
}
if ($afterSaveCallback) {
$afterSaveCallback($xmlContent, $line);
}
}
} //end foreach
} catch (InvalidXmlException $e) {
$message = $e->getMessage();
if (get_class($e) === QueryException::class) {
$message = implode(' ', $e->errorInfo);
}
$this->log(
sprintf(
'File %s could not be parsed => %s on line %d',
@ -146,19 +173,6 @@ abstract class XmlJobs extends XArcadiaFlux
$this->sendInvalidXmlEmail($filename, $e);
$this->moveToInvalid($filename);
continue;
} catch (ErrorException | TypeError $e) {
$this->log(
sprintf(
'CRITICAL: File %s could not be parsed => %s on line %d',
$filename,
$e->getMessage(),
$e->getLine()
),
LogType::critical()
);
$this->sendErrorParseEmailToServiceWeb($e);
continue;
// ignore the file
} catch (AbortXmlImportException $e) {
$this->log(
sprintf(
@ -169,15 +183,16 @@ abstract class XmlJobs extends XArcadiaFlux
),
LogType::error()
);
$this->sendErrorParseEmailToServiceWeb($e);
report($e);
continue;
// ignore the file
} catch (FileNotFoundException | QueryException $e) {
// This happens because of concurent xml jobs read
// * ignore the file
} catch (QueryException $e) {
$this->log(implode(' ', $e->errorInfo), LogType::critical());
report($e);
continue;
} catch (FileNotFoundException $e) {
// This happens because of concurent xml jobs read.
$message = $e->getMessage();
if (get_class($e) === QueryException::class) {
$message = implode(' ', $e->errorInfo);
}
$this->log(
sprintf(
'ERROR: File %s could not be read (not found) => %s on line %d',
@ -188,13 +203,31 @@ abstract class XmlJobs extends XArcadiaFlux
LogType::debug()
);
continue;
// ignore the file
}//end try
// * ignore the file
} catch (\Exception | \ErrorException | \TypeError $e) {
$this->log(
sprintf(
'File %s could not be parsed => %s on line %d',
$filename,
$e->getMessage(),
$e->getLine()
),
LogType::critical()
);
report($e);
continue;
// * ignore the file
} //end try
$this->moveToArchives($filename);
}//end foreach
} //end foreach
// Clear old archives
// Clear old invalids.
$this->clearOldInvalids();
// Clear old archives.
$this->clearOldArchives();
$this->endProgressBar();
}
/**
@ -205,7 +238,7 @@ abstract class XmlJobs extends XArcadiaFlux
*/
protected function extract($sxe = null): array
{
if (!$sxe instanceof SimpleXMLElement) {
if (!$sxe instanceof \SimpleXMLElement) {
return array();
}
$extract = array();
@ -229,7 +262,7 @@ abstract class XmlJobs extends XArcadiaFlux
*
* @param array $fields
* @return array
* @throws AbortXmlImportException
* @throws AbortXmlImportException If a field could not be validated.
*/
protected function processFields(array $fields): array
{
@ -242,8 +275,8 @@ abstract class XmlJobs extends XArcadiaFlux
throw new AbortXmlImportException(sprintf('The field %s is not allowed to be null', $key));
}
if (is_array($field)) {
// Flatten data
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($field));
// * Flatten data
$it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($field));
foreach ($it as $k => $f) {
$ko = strtolower(sprintf('%s.%s', $key, $k));
if (!$this->isFieldNullable($ko) && is_null($f)) {
@ -261,7 +294,7 @@ abstract class XmlJobs extends XArcadiaFlux
} else {
$result[$key] = $field;
}
}//end foreach
} //end foreach
return $result;
}
@ -309,23 +342,6 @@ abstract class XmlJobs extends XArcadiaFlux
return in_array($filename, $this->ignoreFiles) or !$this->checkExtension($filename, 'xml');
}
/**
* Validate xml data against xsd file if provided
*
* @return void
*/
protected function validateSchema($data) {
if($this->xsdFile) {
//check against XSD
$xsd = storage_path($this->disk."/".$this->xsdFile);
$domDocument= new \DOMDocument();
$domDocument->loadXML($data);
//free some memroy to avoid overload
unset($data);
$domDocument->schemaValidate($xsd);
}
}
/**
* Check if the file is an xml file
*
@ -341,78 +357,81 @@ abstract class XmlJobs extends XArcadiaFlux
return $isXml;
}
protected function sendErrorParseEmailToServiceWeb(Throwable $e)
{
Utils::sendEmailErrorManually($e);
}
protected function sendInvalidXmlEmail(string $filename, Throwable $e)
/**
* Send invalid XML email
*
* @param string $filename
* @param \Throwable $e
* @return void
*/
protected function sendInvalidXmlEmail(string $filename, \Throwable $e)
{
$message = $e->getPrevious()->getMessage();
Mail::send('emails.invalidxml', [
Mail::send('mails.invalidxml', [
'strError' => sprintf(
'File %s could not be parsed => %s on line %d',
$filename,
$message,
$e->getLine()
),
'appName' => config('app.name'),
'filename' => $filename,
'exception' => $e->getPrevious()
], function ($m) {
$m->to(
config('mail.contactsList.invalidInco'),
sprintf('%s Exception Handler', config('app.name'))
sprintf('%s XML Exception Handler', config('app.name'))
)->subject('You sent me an invalid XML file, please check its validity before resend ASAP.');
});
}
/**
* Move the file to invalid folder
* Assert XSD is readable if configured
*
* @param string $filename
* @return void
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException If xsd file cant be read.
*/
protected function moveToInvalid(string $filename): void
private function assertXsdIsHereIfAny()
{
$newFileName = $filename;
if (!$this->getDisk()->exists($filename)) {
$this->log(
sprintf(
'File %s could not be moved to invalid (not found)',
$filename
),
LogType::warning()
);
return;
}
if ($this->getDisk()->exists(sprintf('%s/%s', $this->invalidFolderName, $filename))) {
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
$newFileName = \sprintf(
'%s_%d.%s',
\substr($filename, 0, 0 - (strlen($ext) + 1)),
\time(),
$ext
);
}
try {
if (!$this->getDisk()->move($filename, sprintf('%s/%s', $this->invalidFolderName, $newFileName))) {
$this->log(
sprintf(
'File %s could not be moved to invalid',
$filename
),
LogType::warning()
);
}
} catch (Exception $e) {
$this->log(
sprintf(
'File %s could not be moved to invalid',
$filename
),
LogType::warning()
);
if ($this->xsdSchemaPath) {
$this->xsdSchema = File::get($this->xsdSchemaPath);
}
}
/**
* Validate Xml file against XSD if loaded
*
* @param string $fileName
* @param string $fileContent
* @return void
* @throws InvalidXmlException If XML could not be parsed or validated against XSD.
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
private function validateXmlFile(string $fileName, string $fileContent)
{
if ($this->xsdSchema) {
$domDocument = new \DOMDocument('1.0', 'UTF-8');
try {
if (!$domDocument->loadXML($fileContent)) {
throw new InvalidXmlException("XML Validation $fileName content could not be loaded to validate.");
}
if (!$domDocument->schemaValidateSource($this->xsdSchema, LIBXML_SCHEMA_CREATE)) {
throw new InvalidXmlException(
"XML Validation $fileName content could not be validated against xsd."
);
}
} catch (\Exception $e) {
if (\get_class($e) === InvalidXmlException::class) {
throw $e;
}
throw new InvalidXmlException(
"XML Validation $fileName content could not be validated against xsd.",
0,
1,
__FILE__,
__LINE__,
$e
);
} //end try
} //end if
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace App\Jobs;
use App\Models\Service;
use App\Models\ServiceStat;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
/**
* @codeCoverageIgnore
*/
class SetServiceStats implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
private const KEY = '2202020';
/** @var \App\Models\Service */
protected $service;
/**
* Create a new job instance.
*
* @param Service $service
* @return void
*/
public function __construct(Service $service)
{
$this->service = $service;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
/** @var \App\Models\ServiceStat */
$serviceStat = ServiceStat::firstOrNew([
'date' => date('Y-m-d'),
'service_id' => $this->service->id,
]);
if (!$serviceStat->exists) {
$serviceStat->service()->associate($this->service);
}
$serviceStat->nb_calls += 1;
$serviceStat->saveOrFail();
}
/**
* Determine the time at which the job should timeout.
*
* @return \DateTime
*/
public function retryUntil()
{
return now()->addMinutes(rand(0, 360));
}
/**
* Get the middleware the job should pass through.
*
* @return array
*/
public function middleware()
{
return [
(new WithoutOverlapping(self::KEY))
->releaseAfter(rand(90, 180))
];
}
}

180
app/Lib/Helper.php Normal file
View file

@ -0,0 +1,180 @@
<?php
namespace App\Lib;
use App\Exceptions\Api\ApplicationException;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
/**
* Helper Class
*/
class Helper
{
/**
* Package infos
*
* @var array
*/
private static $packageInfos;
/**
* Tells if the request is within apiV1 routes
*
* @param Request $request
* @return boolean
*/
public static function isRequestForApiV1(Request $request): bool
{
// * Small hack to ensure to know if route is From APIv1.
$routePrefix = (strpos(static::getRoutePrefix($request), 'api/v1') ? 'v1' : null) ??
(strpos($request->path(), 'api/v1') === 0 ? 'v1' : null) ?? 'none';
return strpos($routePrefix, 'v1') === 0;
}
/**
* Try to get current route name
*
* @param Request $request
* @return string|null
*/
public static function getRouteName(Request $request)
{
return Route::currentRouteName() ?? ($request->route() ? $request->route()->getName() : null);
}
/**
* Get the bearer token from request
*
* @param Request $request
* @return string|null
*/
public static function getBearerTokenFromRequest(Request $request): ?string
{
$header = $request->header('Authorization', '');
if (Str::contains($header, 'Bearer ')) {
$auths = \explode(',', $header);
foreach ($auths as $auth) {
if (Str::contains($auth, 'Bearer ')) {
return Str::substr($auth, 7);
}
}
}
return null;
}
/**
* Try to get current route prefix
*
* @param Request $request
* @return string|null
*/
public static function getRoutePrefix(Request $request)
{
return (Route::getCurrentRoute() ? Route::getCurrentRoute()->getPrefix() : null) ??
($request->route() ? $request->route()->getPrefix() : null);
}
/**
* Try to get route from request
*
* @param Request $request
* @return \Illuminate\Routing\Route|null
*/
public static function getRoute(Request $request)
{
return Route::getRoutes()->getByName(static::getRouteName($request));
}
/**
* Get composer.json infos
*
* @return array
*/
public static function getPackageInfo(): array
{
if (!static::$packageInfos) {
static::$packageInfos = \json_decode(\file_get_contents(__DIR__ . '/../../composer.json'), true);
}
return static::$packageInfos;
}
/**
* Calculate the request signature
*
* @param string $secret
* @param string $uri
* @param string $method
* @param array $parameters
* @return string request signature
* @throws ApplicationException If an error occurs.
*/
public static function signRequest(string $secret, string $uri, string $method, array $parameters): string
{
try {
$uri = collect(\parse_url($uri));
$method = \strtolower($method);
// * Remove signature from parameters to calculate the hashmac.
$signature = collect([
'uri' => sprintf(
'%s://%s%s',
$uri->get('scheme'),
$uri->get('host'),
$uri->get('path')
),
'method' => $method
]);
$flattenRequestArray = function (array $array) use (&$flattenRequestArray) {
$out = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$subArray = $flattenRequestArray($value);
foreach ($subArray as $subKey => $subValue) {
$out["$key.$subKey"] = $subValue;
}
continue;
}
$out[$key] = $value;
}
return $out;
};
$parameters = $flattenRequestArray($parameters);
$parameters = collect($parameters);
// * Remove signature from parameters.
$parameters->pull('signature');
// * Natural sort all parameters.
$parameters = $parameters->sortKeys(\SORT_NATURAL)
->map(function ($value, $key) {
return sprintf('%s=%s', \rawurlencode($key), \rawurlencode($value));
});
$signature->push($parameters->implode('&'));
$calculatedHash = base64_encode(hash_hmac(
'sha256',
$signature->implode('\n'),
Crypt::decryptString($secret),
true
));
if (!$calculatedHash) {
throw new ApplicationException('Failed to calculate requests hash');
}
return $calculatedHash;
} catch (DecryptException $e) {
throw new ApplicationException('Failed to decrypt service credentials', 0, $e);
} catch (\Exception $e) {
throw new ApplicationException('An error occured when authenticating service', 0, $e);
} //end try
}
}

View file

@ -1,210 +0,0 @@
<?php
namespace App\Lib;
use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Throwable;
/**
* Some utilities methods
*
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
*/
// phpcs:disable Generic.Files.LineLength.TooLong
class Utils
{
/**
* Replace stange accents
*
* https://stackoverflow.com/questions/3371697/replacing-accented-characters-php
* @author Jérémy Munsch <jeremy.munsch@jeremydev.ovh>
* @param string $subject
* @return string
*/
public static function replaceSpecChar(string $subject): string
{
$char_map = array(
"ъ" => "-", "ь" => "-", "Ъ" => "-", "Ь" => "-",
"А" => "A", "Ă" => "A", "Ǎ" => "A", "Ą" => "A", "À" => "A", "Ã" => "A", "Á" => "A", "Æ" => "A", "Â" => "A", "Å" => "A", "Ǻ" => "A", "Ā" => "A", "א" => "A",
"Б" => "B", "ב" => "B", "Þ" => "B",
"Ĉ" => "C", "Ć" => "C", "Ç" => "C", "Ц" => "C", "צ" => "C", "Ċ" => "C", "Č" => "C", "©" => "C", "ץ" => "C",
"Д" => "D", "Ď" => "D", "Đ" => "D", "ד" => "D", "Ð" => "D",
"È" => "E", "Ę" => "E", "É" => "E", "Ë" => "E", "Ê" => "E", "Е" => "E", "Ē" => "E", "Ė" => "E", "Ě" => "E", "Ĕ" => "E", "Є" => "E", "Ə" => "E", "ע" => "E",
"Ф" => "F", "Ƒ" => "F",
"Ğ" => "G", "Ġ" => "G", "Ģ" => "G", "Ĝ" => "G", "Г" => "G", "ג" => "G", "Ґ" => "G",
"ח" => "H", "Ħ" => "H", "Х" => "H", "Ĥ" => "H", "ה" => "H",
"I" => "I", "Ï" => "I", "Î" => "I", "Í" => "I", "Ì" => "I", "Į" => "I", "Ĭ" => "I", "I" => "I", "И" => "I", "Ĩ" => "I", "Ǐ" => "I", "י" => "I", "Ї" => "I", "Ī" => "I", "І" => "I",
"Й" => "J", "Ĵ" => "J",
"ĸ" => "K", "כ" => "K", "Ķ" => "K", "К" => "K", "ך" => "K",
"Ł" => "L", "Ŀ" => "L", "Л" => "L", "Ļ" => "L", "Ĺ" => "L", "Ľ" => "L", "ל" => "L",
"מ" => "M", "М" => "M", "ם" => "M",
"Ñ" => "N", "Ń" => "N", "Н" => "N", "Ņ" => "N", "ן" => "N", "Ŋ" => "N", "נ" => "N", "ʼn" => "N", "Ň" => "N",
"Ø" => "O", "Ó" => "O", "Ò" => "O", "Ô" => "O", "Õ" => "O", "О" => "O", "Ő" => "O", "Ŏ" => "O", "Ō" => "O", "Ǿ" => "O", "Ǒ" => "O", "Ơ" => "O",
"פ" => "P", "ף" => "P", "П" => "P",
"ק" => "Q",
"Ŕ" => "R", "Ř" => "R", "Ŗ" => "R", "ר" => "R", "Р" => "R", "®" => "R",
"Ş" => "S", "Ś" => "S", "Ș" => "S", "Š" => "S", "С" => "S", "Ŝ" => "S", "ס" => "S",
"Т" => "T", "Ț" => "T", "ט" => "T", "Ŧ" => "T", "ת" => "T", "Ť" => "T", "Ţ" => "T",
"Ù" => "U", "Û" => "U", "Ú" => "U", "Ū" => "U", "У" => "U", "Ũ" => "U", "Ư" => "U", "Ǔ" => "U", "Ų" => "U", "Ŭ" => "U", "Ů" => "U", "Ű" => "U", "Ǖ" => "U", "Ǜ" => "U", "Ǚ" => "U", "Ǘ" => "U",
"В" => "V", "ו" => "V",
"Ý" => "Y", "Ы" => "Y", "Ŷ" => "Y", "Ÿ" => "Y",
"Ź" => "Z", "Ž" => "Z", "Ż" => "Z", "З" => "Z", "ז" => "Z",
"а" => "a", "ă" => "a", "ǎ" => "a", "ą" => "a", "à" => "a", "ã" => "a", "á" => "a", "æ" => "a", "â" => "a", "å" => "a", "ǻ" => "a", "ā" => "a", "א" => "a",
"б" => "b", "ב" => "b", "þ" => "b",
"ĉ" => "c", "ć" => "c", "ç" => "c", "ц" => "c", "צ" => "c", "ċ" => "c", "č" => "c", "©" => "c", "ץ" => "c",
"Ч" => "ch", "ч" => "ch",
"д" => "d", "ď" => "d", "đ" => "d", "ד" => "d", "ð" => "d",
"è" => "e", "ę" => "e", "é" => "e", "ë" => "e", "ê" => "e", "е" => "e", "ē" => "e", "ė" => "e", "ě" => "e", "ĕ" => "e", "є" => "e", "ə" => "e", "ע" => "e",
"ф" => "f", "ƒ" => "f",
"ğ" => "g", "ġ" => "g", "ģ" => "g", "ĝ" => "g", "г" => "g", "ג" => "g", "ґ" => "g",
"ח" => "h", "ħ" => "h", "х" => "h", "ĥ" => "h", "ה" => "h",
"i" => "i", "ï" => "i", "î" => "i", "í" => "i", "ì" => "i", "į" => "i", "ĭ" => "i", "ı" => "i", "и" => "i", "ĩ" => "i", "ǐ" => "i", "י" => "i", "ї" => "i", "ī" => "i", "і" => "i",
"й" => "j", "Й" => "j", "Ĵ" => "j", "ĵ" => "j",
"ĸ" => "k", "כ" => "k", "ķ" => "k", "к" => "k", "ך" => "k",
"ł" => "l", "ŀ" => "l", "л" => "l", "ļ" => "l", "ĺ" => "l", "ľ" => "l", "ל" => "l",
"מ" => "m", "м" => "m", "ם" => "m",
"ñ" => "n", "ń" => "n", "н" => "n", "ņ" => "n", "ן" => "n", "ŋ" => "n", "נ" => "n", "ʼn" => "n", "ň" => "n",
"ø" => "o", "ó" => "o", "ò" => "o", "ô" => "o", "õ" => "o", "о" => "o", "ő" => "o", "ŏ" => "o", "ō" => "o", "ǿ" => "o", "ǒ" => "o", "ơ" => "o",
"פ" => "p", "ף" => "p", "п" => "p",
"ק" => "q",
"ŕ" => "r", "ř" => "r", "ŗ" => "r", "ר" => "r", "р" => "r", "®" => "r",
"ş" => "s", "ś" => "s", "ș" => "s", "š" => "s", "с" => "s", "ŝ" => "s", "ס" => "s",
"т" => "t", "ț" => "t", "ט" => "t", "ŧ" => "t", "ת" => "t", "ť" => "t", "ţ" => "t",
"ù" => "u", "û" => "u", "ú" => "u", "ū" => "u", "у" => "u", "ũ" => "u", "ư" => "u", "ǔ" => "u", "ų" => "u", "ŭ" => "u", "ů" => "u", "ű" => "u", "ǖ" => "u", "ǜ" => "u", "ǚ" => "u", "ǘ" => "u",
"в" => "v", "ו" => "v",
"ý" => "y", "ы" => "y", "ŷ" => "y", "ÿ" => "y",
"ź" => "z", "ž" => "z", "ż" => "z", "з" => "z", "ז" => "z", "ſ" => "z",
"™" => "tm",
"@" => "at",
"Ä" => "ae", "Ǽ" => "ae", "ä" => "ae", "æ" => "ae", "ǽ" => "ae",
"ij" => "ij", "IJ" => "ij",
"я" => "ja", "Я" => "ja",
"Э" => "je", "э" => "je",
"ё" => "jo", "Ё" => "jo",
"ю" => "ju", "Ю" => "ju",
"œ" => "oe", "Œ" => "oe", "ö" => "oe", "Ö" => "oe",
"щ" => "sch", "Щ" => "sch",
"ш" => "sh", "Ш" => "sh",
"ß" => "ss",
"Ü" => "ue",
"Ж" => "zh", "ж" => "zh",
);
return self::mbStrtr($subject, $char_map);
}
/**
* Multibyte strtr
*
* @param string $str
* @param string|array $from
* @param string|null $to
* @return string
*/
public static function mbStrtr($str, $from, $to = null): string
{
if (is_array($from)) {
$from = array_map('utf8_decode', $from);
$from = array_map('utf8_decode', array_flip($from));
return utf8_encode(strtr(utf8_decode($str), array_flip($from)));
}
return utf8_encode(strtr(utf8_decode($str), utf8_decode($from), utf8_decode($to)));
}
/**
* Manually send an exception to team instead of going trough the error handler
* (requires package kwaadpepper/exception-handler)
*
* @param Throwable $e
* @return void
*/
public static function sendEmailErrorManually(Throwable $e)
{
Mail::send([
'html' => 'exception-handler::emails.exception',
'text' => 'exception-handler::emails.exception-plain'
], [
'appName' => config('app.name'),
'code' => $e->getCode(),
'line' => $e->getLine(),
'ename' => \get_class($e),
'emessage' => self::getAnonymizedMessage($e->getMessage()),
'file' => str_replace(\realpath(\sprintf('%s/../', \app_path())), '', $e->getFile()),
'stackTrace' => self::getAnonymizedStackTrace($e),
'uri' => app()->runningInConsole() ?
'CONSOLE' : (
(request() and request()->fullUrl()) ?
request()->fullUrl() : 'none')
], function ($m) {
/** @var \Illuminate\Mail\Message $m */
$m->to(
config('mail.contactsList.serviceweb'),
sprintf('%s Exception Handler', config('app.name'))
)->subject('Need help !, an error occured in your application, please check Logs ASAP');
});
Log::alert(sprintf('Envoie d\'une erreur par mail : %s', json_encode($e)));
}
/**
* Anonymize at least Sql queries
*
* @param string $message
* @return string
*/
private static function getAnonymizedMessage(string $message): string
{
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
return $message;
}
/**
* Anonymize stack trace
*
* @param \Exception $e
* @return string
*/
private static function getAnonymizedStackTrace(Throwable $e): string
{
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
$traceStack = $e->getTrace();
$traceStr = '';
$maxStackStage = 0;
foreach ($traceStack as $stackStage => $stack) {
$maxStackStage = $stackStage;
$traceStr .= \sprintf(
'#%s %s(%d): ',
$stackStage,
str_replace($projectPath, '', $stack['file']),
$stack['line']
);
$traceStr .= "{$stack['class']}->{$stack['function']}(";
$traceArgs = '';
if (isset($stack['args'])) {
$argsCount = count($stack['args']);
for ($i = 0; $i < $argsCount; $i++) {
$type = \gettype($stack['args'][$i]);
switch ($type) {
case 'object':
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
break;
case 'array':
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
break;
case 'string':
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
break;
default:
$traceArgs .= \gettype($stack['args'][$i]);
}
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
}
$traceStr .= $traceArgs;
}//end if
$traceStr .= ")\n";
}//end foreach
$maxStackStage++;
$traceStr .= "#$maxStackStage {main}";
return $traceStr;
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Rennokki\QueryCache\Traits\QueryCacheable;
class ArcadiaBrand extends Model
{
use HasFactory;
use QueryCacheable;
/**
* The timeout cache for this model
*
* @var integer
*/
protected $cacheFor = 0;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'arcadia_id',
'libelle'
];
/**
* ArcadiaBrand
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
$this->cacheFor = config('api.arcadiaBrandsDBCache') * 60;
parent::__construct($attributes);
}
/**
* Set the base cache tags that will be present
* on all queries.
*
* ! This is used to flush caching
*
* @return array
*/
protected function getCacheBaseTags(): array
{
return [
'incollector_arcadia_brands',
];
}
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::created(function () {
// * Flush Eloquent cache.
self::flushQueryCache();
});
static::updated(function () {
// * Flush Eloquent cache.
self::flushQueryCache();
});
static::deleted(function () {
// * Flush Eloquent cache.
self::flushQueryCache();
});
}
// * RELATIONS
/**
* Service belongsToMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function services()
{
return $this->belongsToMany(Service::class);
}
}

224
app/Models/Service.php Normal file
View file

@ -0,0 +1,224 @@
<?php
namespace App\Models;
use App\Jobs\SetServiceStats;
use App\Lib\Helper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Str;
use Rennokki\QueryCache\Traits\QueryCacheable;
class Service extends Model
{
use HasFactory;
use QueryCacheable;
/**
* The timeout cache for this model
*
* @var integer
*/
protected $cacheFor = 0;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'name'
];
/**
* Service
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
$this->cacheFor = config('api.servicesDBCache') * 60;
parent::__construct($attributes);
}
/**
* Set the base cache tags that will be present
* on all queries.
*
* ! This is used to flush caching
*
* @return array
*/
protected function getCacheBaseTags(): array
{
return [
'incollector_services',
];
}
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $service) {
$service->generateBearerToken();
$service->generateBearerPassword();
});
static::created(function () {
// * Flush Eloquent cache.
self::flushQueryCache();
});
static::updated(function () {
// * Flush Eloquent cache.
self::flushQueryCache();
});
static::deleted(function () {
// * Flush Eloquent cache.
self::flushQueryCache();
});
}
// * RELATIONS
/**
* ArcadiaBrand belongsToMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function arcadiaBrands()
{
return $this->belongsToMany(ArcadiaBrand::class);
}
/**
* ServiceStat hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function servicesStats()
{
return $this->hasMany(ServiceStat::class);
}
// * METHODS
/**
* Increment API CRM Stat
*
* @return void
*/
public function incrementStat(): void
{
SetServiceStats::dispatch($this)->afterResponse()
->onConnection('database')->onQueue('stats');
}
/**
* Generates new bearer token
*
* @return string
*/
public function generateBearerToken(): string
{
$this->bearer_token = hash('ripemd160', Str::uuid());
$this->bearer_token_date = now();
return $this->bearer_token;
}
/**
* Generates new bearer password
* It store the crypted password in model
* and returns the plain password
*
* @return string
*/
public function generateBearerPassword(): string
{
$pwd = hash('sha256', Str::uuid());
$this->bearer_pwd = Crypt::encryptString($pwd);
return $pwd;
}
/**
* Get Service from bearerToken with its brand
*
* @param string $bearerToken
* @return \App\Models\Service
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException If the token matches no known service.
*/
public static function getFromBearerToken(string $bearerToken): self
{
$services = static::all();
/** @var \App\Models\Service $service */
foreach ($services as $service) {
if ($service->bearer_token and $bearerToken === $service->bearer_token) {
return $service->load([
'arcadiaBrands' => function ($query) use ($service) {
return $query->cacheTags(["service:{$service->id}:arcadiaBrands"]);
},
]);
}
}
throw new ModelNotFoundException('Could not find service from Service Token');
}
/**
* Authenticate the request for this service
*
* This will check hash-mac and that the request is wellformed
*
* The request should have the minimum following parameters
* - timestamp using format Y-m-d H:i:s, and must be UTC (Greenwich Mean Time)
* - bearerToken the public token generated by the crm
* - signature the hashmac signature of the request
*
* ! Signature must be calculated with every parameter urlencode once only
* ! All parameters to calculate the signature must be sorted (natural sort asc)
*
* ! Signature should look like
* * httpVerb\n
* * uri\n
* * queryString
*
* ! httpVerb as well as uri must be lower case
* ! uri should include scheme, host and path and nothing else, if path is empty a trailing slash should be added
* ! eg: https://ldc-crm.fr/customer/1 or https://ldc-crm.fr/
* ! The query string shall be separated with '&', eg param1=value1&param2=value2
* ! Key names and values of the query string must be url encoded using RFC 3986
* ! http://www.faqs.org/rfcs/rfc3986.html
* ! Signature must be hmac sha256 using the private key generatedby the crm
*
* * ! They should then be concat in a string separated by \n
*
*
*
* @param \Illuminate\Http\Request $request
* @return boolean
* @throws \App\Exceptions\Api\V2\CrmException If an error occurs.
*/
public function authenticateApi(Request $request): bool
{
// ! Hack to prevent Laravel to cast empty strings to null or the opposite.
// ! If cannot decode json user legacy all() method.
$data = \json_decode($request->getContent(), true);
$data = (\is_array($data) and \json_last_error() === \JSON_ERROR_NONE) ?
array_replace_recursive($request->all(), $data) : $request->all();
$calculatedHash = Helper::signRequest(
$this->bearer_pwd,
$request->getUri(),
$request->getMethod(),
$data
);
// ! Très important hash_equals https://fr.wikipedia.org/wiki/Attaque_temporelle
return \hash_equals($calculatedHash, $request->signature);
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ServiceStat extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = ['date'];
/**
* The model's attributes.
*
* @var array
*/
protected $attributes = [
'nb_calls' => 0
];
// * RELATIONS
/**
* Service BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function service()
{
return $this->belongsTo(Service::class);
}
}

View file

@ -2,15 +2,17 @@
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
use HasApiTokens;
use HasFactory;
use Notifiable;
/**
* The attributes that are mass assignable.
@ -41,4 +43,21 @@ class User extends Authenticatable
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $user) {
$user->password = Hash::make($user->password);
});
static::updating(function (self $user) {
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($user->password);
}
});
}
}

View file

@ -4,15 +4,26 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Storage;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Collection;
/**
* XmlInco
*
* pictures attribute is organized as following
* [
* 'checksum' => 'storageFilePath'
* ]
*
* ! checksum includes fullfilepath + data
*/
class XmlInco extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'id',
'no_art',
@ -29,72 +40,40 @@ class XmlInco extends Model
'lst_ing',
'lib_allergene',
'nutritional_values',
'picture',
];
protected $casts = [
'picture' => 'array',
'nutritional_values' => 'array',
'pictures',
];
/**
* The Laravel disk name
*
* @var string
*/
protected $disk = 'inco';
protected $imagesPath = 'images';
/**
* The files to ignore
* The attributes that should be cast.
*
* @var array
*/
protected $ignoreFiles = [
'.gitignore', '.gitkeep'
protected $casts = [
'pictures' => 'array',
'nutritional_values' => 'array',
];
/**
* retrieve a collection of medias
*/
public function getMediasAttribute() {
/** @var Filesystem */
$disk = $this->getDisk();
$files = $disk->files($this->imagesPath);
$collFiles = new Collection();
// /** @var File */
foreach ($files as $file) {
$pi = \pathinfo($file, \PATHINFO_BASENAME);
if (in_array($pi, $this->ignoreFiles)) {
continue;
}
if (!in_array($pi, array($this->id))) {
continue;
}
$image = base64_encode($disk->get($file));
$element = array(
"filename" => $pi,
"imgdata" => $image
);
$collFiles->add($element);
}
return $collFiles;
}
// * GETTERS
/**
* Get the concerned disk
* Get niceString attribute
*
* @return Filesystem
* @return string
*/
protected function getDisk(): Filesystem
{
return Storage::disk($this->disk);
}
public function getNiceStringAttribute()
public function getNiceStringAttribute(): string
{
return sprintf('%s - %s', $this->id, $this->descriptif);
}
// * RELATIONS
/**
* ArcadiaBrand belongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\belongsTo
*/
public function arcadiaBrand()
{
return $this->belongsTo(ArcadiaBrand::class);
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace App\Policies;
use App\Models\ArcadiaBrand;
use App\Models\Service;
use App\Policies\Rules\UserStaticRules as USR;
use Illuminate\Auth\Access\HandlesAuthorization;
use Kwaadpepper\CrudPolicies\Policies\RootPolicy;
/**
* @codeCoverageIgnore
*/
class ArcadiaBrandPolicy extends RootPolicy
{
use HandlesAuthorization;
/**
* Init policies on call
*
* @return void
*/
public function setPolicies()
{
/**
* This first rule to return true in a policy action authorises the action
*/
$this->policies = [
'arcadia_brand' => [
'viewAny' => USR::make('everyOne'),
'view' => USR::make('everyOne'),
'create' => USR::make('noOne'),
'update' => USR::make('noOne'),
'delete' => USR::make('noOne')
]
];
}
/**
* Check if the customer belongs to the service
*
* @param Service $service
* @param ArcadiaBrand $arcadiaBrand
* @return boolean
*/
public function arcadiaBrandBelongsToService(Service $service, ArcadiaBrand $arcadiaBrand)
{
return $service->arcadiaBrands()->whereHas('services', function ($q) use ($arcadiaBrand) {
$q->whereId($arcadiaBrand->id);
})->exists();
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Policies\Rules;
use Kwaadpepper\CrudPolicies\Policies\Rules\StaticRules;
/**
* Userlevel Policiy Static Rules
* ! Rules has to return boolean value
* @codeCoverageIgnore
*/
abstract class UserStaticRules extends StaticRules
{
/**
* Allow everyone
*
* @return boolean
*/
protected static function everyOne(): bool
{
return true;
}
/**
* Allow no one
*
* @return boolean
*/
protected static function noOne(): bool
{
return false;
}
}

View file

@ -2,6 +2,14 @@
namespace App\Providers;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -13,7 +21,6 @@ class AppServiceProvider extends ServiceProvider
*/
public function register()
{
//
}
/**
@ -23,6 +30,54 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
//
// ! Important pourra etre retiré quand la V1 sera supprimé
// Empeche l'encapsulation dans data .
JsonResource::withoutWrapping();
// * LOCAL HTTPS FIX.
if (!in_array(config('app.env'), ['local', 'testing'])) {
URL::forceScheme('https');
}
// * HA PROXY FIX.
App::singleton('clientIp', function () {
if (
config('app.env') === 'local'
) {
return request()->getClientIp();
} else {
return request()->server('HTTP_X_FORWARDED_FOR') ??
request()->getClientIp();
}
});
// * LOCAL FORCE eg: for Carbon
// * Remove numeric to prevent float seperator to breaking numeric transactions
\collect([\LC_TIME, \LC_MESSAGES, \LC_COLLATE, \LC_CTYPE, \LC_MONETARY])
->map(function ($contant) {
setlocale(
$contant,
\sprintf(
'%s_%s.UTF-8',
config('app.locale'),
\strtoupper(config('app.locale'))
)
);
});
Carbon::setLocale(config('app.locale'));
// * Force bootstrap paginator
Paginator::useBootstrap();
$appInfos = Cache::remember(
'composer',
360,
function () {
return json_decode(File::get(\app_path('../composer.json')));
}
);
View::share('name', $appInfos->name);
View::share('version', $appInfos->version);
View::share('license', $appInfos->license);
}
}

View file

@ -2,8 +2,12 @@
namespace App\Providers;
use App\Lib\Helper;
use App\Models\Service;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
@ -12,9 +16,7 @@ class AuthServiceProvider extends ServiceProvider
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
protected $policies = [];
/**
* Register any authentication / authorization services.
@ -25,6 +27,50 @@ class AuthServiceProvider extends ServiceProvider
{
$this->registerPolicies();
//
Auth::viaRequest('api-hmac', function (Request $request) {
/**
* Un token devrais être fourni pour chaque requete
*/
if (is_null($request->bearer_token)) {
return null;
}
// * Si la route actuelle n'as pas de nom alors on refuse de la traiter.
if (!(Helper::getRoute($request))) {
// @codeCoverageIgnoreStart
return null;
// @codeCoverageIgnoreEnd
}
$service = null;
try {
// * On récupère le service actuel correspondant au token fourni.
$service = Service::getFromBearerToken($request->bearer_token ?? '');
} catch (ModelNotFoundException $e) {
return null;
}
// * Preload the arcadiaBrands as we will access it often later.
$service = $service->load('arcadiaBrands');
/**
** All Api routes must pass ArcadiaBrand model to controller action since a ArcadiaBrand owns
** a service token, otherwise the api cannot be called.
** This hack is a custom binder instead of passing
** this to route as a parameter
*/
app()->bind(Service::class, function () use ($service) {
return $service;
});
// * On authenticate la request en vérifiant sa signature.
if ($service->authenticateApi($request)) {
return $service;
}
return null;
});
}
}

View file

@ -5,7 +5,6 @@ namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
@ -27,6 +26,5 @@ class EventServiceProvider extends ServiceProvider
*/
public function boot()
{
//
}
}

View file

@ -19,15 +19,6 @@ class RouteServiceProvider extends ServiceProvider
*/
public const HOME = '/home';
/**
* The controller namespace for the application.
*
* When present, controller route declarations will automatically be prefixed with this namespace.
*
* @var string|null
*/
// protected $namespace = 'App\\Http\\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
@ -56,8 +47,16 @@ class RouteServiceProvider extends ServiceProvider
*/
protected function configureRateLimiting()
{
RateLimiter::for('web', function (Request $request) {
// @codeCoverageIgnoreStart
return Limit::perMinute(25)
->by(optional($request->user())->id ?: app('clientIp'));
// @codeCoverageIgnoreEnd
});
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
return Limit::perMinute(config('api.apiRequestRatePerMinute', 100))
->by(app('clientIp'));
});
}
}

View file

@ -1,19 +1,31 @@
{
"name": "laravel/laravel",
"name": "ldc/incollector",
"type": "project",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"version": "1.0.0",
"description": "LDC Incollector - A workaround of Arcadia",
"authors": [
{
"name": "Olivier Legat",
"email": "olivier.legat@ldc.fr"
},
{
"name": "Jérémy Munsch",
"email": "jeremy.munsch@ldc.fr",
"homepage": "https://jeremydev.ovh"
}
],
"license": "MIT",
"require": {
"php": "^7.3|^8.0",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"kwaadpepper/crud-policies": "^0.3.5",
"kwaadpepper/enum": "^2.1",
"kwaadpepper/exception-handler": "^0.0.5",
"laravel/framework": "^8.65",
"laravel/sanctum": "^2.11",
"laravel/tinker": "^2.5",
"squizlabs/php_codesniffer": "^3.6"
"rennokki/laravel-eloquent-query-cache": "^3.3"
},
"require-dev": {
"facade/ignition": "^2.5",
@ -21,7 +33,9 @@
"laravel/sail": "^1.0.1",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^5.10",
"phpunit/phpunit": "^9.5.10"
"phpunit/phpunit": "^9.5.10",
"slevomat/coding-standard": "^7.1",
"squizlabs/php_codesniffer": "^3.6"
},
"autoload": {
"psr-4": {
@ -36,6 +50,16 @@
}
},
"scripts": {
"test": [
"@configclear",
"./test.sh"
],
"configclear": "@php artisan config:clear",
"runtestmigration": "@php artisan migrate:fresh --env=testing --seed",
"runtest": "@php artisan test --no-coverage --testsuite Api",
"runtestWithCoverage": "@php artisan test --testsuite Api",
"runserve": "@php artisan serve --host=127.0.0.1 --port=8888 --env=testing&",
"killserve": "kill $(ps -f | grep '[1]27.0.0.1:8888' | awk '{print $2}')",
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
@ -58,7 +82,10 @@
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"minimum-stability": "dev",
"prefer-stable": true

1140
composer.lock generated

File diff suppressed because it is too large Load diff

30
config/api.php Normal file
View file

@ -0,0 +1,30 @@
<?php
use App\Lib\Helper;
return [
/**
* Api versions
** this value is sent in response
*/
'version' => Helper::getPackageInfo()['version'],
/**
* Timestamp timeout in seconds
* Request with timestamp older than 30sec
* will be rejected
*/
'timestampTimeout' => 30,
/**
* The number of request
* per user or ip per minutes
*/
'apiRequestRatePerMinute' => 256,
/**
* Service requests cache
* time in minutes
*/
'servicesDBCache' => 10
];

View file

@ -3,53 +3,53 @@
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
'debug' => (bool)env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://localhost'),
@ -57,66 +57,66 @@ return [
'asset_url' => env('ASSET_URL', null),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'UTC',
'timezone' => 'Europe/Paris',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'en',
'locale' => 'fr',
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
'faker_locale' => 'fr_FR',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY'),
@ -124,14 +124,14 @@ return [
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
@ -139,6 +139,7 @@ return [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
@ -169,23 +170,23 @@ return [
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => [
@ -218,7 +219,6 @@ return [
'Queue' => Illuminate\Support\Facades\Queue::class,
'RateLimiter' => Illuminate\Support\Facades\RateLimiter::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,

View file

@ -3,14 +3,14 @@
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
@ -19,20 +19,20 @@ return [
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session"
|
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session"
|
*/
'guards' => [
@ -40,50 +40,48 @@ return [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'api-hmac'
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
]
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that the reset token should be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
@ -96,14 +94,14 @@ return [
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => 10800,

View file

@ -3,29 +3,29 @@
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "ably", "redis", "log", "null"
|
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "ably", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_DRIVER', 'null'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [

View file

@ -5,30 +5,30 @@ use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
*/
'default' => env('CACHE_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb", "octane", "null"
|
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb", "octane", "null"
|
*/
'stores' => [
@ -95,16 +95,16 @@ return [
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing a RAM based store such as APC or Memcached, there might
| be other applications utilizing the same cache. So, we'll specify a
| value to get prefixed to all our keys so we can avoid collisions.
|
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing a RAM based store such as APC or Memcached, there might
| be other applications utilizing the same cache. So, we'll specify a
| value to get prefixed to all our keys so we can avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'),
];

View file

@ -3,16 +3,16 @@
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],

View file

@ -5,32 +5,32 @@ use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'mysql'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
@ -94,27 +94,27 @@ return [
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database.
|
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database.
|
*/
'migrations' => 'migrations',
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => [
@ -123,7 +123,7 @@ return [
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [

View file

@ -11,5 +11,7 @@ return [
* Put a developper secured mailbox in here
* so you can receive email if things go mad
*/
'contactsList' => []
'contactsList' => [
env('MAIL_LIST_SERVICEWEB', 'equipe.web@ldc.fr')
]
];

View file

@ -3,29 +3,29 @@
return [
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application. Just store away!
|
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application. Just store away!
|
*/
'default' => env('FILESYSTEM_DRIVER', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Here you may configure as many filesystem "disks" as you wish, and you
| may even configure multiple disks of the same driver. Defaults have
| been setup for each driver as an example of the required options.
|
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Here you may configure as many filesystem "disks" as you wish, and you
| may even configure multiple disks of the same driver. Defaults have
| been setup for each driver as an example of the required options.
|
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
@ -38,7 +38,7 @@ return [
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
],
@ -57,17 +57,21 @@ return [
'driver' => 'local',
'root' => storage_path('inco'),
],
'arcadia_brand' => [
'driver' => 'local',
'root' => storage_path('marque'),
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
|
| Here you may configure the symbolic links that will be created when the
| `storage:link` Artisan command is executed. The array keys should be
| the locations of the links and the values should be their targets.
|
*/
'links' => [

View file

@ -3,29 +3,29 @@
return [
/*
|--------------------------------------------------------------------------
| Default Hash Driver
|--------------------------------------------------------------------------
|
| This option controls the default hash driver that will be used to hash
| passwords for your application. By default, the bcrypt algorithm is
| used; however, you remain free to modify this option if you wish.
|
| Supported: "bcrypt", "argon", "argon2id"
|
|--------------------------------------------------------------------------
| Default Hash Driver
|--------------------------------------------------------------------------
|
| This option controls the default hash driver that will be used to hash
| passwords for your application. By default, the bcrypt algorithm is
| used; however, you remain free to modify this option if you wish.
|
| Supported: "bcrypt", "argon", "argon2id"
|
*/
'driver' => 'bcrypt',
/*
|--------------------------------------------------------------------------
| Bcrypt Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Bcrypt algorithm. This will allow you
| to control the amount of time it takes to hash the given password.
|
|--------------------------------------------------------------------------
| Bcrypt Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Bcrypt algorithm. This will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'bcrypt' => [
@ -33,14 +33,14 @@ return [
],
/*
|--------------------------------------------------------------------------
| Argon Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Argon algorithm. These will allow you
| to control the amount of time it takes to hash the given password.
|
|--------------------------------------------------------------------------
| Argon Options
|--------------------------------------------------------------------------
|
| Here you may specify the configuration options that should be used when
| passwords are hashed using the Argon algorithm. These will allow you
| to control the amount of time it takes to hash the given password.
|
*/
'argon' => [

5
config/ldcweb.php Normal file
View file

@ -0,0 +1,5 @@
<?php
return [
'project_uuid' => env('PROJECT_UUID')
];

View file

@ -7,44 +7,44 @@ use Monolog\Handler\SyslogUdpHandler;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
|--------------------------------------------------------------------------
| Deprecations Log Channel
|--------------------------------------------------------------------------
|
| This option controls the log channel that should be used to log warnings
| regarding deprecated PHP and library features. This allows you to get
| your application ready for upcoming major versions of dependencies.
|
*/
'deprecations' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [

View file

@ -3,34 +3,34 @@
return [
/*
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send any email
| messages sent by your application. Alternative mailers may be setup
| and used as needed; however, this mailer will be used by default.
|
|--------------------------------------------------------------------------
| Default Mailer
|--------------------------------------------------------------------------
|
| This option controls the default mailer that is used to send any email
| messages sent by your application. Alternative mailers may be setup
| and used as needed; however, this mailer will be used by default.
|
*/
'default' => env('MAIL_MAILER', 'smtp'),
/*
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers to be used while
| sending an e-mail. You will specify which one you are using for your
| mailers below. You are free to add additional mailers as required.
|
| Supported: "smtp", "sendmail", "mailgun", "ses",
| "postmark", "log", "array", "failover"
|
|--------------------------------------------------------------------------
| Mailer Configurations
|--------------------------------------------------------------------------
|
| Here you may configure all of the mailers used by your application plus
| their respective settings. Several examples have been configured for
| you and you are free to add your own as your application requires.
|
| Laravel supports a variety of mail "transport" drivers to be used while
| sending an e-mail. You will specify which one you are using for your
| mailers below. You are free to add additional mailers as required.
|
| Supported: "smtp", "sendmail", "mailgun", "ses",
| "postmark", "log", "array", "failover"
|
*/
'mailers' => [
@ -81,14 +81,14 @@ return [
],
/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all e-mails sent by your application to be sent from
| the same address. Here, you may specify a name and address that is
| used globally for all e-mails that are sent by your application.
|
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all e-mails sent by your application to be sent from
| the same address. Here, you may specify a name and address that is
| used globally for all e-mails that are sent by your application.
|
*/
'from' => [
@ -97,19 +97,20 @@ return [
],
'contactsList' => [
'serviceweb' => explode(",", env('MAIL_LIST_SERVICEWEB', 'equipe.web@ldc.fr')),
'invalidInco' => explode(",", env('MAIL_LIST_INVALIDINCO', 'equipe.web@ldc.fr')),
'serviceweb' => explode(',', env('MAIL_LIST_SERVICEWEB', 'equipe.web@ldc.fr')),
// phpcs:ignore Generic.Files.LineLength.TooLong
'invalidInco' => explode(',', env('MAIL_LIST_INVALIDINCO', 'equipe.web@ldc.fr,vincent.pierre@ldc.fr,christelle.manceau@ldc.fr')),
],
/*
|--------------------------------------------------------------------------
| Markdown Mail Settings
|--------------------------------------------------------------------------
|
| If you are using Markdown based email rendering, you may configure your
| theme and component paths here, allowing you to customize the design
| of the emails. Or, you may simply stick with the Laravel defaults!
|
|--------------------------------------------------------------------------
| Markdown Mail Settings
|--------------------------------------------------------------------------
|
| If you are using Markdown based email rendering, you may configure your
| theme and component paths here, allowing you to customize the design
| of the emails. Or, you may simply stick with the Laravel defaults!
|
*/
'markdown' => [

View file

@ -3,29 +3,29 @@
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue API supports an assortment of back-ends via a single
| API, giving you convenient access to each back-end using the same
| syntax for every one. Here you may define a default connection.
|
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue API supports an assortment of back-ends via a single
| API, giving you convenient access to each back-end using the same
| syntax for every one. Here you may define a default connection.
|
*/
'default' => env('QUEUE_CONNECTION', 'sync'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection information for each server that
| is used by your application. A default configuration has been added
| for each back-end shipped with Laravel. You are free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection information for each server that
| is used by your application. A default configuration has been added
| for each back-end shipped with Laravel. You are free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
*/
'connections' => [
@ -74,14 +74,14 @@ return [
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control which database and table are used to store the jobs that
| have failed. You may change them to any database / table you wish.
|
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control which database and table are used to store the jobs that
| have failed. You may change them to any database / table you wish.
|
*/
'failed' => [

View file

@ -3,58 +3,58 @@
return [
/*
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
|--------------------------------------------------------------------------
| Stateful Domains
|--------------------------------------------------------------------------
|
| Requests from the following domains / hosts will receive stateful API
| authentication cookies. Typically, these should include your local
| and production domains which access your API via a frontend SPA.
|
*/
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
/*
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
|--------------------------------------------------------------------------
| Sanctum Guards
|--------------------------------------------------------------------------
|
| This array contains the authentication guards that will be checked when
| Sanctum is trying to authenticate a request. If none of these guards
| are able to authenticate the request, Sanctum will use the bearer
| token that's present on an incoming request for authentication.
|
*/
'guard' => ['web'],
/*
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. If this value is null, personal access tokens do
| not expire. This won't tweak the lifetime of first-party sessions.
|
|--------------------------------------------------------------------------
| Expiration Minutes
|--------------------------------------------------------------------------
|
| This value controls the number of minutes until an issued token will be
| considered expired. If this value is null, personal access tokens do
| not expire. This won't tweak the lifetime of first-party sessions.
|
*/
'expiration' => null,
/*
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
|--------------------------------------------------------------------------
| Sanctum Middleware
|--------------------------------------------------------------------------
|
| When authenticating your first-party SPA with Sanctum you may need to
| customize some of the middleware Sanctum uses while processing the
| request. You may change the middleware listed below as required.
|
*/
'middleware' => [

View file

@ -3,15 +3,15 @@
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'mailgun' => [

View file

@ -5,30 +5,30 @@ use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option controls the default session "driver" that will be used on
| requests. By default, we will use the lightweight native driver but
| you may specify any of the other wonderful drivers provided here.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option controls the default session "driver" that will be used on
| requests. By default, we will use the lightweight native driver but
| you may specify any of the other wonderful drivers provided here.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to immediately expire on the browser closing, set that option.
|
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to immediately expire on the browser closing, set that option.
|
*/
'lifetime' => env('SESSION_LIFETIME', 120),
@ -36,164 +36,164 @@ return [
'expire_on_close' => false,
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it is stored. All encryption will be run
| automatically by Laravel and you can use the Session like normal.
|
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it is stored. All encryption will be run
| automatically by Laravel and you can use the Session like normal.
|
*/
'encrypt' => false,
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When using the native session driver, we need a location where session
| files may be stored. A default has been set for you but a different
| location may be specified. This is only needed for file sessions.
|
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When using the native session driver, we need a location where session
| files may be stored. A default has been set for you but a different
| location may be specified. This is only needed for file sessions.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION', null),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table we
| should use to manage the sessions. Of course, a sensible default is
| provided for you; however, you are free to change this as needed.
|
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table we
| should use to manage the sessions. Of course, a sensible default is
| provided for you; however, you are free to change this as needed.
|
*/
'table' => 'sessions',
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| While using one of the framework's cache driven session backends you may
| list a cache store that should be used for these sessions. This value
| must match with one of the application's configured cache "stores".
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| While using one of the framework's cache driven session backends you may
| list a cache store that should be used for these sessions. This value
| must match with one of the application's configured cache "stores".
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE', null),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the cookie used to identify a session
| instance by ID. The name specified here will get used every time a
| new session cookie is created by the framework for every driver.
|
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the cookie used to identify a session
| instance by ID. The name specified here will get used every time a
| new session cookie is created by the framework for every driver.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
Str::slug(env('APP_NAME', 'laravel'), '_') . '_session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary.
|
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary.
|
*/
'path' => '/',
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
*/
'domain' => env('SESSION_DOMAIN', null),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. You are free to modify this option if needed.
|
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. You are free to modify this option if needed.
|
*/
'http_only' => true,
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" since this is a secure default value.
|
| Supported: "lax", "strict", "none", null
|
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" since this is a secure default value.
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => 'lax',

58
config/trustedproxy.php Normal file
View file

@ -0,0 +1,58 @@
<?php
return [
/*
* Set trusted proxy IP addresses.
*
* Both IPv4 and IPv6 addresses are
* supported, along with CIDR notation.
*
* The "*" character is syntactic sugar
* within TrustedProxy to trust any proxy
* that connects directly to your server,
* a requirement when you cannot know the address
* of your proxy (e.g. if using ELB or similar).
*
*/
'proxies' => [
// * HA PROXY prod "Virtual IP".
'192.168.31.9',
// * HA PROXY prod "Virtual IP" redondance.
'192.168.31.10',
// * HA PROXY dev "Virtual IP".
'192.168.34.15'
],
/*
* To trust one or more specific proxies that connect
* directly to your server, use an array or a string separated by comma of IP addresses:
*/
/*
* Or, to trust all proxies that connect
* directly to your server, use a "*"
*/
/*
* Which headers to use to detect proxy related data (For, Host, Proto, Port)
*
* Options include:
*
* - Illuminate\Http\Request::HEADER_X_FORWARDED_ALL (use all x-forwarded-* headers to establish trust)
* - Illuminate\Http\Request::HEADER_FORWARDED (use the FORWARDED header to establish trust)
* - Illuminate\Http\Request::HEADER_X_FORWARDED_AWS_ELB (If you are using AWS Elastic Load Balancer)
*
* - 'HEADER_X_FORWARDED_ALL' (use all x-forwarded-* headers to establish trust)
* - 'HEADER_FORWARDED' (use the FORWARDED header to establish trust)
* - 'HEADER_X_FORWARDED_AWS_ELB' (If you are using AWS Elastic Load Balancer)
*
* @link https://symfony.com/doc/current/deployment/proxies.html
*/
'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_FOR |
Illuminate\Http\Request::HEADER_X_FORWARDED_HOST |
Illuminate\Http\Request::HEADER_X_FORWARDED_PORT |
Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO,
];

View file

@ -3,14 +3,14 @@
return [
/*
|--------------------------------------------------------------------------
| View Storage Paths
|--------------------------------------------------------------------------
|
| Most templating systems load templates from disk. Here you may specify
| an array of paths that should be checked for your views. Of course
| the usual Laravel view path has already been registered for you.
|
|--------------------------------------------------------------------------
| View Storage Paths
|--------------------------------------------------------------------------
|
| Most templating systems load templates from disk. Here you may specify
| an array of paths that should be checked for your views. Of course
| the usual Laravel view path has already been registered for you.
|
*/
'paths' => [
@ -18,14 +18,14 @@ return [
],
/*
|--------------------------------------------------------------------------
| Compiled View Path
|--------------------------------------------------------------------------
|
| This option determines where all the compiled Blade templates will be
| stored for your application. Typically, this is within the storage
| directory. However, as usual, you are free to change this value.
|
|--------------------------------------------------------------------------
| Compiled View Path
|--------------------------------------------------------------------------
|
| This option determines where all the compiled Blade templates will be
| stored for your application. Typically, this is within the storage
| directory. However, as usual, you are free to change this value.
|
*/
'compiled' => env(

View file

@ -0,0 +1,21 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class ArcadiaBrandFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'arcadia_id' => $this->faker->unique()->numberBetween(1, 999),
'libelle' => $this->faker->unique()->company()
];
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class ServiceFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'name' => $this->faker->unique()->name() . ' Service'
];
}
}

View file

@ -18,7 +18,7 @@ class UserFactory extends Factory
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'password' => 'password',
'remember_token' => Str::random(10),
];
}
@ -30,6 +30,7 @@ class UserFactory extends Factory
*/
public function unverified()
{
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
return $this->state(function (array $attributes) {
return [
'email_verified_at' => null,

View file

@ -0,0 +1,105 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class XmlIncoFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public function definition()
{
return [
'nom' => $this->faker->unique()->words(rand(2, 3), true),
'prod_saison' => $this->faker->boolean(),
'allegations' => $this->faker->boolean(75) ? $this->faker->sentence(7) : null,
'descriptif' => $this->faker->boolean(75) ? 'descriptif ' . $this->faker->text(150) : null,
'pictures' => [],
'astuces' => $this->faker->boolean(75) ? 'astuces ' . $this->faker->text(150) : null,
'lib_conso' => $this->faker->boolean(75) ? $this->faker->sentence(7) : null,
'nb_portion' => $this->faker->boolean(75) ? $this->faker->text(20) : null,
'typ_portion' => $this->faker->boolean(75) ? 'typ_portion ' . $this->faker->text(150) : null,
'pds_moy_theo' => $this->faker->boolean(75) ? $this->faker->numberBetween(0, 250) : null,
'conseil_prepa' => $this->faker->boolean(75) ? 'conseil_prepa ' . $this->faker->text(150) : null,
'arg_vente' => $this->faker->boolean(75) ? 'arg_vente ' . $this->faker->text(150) : null,
'lst_ing' => $this->faker->boolean(75) ? 'lst_ing ' . $this->faker->text(150) : null,
'lib_allergene' => $this->faker->boolean(75) ? 'lib_allergene ' . $this->faker->text(150) : null,
'nutritional_values' => [
'val_energ_kj_r' => $this->faker->numberBetween(250, 1200),
'val_energ_kcal_r' => $this->faker->numberBetween(50, 400),
'mat_grass_r' => $this->faker->numberBetween(6, 30),
'ags_r' => '5.3',
'glucides_r' => '2',
'sucre_r' => '1.4',
'fib_alim_r' => '0.8',
'proteine_r' => '17',
'sel_r' => '0.47',
'sodium_r' => '188',
'fruits_r' => '0',
'fruits_coque_sech_r' => '0',
'pourcent_apport_ref_e14_r' => '11.4',
'pourcent_apport_ref_kjo_r' => '11.5',
'pourcent_apport_ref_fat_r' => '24.7',
'pourcent_apport_ref_fatsat_r' => '29.8',
'pourcent_apport_ref_choval_r' => '0.6',
'pourcent_apport_ref_sugar_r' => '1.7',
'pourcent_apport_ref_fibtg_r' => null,
'pourcent_apport_ref_pro_r' => '33',
'pourcent_apport_ref_salteq_r' => '6.7',
'taille_port' => null,
'val_energ_kj_p' => null,
'val_energ_kcal_p' => null,
'mat_grass_p' => null,
'ags_p' => null,
'glucides_p' => null,
'sucre_p' => null,
'fib_alim_p' => null,
'proteine_p' => null,
'sodium_p' => null,
'fruits_p' => null,
'sel_p' => null,
'fruits_coque_sech_p' => null,
'pourcent_apport_ref_e14_p' => null,
'pourcent_apport_ref_kjo_p' => null,
'pourcent_apport_ref_fat_p' => null,
'pourcent_apport_ref_fatsat_p' => null,
'pourcent_apport_ref_choval_p' => null,
'pourcent_apport_ref_sugar_p' => null,
'pourcent_apport_ref_fibtg_p' => null,
'pourcent_apport_ref_pro_p' => null,
'pourcent_apport_ref_salteq_p' => null,
'apport_port' => 'Apport de r\u00e9f\u00e9rence pour un adulte type (8400Kj-2000Kcal)',
'part_p' => '32',
'part_g' => '4',
'part_l' => '63',
'p_sur_l' => '1.13',
'pourcent_ags' => '35',
'pourcent_agi' => '65',
'pourcent_ss' => '70',
'pourcent_amidon' => '30',
'composante_n' => '9',
'composante_p' => '5',
'pourcent_fruits' => '0',
'score_nutri' => '4',
'classement' => 'C',
'mat_grass_r_prec' => '=',
'ags_r_prec' => '=',
'glucides_r_prec' => '=',
'sucre_r_prec' => '=',
'fib_alim_r_prec' => '=',
'proteine_r_prec' => '=',
'sel_r_prec' => '=',
'sodium_r_prec' => null,
'fruits_r_prec' => null,
'fruits_coque_sech_r_prec' => null
]
];
}
}

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateArcadiaBrandsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('arcadia_brands', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('arcadia_id')->unique();
$table->string('libelle');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('arcadia_brands');
}
}

View file

@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePersonalAccessTokensTable extends Migration
class CreateServicesTable extends Migration
{
/**
* Run the migrations.
@ -13,13 +13,14 @@ class CreatePersonalAccessTokensTable extends Migration
*/
public function up()
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
Schema::create('services', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->string('bearer_token')->unique();
$table->string('bearer_pwd', 312);
$table->timestamp('bearer_token_date');
$table->timestamps();
});
}
@ -31,6 +32,6 @@ class CreatePersonalAccessTokensTable extends Migration
*/
public function down()
{
Schema::dropIfExists('personal_access_tokens');
Schema::dropIfExists('services');
}
}

View file

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateArcadiaBrandServiceTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('arcadia_brand_service', function (Blueprint $table) {
$table->foreignId('service_id')->constrained('services');
$table->foreignId('arcadia_brand_id')->constrained('arcadia_brands');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('arcadia_brand_service');
}
}

View file

@ -15,13 +15,13 @@ class CreateXmlIncosTable extends Migration
public function up()
{
Schema::create('xml_incos', function (Blueprint $table) {
$table->id();
$table->id()->comment('Inco ID (NO_ART)');
$table->string('nom')->nullable();
$table->boolean('prod_saison');
$table->string('allegations')->nullable();
$table->text('descriptif')->nullable();
$table->json('picture');
$table->json('pictures');
$table->text('astuces')->nullable();
$table->string('lib_conso')->nullable();
$table->string('nb_portion', 20)->nullable();
@ -35,6 +35,8 @@ class CreateXmlIncosTable extends Migration
$table->json('nutritional_values');
$table->foreignId('arcadia_brand_id')->constrained('arcadia_brands');
$table->timestamps();
});
}

View file

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace
class CreateServiceStats extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('service_stats', function (Blueprint $table) {
$table->id();
$table->foreignId('service_id')->constrained('services');
$table->date('date');
$table->unsignedBigInteger('nb_calls')->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('service_stats');
}
}

View file

@ -2,7 +2,13 @@
namespace Database\Seeders;
use App\Models\ArcadiaBrand;
use App\Models\Service;
use App\Models\XmlInco;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use SplFileInfo;
class DatabaseSeeder extends Seeder
{
@ -13,6 +19,35 @@ class DatabaseSeeder extends Seeder
*/
public function run()
{
// \App\Models\User::factory(10)->create();
/** @var \Illuminate\Database\Eloquent\Collection $services */
$services = Service::factory(5)->create();
/** @var \Illuminate\Database\Eloquent\Collection $brands */
$brands = ArcadiaBrand::factory(10)->create();
// * Associate brands to services
$services->map(function (Service $service) use (&$brands) {
$pullBrands = $brands->random(rand(1, 2));
$service->arcadiaBrands()->sync($pullBrands);
$brands = $brands->diff($pullBrands);
});
$disk = Storage::disk('inco');
$brands->map(function (ArcadiaBrand $brand) use ($disk) {
XmlInco::factory(rand(150, 355))->make()
->each(function (XmlInco $xmlInco) use ($disk, $brand) {
$xmlInco->pictures = collect([
new SplFileInfo(resource_path('seeders/xml_inco/fakeimage1.png')),
new SplFileInfo(resource_path('seeders/xml_inco/fakeimage2.png'))
])->random(rand(0, 2))->map(function (SplFileInfo $image) use ($disk) {
$imgNamePath = $image->getFilename();
$imageFinalPath = "images/inco/$imgNamePath";
$disk->put($imgNamePath, File::get($image->getPathname()));
return $imageFinalPath;
})->all();
$xmlInco->arcadiaBrand()->associate($brand);
$xmlInco->saveOrFail();
});
});
}
}

16468
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,9 @@
},
"devDependencies": {
"axios": "^0.21",
"clean-webpack-plugin": "^4.0.0",
"laravel-mix": "^6.0.6",
"laravel-mix-polyfill": "^3.0.1",
"lodash": "^4.17.19",
"postcss": "^8.1.14"
}

View file

@ -1,31 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/schema/9.2.xsd" bootstrap="vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
<testsuite name="Api">
<directory suffix="Test.php">./tests/Api</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory>./app</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="tests/coverage" />
</logging>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<!-- <server name="DB_CONNECTION" value="sqlite"/> -->
<!-- <server name="DB_DATABASE" value=":memory:"/> -->
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
<server name="APP_ENV" value="testing" />
<server name="APP_URL" value="http://127.0.0.1:8888" />
<server name="BCRYPT_ROUNDS" value="4" />
<server name="CACHE_DRIVER" value="array" />
<server name="MAIL_MAILER" value="array" />
<env name="QUEUE_DRIVER" value="database" />
<server name="QUEUE_CONNECTION" value="database" />
<server name="SESSION_DRIVER" value="array" />
<server name="TELESCOPE_ENABLED" value="false" />
</php>
</phpunit>

View file

View file

@ -0,0 +1,270 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Created with Liquid Technologies Online Tools 1.0 (https://www.liquid-technologies.com) -->
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="INCO">
<xs:complexType>
<xs:sequence>
<xs:element name="typemouvementinformatique" type="xs:string" />
<xs:element name="NO_ART" type="xs:string" />
<xs:element name="INCO_COMPO">
<xs:complexType>
<xs:sequence>
<xs:element name="TBL_INCO_COMPO">
<xs:complexType>
<xs:sequence>
<xs:element name="ID" type="xs:string" />
<xs:element name="ID_INCO" type="xs:string" />
<xs:element name="NOM" type="xs:string" />
<xs:element name="NUMERO" type="xs:string" />
<xs:element name="TAILLE_REF" type="xs:string" />
<xs:element name="VAL_ENERG_KJ_R" type="xs:string" />
<xs:element name="VAL_ENERG_KCAL_R" type="xs:string" />
<xs:element name="MAT_GRASS_R" type="xs:string" />
<xs:element name="AGS_R" type="xs:string" />
<xs:element name="GLUCIDES_R" type="xs:string" />
<xs:element name="SUCRE_R" type="xs:string" />
<xs:element name="FIB_ALIM_R" type="xs:string" />
<xs:element name="PROTEINE_R" type="xs:string" />
<xs:element name="SEL_R" type="xs:string" />
<xs:element name="SODIUM_R" type="xs:string" />
<xs:element name="FRUITS_R" type="xs:string" />
<xs:element name="FRUITS_COQUE_SECH_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_E14_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_KJO_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FAT_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FATSAT_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_CHOVAL_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_SUGAR_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FIBTG_R" />
<xs:element name="POURCENT_APPORT_REF_PRO_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_SALTEQ_R" type="xs:string" />
<xs:element name="TAILLE_PORT" type="xs:string" />
<xs:element name="VAL_ENERG_KJ_P" type="xs:string" />
<xs:element name="VAL_ENERG_KCAL_P" type="xs:string" />
<xs:element name="MAT_GRASS_P" type="xs:string" />
<xs:element name="AGS_P" type="xs:string" />
<xs:element name="GLUCIDES_P" type="xs:string" />
<xs:element name="SUCRE_P" type="xs:string" />
<xs:element name="FIB_ALIM_P" type="xs:string" />
<xs:element name="PROTEINE_P" type="xs:string" />
<xs:element name="SEL_P" type="xs:string" />
<xs:element name="SODIUM_P" type="xs:string" />
<xs:element name="FRUITS_P" type="xs:string" />
<xs:element name="FRUITS_COQUE_SECH_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_E14_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_KJO_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FAT_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FATSAT_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_CHOVAL_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_SUGAR_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FIBTG_P" />
<xs:element name="POURCENT_APPORT_REF_PRO_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_SALTEQ_P" type="xs:string" />
<xs:element name="APPORT_PORT" type="xs:string" />
<xs:element name="PART_P" type="xs:string" />
<xs:element name="PART_G" type="xs:string" />
<xs:element name="PART_L" type="xs:string" />
<xs:element name="P_SUR_L" type="xs:string" />
<xs:element name="POURCENT_AGS" type="xs:string" />
<xs:element name="POURCENT_AGI" type="xs:string" />
<xs:element name="POURCENT_SS" type="xs:string" />
<xs:element name="POURCENT_AMIDON" type="xs:string" />
<xs:element name="COMPOSANTE_N" type="xs:string" />
<xs:element name="COMPOSANTE_P" type="xs:string" />
<xs:element name="POURCENT_FRUITS" type="xs:string" />
<xs:element name="SCORE_NUTRI" type="xs:string" />
<xs:element name="CLASSEMENT" type="xs:string" />
<xs:element name="MAT_GRASS_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="AGS_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="GLUCIDES_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="SUCRE_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="FIB_ALIM_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="PROTEINE_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="SEL_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="SODIUM_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" />
<xs:element name="libelle" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="FRUITS_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" />
<xs:element name="libelle" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="FRUITS_COQUE_SECH_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" />
<xs:element name="libelle" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="PROD_SAISON">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="CONSEIL_PREPA" type="xs:string" />
<xs:element name="ALLEGATIONS" />
<xs:element name="ASTUCES" />
<xs:element name="DESCRIPTIF" type="xs:string" />
<xs:element name="ARG_VENTE" type="xs:string" />
<xs:element name="NB_PORTION" type="xs:string" />
<xs:element name="TYP_PORTION">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="FAMILLE_VTE">
<xs:complexType>
<xs:sequence>
<xs:element name="COD_FAMVTE">
<xs:complexType>
<xs:sequence>
<xs:element name="FAM_VENTE">
<xs:complexType>
<xs:sequence>
<xs:element name="COD_FAMVTE" type="xs:string" />
<xs:element name="LIB_FAMVTE" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="DATE_STATUT_CHEF_PRODUIT" type="xs:string" />
<xs:element name="EXPLOITANT">
<xs:complexType>
<xs:sequence>
<xs:element name="SOCIETE">
<xs:complexType>
<xs:sequence>
<xs:element name="ID_SOCIETE" type="xs:string" />
<xs:element name="NOM_SOCIETE" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="PDS_MOY_THEO" />
<xs:element name="LIB_CONSO" />
<xs:element name="LST_ING" />
<xs:element name="LST_ALLERGENE" />
<xs:element name="ALLEGAT_SANTE" />
<xs:element name="ALLEGAT_NUTRIT" />
<xs:element name="ALLEGAT_AUTRE" />
<xs:element name="MEDIA_VISUEL_EAI">
<xs:complexType>
<xs:sequence>
<xs:element name="MEDIA_VISUEL_EAI">
<xs:complexType>
<xs:sequence>
<xs:element name="NumeroArticle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="version" type="xs:string" />
<xs:element name="DATEVERSION" type="xs:string" />
<xs:element name="STATUT_CHEF_PRODUIT">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="CHEF_PRODUIT">
<xs:complexType>
<xs:sequence>
<xs:element name="UTILISATEURS">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View file

@ -1,11 +1,11 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{ $appName }} Exception Handler: Unvalid XML</title>
<title>XML Exception Handler: Invalid XML</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>{{ $appName }} à reçu un fichier xml non valide !!</h1>
<h1>{{ config('app.name') }} à reçu un fichier xml non valide</h1>
<h3>{{ $strError }}</h3>
<h4>FICHIER: `{{ $filename }}`</h4>
<p>{{ $exception->getMessage() }}</p>
@ -15,7 +15,7 @@
<b>file</b> : {{$exception->getFile() }}<br>
<b>line</b> : {{$exception->getLine() }}<br>
<p>
<b>Merci de corriger le flux, puis de renvoyer le fichier en provoquant un mouvement EAI.</b><br><br>
<b>Merci de corriger les données, puis de renvoyer le fichier en provoquant un mouvement EAI.</b><br><br>
<b>Pour rappel</b>&nbsp;: Il est recommandé de valider un fichier XML à l'aide d'un <b>DTD</b><br>
<a href="https://www.w3schools.com/xml/xml_dtd.asp">https://www.w3schools.com/xml/xml_dtd.asp</a><br>
ou bien <b>XSD</b> <a href="https://www.w3schools.com/xml/schema_intro.asp">https://www.w3schools.com/xml/schema_intro.asp</a><br><br>

View file

@ -1,19 +1,19 @@
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\V1\ApiController as V1ApiController;
use App\Http\Controllers\Api\V1\XmlIncoController as V1XmlIncoController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::prefix('v1')->name('apiV1.')->group(function () {
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
// * PUBLIC
Route::get('/ping', [V1ApiController::class, 'ping'])->name('ping');
// * PRIVATE
Route::middleware('auth:api')->group(function () {
Route::prefix('inco')->name('incos.')->group(function () {
Route::get('/', [V1XmlIncoController::class, 'index'])->name('index');
Route::get('/{xmlinco}', [V1XmlIncoController::class, 'show'])->name('show');
});
});
});

View file

@ -1,18 +0,0 @@
<?php
use Illuminate\Support\Facades\Broadcast;
/*
|--------------------------------------------------------------------------
| Broadcast Channels
|--------------------------------------------------------------------------
|
| Here you may register all of the event broadcasting channels that your
| application supports. The given channel authorization callbacks are
| used to check if an authenticated user can listen to the channel.
|
*/
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});

View file

@ -1,19 +0,0 @@
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
/*
|--------------------------------------------------------------------------
| Console Routes
|--------------------------------------------------------------------------
|
| This file is where you may define all of your Closure based console
| commands. Each Closure is bound to a command instance allowing a
| simple approach to interacting with each command's IO methods.
|
*/
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');

View file

@ -3,14 +3,14 @@
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {

1
storage/.gitignore vendored
View file

@ -1 +0,0 @@
inco/*

View file

@ -1,273 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Created with Liquid Technologies Online Tools 1.0 (https://www.liquid-technologies.com) -->
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="LIST">
<xs:complexType>
<xs:sequence>
<xs:element name="INCO">
<xs:complexType>
<xs:sequence>
<xs:element name="typemouvementinformatique" type="xs:string" />
<xs:element name="NO_ART" type="xs:string" />
<xs:element name="INCO_COMPO">
<xs:complexType>
<xs:sequence>
<xs:element name="TBL_INCO_COMPO">
<xs:complexType>
<xs:sequence>
<xs:element name="ID" type="xs:string" />
<xs:element name="ID_INCO" type="xs:string" />
<xs:element name="NOM" type="xs:string" />
<xs:element name="NUMERO" type="xs:string" />
<xs:element name="TAILLE_REF" type="xs:string" />
<xs:element name="VAL_ENERG_KJ_R" type="xs:string" />
<xs:element name="VAL_ENERG_KCAL_R" type="xs:string" />
<xs:element name="MAT_GRASS_R" type="xs:string" />
<xs:element name="AGS_R" type="xs:string" />
<xs:element name="GLUCIDES_R" type="xs:string" />
<xs:element name="SUCRE_R" type="xs:string" />
<xs:element name="FIB_ALIM_R" type="xs:string" />
<xs:element name="PROTEINE_R" type="xs:string" />
<xs:element name="SEL_R" type="xs:string" />
<xs:element name="SODIUM_R" type="xs:string" />
<xs:element name="FRUITS_R" type="xs:string" />
<xs:element name="FRUITS_COQUE_SECH_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_E14_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_KJO_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FAT_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FATSAT_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_CHOVAL_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_SUGAR_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FIBTG_R" />
<xs:element name="POURCENT_APPORT_REF_PRO_R" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_SALTEQ_R" type="xs:string" />
<xs:element name="TAILLE_PORT" type="xs:string" />
<xs:element name="VAL_ENERG_KJ_P" type="xs:string" />
<xs:element name="VAL_ENERG_KCAL_P" type="xs:string" />
<xs:element name="MAT_GRASS_P" type="xs:string" />
<xs:element name="AGS_P" type="xs:string" />
<xs:element name="GLUCIDES_P" type="xs:string" />
<xs:element name="SUCRE_P" type="xs:string" />
<xs:element name="FIB_ALIM_P" type="xs:string" />
<xs:element name="PROTEINE_P" type="xs:string" />
<xs:element name="SEL_P" type="xs:string" />
<xs:element name="SODIUM_P" type="xs:string" />
<xs:element name="FRUITS_P" type="xs:string" />
<xs:element name="FRUITS_COQUE_SECH_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_E14_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_KJO_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FAT_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FATSAT_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_CHOVAL_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_SUGAR_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_FIBTG_P" />
<xs:element name="POURCENT_APPORT_REF_PRO_P" type="xs:string" />
<xs:element name="POURCENT_APPORT_REF_SALTEQ_P" type="xs:string" />
<xs:element name="APPORT_PORT" type="xs:string" />
<xs:element name="PART_P" type="xs:string" />
<xs:element name="PART_G" type="xs:string" />
<xs:element name="PART_L" type="xs:string" />
<xs:element name="P_SUR_L" type="xs:string" />
<xs:element name="POURCENT_AGS" type="xs:string" />
<xs:element name="POURCENT_AGI" type="xs:string" />
<xs:element name="POURCENT_SS" type="xs:string" />
<xs:element name="POURCENT_AMIDON" type="xs:string" />
<xs:element name="COMPOSANTE_N" type="xs:string" />
<xs:element name="COMPOSANTE_P" type="xs:string" />
<xs:element name="POURCENT_FRUITS" type="xs:string" />
<xs:element name="SCORE_NUTRI" type="xs:string" />
<xs:element name="CLASSEMENT" type="xs:string" />
<xs:element name="MAT_GRASS_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="AGS_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="GLUCIDES_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="SUCRE_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="FIB_ALIM_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="PROTEINE_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="SEL_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="SODIUM_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" />
<xs:element name="libelle" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="FRUITS_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" />
<xs:element name="libelle" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="FRUITS_COQUE_SECH_R_PREC">
<xs:complexType>
<xs:sequence>
<xs:element name="code" />
<xs:element name="libelle" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="PROD_SAISON">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="CONSEIL_PREPA" type="xs:string" />
<xs:element name="ALLEGATIONS" />
<xs:element name="ASTUCES" />
<xs:element name="DESCRIPTIF" type="xs:string" />
<xs:element name="ARG_VENTE" type="xs:string" />
<xs:element name="NB_PORTION" type="xs:string" />
<xs:element name="TYP_PORTION">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="FAMILLE_VTE">
<xs:complexType>
<xs:sequence>
<xs:element name="COD_FAMVTE">
<xs:complexType>
<xs:sequence>
<xs:element name="FAM_VENTE">
<xs:complexType>
<xs:sequence>
<xs:element name="COD_FAMVTE" type="xs:string" />
<xs:element name="LIB_FAMVTE" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="DATE_STATUT_CHEF_PRODUIT" type="xs:string" />
<xs:element name="EXPLOITANT">
<xs:complexType>
<xs:sequence>
<xs:element name="SOCIETE">
<xs:complexType>
<xs:sequence>
<xs:element name="ID_SOCIETE" type="xs:string" />
<xs:element name="NOM_SOCIETE" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="PDS_MOY_THEO" />
<xs:element name="LIB_CONSO" />
<xs:element name="LST_ING" />
<xs:element name="LST_ALLERGENE" />
<xs:element name="ALLEGAT_SANTE" />
<xs:element name="ALLEGAT_NUTRIT" />
<xs:element name="ALLEGAT_AUTRE" />
<xs:element name="MEDIA_VISUEL_EAI">
<xs:complexType>
<xs:sequence>
<xs:element name="MEDIA_VISUEL_EAI">
<xs:complexType>
<xs:sequence>
<xs:element name="NumeroArticle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="version" type="xs:string" />
<xs:element name="DATEVERSION" type="xs:string" />
<xs:element name="STATUT_CHEF_PRODUIT">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="CHEF_PRODUIT">
<xs:complexType>
<xs:sequence>
<xs:element name="UTILISATEURS">
<xs:complexType>
<xs:sequence>
<xs:element name="code" type="xs:string" />
<xs:element name="libelle" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

View file

Some files were not shown because too many files have changed in this diff Show more