From 205c24182d810083153f62f47c626c7cc89f75b4 Mon Sep 17 00:00:00 2001 From: jeremy bayse Date: Sun, 19 Apr 2026 17:28:13 +0200 Subject: [PATCH] feat: implementation des logs de connexion et correction du chemin de stockage des documents --- app/Console/Commands/CleanupLoginLogs.php | 23 +++++ .../Controllers/Admin/LoginLogController.php | 27 ++++++ app/Listeners/LogSuccessfulLogin.php | 30 +++++++ app/Models/LoginLog.php | 16 ++++ config/filesystems.php | 2 +- ...6_04_19_134436_create_login_logs_table.php | 31 +++++++ package-lock.json | 11 +++ package.json | 1 + resources/js/Layouts/AdminLayout.vue | 12 +++ resources/js/Pages/Admin/Logs.vue | 88 +++++++++++++++++++ routes/console.php | 4 + routes/web.php | 1 + 12 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 app/Console/Commands/CleanupLoginLogs.php create mode 100644 app/Http/Controllers/Admin/LoginLogController.php create mode 100644 app/Listeners/LogSuccessfulLogin.php create mode 100644 app/Models/LoginLog.php create mode 100644 database/migrations/2026_04_19_134436_create_login_logs_table.php create mode 100644 resources/js/Pages/Admin/Logs.vue diff --git a/app/Console/Commands/CleanupLoginLogs.php b/app/Console/Commands/CleanupLoginLogs.php new file mode 100644 index 0000000..7141a9c --- /dev/null +++ b/app/Console/Commands/CleanupLoginLogs.php @@ -0,0 +1,23 @@ +subMonth())->delete(); + $this->info("{$count} logs de connexion supprimés."); + } +} diff --git a/app/Http/Controllers/Admin/LoginLogController.php b/app/Http/Controllers/Admin/LoginLogController.php new file mode 100644 index 0000000..804319c --- /dev/null +++ b/app/Http/Controllers/Admin/LoginLogController.php @@ -0,0 +1,27 @@ +user()->isSuperAdmin()) { + abort(403, 'Unauthorized. Super Admin only.'); + } + + $logs = LoginLog::with('user.tenant') + ->orderBy('login_at', 'desc') + ->paginate(50); + + return Inertia::render('Admin/Logs', [ + 'logs' => $logs + ]); + } +} diff --git a/app/Listeners/LogSuccessfulLogin.php b/app/Listeners/LogSuccessfulLogin.php new file mode 100644 index 0000000..0358c39 --- /dev/null +++ b/app/Listeners/LogSuccessfulLogin.php @@ -0,0 +1,30 @@ +request = $request; + } + + public function handle(Login $event): void + { + LoginLog::create([ + 'user_id' => $event->user->id, + 'ip_address' => $this->request->ip(), + 'user_agent' => $this->request->userAgent(), + 'login_at' => now(), + ]); + } +} diff --git a/app/Models/LoginLog.php b/app/Models/LoginLog.php new file mode 100644 index 0000000..540a394 --- /dev/null +++ b/app/Models/LoginLog.php @@ -0,0 +1,16 @@ +belongsTo(User::class); + } +} diff --git a/config/filesystems.php b/config/filesystems.php index 37d8fca..830a3f6 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -32,7 +32,7 @@ return [ 'local' => [ 'driver' => 'local', - 'root' => storage_path('app/private'), + 'root' => storage_path('app'), 'serve' => true, 'throw' => false, 'report' => false, diff --git a/database/migrations/2026_04_19_134436_create_login_logs_table.php b/database/migrations/2026_04_19_134436_create_login_logs_table.php new file mode 100644 index 0000000..a7cf227 --- /dev/null +++ b/database/migrations/2026_04_19_134436_create_login_logs_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->timestamp('login_at')->useCurrent(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('login_logs'); + } +}; diff --git a/package-lock.json b/package-lock.json index fe899d6..3164d1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "dependencies": { "@tailwindcss/typography": "^0.5.19", "chart.js": "^4.5.1", + "date-fns": "^4.1.0", "marked": "^17.0.4" }, "devDependencies": { @@ -1422,6 +1423,16 @@ "dev": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", diff --git a/package.json b/package.json index febc9d0..42d4099 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dependencies": { "@tailwindcss/typography": "^0.5.19", "chart.js": "^4.5.1", + "date-fns": "^4.1.0", "marked": "^17.0.4" } } diff --git a/resources/js/Layouts/AdminLayout.vue b/resources/js/Layouts/AdminLayout.vue index b10edad..10d2830 100644 --- a/resources/js/Layouts/AdminLayout.vue +++ b/resources/js/Layouts/AdminLayout.vue @@ -112,6 +112,18 @@ const isSidebarOpen = ref(true); Équipe SaaS + + + + + + Logs de connexion +
diff --git a/resources/js/Pages/Admin/Logs.vue b/resources/js/Pages/Admin/Logs.vue new file mode 100644 index 0000000..63ebc92 --- /dev/null +++ b/resources/js/Pages/Admin/Logs.vue @@ -0,0 +1,88 @@ + + + diff --git a/routes/console.php b/routes/console.php index 3c9adf1..f8977e9 100644 --- a/routes/console.php +++ b/routes/console.php @@ -3,6 +3,10 @@ use Illuminate\Foundation\Inspiring; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Schedule; + Artisan::command('inspire', function () { $this->comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); + +Schedule::command('app:cleanup-login-logs')->daily(); diff --git a/routes/web.php b/routes/web.php index f08e9be..0e761b5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -100,6 +100,7 @@ Route::middleware('auth')->group(function () { Route::get('/backup', [\App\Http\Controllers\BackupController::class, 'download'])->name('backup'); Route::delete('/attempts/{attempt}', [\App\Http\Controllers\AttemptController::class, 'destroy'])->name('attempts.destroy'); Route::patch('/answers/{answer}/score', [\App\Http\Controllers\AttemptController::class, 'updateAnswerScore'])->name('answers.update-score'); + Route::get('/logs', [\App\Http\Controllers\Admin\LoginLogController::class, 'index'])->name('logs.index'); }); // Candidate Routes