diff --git a/.gitignore b/.gitignore index be1c766..0127ef6 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ package-lock.json composer.lock resources/front .vscode +storage diff --git a/app/Console/Commands/ExportCodesUniques.php b/app/Console/Commands/ExportCodesUniques.php index 00be9f1..b9d0ccd 100644 --- a/app/Console/Commands/ExportCodesUniques.php +++ b/app/Console/Commands/ExportCodesUniques.php @@ -103,7 +103,7 @@ class ExportCodesUniques extends Command $this->info("L'archive CodesUniques_" . date('dmY') . ".zip a ete creer."); // Envoi de mail à MAIL_ADMIN avec le nom du ZIP "CodesUniques_". date('dmY') .".zip" - Mail::to(config('mail.MAIL_ADMIN'))->send(new MailExportCodesUniques(Settings::find(1)->name, $nbCodeFic, $nbCodeTot, $nbCodeLigne)); + Mail::to(config('mail.MAIL_ADMIN'))->send(new MailExportCodesUniques(app(Settings::class)->name, $nbCodeFic, $nbCodeTot, $nbCodeLigne)); } private function process($nbCodeFic, $nbCodeTot, $nbCodeLigne, $totalExportedCodes, $currentFilename, $currentExportedCodes, $currentLineCodes, $lastExportedCodeId = null) diff --git a/app/Http/Controllers/EndPageController.php b/app/Http/Controllers/EndPageController.php index 699cf39..28d2afe 100644 --- a/app/Http/Controllers/EndPageController.php +++ b/app/Http/Controllers/EndPageController.php @@ -8,7 +8,7 @@ class EndPageController extends Controller { public function index() { - $settings = Settings::find(1)->select('start_date', 'end_date', 'activated_close_page', 'close_page_active_date', 'redirect_close')->first(); + $settings = app(Settings::class)->select('start_date', 'end_date', 'activated_close_page', 'close_page_active_date', 'redirect_close')->first(); if ($settings->end_date < now() && $settings->activated_close_page === 1 && $settings->close_page_active_date > now()) { return view('front.basic.end', compact('settings')); } diff --git a/app/Http/Controllers/EnterCodeController.php b/app/Http/Controllers/EnterCodeController.php index 4b20198..43648ea 100644 --- a/app/Http/Controllers/EnterCodeController.php +++ b/app/Http/Controllers/EnterCodeController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Http\Requests\ParticipateRequest; use App\Mail\CourrierArcadiaError; use App\Mail\NotifAdmin; use App\Mail\NotifWinAdmin; @@ -34,27 +35,21 @@ class EnterCodeController extends Controller public function index() { $nbParticipations = Participation::where('user_id', Auth::user()->id)->count(); - $settings = Settings::find(1)->select('use_ean_codes', 'nb_participation_player')->first(); + $settings = app(Settings::class)->select('use_ean_codes', 'nb_participation_player')->first(); if ($nbParticipations >= $settings->nb_participation_player) { return view('front.basic.participation-exhausted', compact('settings')); } return view('front.basic.enter-code', compact('settings')); } - public function participate(Request $request) + public function participate(ParticipateRequest $request) { // TODO : VALIDATE avec / sans code ean - // Récupération des données à utilisées - $code = strtoupper($request->code); - $codeBase = Codes::where('code_unique', $code)->first(); - $settings = Settings::find(1); + // Récupération des données à utilisées. + $codeBase = Codes::whereId(Codes::serialToInt($request->code))->first(); + $settings = app(Settings::class); - $rules = $request->accept_rules === null ? false : true; - if (!$rules) { - return back()->withErrors("Pour participer, vous devez accepter le règlement du jeu."); - } - - // Cas IP bannie + // * Cas IP bannie. if (config('app.env') == "local") { $ipBanned = BannedIP::where('ip', $_SERVER['REMOTE_ADDR'])->first() === null ? 0 : 1; } else { @@ -64,13 +59,13 @@ class EnterCodeController extends Controller return view('front.basic.participation-exhausted', compact('settings')); } - // Cas nombre de participation maximale atteint + // * Cas nombre de participation maximale atteint. $nbParticipations = Participation::where('user_id', Auth::user()->id)->count(); if ($nbParticipations >= $settings->nb_participation_player) { return view('front.basic.participation-exhausted', compact('settings')); } - // Check le code pour savoir s'il est encore utilisable + // * Check le code pour savoir s'il est encore utilisable. if (!$codeBase) { return back()->withErrors("Code incorrect, veuillez réessayer."); } @@ -78,7 +73,7 @@ class EnterCodeController extends Controller return back()->withErrors("Ce code n'est plus utilisable."); } - // Check si l'utilisateur n'a pas déjà utiliser ce code + // * Check si l'utilisateur n'a pas déjà utiliser ce code. $participUser = Participation::where([ ['code_unique_id', $codeBase->id], ['user_id', Auth::user()->id] @@ -87,7 +82,7 @@ class EnterCodeController extends Controller return back()->withErrors("Vous avez déjà utilisé ce code."); } - // Check si les codes ean sont utilisés, auquel cas on test celui envoyé pour savoir s'il est correct + // * Check si les codes ean sont utilisés, auquel cas on test celui envoyé pour savoir s'il est correct. $codeEan = null; // TODO : Task Algo Sebastien if ($settings->use_ean_codes === 1) { @@ -103,7 +98,7 @@ class EnterCodeController extends Controller $codeNbUsage = Participation::where('code_unique_id', $codeBase->id)->count(); - // Gestion du nombre d'utilisation d'un code + // * Gestion du nombre d'utilisation d'un code. if ($codeNbUsage >= $settings->nb_unique_code_use) { $codeBase->code_used = 1; $codeBase->save(); @@ -114,7 +109,7 @@ class EnterCodeController extends Controller } } - // Tous les cas "bloquants" sont passé, l'utilisateur participe à partir d'ici + // * Tous les cas "bloquants" sont passé, l'utilisateur participe à partir d'ici. $participation = new Participation(); $participation->user_id = Auth::user()->id; $participation->code_unique_id = $codeBase->id; @@ -126,13 +121,13 @@ class EnterCodeController extends Controller } $participation->save(); - // Gestion des instants gagnants + // * Gestion des instants gagnants. $userAlreadyWon = Participation::where([ ['user_id', Auth::user()->id], ['instant_win_id', '<>', null] ])->first(); - // Si l'utilisateur actuel à déjà gagné un gros lot, pas d'instant gagnant + // * Si l'utilisateur actuel à déjà gagné un gros lot, pas d'instant gagnant. if ($userAlreadyWon) { return $this->serveCouponWin($settings,$participation); } else { diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index 690f27d..2063528 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -220,7 +220,7 @@ class LoginController extends Controller $user->password_token = Str::uuid(); $user->save(); - $settings = Settings::find(1); + $settings = app(Settings::class); Mail::to($email)->send(new PasswordReset($user, $settings)); return redirect()->route('login')->with('success', 'Un mail de réinitialisation de votre mot de passe vous a été envoyé(e).'); } diff --git a/app/Http/Controllers/TeaserController.php b/app/Http/Controllers/TeaserController.php index b97643b..634ee82 100644 --- a/app/Http/Controllers/TeaserController.php +++ b/app/Http/Controllers/TeaserController.php @@ -8,7 +8,7 @@ class TeaserController extends Controller { public function index() { - $settings = Settings::find(1)->select('start_date', 'end_date', 'activated_teaser', 'teaser_end_activity')->first(); + $settings = app(Settings::class)->select('start_date', 'end_date', 'activated_teaser', 'teaser_end_activity')->first(); if ($settings->activated_teaser === 1 && $settings->teaser_end_activity > now()) { return view('front.basic.teaser', compact('settings')); } diff --git a/app/Http/Controllers/WinningProofController.php b/app/Http/Controllers/WinningProofController.php index 6a6d4f2..4ed94c2 100644 --- a/app/Http/Controllers/WinningProofController.php +++ b/app/Http/Controllers/WinningProofController.php @@ -99,7 +99,7 @@ class WinningProofController extends Controller $participation->barcode = $barcodeFileName; $participation->save(); - $settings = Settings::find(1); + $settings = app(Settings::class); $mails = json_decode($settings->notification_emails); foreach ($mails as $mail) { Mail::to($mail->email)->send(new WinningProof($stickerPath, $ticketPath, $barcodePath, $participation, $user, $settings)); diff --git a/app/Http/Controllers/bo/CodesController.php b/app/Http/Controllers/bo/CodesController.php index 55edca1..cfd8080 100644 --- a/app/Http/Controllers/bo/CodesController.php +++ b/app/Http/Controllers/bo/CodesController.php @@ -3,42 +3,74 @@ namespace App\Http\Controllers\bo; use App\Http\Controllers\Controller; +use App\Http\Requests\ExportCodesRequest; +use App\Http\Requests\GenerateCodesRequest; use App\Jobs\ProcessExportCodesUniques; use App\Jobs\ProcessUniqueCode; use App\Models\Codes; use App\Models\Settings; -use Illuminate\Http\Request; class CodesController extends Controller { + /** + * Show code form to generate and export + * + * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory + */ public function index() { + $settings = app(Settings::class); $nbCodeBase = Codes::count(); $nbCodeUsed = Codes::where('code_used', 1)->count(); $nbCodeExported = Codes::where('code_exported', 1)->count(); $nbCodeRemainingToExport = Codes::where('code_exported', 0)->count(); - return view('bo.codes.index', compact('nbCodeBase', 'nbCodeUsed', 'nbCodeExported', 'nbCodeRemainingToExport')); + return view('bo.codes.index', compact( + 'settings', + 'nbCodeBase', + 'nbCodeUsed', + 'nbCodeExported', + 'nbCodeRemainingToExport' + )); } - public function generate(Request $request) + /** + * Run generate codes action + * + * @param GenerateCodesRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function generate(GenerateCodesRequest $request) { - $quantity = $request->quantity; - $codeLength = $request->code_length; - $settings = Settings::find(1); + ProcessUniqueCode::dispatch( + $request->quantity, + $request->code_length + ); - ProcessUniqueCode::dispatch($quantity, $codeLength, $settings); - - return back()->with('success', "La génération à commencée, un mail de notification sera envoyé à l'adresse " . config('mail.MAIL_ADMIN') . " lorsque le traitement sera terminé."); + return back()->with( + 'success', + "La génération à commencée, un mail de notification sera envoyé à l'adresse " . + config('mail.MAIL_ADMIN') . " lorsque le traitement sera terminé." + ); } - public function export(Request $request) + /** + * Run export codes + * + * @param ExportCodesRequest $request + * @return \Illuminate\Http\RedirectResponse + */ + public function export(ExportCodesRequest $request) { - $nbCodeFic = intval($request->nb_code_fic); - $nbCodeTot = intval($request->nb_code_tot); - $nbCodeLigne = intval($request->nb_code_ligne); + ProcessExportCodesUniques::dispatch( + intval($request->nb_code_fic), + intval($request->nb_code_tot), + intval($request->nb_code_ligne) + ); - ProcessExportCodesUniques::dispatch($nbCodeFic, $nbCodeTot, $nbCodeLigne); - - return back()->with('success', "L'export des codes à commencé, un mail de notification sera envoyé à l'adresse " . config('mail.MAIL_ADMIN') . " lorsque le traitement sera terminé."); + return back()->with( + 'success', + "L'export des codes à commencé, un mail de notification sera envoyé à l'adresse " . + config('mail.MAIL_ADMIN') . " lorsque le traitement sera terminé." + ); } } diff --git a/app/Http/Controllers/bo/DashboardController.php b/app/Http/Controllers/bo/DashboardController.php index 98e02d1..b3d3703 100644 --- a/app/Http/Controllers/bo/DashboardController.php +++ b/app/Http/Controllers/bo/DashboardController.php @@ -13,7 +13,7 @@ class DashboardController extends Controller public function index() { // Pour les stats - $settings = Settings::find(1); + $settings = app(Settings::class); $participations = Participation::selectRaw("COUNT(*) AS count, DATE(created_at) AS date")->groupByRaw("DATE(created_at)")->get(); $participationsUniquePerDay = Participation::selectRaw("DISTINCT user_id AS user, COUNT(*) AS count, DATE(created_at) AS date")->groupByRaw("DATE(created_at), user")->get(); $statsUnique = []; diff --git a/app/Http/Controllers/bo/SettingsController.php b/app/Http/Controllers/bo/SettingsController.php index d7f9c2e..1ffae20 100644 --- a/app/Http/Controllers/bo/SettingsController.php +++ b/app/Http/Controllers/bo/SettingsController.php @@ -12,7 +12,7 @@ class SettingsController extends Controller { public function manage(Request $request) { - $settings = Settings::find(1); + $settings = app(Settings::class); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = $request->name; @@ -76,7 +76,7 @@ class SettingsController extends Controller $settings->save(); - $settingsGlobal = Settings::find(1)->select('name', 'company', 'analytics', 'start_date', 'end_date')->first(); + $settingsGlobal = app(Settings::class)->select('name', 'company', 'analytics', 'start_date', 'end_date')->first(); Cache::put('settingsGlobal', $settingsGlobal); $success = "Modifications enregistrés !"; diff --git a/app/Http/Controllers/bo/WinnersListController.php b/app/Http/Controllers/bo/WinnersListController.php index 57e425d..ce157ba 100644 --- a/app/Http/Controllers/bo/WinnersListController.php +++ b/app/Http/Controllers/bo/WinnersListController.php @@ -96,7 +96,7 @@ class WinnersListController extends Controller ] ]; - $settings = Settings::find(1); + $settings = app(Settings::class); if ($settings->use_ean_codes === 1) { $fields['orderLine'][] = [ 'fieldName' => 'code_on_pack', diff --git a/app/Http/Controllers/re7Controller.php b/app/Http/Controllers/re7Controller.php index bc6bf1f..3a5b6eb 100644 --- a/app/Http/Controllers/re7Controller.php +++ b/app/Http/Controllers/re7Controller.php @@ -14,7 +14,7 @@ class re7Controller extends Controller public function teaser() { - $settings = Settings::find(1)->select('start_date', 'end_date', 'activated_teaser', 'teaser_end_activity')->first(); + $settings = app(Settings::class)->select('start_date', 'end_date', 'activated_teaser', 'teaser_end_activity')->first(); return view('front.basic.teaser', compact('settings')); } @@ -30,7 +30,7 @@ class re7Controller extends Controller public function br() { - $settings = Settings::find(1); + $settings = app(Settings::class); $coupon = $this->getRandomCoupon($settings); $crmCoupon = new Reductions(); return view('front.basic.reduction', compact('coupon', 'crmCoupon')); diff --git a/app/Http/Middleware/GameCheckDate.php b/app/Http/Middleware/GameCheckDate.php index dab432c..4661022 100644 --- a/app/Http/Middleware/GameCheckDate.php +++ b/app/Http/Middleware/GameCheckDate.php @@ -16,7 +16,7 @@ class GameCheckDate */ public function handle($request, Closure $next) { - $settings = Settings::find(1); + $settings = app(Settings::class); // Check si le teaser est actif if ($settings->start_date > now()) { diff --git a/app/Http/Requests/ExportCodesRequest.php b/app/Http/Requests/ExportCodesRequest.php new file mode 100644 index 0000000..a45361e --- /dev/null +++ b/app/Http/Requests/ExportCodesRequest.php @@ -0,0 +1,62 @@ +orderBy('id', 'desc')->first()->id ?? null; + if ($lastExportedId) { + $codesAvailableToExport = $codesAvailableToExport + ->where('id', '>', $lastExportedId); + } + $this->merge([ + 'available_export' => $codesAvailableToExport->count() + ]); + // ? Is auth and super admin, also is json ? + return auth()->check() and + auth()->user()->role === 1 and $this->json(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'nb_code_fic' => 'required|integer|min:1', + 'nb_code_tot' => 'required|integer|min:1', + 'nb_code_ligne' => 'required|integer|min:1', + 'available_export' => 'required|integer|gte:nb_code_tot' + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'nb_code_fic' => 'Nombre total de codes à exporter', + 'nb_code_tot' => 'Nombre de codes par fichier', + 'nb_code_ligne' => 'Nombre de codes par ligne', + 'available_export' => 'Nombre de codes restant à exporté(s)' + ]; + } +} diff --git a/app/Http/Requests/GenerateCodesRequest.php b/app/Http/Requests/GenerateCodesRequest.php new file mode 100644 index 0000000..9677399 --- /dev/null +++ b/app/Http/Requests/GenerateCodesRequest.php @@ -0,0 +1,32 @@ +check() and auth()->user()->role === 1; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'quantity' => 'required|integer|min:1', + 'code_length' => 'required|integer|min:1', + ]; + } +} diff --git a/app/Http/Requests/ParticipateRequest.php b/app/Http/Requests/ParticipateRequest.php new file mode 100644 index 0000000..80c7dd4 --- /dev/null +++ b/app/Http/Requests/ParticipateRequest.php @@ -0,0 +1,62 @@ +merge([ + 'code' => is_string($this->code) ? Str::upper($this->code) : $this->code, + 'accept_rules' => $this->accept_rules ? true : false + ]); + return true; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'accept_rules' => [ + 'required', 'boolean', + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + if (!$value) { + $fail('Pour participer, vous devez accepter le règlement du jeu.'); + } + } + ], + 'code' => [ + 'required', 'string', + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + try { + $int = Codes::serialToInt($value); + if (!Codes::whereId(Codes::serialToInt($value))->exists()) { + throw new SerialCasterException(); + } + } catch (SerialCasterException $e) { + dd($e); + Log::debug($e->getMessage(), $e->getTrace()); + $fail('Code incorrect, veuillez réessayer.'); + } + } + ] + ]; + } +} diff --git a/app/Jobs/ProcessExportCodesUniques.php b/app/Jobs/ProcessExportCodesUniques.php index 8a11be5..5e6f206 100644 --- a/app/Jobs/ProcessExportCodesUniques.php +++ b/app/Jobs/ProcessExportCodesUniques.php @@ -5,13 +5,13 @@ namespace App\Jobs; use App\Mail\ExportCodesUniques; use App\Mail\JobFailed; use App\Models\Codes; -use App\Models\Settings; use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Mail; class ProcessExportCodesUniques implements ShouldQueue @@ -21,14 +21,51 @@ class ProcessExportCodesUniques implements ShouldQueue use Queueable; use SerializesModels; - private $nbCodeFic, $nbCodeTot, $nbCodeLigne; + /** + * The number of seconds the job can run before timing out. + * + * 7200 seconds (2 hours) + * + * @var integer + */ + public $timeout = 7200; + + /** + * Nombre de codes par fichiers. + * @var integer + */ + private $nbCodeFic; + + /** + * Nombre total de codes à exporter. + * + * @var integer + */ + private $nbCodeTot; + + /** + * Nombre de codes par ligne. + * + * @var integer + */ + private $nbCodeLigne; + + /** + * The export file name + * + * @var string + */ + private $exportFileName = null; /** * Create a new job instance. * + * @param integer $nbCodeFic Nombre de codes par fichiers. + * @param integer $nbCodeTot Nombre total de codes à exporter. + * @param integer $nbCodeLigne Nombre de codes par ligne. * @return void */ - public function __construct($nbCodeFic, $nbCodeTot, $nbCodeLigne) + public function __construct(int $nbCodeFic, int $nbCodeTot, int $nbCodeLigne) { $this->nbCodeFic = $nbCodeFic; $this->nbCodeTot = $nbCodeTot; @@ -42,108 +79,134 @@ class ProcessExportCodesUniques implements ShouldQueue */ public function handle() { - $nbCodeFic = $this->nbCodeFic; // Nombre de code par fichier - $nbCodeTot = $this->nbCodeTot; // Nombre total de code à exporter - $nbCodeLigne = $this->nbCodeLigne; // Nombre de code par ligne - $totalExportedCodes = 0; - $currentFilename = 1; - $currentExportedCodes = 0; - $currentLineCodes = ($nbCodeLigne === 1 ? 1 : 0); + $this->exportFileName = sprintf( + 'CodesUniques_%s', + date('dmY_His') + ); - $this->process($nbCodeFic, $nbCodeTot, $nbCodeLigne, $totalExportedCodes, $currentFilename, $currentExportedCodes, $currentLineCodes); + $lastExportedCodeId = Codes::whereCodeExported(true) + ->orderBy('id', 'desc')->first()->id ?? null; - // Compression des fichiers + $filesPath = $this->generateCsvFiles($lastExportedCodeId); + + // * Compression des fichiers. $zip = new \ZipArchive(); - if ($zip->open(storage_path('export_codes_uniques/') . "CodesUniques_" . date('dmY') . ".zip", \ZipArchive::CREATE) === true) { - if ($handle = opendir(storage_path('export_codes_uniques/tmp'))) { - while (false !== ($file_to_zip = readdir($handle))) { - if (pathinfo($file_to_zip, PATHINFO_EXTENSION) != 'csv') { - continue; - } - $zip->addFile(storage_path('export_codes_uniques/tmp/') . $file_to_zip, "$file_to_zip"); + if ( + $zip->open( + storage_path(sprintf('export_codes_uniques/%s.zip', $this->exportFileName)), + \ZipArchive::CREATE + ) === true + ) { + foreach ($filesPath as $filePath) { + if (File::exists($filePath)) { + $zip->addFile($filePath, \pathinfo($filePath, \PATHINFO_BASENAME)); } - closedir($handle); } $zip->close(); } - // On vide le dossier temporaire des fichiers csv - if ($handle = opendir(storage_path('export_codes_uniques/tmp'))) { - while (false !== ($file_to_zip = readdir($handle))) { - if (pathinfo($file_to_zip, PATHINFO_EXTENSION) != 'csv') { - continue; - } - unlink(storage_path('export_codes_uniques/tmp/') . $file_to_zip); + // * On vide le dossier temporaire des fichiers csv. + foreach ($filesPath as $filePath) { + if (File::exists($filePath)) { + File::delete($filePath); } - closedir($handle); } - // Envoi de mail à MAIL_ADMIN avec le nom du ZIP "CodesUniques_". date('dmY') .".zip" - Mail::to(config('mail.MAIL_ADMIN'))->send(new ExportCodesUniques(Settings::find(1)->name, $nbCodeFic, $nbCodeTot, $nbCodeLigne)); + // * Envoi de mail à MAIL_ADMIN avec le nom du ZIP "CodesUniques_". date('dmY_His') .".zip". + Mail::to(config('mail.MAIL_ADMIN')) + ->send(new ExportCodesUniques( + $this->nbCodeFic, + $this->nbCodeTot, + $this->nbCodeLigne + )); } - private function process($nbCodeFic, $nbCodeTot, $nbCodeLigne, $totalExportedCodes, $currentFilename, $currentExportedCodes, $currentLineCodes, $lastExportedCodeId = null) + /** + * Generate CSV File using the job parameters + * + * @param integer|null $lastExportedCodeId + * @return array Array of temp files + */ + private function generateCsvFiles($lastExportedCodeId = null): array { - $paginate = config('codes.paginate'); + $outPaths = []; + $totalExportedCodes = 0; + $currentFilename = 1; + $currentExportedCodes = 0; + $currentLineCodes = ($this->nbCodeLigne === 1 ? 1 : 0); - if ($nbCodeTot < $paginate) { - $nbCodeToRetrieve = $nbCodeTot; - } else { - if ($totalExportedCodes + $paginate > $nbCodeTot) { - $nbCodeToRetrieve = $nbCodeTot - $totalExportedCodes; - } else { - $nbCodeToRetrieve = $paginate; - } + $paginate = intval(config('codes.paginate', 65535)); + + // ! Guess code ids instead of getting thme from base. + $codes = []; + $id = ($lastExportedCodeId ?? 0) + 1; + for ($i = 0; $i < $this->nbCodeTot; $i++) { + $codes[] = $id++; } - if ($lastExportedCodeId === null) { - $codes = Codes::where('code_exported', 0)->take($nbCodeToRetrieve)->get(); - } else { - $codes = Codes::where([ - ['code_exported', '=', 0], - ['id', '>', $lastExportedCodeId] - ])->take($nbCodeToRetrieve)->get(); - } - - $countCodes = count($codes); - $exportedCodesId = []; - foreach ($codes as $k => $code) { - if ($currentExportedCodes < $nbCodeTot) { - if ($currentLineCodes === ($nbCodeLigne === 1 ? 1 : $nbCodeLigne - 1)) { - file_put_contents(storage_path('export_codes_uniques/tmp/') . $currentFilename . ".csv", $code->code_unique . PHP_EOL, FILE_APPEND); - $totalExportedCodes++; - $currentExportedCodes++; - $currentLineCodes = ($nbCodeLigne === 1 ? 1 : 0); + $tempFilePath = \storage_path(\sprintf( + 'export_codes_uniques/tmp/%s_%s.csv', + $this->exportFileName, + $currentFilename + )); + $outPaths[] = $tempFilePath; + + foreach ($codes as $code) { + if ($currentExportedCodes < $this->nbCodeTot) { + if ($currentLineCodes === ($this->nbCodeLigne === 1 ? 1 : $this->nbCodeLigne - 1)) { + File::append($tempFilePath, Codes::intToSerial($code) . "\n"); + $currentLineCodes = ($this->nbCodeLigne === 1 ? 1 : 0); } else { - file_put_contents(storage_path('export_codes_uniques/tmp/') . $currentFilename . ".csv", $code->code_unique . ";", FILE_APPEND); - $totalExportedCodes++; - $currentExportedCodes++; + File::append($tempFilePath, Codes::intToSerial($code) . ';'); $currentLineCodes++; - } + } //end if - array_push($exportedCodesId, $code->id); - $lastExportedCodeId = $code->id; + $totalExportedCodes++; + $currentExportedCodes++; - if ($currentExportedCodes === $nbCodeFic) { + $exportedCodesId[] = $code; + + if ($currentExportedCodes === $this->nbCodeFic) { $currentFilename++; $currentExportedCodes = 0; + + $tempFilePath = \storage_path(\sprintf( + 'export_codes_uniques/tmp/%s_%s.csv', + $this->exportFileName, + $currentFilename + )); + $outPaths[] = $tempFilePath; } - if ((($k + 1) === $countCodes) && $totalExportedCodes < $nbCodeTot) { + // ? Have we reached pagination limit for mass saving ? + if (count($exportedCodesId) === $paginate) { Codes::whereIn('id', $exportedCodesId)->update(['code_exported' => 1]); - return $this->process($nbCodeFic, $nbCodeTot, $nbCodeLigne, $totalExportedCodes, $currentFilename, $currentExportedCodes, $currentLineCodes, $lastExportedCodeId); + $exportedCodesId = []; } - if (($k + 1) === $countCodes) { - Codes::whereIn('id', $exportedCodesId)->update(['code_exported' => 1]); - } - } + } //end if + } //end foreach + + // ? Are there exported codes to save ? + if (count($exportedCodesId)) { + Codes::whereIn('id', $exportedCodesId)->update(['code_exported' => 1]); } + return $outPaths; } + /** + * In case of job failure + * + * @param Exception $exception + * @return void + */ public function failed(Exception $exception) { - Mail::to(config('mail.MAIL_ADMIN'))->send(new JobFailed("ProcessExportCodeUnique", ['message' => $exception->getMessage(), 'trace' => $exception->getTraceAsString()])); + Mail::to(config('mail.MAIL_ADMIN')) + ->send(new JobFailed("ProcessExportCodeUnique", [ + 'message' => $exception->getMessage(), + 'trace' => $exception->getTraceAsString() + ])); } } diff --git a/app/Jobs/ProcessUniqueCode.php b/app/Jobs/ProcessUniqueCode.php index 0f2cb29..8d8e554 100644 --- a/app/Jobs/ProcessUniqueCode.php +++ b/app/Jobs/ProcessUniqueCode.php @@ -5,12 +5,14 @@ namespace App\Jobs; use App\Mail\CodeGenerationEnd; use App\Mail\JobFailed; use App\Models\Codes; +use App\Models\Settings; use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Mail; class ProcessUniqueCode implements ShouldQueue @@ -20,71 +22,107 @@ class ProcessUniqueCode implements ShouldQueue use Queueable; use SerializesModels; + /** + * The number of seconds the job can run before timing out. + * + * 7200 seconds (2 hours) + * + * @var integer + */ + public $timeout = 7200; + + /** + * Quantity of code to generate + * + * @var integer + */ private $quantity; + /** + * Length of code to generate + * + * @var integer + */ private $length; - private $settings; - /** * Create a new job instance. * + * @param integer $quantity + * @param integer $length * @return void */ - public function __construct($quantity, $length, $settings) + public function __construct(int $quantity, int $length) { $this->quantity = $quantity; $this->length = $length; - $this->settings = $settings; } /** * Execute the job. + * TODO: Mettre tout ce bordel dans une transaction quand les serveur de leurs morts auront été mis à jour Uu. * * @return void */ public function handle() { - set_time_limit(0); - for ($ib = 1; $ib <= $this->quantity;) { - try { - $code = new Codes(); - $code->code_unique = $this->getCode($this->length); - $code->date_creation = now(); - $code->save(); - if ($code) { - $ib++; - } - } catch (\PDOException $e) { - if (!strpos($e->getMessage(), 'Duplicate entry')) { - Mail::to(config('mail.MAIL_ADMIN'))->send(new JobFailed("ProcessUniqueCode", ['message' => $e->getMessage(), 'trace' => $e->getTraceAsString()])); - die(); - } - } + $settings = app(Settings::class); + + $maxSQLPlaceholders = 65535; + + $insertColumns = collect(DB::getSchemaBuilder() + ->getColumnListing((new Codes())->getTable())) + ->filter(function ($name) { + // * Ignorer les colonnes avec valeurs par défaut sur la table. + return !in_array($name, ['id', 'code_used', 'code_exported']); + })->count(); + + /** + * Nombre d'insertions max sur un seule requète + * @var integer $perPage + */ + $perPage = $maxSQLPlaceholders / $insertColumns; + + /** + * Nombre de page totale à insérer. + * @var integer $totalPages + */ + $totalPages = \ceil($this->quantity / $perPage); + + $totalGeneratedInserts = 0; + + for ($currentPage = 1; $currentPage <= $totalPages; $currentPage++) { + $inserts = []; + $insertToGenerate = min($perPage, $this->quantity - $totalGeneratedInserts); + do { + $inserts[] = [ + 'date_creation' => now()->format('Y-m-d'), + ]; + } while ($totalGeneratedInserts < $this->quantity and --$insertToGenerate); + + // * Mass insert + Codes::insert($inserts); + + $totalGeneratedInserts += count($inserts); } - // ---- Fin de la génération - Mail::to(config('mail.MAIL_ADMIN'))->send(new CodeGenerationEnd($this->quantity, $this->settings)); - } - - private function getCode($car) - { - $chaine = "ABCDEFGHJKMNPQRSTUWXYZ123456789"; // Caractères autorisés - $valprec = ""; - $string = ""; - srand(); - for ($i = 0; $i < $car;) { - $val = $chaine[rand() % strlen($chaine)]; - if ($val != $valprec) { - $string .= $val; - $valprec = $val; - $i++; - } - } - return $string; + + // * Fin de la génération. + Mail::to(config('mail.MAIL_ADMIN')) + ->send(new CodeGenerationEnd($this->quantity, $settings->name)); } + /** + * In case of job failure + * + * @param Exception $exception + * @return void + */ public function failed(Exception $exception) { - Mail::to(config('mail.MAIL_ADMIN'))->send(new JobFailed("ProcessUniqueCode", ['message' => $exception->getMessage(), 'trace' => $exception->getTraceAsString()])); + Mail::to(config('mail.MAIL_ADMIN')) + ->send(new JobFailed("ProcessUniqueCode", [ + 'message' => $exception->getMessage(), + 'trace' => $exception->getTraceAsString() + ])); } } diff --git a/app/Mail/CodeGenerationEnd.php b/app/Mail/CodeGenerationEnd.php index 8e7c895..b851a69 100644 --- a/app/Mail/CodeGenerationEnd.php +++ b/app/Mail/CodeGenerationEnd.php @@ -11,16 +11,31 @@ class CodeGenerationEnd extends Mailable use Queueable; use SerializesModels; + /** + * Number of codes generated + * + * @var integer + */ private $quantity; + + /** + * The game name + * + * @var string + */ + private $gameName; + /** * Create a new message instance. * + * @param integer $quantity + * @param string $gameName * @return void */ - public function __construct($quantity, $settings) + public function __construct(int $quantity, string $gameName) { $this->quantity = $quantity; - $this->settings = $settings; + $this->gameName = $gameName; } /** @@ -30,6 +45,8 @@ class CodeGenerationEnd extends Mailable */ public function build() { - return $this->subject('Fin de génération des codes uniques')->view('bo.mails.generate-end-email')->with(['quantity' => $this->quantity, 'settings' => $this->settings]); + return $this->subject('Fin de génération des codes uniques') + ->view('bo.mails.generate-end-email') + ->with(['quantity' => $this->quantity, 'gameName' => $this->gameName]); } } diff --git a/app/Mail/ExportCodesUniques.php b/app/Mail/ExportCodesUniques.php index 9edfd7c..255eb10 100644 --- a/app/Mail/ExportCodesUniques.php +++ b/app/Mail/ExportCodesUniques.php @@ -2,6 +2,7 @@ namespace App\Mail; +use App\Models\Settings; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; @@ -12,16 +13,36 @@ class ExportCodesUniques extends Mailable implements ShouldQueue use Queueable; use SerializesModels; - private $name, $nbCodeFic, $nbCodeTot, $nbCodeLigne; + /** + * Nombre de codes par fichiers. + * @var integer + */ + private $nbCodeFic; + + /** + * Nombre total de codes à exporter. + * + * @var integer + */ + private $nbCodeTot; + + /** + * Nombre de codes par ligne. + * + * @var integer + */ + private $nbCodeLigne; /** * Create a new message instance. * + * @param integer $nbCodeFic Nombre de codes par fichiers. + * @param integer $nbCodeTot Nombre total de codes à exporter. + * @param integer $nbCodeLigne Nombre de codes par ligne. * @return void */ - public function __construct($name, $nbCodeFic, $nbCodeTot, $nbCodeLigne) + public function __construct(int $nbCodeFic, int $nbCodeTot, int $nbCodeLigne) { - $this->name = $name; $this->nbCodeFic = $nbCodeFic; $this->nbCodeTot = $nbCodeTot; $this->nbCodeLigne = $nbCodeLigne; @@ -37,7 +58,7 @@ class ExportCodesUniques extends Mailable implements ShouldQueue $nbCodeFic = $this->nbCodeFic; $nbCodeTot = $this->nbCodeTot; $nbCodeLigne = $this->nbCodeLigne; - return $this->subject("Fin de l'export des codes uniques pour " . $this->name) + return $this->subject("Fin de l'export des codes uniques pour " . app(Settings::class)->name) ->view('bo.mails.export-codes-unique-email', compact('nbCodeFic', 'nbCodeTot', 'nbCodeLigne')); } } diff --git a/app/Models/Codes.php b/app/Models/Codes.php index cf5b236..29aab51 100644 --- a/app/Models/Codes.php +++ b/app/Models/Codes.php @@ -3,16 +3,83 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Kwaadpepper\Serial\SerialCaster; class Codes extends Model { + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'code_used', 'code_exported', + 'date_creation' + ]; - protected $fillable = ['code_unique', 'code_used', 'code_exported', 'date_creation']; - + /** + * Indicates if the model should be timestamped. + * + * @var boolean + */ public $timestamps = false; + // * RELATIONS + + /** + * Has many Participation relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ public function participations() { - return $this->hasMany('App\Models\Participation', 'code_unique_id'); + return $this->hasMany(Participation::class, 'code_unique_id'); + } + + // * FUNCTIONS + /** + * Generate code_unique attribute + * + * @return string|null + */ + public function getCodeUniqueAttribute() + { + if (!$this->id or !\is_integer($this->id) or $this->id < 1) { + return null; + } + return self::intToSerial($this->id); + } + + /** + * Generate a serial from an int. + * + * @param integer $int + * @return string The serial + * @throws SerialCasterException If an error happens du to config. + */ + public static function intToSerial(int $int): string + { + return SerialCaster::encode( + $int, + \abs(\crc32(config('app.key'))), + app(Settings::class)->code_length, + config('codes.codeCharList') + ); + } + + /** + * Generate a serial from an int. + * + * @param string $serial + * @return integer The ID represented + * @throws SerialCasterException If an error happens due to config or wrong serial is given. + */ + public static function serialToInt(string $serial): int + { + return SerialCaster::decode( + $serial, + \abs(\crc32(config('app.key'))), + config('codes.codeCharList') + ); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 90e2a4b..106e3b1 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -34,11 +34,18 @@ class AppServiceProvider extends ServiceProvider setlocale(LC_TIME, config('app.locale') . "_" . strtoupper(config('app.locale')) . '.utf8'); + app()->singleton(Settings::class, function () { + if (!($settings = Settings::find(1))) { + throw new \ErrorException('Settings not found in DB'); + } + return $settings; + }); + if (!app()->runningInConsole()) { // On ramène le nom du jeu et le nom de la société sur toute les pages $settingsGlobal = Cache::get('settingsGlobal'); if (!$settingsGlobal) { - $settingsGlobal = Settings::find(1)->select('name', 'company', 'analytics', 'start_date', 'end_date')->first(); + $settingsGlobal = app(Settings::class)->select('name', 'company', 'analytics', 'start_date', 'end_date')->first(); Cache::put('settingsGlobal', $settingsGlobal); } View::share('settingsGlobal', $settingsGlobal); diff --git a/composer.json b/composer.json index 15d9240..a373091 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "fideloper/proxy": "^4.2", "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^6.3", + "kwaadpepper/serial-int-caster": "^1.0", "laravel/framework": "^7.24", "laravel/tinker": "^2.0", "laravel/ui": "2.4" diff --git a/composer.lock b/composer.lock index 8ef8ade..a72d65b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "556b0ac601a71abe129f7ed12da116f6", + "content-hash": "a1071d5ef8b5a8f12e458a6866973b27", "packages": [ { "name": "asm89/stack-cors", @@ -765,6 +765,50 @@ }, "time": "2021-04-26T09:17:50+00:00" }, + { + "name": "kwaadpepper/serial-int-caster", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/Kwaadpepper/serial-int-caster.git", + "reference": "bbba003ff5379576f05114386b5ebe8b33bc4705" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Kwaadpepper/serial-int-caster/zipball/bbba003ff5379576f05114386b5ebe8b33bc4705", + "reference": "bbba003ff5379576f05114386b5ebe8b33bc4705", + "shasum": "" + }, + "require": { + "php": ">=7.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Kwaadpepper\\Serial\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jérémy Munsch", + "email": "github@jeremydev.ovh" + } + ], + "description": "Convert int To as serial number and reverse", + "support": { + "issues": "https://github.com/Kwaadpepper/serial-int-caster/issues", + "source": "https://github.com/Kwaadpepper/serial-int-caster/tree/1.0.1" + }, + "time": "2021-05-16T15:50:37+00:00" + }, { "name": "laravel/framework", "version": "v7.30.4", @@ -7351,32 +7395,32 @@ }, { "name": "slevomat/coding-standard", - "version": "7.0.13", + "version": "7.0.15", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "122a9bf9a4a2195f74100f47dfb8375982f43cc9" + "reference": "cc80e59f9b4ca642f02dc1b615c37a9afc2a0f80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/122a9bf9a4a2195f74100f47dfb8375982f43cc9", - "reference": "122a9bf9a4a2195f74100f47dfb8375982f43cc9", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/cc80e59f9b4ca642f02dc1b615c37a9afc2a0f80", + "reference": "cc80e59f9b4ca642f02dc1b615c37a9afc2a0f80", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", "php": "^7.1 || ^8.0", - "phpstan/phpdoc-parser": "0.5.1 - 0.5.5", + "phpstan/phpdoc-parser": "0.5.1 - 0.5.6", "squizlabs/php_codesniffer": "^3.6.0" }, "require-dev": { - "phing/phing": "2.16.4", - "php-parallel-lint/php-parallel-lint": "1.3.0", - "phpstan/phpstan": "0.12.93", + "phing/phing": "2.17.0", + "php-parallel-lint/php-parallel-lint": "1.3.1", + "phpstan/phpstan": "0.12.98", "phpstan/phpstan-deprecation-rules": "0.12.6", - "phpstan/phpstan-phpunit": "0.12.21", - "phpstan/phpstan-strict-rules": "0.12.10", - "phpunit/phpunit": "7.5.20|8.5.5|9.5.7" + "phpstan/phpstan-phpunit": "0.12.22", + "phpstan/phpstan-strict-rules": "0.12.11", + "phpunit/phpunit": "7.5.20|8.5.5|9.5.9" }, "type": "phpcodesniffer-standard", "extra": { @@ -7396,7 +7440,7 @@ "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/7.0.13" + "source": "https://github.com/slevomat/coding-standard/tree/7.0.15" }, "funding": [ { @@ -7408,20 +7452,20 @@ "type": "tidelift" } ], - "time": "2021-07-29T14:30:22+00:00" + "time": "2021-09-09T10:29:09+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.6.0", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625" + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625", - "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", "shasum": "" }, "require": { @@ -7464,7 +7508,7 @@ "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "time": "2021-04-09T00:54:41+00:00" + "time": "2021-12-12T21:44:58+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/codes.php b/config/codes.php index eb31d71..3292f76 100644 --- a/config/codes.php +++ b/config/codes.php @@ -1,5 +1,11 @@ env('CODE_PAGINATE', 65530) + // Export pagination. + 'paginate' => env('CODE_PAGINATE', 65530), + + // Dictionnary of chars to use fo generating unique codes. + 'codeCharList' => 'ABCDEFGHJKMNPQRSTUWXYZ123456789', + // Code length. + 'codeLength' => 8 ]; diff --git a/database/migrations/2022_01_07_102059_remove_code_unique_from_codes.php b/database/migrations/2022_01_07_102059_remove_code_unique_from_codes.php new file mode 100644 index 0000000..77b223f --- /dev/null +++ b/database/migrations/2022_01_07_102059_remove_code_unique_from_codes.php @@ -0,0 +1,32 @@ +dropColumn('code_unique'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('codes', function (Blueprint $table) { + $table->string('code_unique')->unique(); + }); + } +} diff --git a/database/migrations/2022_01_07_110932_add_code_length_to_settings_table.php b/database/migrations/2022_01_07_110932_add_code_length_to_settings_table.php new file mode 100644 index 0000000..5c6fc69 --- /dev/null +++ b/database/migrations/2022_01_07_110932_add_code_length_to_settings_table.php @@ -0,0 +1,33 @@ +smallInteger('code_length') + ->default(config('codes.codeLength'))->after('nb_unique_code_use'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('code_length'); + }); + } +} diff --git a/resources/views/bo/codes/index.blade.php b/resources/views/bo/codes/index.blade.php index 8ca548f..9a22f0a 100644 --- a/resources/views/bo/codes/index.blade.php +++ b/resources/views/bo/codes/index.blade.php @@ -3,10 +3,10 @@ @section('content')
Génération des {{$quantity}} codes uniques terminés sur le jeu {{$settings->name}}.
+Génération des {{$quantity}} codes uniques terminés sur le jeu {{$gameName}}.
diff --git a/resources/views/bo/settings/manage.blade.php b/resources/views/bo/settings/manage.blade.php index f481bbe..fa29836 100644 --- a/resources/views/bo/settings/manage.blade.php +++ b/resources/views/bo/settings/manage.blade.php @@ -72,12 +72,24 @@