From 6e559a52b715af0a9e0f0614bb9e478e968e2840 Mon Sep 17 00:00:00 2001 From: caadiq Date: Sun, 7 Jun 2026 15:41:57 +0900 Subject: [PATCH] =?UTF-8?q?fix(scheduler):=20=EB=B4=87=20JSON=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=ED=8C=8C=EC=8B=B1=EC=97=90=20safeParse=20=EA=B0=80?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 깨진 JSON 값 하나가 getAllBots 전체를 throw시켜 startAll(전체 봇 기동)을 막던 위험 제거. weekly_schedule_config/title_filters/default_member_ids/ auto_schedule_config/text_filters를 safeParse(fallback)로 처리. Co-Authored-By: Claude Opus 4.7 --- backend/src/plugins/scheduler.js | 43 +++++++++++++------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/backend/src/plugins/scheduler.js b/backend/src/plugins/scheduler.js index 96e01cd..3a03e2e 100644 --- a/backend/src/plugins/scheduler.js +++ b/backend/src/plugins/scheduler.js @@ -20,6 +20,19 @@ function isActiveMonth(bot) { return months.includes(monthKST()); } +/** + * DB JSON 컬럼 안전 파싱. 깨진 값 하나가 getAllBots 전체를 막지 않도록 fallback 반환. + */ +function safeParse(value, fallback = null) { + if (value == null) return fallback; + if (typeof value !== 'string') return value; + try { + return JSON.parse(value); + } catch { + return fallback; + } +} + /** * 에러 객체에서 활동 로그용 details 구성 * - err.cause(Node fetch failed의 진짜 원인 등), err.code를 함께 포함 @@ -47,11 +60,7 @@ async function schedulerPlugin(fastify, opts) { 'SELECT * FROM bot_youtube' ); return rows.map(row => { - const weekly = row.weekly_schedule_config - ? (typeof row.weekly_schedule_config === 'string' - ? JSON.parse(row.weekly_schedule_config) - : row.weekly_schedule_config) - : null; + const weekly = safeParse(row.weekly_schedule_config, null); // weekly 모드면 시작 시각에만 트리거, 아니면 cron_interval 분 주기 let cronExpr; @@ -72,23 +81,11 @@ async function schedulerPlugin(fastify, opts) { bannerUrl: row.banner_url, cron: cronExpr, enabled: row.enabled === 1, - titleFilters: row.title_filters - ? (typeof row.title_filters === 'string' - ? JSON.parse(row.title_filters) - : row.title_filters) - : [], - defaultMemberIds: row.default_member_ids - ? (typeof row.default_member_ids === 'string' - ? JSON.parse(row.default_member_ids) - : row.default_member_ids) - : [], + titleFilters: safeParse(row.title_filters, []), + defaultMemberIds: safeParse(row.default_member_ids, []), extractMembersFromDesc: row.extract_members_from_desc === 1, extractMembersFromTitle: row.extract_members_from_title === 1, - autoScheduleNext: row.auto_schedule_config - ? (typeof row.auto_schedule_config === 'string' - ? JSON.parse(row.auto_schedule_config) - : row.auto_schedule_config) - : null, + autoScheduleNext: safeParse(row.auto_schedule_config, null), weeklySchedule: weekly, }; }); @@ -111,11 +108,7 @@ async function schedulerPlugin(fastify, opts) { nitterUrl: process.env.NITTER_URL || 'http://nitter:8080', cron: `*/${row.cron_interval} * * * *`, enabled: row.enabled === 1, - textFilters: row.text_filters - ? (typeof row.text_filters === 'string' - ? JSON.parse(row.text_filters) - : row.text_filters) - : [], + textFilters: safeParse(row.text_filters, []), includeRetweets: row.include_retweets === 1, extractYoutube: row.extract_youtube === 1, excludeManagedChannels: row.exclude_managed_channels === 1,