fromis_9/docs/improvement-plan.md

7.8 KiB

fromis_9 개선 작업 계획서

코드 리뷰(2026-06) 결과를 바탕으로 한 단계별 개선 계획. 범위는 fromis_9 프론트+백엔드(Flutter 앱 제외).

원칙

  • 작고 확실한 것 → 성능 → 캐시 → (보류) 리팩터링 순서. 한 번에 다 건드리지 않음.
  • 웨이브 단위로 커밋. 각 항목은 변경 + 검증을 짝으로 진행.
  • 백엔드 변경 후 docker restart fromis9-backend로 부팅/동작 확인, 프론트는 HMR 로그로 컴파일 확인.
  • 회귀 위험이 보이면 멈추고 보고.

웨이브 1 — 버그·보안 핫픽스 (작고 안전)

리스크 낮음, 코드 소량. 한 커밋으로 묶음.

1-1. X봇 생성 ReferenceError (확정 버그)

  • 파일: backend/src/routes/admin/x-bots.js
  • 문제: exclude_managed_channels가 POST body 구조분해(180-188)·스키마(160-171)에 없는데 INSERT(209)에서 사용 → ES strict mode라 X봇 생성 시 ReferenceError 크래시.
  • 변경: body 스키마에 exclude_managed_channels 추가 + 구조분해에 exclude_managed_channels = true 기본값 추가(컬럼 기본값 1과 일치).
  • 검증: 백엔드 재시작 후 X봇 추가 1건 → 정상 생성 확인.

1-2. 죽은/깨진 API 제거

  • 파일: frontend/src/api/admin/schedules.js, frontend/src/api/public/schedules.js, frontend/src/hooks/common/useMemberData.js(+ hooks/common/index.js), frontend/src/api/client.js(JSDoc)
  • 문제: createSchedule/updateSchedule(백엔드 라우트 없음, 미사용), useMemberDetailgetMemberByName(미정의, 호출 시 throw).
  • 변경: 미사용 함수/훅 삭제. export 정리.
  • 검증: grep으로 참조 0 확인 + 프론트 빌드(HMR) 에러 없음.

1-3. /api/bots 정보 노출

  • 파일: backend/src/app.js(또는 해당 라우트), 프론트 api/admin/bots.js
  • 선행 확인: 프론트가 /api/botsfetchAuthApi(토큰)로 부르는지 fetchApi로 부르는지 확인.
    • 토큰으로 부르면 → 라우트에 preHandler: [fastify.authenticate] 추가.
    • 토큰 없이 부르면 → 인증 추가 시 관리자 페이지가 깨지므로, 우선 응답에서 errorMessage 등 민감필드만 제거.
  • 검증: 관리자 봇 페이지 정상 로드 + 무인증 호출 시 민감정보 미노출 확인.

1-4. 방어 코드 2종 (덤)

  • 스케줄러 JSON.parse 가드backend/src/plugins/scheduler.js: weekly_schedule_config/title_filters/default_member_ids/auto_schedule_config/text_filters 파싱을 safeParse(value, fallback)로 감싸 깨진 값 하나가 startAll 전체를 막지 않도록.
  • YouTube fetch 타임아웃backend/src/services/youtube/api.js: og.jsAbortController + setTimeout 패턴 적용(업스트림 행 방지).
  • 검증: 백엔드 재시작 후 봇 정상 기동, YouTube 동기화 로그 정상.

커밋: fix: 웨이브1 버그·보안 핫픽스 (X봇 크래시/죽은 API/정보노출/방어코드) — 항목별로 나눠 커밋해도 됨.


웨이브 2 — 체감 성능 (가성비 최고)

모바일 일정 페이지의 "필터/스크롤마다 전체 리렌더 + 날짜 스트립 재스크롤" 제거.

2-1. selectedDate 객체 안정화

  • 파일: frontend/src/pages/mobile/schedule/Schedule.jsx:40 (PC도 동일 패턴 있으면 함께)
  • 변경: const selectedDate = storedSelectedDate || new Date()useMemo(() => storedSelectedDate || getTodayKST_Date(), [storedSelectedDate]) 또는 스토어 초기값을 오늘로 시드. (다수 useMemo/effect의 의존성이라 매 렌더 재실행 원인)
  • 검증: 렌더 시 daysInMonth/selectedDateSchedules 재계산·scrollIntoView 반복이 사라지는지(React DevTools 또는 동작 체감).

