diff --git a/.env.docker b/.env.docker index 4f8b1c7..aa6228b 100644 --- a/.env.docker +++ b/.env.docker @@ -67,3 +67,4 @@ VITE_APP_NAME="${APP_NAME}" VITE_APP_URL="${APP_URL}" DUSK_DRIVER_URL="http://undetected-chromedriver:4444" +DUSK_START_MAXIMIZED=true diff --git a/.env.example b/.env.example index 7502c8f..a86d213 100644 --- a/.env.example +++ b/.env.example @@ -64,5 +64,7 @@ MEMCACHED_HOST=127.0.0.1 # AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" +VITE_APP_URL="${APP_URL}" DUSK_DRIVER_URL="http://127.0.0.1:4444" +DUSK_START_MAXIMIZED=true diff --git a/Dockerfile b/Dockerfile index e89e536..40df375 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,6 +72,9 @@ RUN mv .env.docker .env RUN composer install --no-interaction --prefer-dist +# CRON +RUN echo "* * * * * cd /usr/app && php artisan schedule:run >> /dev/null 2>&1" > /etc/crontabs/root + # Link the storage directory to the public directory. RUN php artisan storage:link diff --git a/app/Browser/BrowserJob.php b/app/Browser/BrowserJob.php index 06462dd..06334a5 100644 --- a/app/Browser/BrowserJob.php +++ b/app/Browser/BrowserJob.php @@ -2,29 +2,29 @@ namespace App\Browser; -use App\Browser\JobArtifacts\JobRunArtifact; -use App\Exception\JobException; use App\Models\JobArtifact; use App\Models\JobRun; +use Closure; +use Exception; use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\RemoteWebElement; use Facebook\WebDriver\WebDriverBy; -use Illuminate\Support\Collection; +use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; use Laravel\Dusk\Browser; -use Laravel\Dusk\TestCase as BaseTestCase; -use Closure; -use PHPUnit\Framework\Attributes\BeforeClass; -use Exception; use Laravel\Dusk\Chrome\SupportsChrome; use Laravel\Dusk\Concerns\ProvidesBrowser; -use Laravel\Dusk; +use PHPUnit\Framework\Attributes\BeforeClass; use Throwable; -abstract class BrowserJob +abstract class BrowserJob implements ShouldQueue { - use SupportsChrome, ProvidesBrowser; + use SupportsChrome, ProvidesBrowser, Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public int $jobId; @@ -209,8 +209,16 @@ abstract class BrowserJob return "dataTest"; } + /** + * Execute the job. + */ + public function handle(): void + { + $this->execute(); + } - // BROWSER MACROS + + // === BROWSER MACROS === protected function waitForAndClickText(Browser $browser, string $text, int $timeout = 30, bool $ignoreCase = true) { $browser->waitForText($text, $timeout, $ignoreCase); $this->findElementContainingText($browser, $text, $ignoreCase)?->click(); diff --git a/app/Browser/Jobs/Hellcase/HellcaseJob.php b/app/Browser/Jobs/Hellcase/HellcaseJob.php index 6d93561..393db84 100644 --- a/app/Browser/Jobs/Hellcase/HellcaseJob.php +++ b/app/Browser/Jobs/Hellcase/HellcaseJob.php @@ -15,6 +15,7 @@ use App\Notification\Notifications\Hellcase\HellcaseNotificationLogin; use App\Notification\Providers\AllNotification; use Dom\XPath; use Facebook\WebDriver\WebDriverBy; +use Illuminate\Support\Facades\Schedule; use Laravel\Dusk\Browser; class HellcaseJob extends BrowserJob @@ -169,23 +170,24 @@ class HellcaseJob extends BrowserJob } // If we see "availible in 20 HR 49 MIN", parse the hours and minute and reschedule - $availibleInButton = $this->waitForElementContainingTextAndGetIt($browser, "available", 30); - if ($availibleInButton != null) { + $availibleInButton = $browser->driver->findElement(WebDriverBy::xpath('//button[contains(@class, "daily-free-banner__button")]')); + if ($availibleInButton->getAttribute("disabled") == "true") { $hours = $availibleInButton->getText(); $hours = explode(" ", $hours); - $minutes = $hours[2]; - $hours = $hours[0]; + $minutes = $hours[4]; + $hours = $hours[2]; // $this->reschedule($hours); $this->jobRun->addArtifact(new JobArtifact([ "name" => "Cadeau gratuit pas encore disponible", - "content" => "Le cadeau gratuit journalier sera disponible dans $hours heures et $minutes minutes.\nDatboi se fera un plaisir d'aller le chercher pour vous." + "content" => "Le cadeau gratuit journalier sera disponible dans {$hours} heures et {$minutes} minutes.\nDatboi se fera un plaisir d'aller le chercher pour vous." ])); return; } - $this->waitForAndClickText($browser, "Get Free Bonus", 30, true); + // Click the dailyfree button + $availibleInButton->click(); $lootElement = $browser->driver->findElement(WebDriverBy::xpath('//div[contains(@class, "daily-free-win-bonus")]')); $lootElement->takeElementScreenshot(HellcaseDailyFreeScreenshot::getImgFileAbsolutePath()); @@ -193,8 +195,6 @@ class HellcaseJob extends BrowserJob AllNotification::send( new HellcaseNotificationDailyFree($this->jobId, new HellcaseNotificationDailyFreeBody()) ); - - sleep(5000); } /** diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php deleted file mode 100644 index d44fe97..0000000 --- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ /dev/null @@ -1,52 +0,0 @@ - Route::has('password.request'), - 'status' => session('status'), - ]); - } - - /** - * Handle an incoming authentication request. - */ - public function store(LoginRequest $request): RedirectResponse - { - $request->authenticate(); - - $request->session()->regenerate(); - - return redirect()->intended(route('dashboard', absolute: false)); - } - - /** - * Destroy an authenticated session. - */ - public function destroy(Request $request): RedirectResponse - { - Auth::guard('web')->logout(); - - $request->session()->invalidate(); - - $request->session()->regenerateToken(); - - return redirect('/'); - } -} diff --git a/app/Http/Controllers/Auth/ConfirmablePasswordController.php b/app/Http/Controllers/Auth/ConfirmablePasswordController.php deleted file mode 100644 index d2b1f14..0000000 --- a/app/Http/Controllers/Auth/ConfirmablePasswordController.php +++ /dev/null @@ -1,41 +0,0 @@ -validate([ - 'email' => $request->user()->email, - 'password' => $request->password, - ])) { - throw ValidationException::withMessages([ - 'password' => __('auth.password'), - ]); - } - - $request->session()->put('auth.password_confirmed_at', time()); - - return redirect()->intended(route('dashboard', absolute: false)); - } -} diff --git a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php deleted file mode 100644 index f64fa9b..0000000 --- a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php +++ /dev/null @@ -1,24 +0,0 @@ -user()->hasVerifiedEmail()) { - return redirect()->intended(route('dashboard', absolute: false)); - } - - $request->user()->sendEmailVerificationNotification(); - - return back()->with('status', 'verification-link-sent'); - } -} diff --git a/app/Http/Controllers/Auth/EmailVerificationPromptController.php b/app/Http/Controllers/Auth/EmailVerificationPromptController.php deleted file mode 100644 index b42e0d5..0000000 --- a/app/Http/Controllers/Auth/EmailVerificationPromptController.php +++ /dev/null @@ -1,22 +0,0 @@ -user()->hasVerifiedEmail() - ? redirect()->intended(route('dashboard', absolute: false)) - : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]); - } -} diff --git a/app/Http/Controllers/Auth/NewPasswordController.php b/app/Http/Controllers/Auth/NewPasswordController.php deleted file mode 100644 index 394cc4a..0000000 --- a/app/Http/Controllers/Auth/NewPasswordController.php +++ /dev/null @@ -1,69 +0,0 @@ - $request->email, - 'token' => $request->route('token'), - ]); - } - - /** - * Handle an incoming new password request. - * - * @throws \Illuminate\Validation\ValidationException - */ - public function store(Request $request): RedirectResponse - { - $request->validate([ - 'token' => 'required', - 'email' => 'required|email', - 'password' => ['required', 'confirmed', Rules\Password::defaults()], - ]); - - // Here we will attempt to reset the user's password. If it is successful we - // will update the password on an actual user model and persist it to the - // database. Otherwise we will parse the error and return the response. - $status = Password::reset( - $request->only('email', 'password', 'password_confirmation', 'token'), - function ($user) use ($request) { - $user->forceFill([ - 'password' => Hash::make($request->password), - 'remember_token' => Str::random(60), - ])->save(); - - event(new PasswordReset($user)); - } - ); - - // If the password was successfully reset, we will redirect the user back to - // the application's home authenticated view. If there is an error we can - // redirect them back to where they came from with their error message. - if ($status == Password::PASSWORD_RESET) { - return redirect()->route('login')->with('status', __($status)); - } - - throw ValidationException::withMessages([ - 'email' => [trans($status)], - ]); - } -} diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php deleted file mode 100644 index 57a82b5..0000000 --- a/app/Http/Controllers/Auth/PasswordController.php +++ /dev/null @@ -1,29 +0,0 @@ -validate([ - 'current_password' => ['required', 'current_password'], - 'password' => ['required', Password::defaults(), 'confirmed'], - ]); - - $request->user()->update([ - 'password' => Hash::make($validated['password']), - ]); - - return back(); - } -} diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php deleted file mode 100644 index b22c544..0000000 --- a/app/Http/Controllers/Auth/PasswordResetLinkController.php +++ /dev/null @@ -1,51 +0,0 @@ - session('status'), - ]); - } - - /** - * Handle an incoming password reset link request. - * - * @throws \Illuminate\Validation\ValidationException - */ - public function store(Request $request): RedirectResponse - { - $request->validate([ - 'email' => 'required|email', - ]); - - // We will send the password reset link to this user. Once we have attempted - // to send the link, we will examine the response then see the message we - // need to show to the user. Finally, we'll send out a proper response. - $status = Password::sendResetLink( - $request->only('email') - ); - - if ($status == Password::RESET_LINK_SENT) { - return back()->with('status', __($status)); - } - - throw ValidationException::withMessages([ - 'email' => [trans($status)], - ]); - } -} diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php deleted file mode 100644 index 53a546b..0000000 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ /dev/null @@ -1,51 +0,0 @@ -validate([ - 'name' => 'required|string|max:255', - 'email' => 'required|string|lowercase|email|max:255|unique:'.User::class, - 'password' => ['required', 'confirmed', Rules\Password::defaults()], - ]); - - $user = User::create([ - 'name' => $request->name, - 'email' => $request->email, - 'password' => Hash::make($request->password), - ]); - - event(new Registered($user)); - - Auth::login($user); - - return redirect(route('dashboard', absolute: false)); - } -} diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php deleted file mode 100644 index 784765e..0000000 --- a/app/Http/Controllers/Auth/VerifyEmailController.php +++ /dev/null @@ -1,27 +0,0 @@ -user()->hasVerifiedEmail()) { - return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); - } - - if ($request->user()->markEmailAsVerified()) { - event(new Verified($request->user())); - } - - return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); - } -} diff --git a/compose.prod.yaml b/compose.prod.yaml index 157a9de..8a410b6 100644 --- a/compose.prod.yaml +++ b/compose.prod.yaml @@ -1,18 +1,15 @@ -# Comments are provided throughout this file to help you get started. -# If you need more help, visit the Docker Compose reference guide at -# https://docs.docker.com/go/compose-spec-reference/ - -# Here the instructions define your application as a service called "app". -# This service is built from the Dockerfile in the current directory. -# You can add other services your application may depend on here, such as a -# database or a cache. For examples, see the Awesome Compose repository: -# https://github.com/docker/awesome-compose services: app: image: git.matthiasg.dev/ninluc/datbrowser:latest restart: unless-stopped ports: - 80:80 + environment: + APP_URL: datbrowser.matthiasg.dev + volumes: + - browserDownloads:/app/Browser/downloads + - browserScreenshots:/app/Browser/screenshots + - browserSource:/app/Browser/source depends_on: db: condition: service_healthy @@ -52,3 +49,6 @@ services: volumes: dbdata: + browserDownloads: + browserScreenshots: + browserSource: diff --git a/config/queue.php b/config/queue.php index 116bd8d..44f9635 100644 --- a/config/queue.php +++ b/config/queue.php @@ -37,7 +37,7 @@ return [ 'database' => [ 'driver' => 'database', 'connection' => env('DB_QUEUE_CONNECTION'), - 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'table' => env('DB_QUEUE_TABLE', 'queue_jobs'), 'queue' => env('DB_QUEUE', 'default'), 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), 'after_commit' => false, diff --git a/database/migrations/2025_00_00_000000_create_queue_jobs_table.php b/database/migrations/2025_00_00_000000_create_queue_jobs_table.php new file mode 100644 index 0000000..a60f5fa --- /dev/null +++ b/database/migrations/2025_00_00_000000_create_queue_jobs_table.php @@ -0,0 +1,32 @@ +bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('queue_jobs'); + } +}; diff --git a/database/migrations/2025_01_23_190836_create_jobs_table.php b/database/migrations/2025_01_23_190836_create_jobs_table.php index 291ad60..744dfde 100644 --- a/database/migrations/2025_01_23_190836_create_jobs_table.php +++ b/database/migrations/2025_01_23_190836_create_jobs_table.php @@ -47,16 +47,16 @@ return new class extends Migration 'name' => 'Hellcase', 'description' => 'Prends le daily free et rentre dans les concours. Tourne toutes les 24h.', ], - [ - 'id' => 3, - 'name' => 'Jeu gratuit Epic Games', - 'description' => 'Prends le jeu gratuit Epic games. Tourne tous les mois et tous les jours pendant la période de Noël.', - ], - [ - 'id' => 4, - 'name' => 'Envoyer un post instagram', - 'description' => "Envoye un post instagram avec l'image et le texte fourni. Tourne tous les jours.", - ], + // [ + // 'id' => 3, + // 'name' => 'Jeu gratuit Epic Games', + // 'description' => 'Prends le jeu gratuit Epic games. Tourne tous les mois et tous les jours pendant la période de Noël.', + // ], + // [ + // 'id' => 4, + // 'name' => 'Envoyer un post instagram', + // 'description' => "Envoye un post instagram avec l'image et le texte fourni. Tourne tous les jours.", + // ], ]; foreach ($jobs as $job) { diff --git a/database/migrations/2025_01_23_190849_create_job_infos_table.php b/database/migrations/2025_01_23_190849_create_job_infos_table.php index a739fe2..6a3862a 100644 --- a/database/migrations/2025_01_23_190849_create_job_infos_table.php +++ b/database/migrations/2025_01_23_190849_create_job_infos_table.php @@ -67,37 +67,37 @@ return new class extends Migration "job_id" => 1, ], - /* EPIC GAMES */ - [ - "key" => "epicgames_account_email", - "name" => "E-mail", - "description" => "L'adresse e-mail utilisée pour votre compte Epic Games.", - "job_info_type_id" => 2, - "job_id" => 3, - ], - [ - "key" => "epicgames_account_password", - "name" => "Mot de passe", - "description" => "Le mot de passe utilisé pour votre compte Epic Games.", - "job_info_type_id" => 3, - "job_id" => 3, - ], + // /* EPIC GAMES */ + // [ + // "key" => "epicgames_account_email", + // "name" => "E-mail", + // "description" => "L'adresse e-mail utilisée pour votre compte Epic Games.", + // "job_info_type_id" => 2, + // "job_id" => 3, + // ], + // [ + // "key" => "epicgames_account_password", + // "name" => "Mot de passe", + // "description" => "Le mot de passe utilisé pour votre compte Epic Games.", + // "job_info_type_id" => 3, + // "job_id" => 3, + // ], - /* INSTAGRAM */ - [ - "key" => "instagram_account_email", - "name" => "E-mail", - "description" => "L'adresse e-mail utilisée pour votre compte Instagram.", - "job_info_type_id" => 2, - "job_id" => 4, - ], - [ - "key" => "instagram_account_password", - "name" => "Mot de passe", - "description" => "Le mot de passe utilisé pour votre compte Instagram.", - "job_info_type_id" => 3, - "job_id" => 4, - ], + // /* INSTAGRAM */ + // [ + // "key" => "instagram_account_email", + // "name" => "E-mail", + // "description" => "L'adresse e-mail utilisée pour votre compte Instagram.", + // "job_info_type_id" => 2, + // "job_id" => 4, + // ], + // [ + // "key" => "instagram_account_password", + // "name" => "Mot de passe", + // "description" => "Le mot de passe utilisé pour votre compte Instagram.", + // "job_info_type_id" => 3, + // "job_id" => 4, + // ], ]; foreach ($jobInfos as $jobInfo) { diff --git a/dockerEntryPoint.sh b/dockerEntryPoint.sh index 478995e..0c6bb01 100644 --- a/dockerEntryPoint.sh +++ b/dockerEntryPoint.sh @@ -3,5 +3,9 @@ # Migrate the database php ./artisan migrate --force + +# Queue worker +php ./artisan queue:work --queue=high,default --tries=3 --timeout=90 --sleep=3 --daemon --no-interaction & + # Start all of the server sumulataneously php ./artisan serve --no-interaction -vvv --port=80 --host=0.0.0.0 diff --git a/routes/api.php b/routes/api.php index df6b313..29cbc01 100644 --- a/routes/api.php +++ b/routes/api.php @@ -10,10 +10,9 @@ Route::get('/jobs', function (Request $request) { return response()->json(Job::all())->header('Cache-Control', 'public, max-age=30'); }); - Route::get('/test/{id}', function (Request $request, $id, BrowserJobsInstances $BrowserJobsInstances) { $log = $BrowserJobsInstances->getJobInstance($id)->execute(); - return response()->json(['message' => 'Job ' . $id . ' ran : ' . $log]); + return response()->json(['message' => 'Job ' . $id . ' ran', 'jobRun' => $log->load('artifacts')]); }); Route::get('jobs/{job}/test', [JobController::class, 'test'])->name('jobs.test'); diff --git a/routes/console.php b/routes/console.php index 3c9adf1..fd6eca9 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,8 +1,14 @@ comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); + +Schedule::job(new HellcaseJob)->daily()->onOneServer()->withoutOverlapping()->name('hellcase')->description('Hellcase job'); +// Schedule::job(new HellcaseJob)->everyMinute()->onOneServer()->withoutOverlapping()->name('hellcase')->description('Hellcase job');