feat: forum rich-text editor, emoji picker, mentions, discover nav, feed, uploads, profile
Forum: - TipTap WYSIWYG editor with full toolbar - @emoji-mart/react emoji picker (consistent with tweets) - @mention autocomplete with user search API - Fix PHP 8.4 parse errors in Blade templates - Fix thread data display (paginator items) - Align forum page widths to max-w-5xl Discover: - Extract shared _nav.blade.php partial - Add missing nav links to for-you page - Add Following link for authenticated users Feed/Posts: - Post model, controllers, policies, migrations - Feed page components (PostComposer, FeedCard, etc) - Post reactions, comments, saves, reports, sharing - Scheduled publishing support - Link preview controller Profile: - Profile page components (ProfileHero, ProfileTabs) - Profile API controller Uploads: - Upload wizard enhancements - Scheduled publish picker - Studio status bar and readiness checklist
This commit is contained in:
85
app/Http/Controllers/Api/Posts/PostSearchController.php
Normal file
85
app/Http/Controllers/Api/Posts/PostSearchController.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Posts;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Post;
|
||||
use App\Services\Posts\PostFeedService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* GET /api/feed/search?q=...
|
||||
*
|
||||
* Searches posts body + hashtags via Meilisearch (Laravel Scout).
|
||||
* Falls back to a simple LIKE query if Scout is unavailable.
|
||||
*/
|
||||
class PostSearchController extends Controller
|
||||
{
|
||||
public function __construct(private PostFeedService $feedService) {}
|
||||
|
||||
public function search(Request $request): JsonResponse
|
||||
{
|
||||
$request->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(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user