resolveSearchTerm($request); // Construire les filtres Meilisearch $filters = $this->buildMeilisearchFilters($request); // Toujours utiliser Meilisearch (avec ou sans terme de recherche) $hunts = Hunts::search($searchTerm ?? '') ->options([ 'filter' => $filters, ]) ->query(fn ($query) => $query->with('participants:id,avatar_uri')) ->paginate(10); return response()->json($hunts); } catch (\Throwable $e) { Log::error('Error fetching hunts: '.$e->getMessage()); return response()->json([ 'error' => 'Server Error', 'message' => $e->getMessage(), ], 500); } } /** * Build Meilisearch filter string from request parameters */ private function buildMeilisearchFilters(Request $request): string { $filters = []; // Filtre "participating" (hunts auxquels l'utilisateur participe ou non) if ($request->has('participating')) { $participating = filter_var( $request->query('participating'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); $userId = auth('sanctum')->id(); if ($userId) { if ($participating === true) { $filters[] = "participant_ids = {$userId}"; } elseif ($participating === false) { $filters[] = "participant_ids != {$userId}"; } } elseif ($participating === true) { // User non authentifié mais demande ses participations -> retourne rien $filters[] = "id < 0"; // Impossible, retourne 0 résultat } } // Filtre "active" (hunts actuellement actifs ou non) if ($request->has('active')) { $now = Carbon::now()->timestamp; $active = filter_var($request->query('active'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($active === true) { $filters[] = "start_at_timestamp <= {$now}"; $filters[] = "(end_at_timestamp >= {$now} OR end_at_timestamp IS NULL)"; } else { $filters[] = "(start_at_timestamp > {$now} OR end_at_timestamp < {$now})"; } } // Filtre "city" (recherche par ville) if ($request->filled('city')) { $city = $request->query('city'); if (is_string($city)) { $city = addslashes($city); // Échapper les guillemets $filters[] = "city = \"{$city}\""; } } // Filtre "difficulty" (niveau de difficulté) if ($request->filled('difficulty')) { $difficulties = $request->query('difficulty'); if (is_string($difficulties)) { $difficulties = explode(',', $difficulties); } if (is_array($difficulties)) { $validDifficulties = array_values(array_intersect( HuntDifficulty::values(), array_map('strtolower', $difficulties) )); if (! empty($validDifficulties)) { $difficultyFilters = array_map(fn($d) => "difficulty = \"{$d}\"", $validDifficulties); $filters[] = '('.implode(' OR ', $difficultyFilters).')'; } } } // Filtres de dates $startFrom = $this->parseDateFilter($request->query('start_from')); $startTo = $this->parseDateFilter($request->query('start_to')); $endFrom = $this->parseDateFilter($request->query('end_from')); $endTo = $this->parseDateFilter($request->query('end_to')); if ($startFrom) { $filters[] = "start_at_timestamp >= {$startFrom->timestamp}"; } if ($startTo) { $filters[] = "start_at_timestamp <= {$startTo->timestamp}"; } if ($endFrom) { $filters[] = "end_at_timestamp >= {$endFrom->timestamp}"; } if ($endTo) { $filters[] = "end_at_timestamp <= {$endTo->timestamp}"; } // Joindre tous les filtres avec AND return implode(' AND ', $filters); } private function resolveSearchTerm(Request $request): ?string { $searchTerm = $request->query('search') ?? $request->query('q'); if (! is_string($searchTerm)) { return null; } $searchTerm = trim($searchTerm); return $searchTerm !== '' ? $searchTerm : null; } private function parseDateFilter(mixed $value): ?Carbon { if (! is_string($value) || trim($value) === '') { return null; } try { return Carbon::parse($value); } catch (\Throwable $e) { return null; } } /** * Store a newly created resource in storage. */ public function store(StoreHuntRequest $request): JsonResponse { $validated = $request->validated(); $validated['creator_id'] = auth()->id(); $hunt = Hunts::create($validated); return response()->json($hunt, 201); } /** * Display the specified resource. */ /** * Display the specified resource. */ public function show(Request $request, int $id): JsonResponse { $hunt = Hunts::with(['steps', 'participants:id,avatar_uri'])->find($id); if (! $hunt) { return response()->json(['message' => 'Hunt not found'], 404); } $user = $request->user('sanctum'); if ($user) { $participation = $user->participations()->where('hunt_id', $hunt->id)->first(); if ($participation) { // User is a participant $currentStepNumber = $participation->pivot->current_step_number; // Filter steps $hunt->setRelation('steps', $hunt->steps->filter(function ($step) use ($currentStepNumber) { if ($step->step_number < $currentStepNumber) { return true; // Show past steps fully } if ($step->step_number == $currentStepNumber) { // Show hints but hide location for current step $step->makeHidden(['latitude', 'longitude', 'radius_m']); return true; } return false; // Hide future steps })->values()); $hunt->participation_status = $participation->pivot; } elseif ($hunt->creator_id !== $user->id) { // User is logged in but not participant and not creator // Show only first step or nothing specific (keep as is for now, maybe hide all steps or just show basic info) // For now, let's just hide hints of all steps if not participating? // Or maybe just show nothing of steps if not participating? // Requirement: "fetch the hunt in question it retrieves me the hunt steps already passed and the next one" implying context of participation. // If not participating, maybe just return basic hunt info without steps? $hunt->unsetRelation('steps'); } // If creator, show everything (default) } else { // Guest $hunt->unsetRelation('steps'); } return response()->json($hunt); } public function participate(Request $request, int $id): JsonResponse { $hunt = Hunts::findOrFail($id); $user = $request->user(); if ($user->participations()->where('hunt_id', $hunt->id)->exists()) { return response()->json(['message' => 'Already participating'], 409); } $hunt->participants()->attach($user->id, ['current_step_number' => 1, 'status' => 'started']); return response()->json(['message' => 'Participation started'], 201); } public function unparticipate(Request $request, int $id): JsonResponse { $hunt = Hunts::findOrFail($id); $user = $request->user(); if (! $user->participations()->where('hunt_id', $hunt->id)->exists()) { return response()->json(['message' => 'Not participating'], 404); } $hunt->participants()->detach($user->id); return response()->json(['message' => 'Participation stopped'], 200); } /** * Update the specified resource in storage. */ public function update(UpdateHuntRequest $request, int $id): JsonResponse { $hunt = Hunts::find($id); if (! $hunt) { return response()->json(['message' => 'Hunt not found'], 404); } $validated = $request->validated(); $hunt->update($validated); return response()->json($hunt); } /** * Remove the specified resource from storage. */ public function destroy(int $id): JsonResponse { $hunt = Hunts::find($id); if (! $hunt) { return response()->json(['message' => 'Hunt not found'], 404); } $hunt->delete(); return response()->json(['message' => 'Hunt deleted successfully']); } }