From 0aac8055fc7553131b13a35e1dd3ccb2413cf40a Mon Sep 17 00:00:00 2001 From: GulSam00 Date: Tue, 21 Oct 2025 23:06:18 +0900 Subject: [PATCH 1/7] feat : update youtube crawl git action cron --- .github/workflows/update-ky-youtube.yml | 62 +++++++++++++++++++ .../crawling/src/crawling/crawlYoutube.ts | 5 ++ 2 files changed, 67 insertions(+) create mode 100644 .github/workflows/update-ky-youtube.yml diff --git a/.github/workflows/update-ky-youtube.yml b/.github/workflows/update-ky-youtube.yml new file mode 100644 index 0000000..181584d --- /dev/null +++ b/.github/workflows/update-ky-youtube.yml @@ -0,0 +1,62 @@ +name: Crawl Recent TJ Songs + +on: + schedule: + - cron: "0 14 * * *" # 한국 시간 23:00 실행 (UTC+9 → UTC 14:00) + workflow_dispatch: + +permissions: + contents: write # push 권한을 위해 필요 + +jobs: + run-npm-task: + runs-on: ubuntu-latest + + steps: + - name: Checkout branch + uses: actions/checkout@v4 + with: + ref: feat/songUpdate + persist-credentials: false # 수동 인증으로 푸시 제어 + + - name: Use Node.js 18 + uses: actions/setup-node@v4 + with: + node-version: "18" + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 9 + run_install: false + + - name: Install dependencies + working-directory: packages/crawling + run: pnpm install + + - name: Create .env file + working-directory: packages/crawling + run: | + echo "SUPABASE_URL=${{ secrets.SUPABASE_URL }}" >> .env + echo "SUPABASE_KEY=${{ secrets.SUPABASE_KEY }}" >> .env + + - name: run update script - packages/crawling/crawlYoutube.ts + working-directory: packages/crawling + run: pnpm run ky-youtube + + - name: Commit and push changes to feat/songUpdate branch + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + git checkout feat/songUpdate + + git add . + if git diff --cached --quiet; then + echo "✅ No changes to commit" + else + git commit -m "chore: update crawled TJ song data [skip ci]" + git push origin feat/songUpdate + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/packages/crawling/src/crawling/crawlYoutube.ts b/packages/crawling/src/crawling/crawlYoutube.ts index 3387d8e..feec202 100644 --- a/packages/crawling/src/crawling/crawlYoutube.ts +++ b/packages/crawling/src/crawling/crawlYoutube.ts @@ -65,6 +65,11 @@ console.log('getSongsKyNullDB : ', data.length); let index = 0; for (const song of data) { + // 테스트를 위해 100회 반복 후 종료시키기 + if (index >= 100) { + break; + } + const query = song.title + '-' + song.artist; if (failedSongs.has(query)) { From fe76af2fc01a23d2f261282844ab5d0980a17413 Mon Sep 17 00:00:00 2001 From: GulSam00 Date: Wed, 22 Oct 2025 21:33:47 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat=20:=20=EC=9A=B0=EB=B6=84=ED=88=AC=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=EC=9D=98=20=ED=98=B8?= =?UTF-8?q?=ED=99=98=EC=9D=84=20=EC=9C=84=ED=95=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/update-ky-youtube.yml | 2 +- packages/crawling/src/assets/crawlKYYoutubeFailedList.txt | 1 + packages/crawling/src/crawling/crawlRecentTJ.ts | 1 + packages/crawling/src/crawling/crawlYoutube.ts | 7 ++++++- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-ky-youtube.yml b/.github/workflows/update-ky-youtube.yml index 181584d..971759a 100644 --- a/.github/workflows/update-ky-youtube.yml +++ b/.github/workflows/update-ky-youtube.yml @@ -1,4 +1,4 @@ -name: Crawl Recent TJ Songs +name: Update ky by Youtube on: schedule: diff --git a/packages/crawling/src/assets/crawlKYYoutubeFailedList.txt b/packages/crawling/src/assets/crawlKYYoutubeFailedList.txt index f39258d..32cc8e9 100644 --- a/packages/crawling/src/assets/crawlKYYoutubeFailedList.txt +++ b/packages/crawling/src/assets/crawlKYYoutubeFailedList.txt @@ -25805,3 +25805,4 @@ Nightwalker-텐(TEN) My Love Mine All Mine-Mitski MONSTERS(최강야구OST)-이원석 Lose Control-Teddy Swims +Fruit Fly-Leah Dou,검정치마 diff --git a/packages/crawling/src/crawling/crawlRecentTJ.ts b/packages/crawling/src/crawling/crawlRecentTJ.ts index a173c7c..8df2fa3 100644 --- a/packages/crawling/src/crawling/crawlRecentTJ.ts +++ b/packages/crawling/src/crawling/crawlRecentTJ.ts @@ -15,6 +15,7 @@ dotenv.config(); // action 우분투 환경에서의 호환을 위해 추가 const browser = await puppeteer.launch({ + headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'], }); diff --git a/packages/crawling/src/crawling/crawlYoutube.ts b/packages/crawling/src/crawling/crawlYoutube.ts index feec202..c8ab3cd 100644 --- a/packages/crawling/src/crawling/crawlYoutube.ts +++ b/packages/crawling/src/crawling/crawlYoutube.ts @@ -15,7 +15,12 @@ import { isValidKYExistNumber } from './isValidKYExistNumber'; // youtube에서 KY 노래방 번호 크롤링 // crawlYoutubeValid에서 진행하는 실제 사이트 검증도 포함 -const browser = await puppeteer.launch(); +// action 우분투 환경에서의 호환을 위해 추가 +const browser = await puppeteer.launch({ + headless: true, + args: ['--no-sandbox', '--disable-setuid-sandbox'], +}); + const page = await browser.newPage(); const baseUrl = 'https://www.youtube.com/@KARAOKEKY/search'; From 55cff8815114408f78a380f5194087aa9e3438cb Mon Sep 17 00:00:00 2001 From: GulSam00 Date: Wed, 22 Oct 2025 21:41:50 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix=20:=20git=20aciton=20=EC=9A=B0=EB=B6=84?= =?UTF-8?q?=ED=88=AC=20=ED=98=B8=ED=99=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/crawling/src/crawling/crawlYoutube.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/crawling/src/crawling/crawlYoutube.ts b/packages/crawling/src/crawling/crawlYoutube.ts index c8ab3cd..7d2a286 100644 --- a/packages/crawling/src/crawling/crawlYoutube.ts +++ b/packages/crawling/src/crawling/crawlYoutube.ts @@ -18,7 +18,14 @@ import { isValidKYExistNumber } from './isValidKYExistNumber'; // action 우분투 환경에서의 호환을 위해 추가 const browser = await puppeteer.launch({ headless: true, - args: ['--no-sandbox', '--disable-setuid-sandbox'], + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', // 리눅스 메모리 제한 대응 + '--disable-gpu', + '--disable-infobars', + '--window-size=1920,1080', + ], }); const page = await browser.newPage(); From 1c7b8872e3ba780266be4101eab77cb1eee46923 Mon Sep 17 00:00:00 2001 From: GulSam00 Date: Wed, 22 Oct 2025 21:49:33 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix=20:=20single-process=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/crawling/src/crawling/crawlYoutube.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/crawling/src/crawling/crawlYoutube.ts b/packages/crawling/src/crawling/crawlYoutube.ts index 7d2a286..a1d013e 100644 --- a/packages/crawling/src/crawling/crawlYoutube.ts +++ b/packages/crawling/src/crawling/crawlYoutube.ts @@ -24,6 +24,7 @@ const browser = await puppeteer.launch({ '--disable-dev-shm-usage', // 리눅스 메모리 제한 대응 '--disable-gpu', '--disable-infobars', + '--single-process', '--window-size=1920,1080', ], }); From a7a918c67243d6632492839b2f1173229dfc97ac Mon Sep 17 00:00:00 2001 From: GulSam00 Date: Wed, 22 Oct 2025 22:17:09 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix=20:=20=EC=9A=B0=EB=B6=84=ED=88=AC?= =?UTF-8?q?=EC=9A=A9=20=ED=8C=8C=EC=9D=BC=20=EB=B6=84=EB=A6=AC,=20action?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/update-ky-youtube.yml | 6 + packages/crawling/package.json | 2 +- .../crawling/src/crawling/crawlYoutube.ts | 14 +- .../src/crawling/crawlYoutubeUbuntu.ts | 122 ++++++++++++++++++ 4 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 packages/crawling/src/crawling/crawlYoutubeUbuntu.ts diff --git a/.github/workflows/update-ky-youtube.yml b/.github/workflows/update-ky-youtube.yml index 971759a..f7bdbfd 100644 --- a/.github/workflows/update-ky-youtube.yml +++ b/.github/workflows/update-ky-youtube.yml @@ -34,6 +34,12 @@ jobs: working-directory: packages/crawling run: pnpm install + - name: Install puppeteer dependencies + working-directory: packages/crawling + run: | + sudo apt-get update + sudo apt-get install -y chromium + - name: Create .env file working-directory: packages/crawling run: | diff --git a/packages/crawling/package.json b/packages/crawling/package.json index 92b0417..51aa2e2 100644 --- a/packages/crawling/package.json +++ b/packages/crawling/package.json @@ -9,7 +9,7 @@ "scripts": { "ky-open": "tsx src/findKYByOpen.ts", "ky-youtube": "tsx src/crawling/crawlYoutube.ts", - "ky-valid": "tsx src/crawling/crawlYoutubeValid.ts", + "ky-youtube-ubuntu": "tsx src/crawling/crawlYoutubeUbuntu.ts", "ky-update": "pnpm run ky-youtube & pnpm run ky-valid", "trans": "tsx src/postTransDictionary.ts", "recent-tj": "tsx src/crawling/crawlRecentTJ.ts", diff --git a/packages/crawling/src/crawling/crawlYoutube.ts b/packages/crawling/src/crawling/crawlYoutube.ts index a1d013e..80eee5c 100644 --- a/packages/crawling/src/crawling/crawlYoutube.ts +++ b/packages/crawling/src/crawling/crawlYoutube.ts @@ -18,15 +18,6 @@ import { isValidKYExistNumber } from './isValidKYExistNumber'; // action 우분투 환경에서의 호환을 위해 추가 const browser = await puppeteer.launch({ headless: true, - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-dev-shm-usage', // 리눅스 메모리 제한 대응 - '--disable-gpu', - '--disable-infobars', - '--single-process', - '--window-size=1920,1080', - ], }); const page = await browser.newPage(); @@ -75,6 +66,7 @@ const data = await getSongsKyNullDB(); const failedSongs = loadCrawlYoutubeFailedKYSongs(); console.log('getSongsKyNullDB : ', data.length); +console.log(failedSongs.size); let index = 0; for (const song of data) { @@ -86,11 +78,10 @@ for (const song of data) { const query = song.title + '-' + song.artist; if (failedSongs.has(query)) { + console.log('failedSongs has : ', query); continue; } - console.log(song.title, ' - ', song.artist); - let resultKyNum = null; try { resultKyNum = await scrapeSongNumber(query); @@ -115,6 +106,7 @@ for (const song of data) { } else saveCrawlYoutubeFailedKYSongs(song.title, song.artist); index++; + console.log(query); console.log('scrapeSongNumber : ', index); } diff --git a/packages/crawling/src/crawling/crawlYoutubeUbuntu.ts b/packages/crawling/src/crawling/crawlYoutubeUbuntu.ts new file mode 100644 index 0000000..858e703 --- /dev/null +++ b/packages/crawling/src/crawling/crawlYoutubeUbuntu.ts @@ -0,0 +1,122 @@ +import * as cheerio from 'cheerio'; +import puppeteer from 'puppeteer'; + +import { getSongsKyNullDB } from '@/supabase/getDB'; +import { updateSongsKyDB } from '@/supabase/updateDB'; +import { Song } from '@/types'; +import { + loadCrawlYoutubeFailedKYSongs, + saveCrawlYoutubeFailedKYSongs, + updateDataLog, +} from '@/utils/logData'; + +import { isValidKYExistNumber } from './isValidKYExistNumber'; + +// youtube에서 KY 노래방 번호 크롤링 +// crawlYoutubeValid에서 진행하는 실제 사이트 검증도 포함 + +// action 우분투 환경에서의 호환을 위해 추가 +const browser = await puppeteer.launch({ + headless: true, + executablePath: '/usr/bin/chromium-browser', // 또는 "/usr/bin/chromium" + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', // 리눅스 메모리 제한 대응 + '--disable-gpu', + '--disable-infobars', + '--single-process', + '--window-size=1920,1080', + ], +}); + +const page = await browser.newPage(); + +const baseUrl = 'https://www.youtube.com/@KARAOKEKY/search'; + +const scrapeSongNumber = async (query: string) => { + const searchUrl = `${baseUrl}?query=${encodeURIComponent(query)}`; + + // page.goto의 waitUntil 문제였음! + await page.goto(searchUrl, { + waitUntil: 'networkidle2', + timeout: 0, + }); + + const html = await page.content(); + const $ = cheerio.load(html); + + // id contents 의 첫번째 ytd-item-section-renderer 찾기 + // const firstItem = $("#contents ytd-item-section-renderer").first(); + + const firstItem = $('ytd-video-renderer').first(); + + // yt-formatted-string 찾기 + const title = firstItem.find('yt-formatted-string').first().text().trim(); + + const karaokeNumber = extractKaraokeNumber(title); + + return karaokeNumber; +}; + +const extractKaraokeNumber = (title: string) => { + // KY. 찾고 ) 가 올때까지 찾기 + const matchResult = title.match(/KY\.\s*(\d{2,5})\)/); + const karaokeNumber = matchResult ? matchResult[1] : null; + return karaokeNumber; +}; + +const updateData = async (data: Song) => { + const result = await updateSongsKyDB(data); + updateDataLog(result.success, 'crawlYoutubeSuccess.txt'); + updateDataLog(result.failed, 'crawlYoutubeFailed.txt'); +}; + +const data = await getSongsKyNullDB(); +const failedSongs = loadCrawlYoutubeFailedKYSongs(); + +console.log('getSongsKyNullDB : ', data.length); +let index = 0; + +for (const song of data) { + // 테스트를 위해 100회 반복 후 종료시키기 + if (index >= 100) { + break; + } + + const query = song.title + '-' + song.artist; + + if (failedSongs.has(query)) { + continue; + } + + console.log(song.title, ' - ', song.artist); + + let resultKyNum = null; + try { + resultKyNum = await scrapeSongNumber(query); + } catch (error) { + continue; + } + + if (resultKyNum) { + let isValid = true; + try { + isValid = await isValidKYExistNumber(page, resultKyNum, song.title, song.artist); + } catch (error) { + continue; + } + + if (!isValid) { + saveCrawlYoutubeFailedKYSongs(song.title, song.artist); + continue; + } else { + await updateData({ ...song, num_ky: resultKyNum }); + } + } else saveCrawlYoutubeFailedKYSongs(song.title, song.artist); + + index++; + console.log('scrapeSongNumber : ', index); +} + +browser.close(); From ac29aafbe758a1cc89ad0efe2c1da7a1faef5ce6 Mon Sep 17 00:00:00 2001 From: GulSam00 Date: Sun, 26 Oct 2025 00:35:26 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix=20:=20recent-tj=EC=99=80=20=EB=8F=99?= =?UTF-8?q?=EC=9D=BC=ED=95=98=EA=B2=8C=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/update-ky-youtube.yml | 6 ----- .../src/crawling/crawlYoutubeUbuntu.ts | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/update-ky-youtube.yml b/.github/workflows/update-ky-youtube.yml index f7bdbfd..971759a 100644 --- a/.github/workflows/update-ky-youtube.yml +++ b/.github/workflows/update-ky-youtube.yml @@ -34,12 +34,6 @@ jobs: working-directory: packages/crawling run: pnpm install - - name: Install puppeteer dependencies - working-directory: packages/crawling - run: | - sudo apt-get update - sudo apt-get install -y chromium - - name: Create .env file working-directory: packages/crawling run: | diff --git a/packages/crawling/src/crawling/crawlYoutubeUbuntu.ts b/packages/crawling/src/crawling/crawlYoutubeUbuntu.ts index 858e703..4e7c223 100644 --- a/packages/crawling/src/crawling/crawlYoutubeUbuntu.ts +++ b/packages/crawling/src/crawling/crawlYoutubeUbuntu.ts @@ -16,18 +16,23 @@ import { isValidKYExistNumber } from './isValidKYExistNumber'; // crawlYoutubeValid에서 진행하는 실제 사이트 검증도 포함 // action 우분투 환경에서의 호환을 위해 추가 +// const browser = await puppeteer.launch({ +// headless: true, +// executablePath: '/usr/bin/chromium-browser', // 또는 "/usr/bin/chromium" +// args: [ +// '--no-sandbox', +// '--disable-setuid-sandbox', +// '--disable-dev-shm-usage', // 리눅스 메모리 제한 대응 +// '--disable-gpu', +// '--disable-infobars', +// '--single-process', +// '--window-size=1920,1080', +// ], +// }); + const browser = await puppeteer.launch({ headless: true, - executablePath: '/usr/bin/chromium-browser', // 또는 "/usr/bin/chromium" - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-dev-shm-usage', // 리눅스 메모리 제한 대응 - '--disable-gpu', - '--disable-infobars', - '--single-process', - '--window-size=1920,1080', - ], + args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); From ba9b4a82a228b9f31acd604d1738d1c8df17eb9d Mon Sep 17 00:00:00 2001 From: GulSam00 Date: Sun, 26 Oct 2025 00:59:53 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat=20:=20=EB=82=99=EA=B4=80=EC=A0=81=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=9D=B4=EC=A0=90=20?= =?UTF-8?q?=ED=8F=AC=EA=B8=B0.=20isPending=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/hooks/useSearchSong.ts | 28 ++++++++++++++++++++---- apps/web/src/queries/tosingSongQuery.ts | 29 ------------------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/apps/web/src/hooks/useSearchSong.ts b/apps/web/src/hooks/useSearchSong.ts index 9613556..386f895 100644 --- a/apps/web/src/hooks/useSearchSong.ts +++ b/apps/web/src/hooks/useSearchSong.ts @@ -25,10 +25,10 @@ export default function useSearchSong() { const [saveModalType, setSaveModalType] = useState(''); const [selectedSaveSong, setSelectedSaveSong] = useState(null); // const { data: searchResults, isLoading } = useSearchSongQuery(query, searchType, isAuthenticated); - const { mutate: toggleToSing } = useToggleToSingMutation(); - const { mutate: toggleLike } = useToggleLikeMutation(); - const { mutate: postSong } = useSaveMutation(); - const { mutate: moveSong } = useMoveSaveSongMutation(); + const { mutate: toggleToSing, isPending: isToggleToSingPending } = useToggleToSingMutation(); + const { mutate: toggleLike, isPending: isToggleLikePending } = useToggleLikeMutation(); + const { mutate: postSong, isPending: isPostSongPending } = useSaveMutation(); + const { mutate: moveSong, isPending: isMoveSongPending } = useMoveSaveSongMutation(); const { data: searchResults, @@ -56,6 +56,11 @@ export default function useSearchSong() { toast.error('로그인이 필요해요.'); return; } + + if (isToggleToSingPending) { + toast.error('요청 중입니다. 잠시 후 다시 시도해주세요.'); + return; + } toggleToSing({ songId, method, query, searchType }); }; @@ -64,6 +69,11 @@ export default function useSearchSong() { toast.error('로그인이 필요해요.'); return; } + + if (isToggleLikePending) { + toast.error('요청 중입니다. 잠시 후 다시 시도해주세요.'); + return; + } toggleLike({ songId, method, query, searchType }); }; @@ -72,15 +82,25 @@ export default function useSearchSong() { toast.error('로그인이 필요해요.'); return; } + setSelectedSaveSong(song); setSaveModalType(method === 'POST' ? 'POST' : 'PATCH'); }; const postSaveSong = async (songId: string, folderName: string) => { + if (isPostSongPending) { + toast.error('요청 중입니다. 잠시 후 다시 시도해주세요.'); + return; + } postSong({ songId, folderName, query, searchType }); }; const patchSaveSong = async (songId: string, folderId: string) => { + if (isMoveSongPending) { + toast.error('요청 중입니다. 잠시 후 다시 시도해주세요.'); + return; + } + moveSong({ songIdArray: [songId], folderId }); }; diff --git a/apps/web/src/queries/tosingSongQuery.ts b/apps/web/src/queries/tosingSongQuery.ts index 0dc054f..fe6c70f 100644 --- a/apps/web/src/queries/tosingSongQuery.ts +++ b/apps/web/src/queries/tosingSongQuery.ts @@ -102,35 +102,6 @@ export function useDeleteToSingSongMutation() { }); } -// 여러 곡 부를 노래 삭제 - 미사용? - -// export function useDeleteToSingSongArrayMutation() { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: (songIds: string[]) => deleteToSingSongArray({ songIds }), -// onMutate: async (songIds: string[]) => { -// queryClient.cancelQueries({ queryKey: ['toSingSong'] }); -// const prev = queryClient.getQueryData(['toSingSong']); -// queryClient.setQueryData(['toSingSong'], (old: ToSingSong[]) => -// old.filter(song => !songIds.includes(song.songs.id)), -// ); -// return { prev }; -// }, -// onError: (error, variables, context) => { -// console.error('error', error); -// alert(error.message ?? 'DELETE 실패'); -// queryClient.setQueryData(['toSingSong'], context?.prev); -// }, -// onSettled: () => { -// queryClient.invalidateQueries({ queryKey: ['toSingSong'] }); -// queryClient.invalidateQueries({ queryKey: ['likeSong'] }); -// queryClient.invalidateQueries({ queryKey: ['saveSongFolder'] }); -// queryClient.invalidateQueries({ queryKey: ['recentSingLog'] }); -// }, -// }); -// } - // 🎵 부를 노래 순서 변경 export function usePatchToSingSongMutation() { const queryClient = useQueryClient();