Wire admin studio SSR and search infrastructure

This commit is contained in:
2026-05-01 11:46:06 +02:00
parent 257b0dbef6
commit 18cea8b0f0
329 changed files with 197465 additions and 2741 deletions

View File

@@ -9,21 +9,32 @@ use App\Http\Resources\ArtworkListResource;
use App\Services\ArtworkSearchService;
use App\Services\GroupDiscoveryService;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\View\View;
use cPad\Plugins\News\Models\NewsArticle;
final class SearchController extends Controller
{
private const ALLOWED_SORTS = ['latest', 'popular', 'likes', 'downloads'];
public function __construct(
private readonly ArtworkSearchService $search,
private readonly GroupDiscoveryService $groups,
) {}
public function index(Request $request): View
public function index(Request $request): View|RedirectResponse
{
$q = trim((string) $request->query('q', ''));
$sort = $request->query('sort', 'latest');
$canonicalQuery = $this->canonicalQueryParameters($request);
$canonicalUrl = $this->canonicalSearchUrl($request, $canonicalQuery);
if ($request->fullUrl() !== $canonicalUrl) {
return redirect()->to($canonicalUrl, 301);
}
$q = (string) ($canonicalQuery['q'] ?? '');
$sort = (string) ($canonicalQuery['sort'] ?? 'latest');
$hasQuery = $q !== '';
$sortMap = [
@@ -98,4 +109,81 @@ final class SearchController extends Controller
'page_robots' => 'noindex,follow',
]);
}
/**
* @return array<string, int|string>
*/
private function canonicalQueryParameters(Request $request): array
{
$q = $this->normalizeSearchQuery($request->query('q', ''));
if ($q === '') {
return [];
}
$params = ['q' => $q];
$sort = $this->normalizeSort($request->query('sort', 'latest'));
$page = $this->normalizePage($request->query('page', 1));
if ($sort !== 'latest') {
$params['sort'] = $sort;
}
if ($page > 1) {
$params['page'] = $page;
}
return $params;
}
/**
* @param array<string, int|string> $params
*/
private function canonicalSearchUrl(Request $request, array $params): string
{
$query = Arr::query($params);
return $query === '' ? $request->url() : $request->url() . '?' . $query;
}
private function normalizeSearchQuery(mixed $value): string
{
$query = html_entity_decode($this->firstScalarValue($value), ENT_QUOTES | ENT_HTML5, 'UTF-8');
$query = preg_replace('/(?:\?|&)(?:amp;)?(?:page|sort|filter|group|id|txtfilter|q)=.*$/i', '', $query) ?? $query;
$query = preg_replace('/\s+/u', ' ', $query) ?? $query;
return trim($query, " \t\n\r\0\x0B?&");
}
private function normalizeSort(mixed $value): string
{
$sort = strtolower($this->firstScalarValue($value));
$sort = preg_replace('/(?:\?|&).*/', '', $sort) ?? $sort;
return in_array($sort, self::ALLOWED_SORTS, true) ? $sort : 'latest';
}
private function normalizePage(mixed $value): int
{
$page = $this->firstScalarValue($value);
if (preg_match('/\d+/', $page, $matches) !== 1) {
return 1;
}
return max(1, (int) $matches[0]);
}
private function firstScalarValue(mixed $value): string
{
if (is_array($value)) {
$value = reset($value);
}
if (! is_scalar($value) && $value !== null) {
return '';
}
return trim((string) $value);
}
}