From 2fec6c552daa11645e60ea2e1fa2d14ee59f3788 Mon Sep 17 00:00:00 2001 From: caadiq Date: Sun, 25 Jan 2026 11:26:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=B4=87=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=8B=9C=20Meilisearch=20=EC=8B=A4?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - syncScheduleById 함수 추가: 개별 일정 동기화 - YouTube 봇: 영상 추가 시 Meilisearch 동기화 - X 봇: 트윗/유튜브 링크 추가 시 Meilisearch 동기화 - description 컬럼 제거 (schedules 테이블에 없음) Co-Authored-By: Claude Opus 4.5 --- backend/src/services/meilisearch/index.js | 60 +++++++++++++++++++++-- backend/src/services/x/index.js | 13 ++++- backend/src/services/youtube/index.js | 5 ++ 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/backend/src/services/meilisearch/index.js b/backend/src/services/meilisearch/index.js index 308c10b..f3ad3ca 100644 --- a/backend/src/services/meilisearch/index.js +++ b/backend/src/services/meilisearch/index.js @@ -178,7 +178,7 @@ function formatScheduleResponse(hit) { } /** - * 일정 추가/업데이트 + * 일정 추가/업데이트 (데이터 직접 전달) */ export async function addOrUpdateSchedule(meilisearch, schedule) { try { @@ -187,7 +187,6 @@ export async function addOrUpdateSchedule(meilisearch, schedule) { const document = { id: schedule.id, title: schedule.title, - description: schedule.description || '', date: schedule.date, time: schedule.time || '', category_id: schedule.category_id, @@ -204,6 +203,59 @@ export async function addOrUpdateSchedule(meilisearch, schedule) { } } +/** + * 일정 ID로 DB에서 조회 후 Meilisearch에 동기화 + */ +export async function syncScheduleById(meilisearch, db, scheduleId) { + try { + const [rows] = await db.query(` + SELECT + s.id, + s.title, + s.date, + s.time, + s.category_id, + c.name as category_name, + c.color as category_color, + sy.channel_name as source_name, + GROUP_CONCAT(DISTINCT m.name ORDER BY m.id SEPARATOR ',') as member_names + FROM schedules s + LEFT JOIN schedule_categories c ON s.category_id = c.id + LEFT JOIN schedule_youtube sy ON s.id = sy.schedule_id + LEFT JOIN schedule_members sm ON s.id = sm.schedule_id + LEFT JOIN members m ON sm.member_id = m.id AND m.is_former = 0 + WHERE s.id = ? + GROUP BY s.id + `, [scheduleId]); + + if (rows.length === 0) { + logger.warn(`일정을 찾을 수 없음: ${scheduleId}`); + return false; + } + + const s = rows[0]; + const document = { + id: s.id, + title: s.title, + date: s.date instanceof Date ? s.date.toISOString().split('T')[0] : s.date, + time: s.time || '', + category_id: s.category_id, + category_name: s.category_name || '', + category_color: s.category_color || '', + source_name: s.source_name || '', + member_names: s.member_names || '', + }; + + const index = meilisearch.index(INDEX_NAME); + await index.addDocuments([document]); + logger.info(`일정 동기화: ${scheduleId}`); + return true; + } catch (err) { + logger.error(`일정 동기화 오류 (${scheduleId}): ${err.message}`); + return false; + } +} + /** * 일정 삭제 */ @@ -233,7 +285,6 @@ export async function syncAllSchedules(meilisearch, db) { SELECT s.id, s.title, - s.description, s.date, s.time, s.category_id, @@ -255,7 +306,6 @@ export async function syncAllSchedules(meilisearch, db) { const documents = schedules.map(s => ({ id: s.id, title: s.title, - description: s.description || '', date: s.date instanceof Date ? s.date.toISOString().split('T')[0] : s.date, time: s.time || '', category_id: s.category_id, @@ -310,7 +360,7 @@ async function recreateIndex(meilisearch) { // 설정 복원 await index.updateSearchableAttributes([ - 'title', 'member_names', 'description', 'source_name', 'category_name', + 'title', 'member_names', 'source_name', 'category_name', ]); await index.updateFilterableAttributes(['category_id', 'date']); await index.updateSortableAttributes(['date', 'time']); diff --git a/backend/src/services/x/index.js b/backend/src/services/x/index.js index cbc6cbe..a4a4dce 100644 --- a/backend/src/services/x/index.js +++ b/backend/src/services/x/index.js @@ -4,6 +4,7 @@ import { fetchVideoInfo } from '../youtube/api.js'; import { formatDate, formatTime, nowKST } from '../../utils/date.js'; import bots from '../../config/bots.js'; import { withTransaction } from '../../utils/transaction.js'; +import { syncScheduleById } from '../meilisearch/index.js'; const X_CATEGORY_ID = 3; const YOUTUBE_CATEGORY_ID = 2; @@ -141,8 +142,12 @@ async function xBotPlugin(fastify, opts) { // 관리 중인 채널이면 스킵 if (managedChannels.includes(video.channelId)) continue; - const saved = await saveYoutubeFromTweet(video); - if (saved) addedCount++; + const scheduleId = await saveYoutubeFromTweet(video); + if (scheduleId) { + // Meilisearch 동기화 + await syncScheduleById(fastify.meilisearch, fastify.db, scheduleId); + addedCount++; + } } catch (err) { fastify.log.error(`YouTube 영상 처리 오류 (${videoId}): ${err.message}`); } @@ -166,6 +171,8 @@ async function xBotPlugin(fastify, opts) { for (const tweet of tweets) { const scheduleId = await saveTweet(tweet); if (scheduleId) { + // Meilisearch 동기화 + await syncScheduleById(fastify.meilisearch, fastify.db, scheduleId); addedCount++; // YouTube 링크 처리 ytAddedCount += await processYoutubeLinks(tweet); @@ -187,6 +194,8 @@ async function xBotPlugin(fastify, opts) { for (const tweet of tweets) { const scheduleId = await saveTweet(tweet); if (scheduleId) { + // Meilisearch 동기화 + await syncScheduleById(fastify.meilisearch, fastify.db, scheduleId); addedCount++; ytAddedCount += await processYoutubeLinks(tweet); } diff --git a/backend/src/services/youtube/index.js b/backend/src/services/youtube/index.js index 28d9696..614df21 100644 --- a/backend/src/services/youtube/index.js +++ b/backend/src/services/youtube/index.js @@ -3,6 +3,7 @@ import { fetchRecentVideos, fetchAllVideos, getUploadsPlaylistId } from './api.j import bots from '../../config/bots.js'; import { CATEGORY_IDS } from '../../config/index.js'; import { withTransaction } from '../../utils/transaction.js'; +import { syncScheduleById } from '../meilisearch/index.js'; const YOUTUBE_CATEGORY_ID = CATEGORY_IDS.YOUTUBE; const PLAYLIST_CACHE_PREFIX = 'yt_uploads:'; @@ -126,6 +127,8 @@ async function youtubeBotPlugin(fastify, opts) { for (const video of videos) { const scheduleId = await saveVideo(video, bot); if (scheduleId) { + // Meilisearch 동기화 + await syncScheduleById(fastify.meilisearch, fastify.db, scheduleId); addedCount++; } } @@ -144,6 +147,8 @@ async function youtubeBotPlugin(fastify, opts) { for (const video of videos) { const scheduleId = await saveVideo(video, bot); if (scheduleId) { + // Meilisearch 동기화 + await syncScheduleById(fastify.meilisearch, fastify.db, scheduleId); addedCount++; } }