From 338f189f33220d03d1ba8ad4a91868905a5b131a Mon Sep 17 00:00:00 2001 From: nk Date: Wed, 3 Jun 2026 18:09:54 +0300 Subject: [PATCH] =?UTF-8?q?fix(enrich):=20=D0=BD=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BC=D0=B5=D1=87=D0=B0=D1=82=D1=8C=20done=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D1=81=D0=B1=D0=BE=D0=B5=20=D0=B7=D0=B0=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D0=B0=20iTunes=20(=D0=BF=D1=80=D0=BE=D0=BC=D0=B0?= =?UTF-8?q?=D1=85=20=D0=BD=D0=B5=20=D0=B7=D0=B0=D1=81=D1=82=D1=8B=D0=B2?= =?UTF-8?q?=D0=B0=D0=B5=D1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Отличаем сбой запроса (сеть/HTTP-ошибка → ретраить, оставляем pending) от чистого 'не найдено' (done). Раньше транзиентный сбой iTunes под нагрузкой навсегда лишал трек обложки. fetchItunes теперь бросает при !res.ok. Co-Authored-By: Claude Opus 4.8 --- src/enrich/enrichment.service.ts | 89 +++++++++++++++++--------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/src/enrich/enrichment.service.ts b/src/enrich/enrichment.service.ts index f12035e..fb11e44 100644 --- a/src/enrich/enrichment.service.ts +++ b/src/enrich/enrichment.service.ts @@ -117,7 +117,14 @@ export class EnrichmentService { // iTunes: обложка (покрытие почти как у Record) + альбом/год/жанр как // фолбэк к Discogs. Гибрид: стили и лейбл — только Discogs. - const itunes = await this.fetchItunes(track.artist, track.song); + // Отличаем сбой запроса (ретраить) от чистого «не найдено» (done). + let itunes: Awaited> = null; + let itunesFailed = false; + try { + itunes = await this.fetchItunes(track.artist, track.song); + } catch { + itunesFailed = true; + } // Обложка → WebP к себе (если ещё не наша) let coverUrl = track.coverUrl; @@ -136,9 +143,10 @@ export class EnrichmentService { itunes?.releaseDate ?? (data?.year ? new Date(Date.UTC(data.year, 0, 1)) : null); - // Без токена Discogs стили/лейбл не получим — оставляем pending, чтобы - // добрать позже (но обложку/жанр-iTunes уже сохранили). - const enriched = this.discogs.enabled; + // Помечаем done, если обогатились. НЕ помечаем (оставляем pending для + // ретрая), если: нет токена Discogs, ИЛИ запрос к iTunes упал И обложку + // так и не получили (транзиентный сбой — промах не должен застывать). + const enriched = this.discogs.enabled && !(itunesFailed && !coverUrl); await this.prisma.track.update({ where: { id: trackId }, @@ -179,46 +187,43 @@ export class EnrichmentService { releaseDate: Date | null; genre: string | null; } | null> { - try { - // Пунктуация в названии («St.Thomas», «feat.») ломает поиск iTunes — - // заменяем все не-буквенно-цифровые символы на пробел и схлопываем. - const clean = `${artist} ${song}` - .replace(/[^\p{L}\p{N}]+/gu, ' ') - .replace(/\s+/g, ' ') - .trim(); - const term = encodeURIComponent(clean); - const url = `https://itunes.apple.com/search?term=${term}&entity=song&limit=1`; - const res = await fetch(url, { - headers: { 'User-Agent': 'radiOLA/1.0 +https://radiola.app' }, - }); - if (!res.ok) return null; - const data = (await res.json()) as { - results?: Array<{ - artworkUrl100?: string; - collectionName?: string; - releaseDate?: string; - primaryGenreName?: string; - }>; - }; - const r = data.results?.[0]; - if (!r) return null; + // Пунктуация в названии («St.Thomas», «feat.») ломает поиск iTunes — + // заменяем все не-буквенно-цифровые символы на пробел и схлопываем. + const clean = `${artist} ${song}` + .replace(/[^\p{L}\p{N}]+/gu, ' ') + .replace(/\s+/g, ' ') + .trim(); + const term = encodeURIComponent(clean); + const url = `https://itunes.apple.com/search?term=${term}&entity=song&limit=1`; + // Бросаем при сетевой/HTTP-ошибке — это сбой, а не «не найдено». + const res = await fetch(url, { + headers: { 'User-Agent': 'radiOLA/1.0 +https://radiola.app' }, + }); + if (!res.ok) throw new Error(`iTunes ${res.status}`); + const data = (await res.json()) as { + results?: Array<{ + artworkUrl100?: string; + collectionName?: string; + releaseDate?: string; + primaryGenreName?: string; + }>; + }; + const r = data.results?.[0]; + if (!r) return null; // чистое «не найдено» - const cover = r.artworkUrl100 - ? r.artworkUrl100.replace(/\/\d+x\d+bb\./, '/600x600bb.') - : null; - const rd = r.releaseDate ? new Date(r.releaseDate) : null; - const validDate = rd && !isNaN(rd.getTime()) ? rd : null; + const cover = r.artworkUrl100 + ? r.artworkUrl100.replace(/\/\d+x\d+bb\./, '/600x600bb.') + : null; + const rd = r.releaseDate ? new Date(r.releaseDate) : null; + const validDate = rd && !isNaN(rd.getTime()) ? rd : null; - return { - coverUrl: cover, - album: r.collectionName ?? null, - year: validDate ? validDate.getUTCFullYear() : null, - releaseDate: validDate, - genre: r.primaryGenreName ?? null, - }; - } catch { - return null; - } + return { + coverUrl: cover, + album: r.collectionName ?? null, + year: validDate ? validDate.getUTCFullYear() : null, + releaseDate: validDate, + genre: r.primaryGenreName ?? null, + }; } private isSelfHosted(url: string): boolean {