fix(scheduler): 봇 JSON 컬럼 파싱에 safeParse 가드 추가

깨진 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 <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-06-07 15:41:57 +09:00
parent e16d3f1230
commit 6e559a52b7

View file

@ -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,