documentation and env changes
CI (Gitea) / php-tests (push) Successful in 10m8s
CI (Gitea) / docker-image (push) Successful in 2m18s

This commit is contained in:
boris committed 2025-11-28 08:14:13 +00:00
1 parent f77f3a9e40
commit d52eb6bd81
59 files changed
+932 -565

No files matched your search

+31 -21
View File
@@ -18,10 +18,10 @@ use Psr\Log\LoggerInterface;
class AlbumSearchService
{
public function __construct(
private readonly SpotifyClient $spotify,
private readonly SpotifyClient $spotifyClient,
private readonly AlbumRepository $albumRepository,
private readonly ReviewRepository $reviewRepository,
private readonly EntityManagerInterface $em,
private readonly EntityManagerInterface $entityManager,
private readonly LoggerInterface $logger,
private readonly SpotifyGenreResolver $genreResolver,
) {
@@ -33,12 +33,12 @@ class AlbumSearchService
*/
public function search(AlbumSearchCriteria $criteria): AlbumSearchResult
{
$spotifyQuery = $this->buildSpotifyQuery($criteria);
$spotifyQuery = $this->buildSpotifySearchQuery($criteria);
$hasUserFilters = $this->hasUserFilters($criteria, $spotifyQuery);
// Spotify only gets pinged when callers explicitly enable it and we actually have
// something to ask for (bare "all" requests would otherwise waste API calls).
$shouldQuerySpotify = $criteria->useSpotify()
&& ($spotifyQuery !== '' || $criteria->getGenre() !== '' || $criteria->source === 'spotify');
$shouldQuerySpotify = $criteria->shouldUseSpotify()
&& ($spotifyQuery !== '' || $criteria->genre !== '' || $criteria->source === 'spotify');
$stats = [];
$savedIds = [];
@@ -53,7 +53,7 @@ class AlbumSearchService
$savedIds = $this->mergeSavedIds($savedIds, $spotifyData['savedIds']);
}
if ($criteria->useUser() && $hasUserFilters) {
if ($criteria->shouldUseUserCatalog() && $hasUserFilters) {
// Skip the user query unless at least one meaningful filter is present.
$userData = $this->resolveUserAlbums($criteria);
$userPayloads = $userData['payloads'];
@@ -64,11 +64,19 @@ class AlbumSearchService
return new AlbumSearchResult($criteria, $albums, $stats, $savedIds);
}
/**
* Indicates whether Spotify credentials are configured.
*/
public function isSpotifyConfigured(): bool
{
return $this->spotifyClient->isConfigured();
}
/**
* Turns structured filters into Spotify's free-form query syntax.
*/
private function buildSpotifyQuery(AlbumSearchCriteria $criteria): string
private function buildSpotifySearchQuery(AlbumSearchCriteria $criteria): string
{
$parts = [];
if ($criteria->albumName !== '') {
@@ -105,7 +113,7 @@ class AlbumSearchService
return $spotifyQuery !== ''
|| $criteria->albumName !== ''
|| $criteria->artist !== ''
|| $criteria->getGenre() !== ''
|| $criteria->genre !== ''
|| $criteria->yearFrom !== null
|| $criteria->yearTo !== null;
}
@@ -117,22 +125,22 @@ class AlbumSearchService
*/
private function resolveSpotifyAlbums(AlbumSearchCriteria $criteria, string $spotifyQuery): array
{
$stored = $this->albumRepository->searchSpotifyAlbums(
$storedSpotifyAlbums = $this->albumRepository->searchSpotifyAlbums(
$criteria->query,
$criteria->albumName,
$criteria->artist,
$criteria->getGenre(),
$criteria->genre,
$criteria->yearFrom ?? 0,
$criteria->yearTo ?? 0,
$criteria->limit
);
$storedPayloads = array_map(static fn(Album $album) => $album->toTemplateArray(), $stored);
$storedPayloads = $this->filterPayloadsByGenre($storedPayloads, $criteria->getGenre());
$storedIds = $this->collectSpotifyIds($stored);
$storedPayloads = array_map(static fn(Album $album) => $album->toTemplateArray(), $storedSpotifyAlbums);
$storedPayloads = $this->filterPayloadsByGenre($storedPayloads, $criteria->genre);
$storedIds = $this->collectSpotifyIds($storedSpotifyAlbums);
$stats = $storedIds ? $this->reviewRepository->getAggregatesForAlbumIds($storedIds) : [];
$savedIds = $storedIds;
$shouldFetchFromSpotify = $spotifyQuery !== '' && count($stored) < $criteria->limit;
$shouldFetchFromSpotify = $spotifyQuery !== '' && count($storedSpotifyAlbums) < $criteria->limit;
if (!$shouldFetchFromSpotify) {
return [
@@ -142,9 +150,10 @@ class AlbumSearchService
];
}
// Mix cached payloads with just enough fresh API data to satisfy the limit.
// Mix cached payloads with just enough fresh API data to satisfy the limit.
// This has the consequence of preferring cached data, but reduces API calls.
$apiPayloads = $this->fetchSpotifyPayloads($criteria, $spotifyQuery, $storedPayloads);
$filteredApiPayloads = $this->filterPayloadsByGenre($apiPayloads['payloads'], $criteria->getGenre());
$filteredApiPayloads = $this->filterPayloadsByGenre($apiPayloads['payloads'], $criteria->genre);
$payloads = $this->mergePayloadLists($filteredApiPayloads, $storedPayloads, $criteria->limit);
$stats = $this->mergeStats($stats, $apiPayloads['stats']);
$savedIds = $this->mergeSavedIds($savedIds, $apiPayloads['savedIds']);
@@ -160,7 +169,7 @@ class AlbumSearchService
*/
private function fetchSpotifyPayloads(AlbumSearchCriteria $criteria, string $spotifyQuery, array $storedPayloads): array
{
$result = $this->spotify->searchAlbums($spotifyQuery, $criteria->limit);
$result = $this->spotifyClient->searchAlbums($spotifyQuery, $criteria->limit);
$searchItems = $result['albums']['items'] ?? [];
$this->logger->info('Album search results received', [
'query' => $spotifyQuery,
@@ -176,7 +185,7 @@ class AlbumSearchService
return ['payloads' => [], 'stats' => [], 'savedIds' => []];
}
$full = $this->spotify->getAlbums($ids);
$full = $this->spotifyClient->getAlbums($ids);
$albumsPayload = is_array($full) ? ($full['albums'] ?? []) : [];
if ($albumsPayload === [] && $searchItems !== []) {
$albumsPayload = $searchItems;
@@ -196,7 +205,7 @@ class AlbumSearchService
);
$upserted++;
}
$this->em->flush();
$this->entityManager->flush();
$this->logger->info('Albums upserted to DB', ['upserted' => $upserted]);
$existing = $this->albumRepository->findBySpotifyIdsKeyed($ids);
@@ -227,7 +236,7 @@ class AlbumSearchService
$criteria->query,
$criteria->albumName,
$criteria->artist,
$criteria->getGenre(),
$criteria->genre,
$criteria->yearFrom ?? 0,
$criteria->yearTo ?? 0,
$criteria->limit
@@ -260,6 +269,7 @@ class AlbumSearchService
$entityId = (int) $album->getId();
$localId = (string) $album->getLocalId();
if ($localId !== '' && isset($userStats[$entityId])) {
// Templates never see entity IDs, so stats must be re-keyed to the user-facing local IDs.
$mapped[$localId] = $userStats[$entityId];
}
}
@@ -373,7 +383,7 @@ class AlbumSearchService
if ($id !== null && isset($seen[$id])) {
continue;
}
// Fill the remainder of the list with secondary payloads that have not already been emitted.
// Secondary payloads often duplicate the primary list; skip anything we've already emitted.
$merged[] = $payload;
if ($id !== null) {
$seen[$id] = true;