import type { RealEstateArticle } from "../generated/prisma"; import type { NaverRealEstate } from "./naver.service"; import { prisma } from "../../lib/prisma"; import pLimit from "p-limit"; export class RankingService { private naver: NaverRealEstate; constructor(naver: NaverRealEstate) { this.naver = naver; } async updateRanking(articles: RealEstateArticle[], checkDate: Date) { const { token, cookie } = await this.naver.getRankingToken(); for (let i = 0; i < articles.length; i += 20) { const batch = articles.slice(i, i + 20); console.log(`πŸš€ ${i + 1} ~ ${i + batch.length}번 λ§€λ¬Ό 처리 μ‹œμž‘`); const tasks = batch.map(async (article) => { try { // cortarNo, lgeo 확인 if (!article.cortarNo || !article.lgeo) { const { cortarNo, lgeo } = await this.updateCortarNoAndLgeo( article ); article.cortarNo = cortarNo; article.lgeo = lgeo; } // λž­ν‚Ή 쑰회 const ranking = await this.naver.getRanking(article, token, cookie); // DB μ—…λ°μ΄νŠΈ await prisma.realEstateArticle.update({ where: { id: article.id }, data: { ranking, rankCheckDate: checkDate.toISOString(), }, }); console.log(`βœ… ${article.articleNumber} - λž­ν‚Ή μ™„λ£Œ : ${ranking}`); } catch (err) { console.error(`❌ 였λ₯˜: ${article.articleNumber}`, err); } }); // **이 10개(λ˜λŠ” λ§ˆμ§€λ§‰ batch)κ°€ λͺ¨λ‘ 끝날 λ•ŒκΉŒμ§€ κΈ°λ‹€λ¦Ό** await Promise.allSettled(tasks); // λ‹€μŒ λ°°μΉ˜κ°€ λ‚¨μ•„μžˆλ‹€λ©΄ λ”œλ ˆμ΄ if (i + 20 < articles.length) { console.log(`⏸ 10개 처리 μ™„λ£Œ β†’ 10초 νœ΄μ‹`); await Bun.sleep(5000); } } console.log("πŸŽ‰ 전체 λž­ν‚Ή μ—…λ°μ΄νŠΈ μ™„λ£Œ!"); await Bun.sleep(100); } async updateCortarNoAndLgeo(article: RealEstateArticle): Promise<{ cortarNo: string | null; lgeo: string | null; }> { try { const { cortarNo, lgeo } = await this.naver.getCortarNoAndLgeo(article); await prisma.realEstateArticle.update({ where: { id: article.id }, data: { cortarNo: cortarNo ?? null, lgeo: lgeo ?? null }, }); return { cortarNo: cortarNo ?? null, lgeo: lgeo ?? null }; } catch (error) { console.error(`❌ 였λ₯˜ λ°œμƒ:`, error); return { cortarNo: null, lgeo: null }; } } }