Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -8,6 +8,7 @@ use App\Models\Artwork;
use App\Models\Tag;
use App\Services\EarlyGrowth\AdaptiveTimeWindow;
use App\Services\Maturity\ArtworkMaturityService;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\LengthAwarePaginator as PaginationLengthAwarePaginator;
use Illuminate\Support\Collection;
@@ -81,11 +82,28 @@ final class ArtworkSearchService
$options['sort'] = $sort;
}
$options = $this->viewerAwareOptions($options);
return Artwork::search($q ?: '')
->options($options)
$results = Artwork::search($q ?: '')
->options($this->viewerAwareOptions($options))
->paginate($perPage);
if (! $this->shouldFallbackToViewerVisibilityFiltering($results)) {
return $results;
}
$page = max(1, (int) request()->get('page', 1));
$candidateCount = $this->determineSearchCandidatePoolSize($perPage, $page);
$fallbackResults = Artwork::search($q ?: '')
->options($options)
->paginate($candidateCount, 'page', 1);
$visibleItems = $this->filterSearchCollectionByCatalogVisibility($fallbackResults->getCollection());
$offset = max(0, ($page - 1) * $perPage);
$slice = $visibleItems->slice($offset, $perPage)->values();
$visibleTotal = (int) ($fallbackResults->total() <= $candidateCount
? $visibleItems->count()
: $fallbackResults->total());
return $this->makeModelPaginator($slice, $visibleTotal, $perPage, $page);
}
public function searchWithThumbnailPreference(array $options, int $perPage, bool $excludeMissing = false, ?int $page = null): LengthAwarePaginator
@@ -96,21 +114,18 @@ final class ArtworkSearchService
->options($this->viewerAwareOptions($options))
->paginate($candidateCount, 'page', 1);
$ordered = $this->rerankSearchCollectionByThumbnailHealth($results->getCollection(), $excludeMissing);
if ($this->shouldFallbackToViewerVisibilityFiltering($results)) {
$results = Artwork::search('')
->options($options)
->paginate($candidateCount, 'page', 1);
}
$visibleItems = $this->filterSearchCollectionByCatalogVisibility($results->getCollection());
$ordered = $this->rerankSearchCollectionByThumbnailHealth($visibleItems, $excludeMissing);
$offset = max(0, ($page - 1) * $perPage);
$slice = $ordered->slice($offset, $perPage)->values();
return new PaginationLengthAwarePaginator(
$slice->all(),
(int) $results->total(),
$perPage,
$page,
[
'path' => request()->url(),
'query' => request()->query(),
'pageName' => 'page',
]
);
return $this->makeModelPaginator($slice, (int) $results->total(), $perPage, $page);
}
/**
@@ -165,15 +180,14 @@ final class ArtworkSearchService
*/
public function byCategory(string $cat, int $perPage = 24, array $filters = []): LengthAwarePaginator
{
$cacheKey = "search.cat.{$cat}.{$this->viewerCacheSegment()}.page." . request()->get('page', 1);
$page = (int) request()->get('page', 1);
$cacheKey = "search.cat.catalog-visible.v2.{$cat}.{$this->viewerCacheSegment()}.page." . $page;
return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($cat, $perPage) {
return Artwork::search('')
->options($this->viewerAwareOptions([
'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($cat) . '"',
'sort' => ['created_at:desc'],
]))
->paginate($perPage);
return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($cat, $perPage, $page) {
return $this->searchWithThumbnailPreference([
'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($cat) . '"',
'sort' => ['created_at:desc'],
], $perPage, false, $page);
});
}
@@ -214,15 +228,13 @@ final class ArtworkSearchService
$sort = array_key_exists($sort, self::CATEGORY_SORT_FIELDS) ? $sort : 'trending';
$page = (int) request()->get('page', 1);
$ttl = self::CATEGORY_SORT_TTL[$sort] ?? self::CACHE_TTL;
$cacheKey = "category.{$categorySlug}.{$sort}.{$this->viewerCacheSegment()}.{$page}";
$cacheKey = "category.catalog-visible.v2.{$categorySlug}.{$sort}.{$this->viewerCacheSegment()}.{$page}";
return Cache::remember($cacheKey, $ttl, function () use ($categorySlug, $sort, $perPage) {
return Artwork::search('')
->options($this->viewerAwareOptions([
'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($categorySlug) . '"',
'sort' => self::CATEGORY_SORT_FIELDS[$sort],
]))
->paginate($perPage);
return $this->searchWithThumbnailPreference([
'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($categorySlug) . '"',
'sort' => self::CATEGORY_SORT_FIELDS[$sort],
], $perPage, false, (int) request()->get('page', 1));
});
}
@@ -237,15 +249,13 @@ final class ArtworkSearchService
$sort = array_key_exists($sort, self::CATEGORY_SORT_FIELDS) ? $sort : 'trending';
$page = (int) request()->get('page', 1);
$ttl = self::CATEGORY_SORT_TTL[$sort] ?? self::CACHE_TTL;
$cacheKey = "content_type.{$contentTypeSlug}.{$sort}.{$this->viewerCacheSegment()}.{$page}";
$cacheKey = "content_type.catalog-visible.v2.{$contentTypeSlug}.{$sort}.{$this->viewerCacheSegment()}.{$page}";
return Cache::remember($cacheKey, $ttl, function () use ($contentTypeSlug, $sort, $perPage) {
return Artwork::search('')
->options($this->viewerAwareOptions([
'filter' => self::BASE_FILTER . ' AND content_type = "' . addslashes($contentTypeSlug) . '"',
'sort' => self::CATEGORY_SORT_FIELDS[$sort],
]))
->paginate($perPage);
return $this->searchWithThumbnailPreference([
'filter' => self::BASE_FILTER . ' AND content_type = "' . addslashes($contentTypeSlug) . '"',
'sort' => self::CATEGORY_SORT_FIELDS[$sort],
], $perPage, false, (int) request()->get('page', 1));
});
}
@@ -431,6 +441,15 @@ final class ArtworkSearchService
return $options;
}
private function shouldFallbackToViewerVisibilityFiltering(LengthAwarePaginator $results): bool
{
if ($results->total() > 0) {
return false;
}
return $this->maturity->viewerPreferences(request()->user())['visibility'] === ArtworkMaturityService::VIEW_HIDE;
}
private function viewerCacheSegment(): string
{
return 'visibility-' . $this->maturity->viewerPreferences(request()->user())['visibility'];
@@ -513,6 +532,37 @@ final class ArtworkSearchService
->values();
}
private function filterSearchCollectionByCatalogVisibility(Collection $items): Collection
{
if ($items->isEmpty()) {
return $items;
}
$ids = $items
->pluck('id')
->filter(fn ($id) => is_numeric($id) && (int) $id > 0)
->map(fn ($id) => (int) $id)
->values();
if ($ids->isEmpty()) {
return $items->values();
}
$visibilityQuery = Artwork::query()
->catalogVisible()
->whereIn('id', $ids);
$visibleIds = $this->maturity
->applyViewerFilter($visibilityQuery, request()->user())
->pluck('id')
->map(fn ($id) => (int) $id)
->flip();
return $items
->filter(fn ($item) => $visibleIds->has((int) ($item->id ?? 0)))
->values();
}
private function determineSearchCandidatePoolSize(int $perPage, int $page): int
{
return min(
@@ -521,6 +571,23 @@ final class ArtworkSearchService
);
}
private function makeModelPaginator(Collection $items, int $total, int $perPage, int $page): LengthAwarePaginator
{
$paginator = new PaginationLengthAwarePaginator(
[],
$total,
$perPage,
$page,
[
'path' => request()->url(),
'query' => request()->query(),
'pageName' => 'page',
]
);
return $paginator->setCollection(new EloquentCollection($items->all()));
}
private function emptyPaginator(int $perPage): LengthAwarePaginator
{
return new \Illuminate\Pagination\LengthAwarePaginator([], 0, $perPage);