listViaMeilisearch($userId, $filters, $perPage); } catch (\Throwable $e) { Log::warning('Studio: Meilisearch unavailable during text search, falling back to DB', [ 'error' => $e->getMessage(), 'user_id' => $userId, ]); // fall through to DB } } return $this->listViaDatabase($userId, $filters, $perPage); } private function listViaMeilisearch(int $userId, array $filters, int $perPage): LengthAwarePaginator { $q = $filters['q'] ?? ''; $filterParts = ["author_id = {$userId}"]; $sort = []; // Status filter $status = $filters['status'] ?? null; if ($status === 'published') { $filterParts[] = 'is_public = true AND is_approved = true'; } elseif ($status === 'draft') { $filterParts[] = 'is_public = false'; } // archived handled at DB level since Meili doesn't see soft-deleted // Category filter if (!empty($filters['category'])) { $filterParts[] = 'category = "' . addslashes((string) $filters['category']) . '"'; } // Tag filter if (!empty($filters['tags'])) { foreach ((array) $filters['tags'] as $tag) { $filterParts[] = 'tags = "' . addslashes((string) $tag) . '"'; } } // Date range if (!empty($filters['date_from'])) { $filterParts[] = 'created_at >= "' . $filters['date_from'] . '"'; } if (!empty($filters['date_to'])) { $filterParts[] = 'created_at <= "' . $filters['date_to'] . '"'; } // Performance quick filters if (!empty($filters['performance'])) { match ($filters['performance']) { 'rising' => $filterParts[] = 'heat_score > 5', 'top' => $filterParts[] = 'ranking_score > 50', 'low' => $filterParts[] = 'views < 10', default => null, }; } // Sort $sortParam = $filters['sort'] ?? 'created_at:desc'; $validSortFields = [ 'created_at', 'ranking_score', 'heat_score', 'views', 'likes', 'shares_count', 'downloads', 'comments_count', 'favorites_count', ]; $parts = explode(':', $sortParam); if (count($parts) === 2 && in_array($parts[0], $validSortFields, true)) { $sort[] = $parts[0] . ':' . ($parts[1] === 'asc' ? 'asc' : 'desc'); } $options = ['filter' => implode(' AND ', $filterParts)]; if ($sort !== []) { $options['sort'] = $sort; } return Artwork::search($q ?: '') ->options($options) ->query(fn (Builder $query) => $query ->with(['stats', 'categories', 'tags']) ->withCount(['comments', 'downloads']) ) ->paginate($perPage); } private function listViaDatabase(int $userId, array $filters, int $perPage): LengthAwarePaginator { $query = Artwork::where('user_id', $userId) ->with(['stats', 'categories', 'tags']) ->withCount(['comments', 'downloads']); $status = $filters['status'] ?? null; if ($status === 'published') { $query->where('is_public', true)->where('is_approved', true); } elseif ($status === 'draft') { $query->where('is_public', false); } elseif ($status === 'archived') { $query->onlyTrashed(); } else { // Show all except archived by default $query->whereNull('deleted_at'); } // Free-text search if (!empty($filters['q'])) { $q = $filters['q']; $query->where(function (Builder $w) use ($q) { $w->where('title', 'LIKE', "%{$q}%") ->orWhereHas('tags', fn (Builder $t) => $t->where('slug', 'LIKE', "%{$q}%")); }); } // Category if (!empty($filters['category'])) { $query->whereHas('categories', fn (Builder $c) => $c->where('slug', $filters['category'])); } // Tags if (!empty($filters['tags'])) { foreach ((array) $filters['tags'] as $tag) { $query->whereHas('tags', fn (Builder $t) => $t->where('slug', $tag)); } } // Date range if (!empty($filters['date_from'])) { $query->where('created_at', '>=', $filters['date_from']); } if (!empty($filters['date_to'])) { $query->where('created_at', '<=', $filters['date_to']); } // Performance if (!empty($filters['performance'])) { $query->whereHas('stats', function (Builder $s) use ($filters) { match ($filters['performance']) { 'rising' => $s->where('heat_score', '>', 5), 'top' => $s->where('ranking_score', '>', 50), 'low' => $s->where('views', '<', 10), default => null, }; }); } // Sort $sortParam = $filters['sort'] ?? 'created_at:desc'; $parts = explode(':', $sortParam); $sortField = $parts[0] ?? 'created_at'; $sortDir = ($parts[1] ?? 'desc') === 'asc' ? 'asc' : 'desc'; $dbSortMap = [ 'created_at' => 'artworks.created_at', 'ranking_score' => 'ranking_score', 'heat_score' => 'heat_score', 'views' => 'views', 'likes' => 'favorites', 'shares_count' => 'shares_count', 'downloads' => 'downloads', 'comments_count' => 'comments_count', 'favorites_count' => 'favorites', ]; $statsSortFields = ['ranking_score', 'heat_score', 'views', 'likes', 'shares_count', 'downloads', 'comments_count', 'favorites_count']; if (in_array($sortField, $statsSortFields, true)) { $dbCol = $dbSortMap[$sortField] ?? $sortField; $query->leftJoin('artwork_stats', 'artworks.id', '=', 'artwork_stats.artwork_id') ->orderBy("artwork_stats.{$dbCol}", $sortDir) ->select('artworks.*'); } else { $query->orderBy($dbSortMap[$sortField] ?? 'artworks.created_at', $sortDir); } return $query->paginate($perPage); } }