feat: Meilisearch 동기화 봇 추가

- 매일 새벽 4시 5분 자동 재색인 (Watchtower 업데이트 후)
- 봇 관리 페이지에서 수동 동기화 및 시작/정지 가능
- bots.js에 meilisearch 타입 봇 설정 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-01-23 11:14:17 +09:00
parent b8137935c2
commit 897bdc471c
3 changed files with 29 additions and 10 deletions

View file

@ -1,4 +1,11 @@
export default [ export default [
{
id: 'meilisearch-sync',
type: 'meilisearch',
name: 'Meilisearch 동기화',
cron: '5 4 * * *',
enabled: true,
},
{ {
id: 'youtube-fromis9', id: 'youtube-fromis9',
type: 'youtube', type: 'youtube',

View file

@ -1,6 +1,7 @@
import fp from 'fastify-plugin'; import fp from 'fastify-plugin';
import cron from 'node-cron'; import cron from 'node-cron';
import bots from '../config/bots.js'; import bots from '../config/bots.js';
import { syncAllSchedules } from '../services/meilisearch/index.js';
const REDIS_PREFIX = 'bot:status:'; const REDIS_PREFIX = 'bot:status:';
@ -42,6 +43,11 @@ async function schedulerPlugin(fastify, opts) {
return fastify.youtubeBot.syncNewVideos; return fastify.youtubeBot.syncNewVideos;
} else if (bot.type === 'x') { } else if (bot.type === 'x') {
return fastify.xBot.syncNewTweets; return fastify.xBot.syncNewTweets;
} else if (bot.type === 'meilisearch') {
return async () => {
const count = await syncAllSchedules(fastify.meilisearch, fastify.db);
return { addedCount: count, total: count };
};
} }
return null; return null;
} }
@ -108,13 +114,15 @@ async function schedulerPlugin(fastify, opts) {
await updateStatus(botId, { status: 'running' }); await updateStatus(botId, { status: 'running' });
fastify.log.info(`[${botId}] 스케줄 시작 (cron: ${bot.cron})`); fastify.log.info(`[${botId}] 스케줄 시작 (cron: ${bot.cron})`);
// 즉시 1회 실행 // 즉시 1회 실행 (meilisearch는 스케줄 시간에만 실행)
try { if (bot.type !== 'meilisearch') {
const result = await syncFn(bot); try {
const addedCount = await handleSyncResult(botId, result); const result = await syncFn(bot);
fastify.log.info(`[${botId}] 초기 동기화 완료: ${addedCount}개 추가`); const addedCount = await handleSyncResult(botId, result);
} catch (err) { fastify.log.info(`[${botId}] 초기 동기화 완료: ${addedCount}개 추가`);
fastify.log.error(`[${botId}] 초기 동기화 오류: ${err.message}`); } catch (err) {
fastify.log.error(`[${botId}] 초기 동기화 오류: ${err.message}`);
}
} }
} }
@ -175,5 +183,5 @@ async function schedulerPlugin(fastify, opts) {
export default fp(schedulerPlugin, { export default fp(schedulerPlugin, {
name: 'scheduler', name: 'scheduler',
dependencies: ['db', 'redis', 'youtubeBot', 'xBot'], dependencies: ['db', 'redis', 'meilisearch', 'youtubeBot', 'xBot'],
}); });

View file

@ -1,5 +1,6 @@
import bots from '../../config/bots.js'; import bots from '../../config/bots.js';
import { errorResponse } from '../../schemas/index.js'; import { errorResponse } from '../../schemas/index.js';
import { syncAllSchedules } from '../../services/meilisearch/index.js';
// 봇 관련 스키마 // 봇 관련 스키마
const botResponse = { const botResponse = {
@ -7,7 +8,7 @@ const botResponse = {
properties: { properties: {
id: { type: 'string' }, id: { type: 'string' },
name: { type: 'string' }, name: { type: 'string' },
type: { type: 'string', enum: ['youtube', 'x'] }, type: { type: 'string', enum: ['youtube', 'x', 'meilisearch'] },
status: { type: 'string', enum: ['running', 'stopped', 'error'] }, status: { type: 'string', enum: ['running', 'stopped', 'error'] },
last_check_at: { type: 'string', format: 'date-time' }, last_check_at: { type: 'string', format: 'date-time' },
last_added_count: { type: 'integer' }, last_added_count: { type: 'integer' },
@ -67,7 +68,7 @@ export default async function botsRoutes(fastify) {
result.push({ result.push({
id: bot.id, id: bot.id,
name: bot.channelName || bot.username || bot.id, name: bot.name || bot.channelName || bot.username || bot.id,
type: bot.type, type: bot.type,
status: status.status, status: status.status,
last_check_at: status.lastCheckAt, last_check_at: status.lastCheckAt,
@ -190,6 +191,9 @@ export default async function botsRoutes(fastify) {
result = await fastify.youtubeBot.syncAllVideos(bot); result = await fastify.youtubeBot.syncAllVideos(bot);
} else if (bot.type === 'x') { } else if (bot.type === 'x') {
result = await fastify.xBot.syncAllTweets(bot); result = await fastify.xBot.syncAllTweets(bot);
} else if (bot.type === 'meilisearch') {
const count = await syncAllSchedules(fastify.meilisearch, fastify.db);
result = { addedCount: count, total: count };
} else { } else {
return reply.code(400).send({ error: '지원하지 않는 봇 타입입니다.' }); return reply.code(400).send({ error: '지원하지 않는 봇 타입입니다.' });
} }