diff --git a/src/main/java/com/hlab/yanalyst/domain/channel/ChannelDiscoveryService.java b/src/main/java/com/hlab/yanalyst/domain/channel/ChannelDiscoveryService.java index e20e521..bd79ceb 100644 --- a/src/main/java/com/hlab/yanalyst/domain/channel/ChannelDiscoveryService.java +++ b/src/main/java/com/hlab/yanalyst/domain/channel/ChannelDiscoveryService.java @@ -62,7 +62,11 @@ public class ChannelDiscoveryService { cond.setOrder("viewCount"); // 떡상 발굴: 최신순이 아니라 조회수순 // 광범위 검색: 키워드 없이 인기 Shorts. 결과 빈약 시 검색 API 보강 필요(스펙 §4.2). YoutubeSearchPageDto page = youtubeSearchService.searchYoutubeVideos(cond); - if (page != null && page.getItems() != null) all.addAll(page.getItems()); + if (page != null && page.getItems() != null) { + // 발견 지역(검색 regionCode)으로 태깅 — 채널 국가가 아닌 '어느 지역 검색에서 떴나' + for (YoutubeSearchResultDto it : page.getItems()) it.setChannelCountry(region); + all.addAll(page.getItems()); + } searchedRegions.add(region); } catch (Exception e) { log.error("[Discovery] 지역 {} 검색 실패 — 건너뜀", region, e); @@ -83,8 +87,8 @@ public class ChannelDiscoveryService { RecommendedChannel rc = recommendedChannelRepository.findByChannelId(c.channelId()) .orElseGet(RecommendedChannel::new); if ("EXCLUDED".equals(rc.getStatus()) || "REGISTERED".equals(rc.getStatus())) continue; - // 기존 NEW 항목이면 더 높은 배율일 때만 갱신 - if (rc.getId() != null && rc.getRatio() != null && c.ratio() <= rc.getRatio()) continue; + // 기존 NEW 항목이면 더 낮은 배율일 때만 건너뜀(같거나 높으면 갱신 — 지역 등 메타 최신화) + if (rc.getId() != null && rc.getRatio() != null && c.ratio() < rc.getRatio()) continue; rc.setChannelId(c.channelId()); rc.setChannelTitle(c.channelTitle()); rc.setThumbnailUrl(c.thumbnailUrl()); diff --git a/src/main/resources/templates/recommend.html b/src/main/resources/templates/recommend.html index 7811819..ee199a0 100644 --- a/src/main/resources/templates/recommend.html +++ b/src/main/resources/templates/recommend.html @@ -15,6 +15,15 @@ + + +
+ + + + +
+
@@ -43,29 +52,39 @@ function esc(s){ return (s==null?'':String(s)).replace(/&/g,'&').replace(//g,'>'); } function fmt(n){ return (n==null)?'-':Number(n).toLocaleString(); } + let ALL_RECS = []; + let CUR_REGION = ''; // ''=전체 + async function load(){ - const grid=document.getElementById('grid'); - try { - const list=await api(API); - if(!list.length){ grid.innerHTML='
아직 발굴된 추천 채널이 없습니다. ‘지금 발굴’을 눌러보세요.
'; return; } - grid.innerHTML=list.map(c=> - '
' - + '' - + '
' - + '
'+esc(c.channelTitle)+'
' - + '
👤 '+fmt(c.subscriberCount)+'명 · '+esc(c.region||'')+'
' - + '
🔥 배율 '+(c.ratio?Number(c.ratio).toFixed(1):'-')+'x 👁 '+fmt(c.topVideoViewCount)+'
' - + '
'+esc(c.topVideoTitle||'')+'
' - + '
' - + '' - + '' - + '
' - + '
').join(''); - if(window.lucide) lucide.createIcons(); - } catch(e){ grid.innerHTML='
불러오기 실패: '+esc(e.message)+'
'; } + try { ALL_RECS = await api(API); renderGrid(); } + catch(e){ document.getElementById('grid').innerHTML='
불러오기 실패: '+esc(e.message)+'
'; } } - async function register(id){ try{ await api(API+'/'+id+'/register',{method:'POST'}); document.getElementById('rc-'+id)?.remove(); }catch(e){ alert('등록 실패: '+e.message); } } - async function exclude(id){ try{ await api(API+'/'+id+'/exclude',{method:'POST'}); document.getElementById('rc-'+id)?.remove(); }catch(e){ alert('제외 실패: '+e.message); } } + function setTab(btn){ + CUR_REGION = btn.dataset.region || ''; + document.querySelectorAll('.rc-tab').forEach(b=>{ const on=(b===btn); b.classList.toggle('btn-primary',on); b.classList.toggle('btn-secondary',!on); }); + renderGrid(); + } + function renderGrid(){ + const grid=document.getElementById('grid'); + const list = CUR_REGION ? ALL_RECS.filter(c=>c.region===CUR_REGION) : ALL_RECS; + if(!list.length){ grid.innerHTML='
'+(CUR_REGION ? (CUR_REGION+' 지역 추천 채널이 없습니다.') : '아직 발굴된 추천 채널이 없습니다. ‘지금 발굴’을 눌러보세요.')+'
'; return; } + grid.innerHTML=list.map(c=> + '
' + + '' + + '
' + + '
'+esc(c.channelTitle)+'
' + + '
👤 '+fmt(c.subscriberCount)+'명 · '+esc(c.region||'')+'
' + + '
🔥 배율 '+(c.ratio?Number(c.ratio).toFixed(1):'-')+'x 👁 '+fmt(c.topVideoViewCount)+'
' + + '
'+esc(c.topVideoTitle||'')+'
' + + '
' + + '' + + '' + + '
' + + '
').join(''); + if(window.lucide) lucide.createIcons(); + } + async function register(id){ try{ await api(API+'/'+id+'/register',{method:'POST'}); ALL_RECS=ALL_RECS.filter(c=>c.id!==id); renderGrid(); }catch(e){ alert('등록 실패: '+e.message); } } + async function exclude(id){ try{ await api(API+'/'+id+'/exclude',{method:'POST'}); ALL_RECS=ALL_RECS.filter(c=>c.id!==id); renderGrid(); }catch(e){ alert('제외 실패: '+e.message); } } function openVideoModal(el){ const vid = el.dataset.vid || ''; document.getElementById('modalTitle').textContent = el.dataset.title || '';