api/app/Http/Controllers/HuntsController.php

180 lines
6.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use App\Enums\HuntDifficulty;
use App\Models\Hunts;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
class HuntsController extends Controller
{
public function index(Request $request): JsonResponse
{
try {
$searchTerm = $this->resolveSearchTerm($request);
$filters = $this->buildMeilisearchFilters($request);
$search = Hunts::search($searchTerm ?? '')
->query(fn ($query) => $query->with('participants:id,avatar_uri'));
// ✅ IMPORTANT: nenvoie PAS filter sil est vide
if ($filters !== '') {
$search->options(['filter' => $filters]);
}
Log::info('HUNTS_SEARCH', [
'q' => $searchTerm ?? '',
'filters' => $filters,
'meili_host' => config('scout.meilisearch.host'),
'index' => (new Hunts)->searchableAs(),
]);
return response()->json($search->paginate(10));
} catch (\Throwable $e) {
Log::error('Error fetching hunts: '.$e->getMessage());
return response()->json([
'error' => 'Server Error',
'message' => $e->getMessage(),
], 500);
}
}
private function buildMeilisearchFilters(Request $request): string
{
$filters = [];
// participating
if ($request->has('participating')) {
$participating = filter_var(
$request->query('participating'),
FILTER_VALIDATE_BOOLEAN,
FILTER_NULL_ON_FAILURE
);
$userId = auth('sanctum')->id(); // chez toi => UUID string
if ($userId) {
$escapedUserId = $this->escapeMeiliString((string) $userId);
if ($participating === true) {
// ✅ UUID => string
$filters[] = "participant_ids = \"{$escapedUserId}\"";
} elseif ($participating === false) {
$filters[] = "participant_ids != \"{$escapedUserId}\"";
}
} elseif ($participating === true) {
$filters[] = "id < 0";
}
}
// active
if ($request->has('active')) {
$now = Carbon::now('UTC')->timestamp;
$active = filter_var($request->query('active'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($active === true) {
// Hunt actif = démarré ET (pas terminé OU pas de date de fin)
$filters[] = "start_at_timestamp <= {$now}";
// Meilisearch : la syntaxe est 'field EXISTS' et non 'EXISTS field'
// Un hunt est actif si end_at_timestamp n'existe pas OU >= now
$filters[] = "(end_at_timestamp >= {$now} OR NOT end_at_timestamp EXISTS)";
} elseif ($active === false) {
// Hunt non actif = pas encore commencé OU (déjà terminé ET a une date de fin)
// Si pas de end_at_timestamp, le hunt ne peut pas être terminé
$filters[] = "(start_at_timestamp > {$now} OR (end_at_timestamp < {$now} AND end_at_timestamp EXISTS))";
}
}
// city
if ($request->filled('city')) {
$city = $request->query('city');
if (is_string($city)) {
$city = $this->escapeMeiliString($city);
$filters[] = "city = \"{$city}\"";
}
}
// difficulty
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 = "'.$this->escapeMeiliString($d).'"',
$validDifficulties
);
$filters[] = '(' . implode(' OR ', $difficultyFilters) . ')';
}
}
}
// dates (UTC)
$startFrom = $this->parseDateFilterUtc($request->query('start_from'));
$startTo = $this->parseDateFilterUtc($request->query('start_to'));
$endFrom = $this->parseDateFilterUtc($request->query('end_from'));
$endTo = $this->parseDateFilterUtc($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}";
}
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 parseDateFilterUtc(mixed $value): ?Carbon
{
if (!is_string($value) || trim($value) === '') {
return null;
}
try {
return Carbon::parse(trim($value))->utc();
} catch (\Throwable $e) {
return null;
}
}
private function escapeMeiliString(string $value): string
{
// Échappe les backslashes et les guillemets pour une string Meilisearch
$value = str_replace('\\', '\\\\', $value);
return str_replace('"', '\"', $value);
}
}