110 lines
3.5 KiB
PHP
110 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\Api\Search;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Resources\ArtworkListResource;
|
|
use App\Models\Artwork;
|
|
use App\Models\Tag;
|
|
use App\Services\ArtworkSearchService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
|
|
/**
|
|
* Artwork search endpoints powered by Meilisearch.
|
|
*
|
|
* GET /api/search/artworks?q=&tags[]=&category=&orientation=&sort=
|
|
* GET /api/search/artworks/tag/{slug}
|
|
* GET /api/search/artworks/category/{cat}
|
|
* GET /api/search/artworks/related/{id}
|
|
*/
|
|
class ArtworkSearchController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly ArtworkSearchService $search
|
|
) {}
|
|
|
|
/**
|
|
* GET /api/search/artworks
|
|
*/
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'q' => ['nullable', 'string', 'max:200'],
|
|
'tags' => ['nullable', 'array', 'max:10'],
|
|
'tags.*' => ['string', 'max:80'],
|
|
'category' => ['nullable', 'string', 'max:80'],
|
|
'orientation' => ['nullable', 'in:landscape,portrait,square'],
|
|
'resolution' => ['nullable', 'string', 'max:20'],
|
|
'author_id' => ['nullable', 'integer', 'min:1'],
|
|
'sort' => ['nullable', 'string', 'regex:/^(created_at|downloads|likes|views):(asc|desc)$/'],
|
|
'per_page' => ['nullable', 'integer', 'min:1', 'max:100'],
|
|
]);
|
|
|
|
$results = $this->search->search(
|
|
q: (string) ($validated['q'] ?? ''),
|
|
filters: array_filter([
|
|
'tags' => $validated['tags'] ?? [],
|
|
'category' => $validated['category'] ?? null,
|
|
'orientation' => $validated['orientation'] ?? null,
|
|
'resolution' => $validated['resolution'] ?? null,
|
|
'author_id' => $validated['author_id'] ?? null,
|
|
'sort' => $validated['sort'] ?? null,
|
|
]),
|
|
perPage: (int) ($validated['per_page'] ?? 24),
|
|
);
|
|
|
|
// Eager-load relations needed by ArtworkListResource
|
|
$results->getCollection()->loadMissing(['user', 'categories.contentType']);
|
|
|
|
return ArtworkListResource::collection($results)->response();
|
|
}
|
|
|
|
/**
|
|
* GET /api/search/artworks/tag/{slug}
|
|
*/
|
|
public function byTag(Request $request, string $slug): JsonResponse
|
|
{
|
|
$tag = Tag::where('slug', $slug)->first();
|
|
|
|
if (! $tag) {
|
|
return response()->json(['message' => 'Tag not found.'], 404);
|
|
}
|
|
|
|
$results = $this->search->byTag($slug, (int) $request->query('per_page', 24));
|
|
|
|
return response()->json([
|
|
'tag' => ['id' => $tag->id, 'name' => $tag->name, 'slug' => $tag->slug],
|
|
'results' => $results,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* GET /api/search/artworks/category/{cat}
|
|
*/
|
|
public function byCategory(Request $request, string $cat): JsonResponse
|
|
{
|
|
$results = $this->search->byCategory($cat, (int) $request->query('per_page', 24));
|
|
|
|
return response()->json($results);
|
|
}
|
|
|
|
/**
|
|
* GET /api/search/artworks/related/{id}
|
|
*/
|
|
public function related(int $id): JsonResponse
|
|
{
|
|
$artwork = Artwork::with(['tags'])->find($id);
|
|
|
|
if (! $artwork) {
|
|
return response()->json(['message' => 'Artwork not found.'], 404);
|
|
}
|
|
|
|
$results = $this->search->related($artwork, 12);
|
|
|
|
return response()->json($results);
|
|
}
|
|
}
|