diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 5f15b9d..37e374f 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -1,7 +1,109 @@
{
"permissions": {
"allow": [
- "Bash(npm run *)"
+ "Bash(npm run *)",
+ "Bash(npm --version)",
+ "Bash(npx --version)",
+ "Bash(npx --yes create-next-app@latest . --typescript --tailwind --app --src-dir --import-alias \"@/*\" --use-npm --eslint --no-turbopack --yes)",
+ "Bash(npm install *)",
+ "Bash(npx prisma *)",
+ "Bash(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000__TRACKED_VAR__)",
+ "Bash(curl -s -X POST http://localhost:3000/api/readings -H \"Content-Type: application/json\" -d '{\"moment\":\"FASTING\",\"value\":1.05,\"notes\":\"Test smoke\"}')",
+ "Bash(curl -s \"http://localhost:3000/api/stats\")",
+ "Bash(curl -s -X DELETE http://localhost:3000/api/readings/91)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://localhost:3000/api/export)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://localhost:3000/profil)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://localhost:3000/api/patient)",
+ "Bash(curl -s -X PUT http://localhost:3000/api/patient -H \"Content-Type: application/json\" -d '{\"firstName\":\"Jeremy\",\"lastName\":\"Bayse\",\"email\":\"jeremy.bayse@gmail.com\",\"birthDate\":\"1985-06-15\",\"heightCm\":180,\"weightKg\":78.5}')",
+ "Bash(curl -s http://localhost:3000/api/patient)",
+ "Bash(taskkill //PID 40172 //F)",
+ "Bash(curl -s -o /dev/null -w \"GET /profil %{http_code}\\\\n\" http://localhost:3000/profil)",
+ "Bash(curl -s -o /dev/null -w \"GET /api/patient %{http_code}\\\\n\" http://localhost:3000/api/patient)",
+ "Bash(curl -s http://localhost:3000/)",
+ "Bash(curl -s -X PUT http://localhost:3000/api/patient -H \"Content-Type: application/json\" -d '{\"firstName\":\"Jeremy\",\"lastName\":\"Bayse\",\"email\":\"jeremy.bayse@gmail.com\",\"birthDate\":\"1985-06-15\",\"heightCm\":180,\"weightKg\":78.5,\"sex\":\"M\",\"diabetesType\":\"TYPE_2\",\"treatment\":\"Metformine 1000 mg matin et soir\"}')",
+ "Bash(curl -s -X PUT http://localhost:3000/api/patient -H \"Content-Type: application/json\" -d '{\"firstName\":\"Jeremy\",\"lastName\":\"Bayse\",\"sex\":\"INVALID\"}')",
+ "Bash(curl -s http://localhost:3000/profil)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" http://localhost:3001/profil)",
+ "Bash(curl -s -X PUT http://localhost:3001/api/patient -H \"Content-Type: application/json\" -d '{\"firstName\":\"Jeremy\",\"lastName\":\"Bayse\",\"email\":\"jeremy.bayse@gmail.com\",\"birthDate\":\"1985-06-15\",\"heightCm\":180,\"weightKg\":78.5,\"sex\":\"M\",\"diabetesType\":\"TYPE_2\",\"treatment\":\"Metformine 1000 mg matin et soir\"}')",
+ "Bash(curl -s http://localhost:3001/api/patient)",
+ "Bash(curl -s http://localhost:3001/)",
+ "Bash(grep -oE \"Diab.{1,30}\")",
+ "Bash(taskkill //PID 37932 //F)",
+ "Bash(curl -s -X POST http://localhost:3001/api/chat -H \"Content-Type: application/json\" -d '{\"message\":\"Bonjour, comment se passe mon suivi cette semaine ?\",\"history\":[]}' --max-time 30)",
+ "Bash(curl -s \"https://generativelanguage.googleapis.com/v1beta/models?key=AIzaSyD7ltywmUmEooMOBiMkfyhQygCEU06LbR4\")",
+ "Bash(curl -s -X POST \"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=AIzaSyD7ltywmUmEooMOBiMkfyhQygCEU06LbR4\" -H \"Content-Type: application/json\" -d '{\"contents\":[{\"parts\":[{\"text\":\"Dis bonjour en une phrase.\"}]}]}')",
+ "Bash(curl -s -X POST \"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=AIzaSyD7ltywmUmEooMOBiMkfyhQygCEU06LbR4\" -H \"Content-Type: application/json\" -d '{\"contents\":[{\"parts\":[{\"text\":\"Dis bonjour en une phrase.\"}]}]}')",
+ "Bash(curl -s -X POST http://localhost:3001/api/chat -H \"Content-Type: application/json\" -d '{\"message\":\"Comment se passe mon suivi cette semaine ?\",\"history\":[]}' --max-time 30)",
+ "Bash(curl -s http://localhost:3001/api/daily-analysis --max-time 30)",
+ "Bash(taskkill //PID 42196 //F)",
+ "Bash(curl -s http://localhost:3001/api/daily-analysis --max-time 35)",
+ "Bash(curl -s http://localhost:3001/api/daily-analysis --max-time 10)",
+ "Bash(python -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\('fresh:', d.get\\('fresh'\\), '| generatedAt:', d.get\\('generatedAt'\\)\\)\")",
+ "Bash(taskkill //PID 33804 //F)",
+ "Bash(curl -s http://localhost:3000/mobile)",
+ "Bash(curl -s http://localhost:3001/mobile)",
+ "Bash(npx tsc *)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3000/)",
+ "Bash(curl -s http://localhost:3001/dashboard)",
+ "Bash(curl -sv http://localhost:3001/dashboard)",
+ "Bash(python3 -c \"import sys; data=sys.stdin.read\\(\\); print\\(data[data.find\\('Error'\\):data.find\\('Error'\\)+500] if 'Error' in data else data[:500]\\)\")",
+ "Bash(node -e \"console.log\\(require\\('./node_modules/next/package.json'\\).version\\)\")",
+ "Bash(rm -rf .next)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3001/)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3001/dashboard)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3002/)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3002/dashboard)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3002/auth/login)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3002/pricing)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3002/auth/register)",
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3002/auth/verify-pending)",
+ "Bash(taskkill //F //IM node.exe)",
+ "Bash(curl -s -X POST http://localhost:3000/api/auth/register -H 'Content-Type: application/json' -d '{\"name\":\"Test User\",\"email\":\"test@test.com\",\"password\":\"password123\"}')",
+ "Bash(curl -s -X POST http://localhost:3000/api/auth/register -H 'Content-Type: application/json' -d '{\"name\":\"Jean Dupont\",\"email\":\"jean__CMDSUB_OUTPUT__@example.com\",\"password\":\"motdepasse123\"}')",
+ "mcp__Claude_in_Chrome__tabs_context_mcp",
+ "mcp__Claude_in_Chrome__browser_batch",
+ "mcp__Claude_in_Chrome__switch_browser",
+ "mcp__Claude_in_Chrome__list_connected_browsers",
+ "mcp__Claude_in_Chrome__select_browser",
+ "Bash(taskkill /F /IM node.exe)",
+ "PowerShell(Stop-Process -Name node -Force -ErrorAction SilentlyContinue)",
+ "Bash(curl -s http://localhost:3000/pricing)",
+ "Bash(cat)",
+ "Bash(chmod +x test-stripe.sh)",
+ "Bash(./test-stripe.sh)",
+ "Bash(curl -s http://localhost:3000/pricing -X POST -H \"Content-Type: application/json\")",
+ "Bash(pkill -9 node)",
+ "mcp__Claude_in_Chrome__navigate",
+ "mcp__Claude_in_Chrome__computer",
+ "mcp__Claude_in_Chrome__form_input",
+ "Bash(sqlite3 prisma/dev.db \"SELECT id, email, plan FROM User LIMIT 5;\")",
+ "Bash(node -e ' *)",
+ "Bash(npm exec *)",
+ "Bash(node test-cancel-subscription.mjs)",
+ "Bash(pkill -f \"next dev\")",
+ "mcp__Claude_in_Chrome__find",
+ "Bash(curl -s http://localhost:3000)",
+ "Bash(node add_readings.js)",
+ "mcp__Claude_in_Chrome__read_network_requests",
+ "mcp__Claude_in_Chrome__read_console_messages",
+ "Bash(taskkill /PID 54104 /F)",
+ "Bash(file ~/Downloads/rapport_glycemie*.pdf)",
+ "Bash(pdftotext ~/Downloads/rapport_glycemie_2026-04*.pdf -)",
+ "Bash(node /tmp/check_pdf.js)",
+ "Bash(tasklist)",
+ "Bash(curl -s http://localhost:3000/dashboard/rapports -c /tmp/cookies.txt)",
+ "Bash(curl -s \"http://localhost:3000/api/reports/generate-pdf?month=2026-04-01\" -H \"Cookie: $\\(curl -s http://localhost:3000/dashboard/rapports -c /tmp/cookies.txt)",
+ "Bash(grep -o '[^ ]*$')",
+ "Bash(chmod +x /tmp/deploy-setup.sh)",
+ "Bash(git remote *)",
+ "Bash(git add *)",
+ "Bash(git commit -m ' *)",
+ "Bash(git push *)",
+ "Bash(tar -czf diabetix-build.tar.gz .next/ node_modules/ package.json package-lock.json public/ prisma/ src/ .env.production next.config.js tsconfig.json)",
+ "Bash(rm diabetix-build.tar.gz)",
+ "Bash(tar -czf diabetix-build.tar.gz .next/ node_modules/ package.json package-lock.json public/ prisma/schema.prisma prisma/migrations/ src/ next.config.ts tsconfig.json)",
+ "Bash(scp diabetix-build.tar.gz root@192.168.20.28:/tmp/)",
+ "Bash(sshpass -p \"Lucas1978!\" scp -o StrictHostKeyChecking=no diabetix-build.tar.gz root@192.168.20.28:/tmp/)"
]
}
}
diff --git a/app/Http/Controllers/Api/CandidateHoneypotController.php b/app/Http/Controllers/Api/CandidateHoneypotController.php
new file mode 100644
index 0000000..acb3298
--- /dev/null
+++ b/app/Http/Controllers/Api/CandidateHoneypotController.php
@@ -0,0 +1,55 @@
+logSecurityAlert('directory_traversal', $request);
+
+ // Fausse réponse pour faire croire que le serveur est vulnérable
+ return response(
+ "
Index of /documents/private
",
+ 200
+ )->header('Content-Type', 'text/html');
+ }
+
+ public function logMassAssignment(Request $request)
+ {
+ $this->logSecurityAlert('mass_assignment', $request);
+
+ // Faire croire que l'opération a réussi mais renvoyer une erreur 403 discrètement
+ return response()->json([
+ 'status' => 'success',
+ 'message' => 'Profil mis à jour.',
+ 'debug' => 'Attempt logged.'
+ ], 403);
+ }
+
+ public function downloadFakeFile(Request $request, $filename)
+ {
+ $this->logSecurityAlert('file_exfiltration', $request, ['filename' => $filename]);
+
+ // Faux contenu
+ $content = "Ceci est un honeypot de sécurité. Votre action a été journalisée.";
+ return response($content, 200)
+ ->header('Content-Type', 'text/plain')
+ ->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
+ }
+
+ private function logSecurityAlert(string $type, Request $request, array $extraPayload = [])
+ {
+ \App\Models\SecurityAlert::create([
+ 'user_id' => auth()->id(),
+ 'type' => $type,
+ 'endpoint' => $request->path(),
+ 'payload' => array_merge($request->all(), $extraPayload),
+ 'ip_address' => $request->ip(),
+ 'user_agent' => $request->userAgent(),
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/CandidateController.php b/app/Http/Controllers/CandidateController.php
index 61e7975..bc9f196 100644
--- a/app/Http/Controllers/CandidateController.php
+++ b/app/Http/Controllers/CandidateController.php
@@ -132,7 +132,7 @@ class CandidateController extends Controller
public function show(Candidate $candidate)
{
$candidate->load([
- 'user',
+ 'user.securityAlerts',
'documents',
'jobPosition',
'tenant'
diff --git a/app/Http/Controllers/PublicJobApplicationController.php b/app/Http/Controllers/PublicJobApplicationController.php
new file mode 100644
index 0000000..46593c2
--- /dev/null
+++ b/app/Http/Controllers/PublicJobApplicationController.php
@@ -0,0 +1,91 @@
+orderBy('created_at', 'desc')->get();
+ return Inertia::render('Public/Jobs/Index', [
+ 'jobs' => $jobs
+ ]);
+ }
+
+ public function show(JobPosition $jobPosition)
+ {
+ return Inertia::render('Public/Jobs/Show', [
+ 'jobPosition' => $jobPosition
+ ]);
+ }
+
+ public function store(Request $request, JobPosition $jobPosition)
+ {
+ $request->validate([
+ 'name' => 'required|string|max:255',
+ 'email' => 'required|string|email|max:255|unique:users',
+ 'phone' => 'nullable|string|max:20',
+ 'linkedin_url' => 'nullable|url|max:255',
+ 'city' => 'nullable|string|max:255',
+ 'cv' => 'nullable|mimes:pdf|max:5120',
+ 'cover_letter' => 'nullable|mimes:pdf|max:5120',
+ ]);
+
+ $password = Str::random(10);
+
+ $user = User::create([
+ 'name' => $request->name,
+ 'email' => $request->email,
+ 'password' => Hash::make($password),
+ 'role' => 'candidate',
+ 'tenant_id' => $jobPosition->tenant_id,
+ ]);
+
+ $candidate = $user->candidate()->create([
+ 'phone' => $request->phone,
+ 'linkedin_url' => $request->linkedin_url,
+ 'city' => $request->city,
+ 'status' => 'en_attente',
+ 'tenant_id' => $jobPosition->tenant_id,
+ 'job_position_id' => $jobPosition->id,
+ ]);
+
+ if ($request->hasFile('cv')) {
+ $this->storeDocument($candidate, $request->file('cv'), 'cv');
+ }
+ if ($request->hasFile('cover_letter')) {
+ $this->storeDocument($candidate, $request->file('cover_letter'), 'cover_letter');
+ }
+
+ // Auto-login the candidate so they can take the quiz immediately if they want
+ Auth::login($user);
+
+ return redirect()->route('dashboard')->with('success', 'Votre candidature a bien été enregistrée. Voici votre mot de passe temporaire pour vous reconnecter : ' . $password);
+ }
+
+ private function storeDocument(Candidate $candidate, $file, string $type)
+ {
+ if (!$file) {
+ return;
+ }
+
+ $path = $file->store('private/documents/' . $candidate->id, 'local');
+
+ Document::create([
+ 'candidate_id' => $candidate->id,
+ 'type' => $type,
+ 'file_path' => $path,
+ 'original_name' => $file->getClientOriginalName(),
+ ]);
+ }
+}
diff --git a/app/Models/SecurityAlert.php b/app/Models/SecurityAlert.php
new file mode 100644
index 0000000..fa4ed5c
--- /dev/null
+++ b/app/Models/SecurityAlert.php
@@ -0,0 +1,26 @@
+ 'array',
+ ];
+
+ public function user()
+ {
+ return $this->belongsTo(User::class);
+ }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index d07a2d9..33af870 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -42,6 +42,11 @@ class User extends Authenticatable
return $this->belongsTo(Tenant::class);
}
+ public function securityAlerts()
+ {
+ return $this->hasMany(SecurityAlert::class);
+ }
+
/**
* Get the attributes that should be cast.
*
diff --git a/database/migrations/2026_05_08_075358_create_security_alerts_table.php b/database/migrations/2026_05_08_075358_create_security_alerts_table.php
new file mode 100644
index 0000000..f56f75e
--- /dev/null
+++ b/database/migrations/2026_05_08_075358_create_security_alerts_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
+ $table->string('type'); // 'mass_assignment', 'directory_traversal', etc.
+ $table->string('endpoint')->nullable();
+ $table->json('payload')->nullable();
+ $table->string('ip_address')->nullable();
+ $table->text('user_agent')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('security_alerts');
+ }
+};
diff --git a/resources/js/Components/Dropdown.vue b/resources/js/Components/Dropdown.vue
index 0f4b441..90b9c38 100644
--- a/resources/js/Components/Dropdown.vue
+++ b/resources/js/Components/Dropdown.vue
@@ -69,13 +69,13 @@ const open = ref(false);
>
diff --git a/resources/js/Components/DropdownLink.vue b/resources/js/Components/DropdownLink.vue
index 705f59b..8d9b24d 100644
--- a/resources/js/Components/DropdownLink.vue
+++ b/resources/js/Components/DropdownLink.vue
@@ -12,8 +12,7 @@ defineProps({
diff --git a/resources/js/Components/NavLink.vue b/resources/js/Components/NavLink.vue
index 627c364..1507c6a 100644
--- a/resources/js/Components/NavLink.vue
+++ b/resources/js/Components/NavLink.vue
@@ -14,8 +14,8 @@ const props = defineProps({
const classes = computed(() =>
props.active
- ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-600 text-sm font-bold leading-5 text-indigo-700 focus:outline-none transition duration-150 ease-in-out'
- : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-bold leading-5 text-slate-700 hover:text-indigo-600 hover:border-indigo-400 focus:outline-none transition duration-150 ease-in-out',
+ ? 'inline-flex items-center px-1 pt-1 border-b-2 border-primary text-sm font-subtitle font-bold leading-5 text-primary focus:outline-none transition duration-150 ease-in-out'
+ : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-subtitle font-bold leading-5 text-anthracite/60 hover:text-primary hover:border-primary/30 focus:outline-none transition duration-150 ease-in-out',
);
diff --git a/resources/js/Components/ResponsiveNavLink.vue b/resources/js/Components/ResponsiveNavLink.vue
index f6c4566..68b8f9c 100644
--- a/resources/js/Components/ResponsiveNavLink.vue
+++ b/resources/js/Components/ResponsiveNavLink.vue
@@ -14,8 +14,8 @@ const props = defineProps({
const classes = computed(() =>
props.active
- ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
- : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out',
+ ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-primary text-start text-base font-subtitle font-bold text-primary bg-primary/5 focus:outline-none focus:text-primary focus:bg-primary/10 focus:border-primary transition duration-150 ease-in-out'
+ : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-subtitle font-medium text-anthracite/60 hover:text-primary hover:bg-sand/30 hover:border-anthracite/20 focus:outline-none focus:text-primary focus:bg-sand/30 focus:border-anthracite/20 transition duration-150 ease-in-out',
);
diff --git a/resources/js/Layouts/AuthenticatedLayout.vue b/resources/js/Layouts/AuthenticatedLayout.vue
index 148bd6a..fdcafb0 100644
--- a/resources/js/Layouts/AuthenticatedLayout.vue
+++ b/resources/js/Layouts/AuthenticatedLayout.vue
@@ -1,202 +1,143 @@
-
-
-
diff --git a/resources/js/Pages/Admin/JobPositions/Index.vue b/resources/js/Pages/Admin/JobPositions/Index.vue
index 2163f27..99f21a4 100644
--- a/resources/js/Pages/Admin/JobPositions/Index.vue
+++ b/resources/js/Pages/Admin/JobPositions/Index.vue
@@ -73,6 +73,13 @@ const addRequirement = () => {
const removeRequirement = (index) => {
form.requirements.splice(index, 1);
};
+
+const copyLink = (position) => {
+ const url = route('jobs.show', position.id);
+ navigator.clipboard.writeText(url).then(() => {
+ alert('Lien copié dans le presse-papier!');
+ });
+};
@@ -128,14 +135,26 @@ const removeRequirement = (index) => {
diff --git a/resources/js/Pages/Dashboard.vue b/resources/js/Pages/Dashboard.vue
index 156e827..1e81279 100644
--- a/resources/js/Pages/Dashboard.vue
+++ b/resources/js/Pages/Dashboard.vue
@@ -15,6 +15,8 @@ const user = computed(() => page.props.auth.user);
const isAdmin = computed(() => ['admin', 'super_admin'].includes(user.value?.role));
const layout = computed(() => isAdmin.value ? AdminLayout : AuthenticatedLayout);
+import axios from 'axios';
+
const getStatusColor = (status) => {
const colors = {
'en_attente': 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-400',
@@ -24,6 +26,17 @@ const getStatusColor = (status) => {
};
return colors[status] || colors['en_attente'];
};
+
+const triggerMassAssignmentHoneypot = async () => {
+ try {
+ await axios.patch('/api/candidate/me', {
+ is_admin: true,
+ role: 'super_admin'
+ });
+ } catch (e) {
+ // Silently fail
+ }
+};
@@ -185,11 +198,22 @@ const getStatusColor = (status) => {
✦ Espace Candidat
-
+
Bienvenue, {{ user.name }} !
+
+
+
-
+
Voici les tests techniques préparés pour votre candidature. Installez-vous confortablement avant de commencer.
+
+
+ Fichiers internes
diff --git a/resources/js/Pages/Public/Jobs/Index.vue b/resources/js/Pages/Public/Jobs/Index.vue
new file mode 100644
index 0000000..42ad9fb
--- /dev/null
+++ b/resources/js/Pages/Public/Jobs/Index.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
Offres d'emploi disponibles
+
Découvrez nos opportunités et rejoignez-nous.
+
+
+
+
+
Aucune offre pour le moment
+
Revenez plus tard pour découvrir nos futures opportunités.
+
+
+
+
+
+
+
+ {{ job.tenant.name }}
+
+ Temps plein
+
+
+ {{ job.title }}
+
+
+ {{ job.description }}
+
+
+
+
+ {{ req }}
+
+
+ +{{ job.requirements.length - 3 }} autres
+
+
+
+
+
+
+ Voir l'offre
+
+
+
+
+
+
+
diff --git a/resources/js/Pages/Public/Jobs/Show.vue b/resources/js/Pages/Public/Jobs/Show.vue
new file mode 100644
index 0000000..1ae4f55
--- /dev/null
+++ b/resources/js/Pages/Public/Jobs/Show.vue
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ jobPosition.title }}
+
+
+
+
+
+
+
+
Description du poste
+
{{ jobPosition.description }}
+
+
+
+
+
+
+
+
Soumettre votre candidature
+
+
+
+
+
+
+
+
diff --git a/routes/web.php b/routes/web.php
index 18017c8..6d45ace 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -71,6 +71,11 @@ Route::get('/dashboard', function () {
]);
})->middleware(['auth', 'verified'])->name('dashboard');
+// Public Job Routes
+Route::get('/jobs', [App\Http\Controllers\PublicJobApplicationController::class, 'index'])->name('jobs.index');
+Route::get('/jobs/{jobPosition}', [App\Http\Controllers\PublicJobApplicationController::class, 'show'])->name('jobs.show');
+Route::post('/jobs/{jobPosition}/apply', [App\Http\Controllers\PublicJobApplicationController::class, 'store'])->name('jobs.apply');
+
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
@@ -110,6 +115,11 @@ Route::middleware('auth')->group(function () {
Route::get('/quizzes/{quiz}', [\App\Http\Controllers\AttemptController::class, 'show'])->name('quizzes.take');
Route::post('/attempts/{attempt}/save', [\App\Http\Controllers\AttemptController::class, 'saveAnswer'])->name('attempts.save');
Route::post('/attempts/{attempt}/finish', [\App\Http\Controllers\AttemptController::class, 'finish'])->name('attempts.finish');
+
+ // Security Honeypots
+ Route::get('/documents/private', [\App\Http\Controllers\Api\CandidateHoneypotController::class, 'logDirectoryTraversal']);
+ Route::get('/documents/private/{filename}', [\App\Http\Controllers\Api\CandidateHoneypotController::class, 'downloadFakeFile']);
+ Route::patch('/api/candidate/me', [\App\Http\Controllers\Api\CandidateHoneypotController::class, 'logMassAssignment']);
});
require __DIR__.'/auth.php';