validate([ 'q' => ['required', 'string', 'min:2', 'max:100'], 'page' => ['nullable', 'integer', 'min:1'], ]); $query = trim($request->input('q')); $page = max(1, (int) $request->query('page', 1)); $perPage = 20; $viewerId = $request->user()?->id; // Scout search (Meilisearch) try { $results = Post::search($query) ->where('visibility', Post::VISIBILITY_PUBLIC) ->where('status', Post::STATUS_PUBLISHED) ->paginate($perPage, 'page', $page); // Load relations $results->load($this->feedService->publicEagerLoads()); $formatted = $results->getCollection() ->map(fn ($post) => $this->feedService->formatPost($post, $viewerId)) ->values(); return response()->json([ 'data' => $formatted, 'query' => $query, 'meta' => [ 'total' => $results->total(), 'current_page' => $results->currentPage(), 'last_page' => $results->lastPage(), 'per_page' => $results->perPage(), ], ]); } catch (\Exception $e) { // Fallback: basic LIKE search on body $paginated = Post::with($this->feedService->publicEagerLoads()) ->where('status', Post::STATUS_PUBLISHED) ->where('visibility', Post::VISIBILITY_PUBLIC) ->where(function ($q) use ($query) { $q->where('body', 'like', '%' . $query . '%') ->orWhereHas('hashtags', fn ($hq) => $hq->where('tag', 'like', '%' . mb_strtolower($query) . '%')); }) ->orderByDesc('created_at') ->paginate($perPage, ['*'], 'page', $page); $formatted = $paginated->getCollection() ->map(fn ($post) => $this->feedService->formatPost($post, $viewerId)) ->values(); return response()->json([ 'data' => $formatted, 'query' => $query, 'meta' => [ 'total' => $paginated->total(), 'current_page' => $paginated->currentPage(), 'last_page' => $paginated->lastPage(), 'per_page' => $paginated->perPage(), ], ]); } } }