2-2. 카드 onClick memo 복구

  • 파일: 모바일 Schedule.jsx(887/907/925), PC Schedule.jsx(589/593/621/625/645), 관련 카드 컴포넌트
  • 변경: 인라인 onClick={() => navigate(...)} 제거. 카드가 onClick(schedule)을 호출하도록 시그니처 통일하고, 페이지에서 useCallback으로 안정적 핸들러 1개 전달. PC handleScheduleClickuseCallback.
  • 검증: 필터 토글·스크롤 시 카드들이 불필요하게 리렌더되지 않는지 확인(엔트런스 애니메이션 재생 안 됨으로 체감).

커밋: perf(mobile-schedule): selectedDate 안정화 + 카드 onClick memo 복구


웨이브 3 — 캐시 (신중히, 단독)

3-1. 월별 일정 캐시 + 무효화

  • 파일: backend/src/services/schedule.js(getMonthlySchedules), backend/src/routes/schedules/index.js, backend/src/utils/cache.js
  • 변경: getMonthlySchedulesgetOrSet(redis, cacheKeys.scheduleMonthly(y,m), fn, TTL.MEDIUM)로 래핑.
  • 핵심: 일정 생성/수정/삭제 경로 전부(events/variety/concert/x/youtube/bot sync/삭제)에서 해당 월 캐시 무효화(invalidatePattern(redis,'schedule:monthly:*')) 호출 누락 없이 추가. ← 이게 빠지면 stale 발생.
  • 검증: 일정 추가/수정/삭제 후 캘린더 즉시 반영 확인. 캐시 적중 시 응답 빨라지는지 확인.

커밋: perf(schedule): 월별 일정 Redis 캐시 + 쓰기 시 무효화


보류 (지금 안 함 — 버그/보안 아님, 유지보수 부채)

당장 깨지는 문제가 아니므로 해당 영역을 어차피 만질 때 곁들여 처리 권장. "같은 수정을 두 군데 반복하게 돼 짜증날 때"가 착수 신호.

A. 중복 제거 (비용 가장 큼)

  • 일정 타입별 CRUD 공통화 — events/variety/concert/x/youtube가 "schedules insert + members + 이미지 저장 + meili 동기화"를 각자 복붙. 공통 scheduleCore(createBase/updateBase/setMembers/saveImage)로 추출. (미완성 generic /admin/schedules 경로 정리도 함께)
  • 봇 라우트 3종(festival/x/youtube) CRUD 팩토리화 — formatRow·GET·POST·UPDATE빌더·DELETE가 거의 동일 → createBotRoutes() 팩토리
  • PC/모바일 카드 공통 파생 훅ScheduleCard/BirthdayCard/DebutCard의 날짜·카테고리·멤버 계산을 useScheduleCardData(schedule)로, JSX만 플랫폼별 유지
  • 모바일 ScheduleDetail.jsx(1304줄) → sections/ 분리 (PC sections/ 구조와 일치시켜 이중 관리 해소)
  • getActiveMemberCount 중복 통합(4곳), 멤버 API 모듈 이중화(admin/members.js vs public/members.js) 정리

B. 거대 컴포넌트 분리

  • admin Schedules.jsx, 봇 다이얼로그(YouTubeBotDialog 912줄 등) — 데이터/UI 관심사 분리

C. 일관성·정리

  • React Query 캐시 키 팩토리 중앙화(['members'] vs ["members"] 등) + 공통 데이터 훅(useScheduleData 등) 실제 사용
  • 에러 핸들링 컨벤션 통일reply.code().send({error}) 직접 vs utils/error.js 헬퍼 혼용 → 헬퍼로 통일
  • 타임존(KST) 처리 통일getTodayKST vs 로컬 new Date() 혼용
  • 미완성 legacy ScheduleForm 제거 (죽은 generic 경로 관련)
  • 매직넘버/Nitter URL config로 이전, JSON 컬럼 파싱 parseJsonColumn 유틸화
  • React key에 index 사용 지양(특히 PC 검색 추천), getScheduleDetails.* → 필요 컬럼만 명시

D. 안전·견고성 (소규모)

  • 멤버 PUT 트랜잭션화, saveTweet post_id UNIQUE + ER_DUP_ENTRY catch (스키마 변경 동반)
  • suggestions 수동 fetchAbortController(언마운트 후 setState 방지)
  • JWT 수명 단축, CORS 불필요한 credentials:true 제거, Gemini 키 쿼리스트링 → 헤더

진행 현황

  • 웨이브 1 (X봇 버그/죽은 API/정보노출/JSON가드/fetch 타임아웃)
  • 웨이브 2 (selectedDate 안정화 + 카드 onClick memo 복구)
  • 웨이브 3 (월별 일정 Redis 캐시 TTL 60s + 쓰기 시 무효화)