Commit graph

631 commits

Author SHA1 Message Date
7d58d19737 feat(youtube-bot): 예정 일정에 고정 멤버 포함 옵션 추가
예정 일정 자동 생성 시 봇의 고정 멤버(default_member_ids)를 함께
추가하는 옵션(auto_schedule_config.includeDefaultMembers). 워크돌처럼
멤버가 고정인 채널의 다음 주 예정 일정에 멤버가 자동으로 붙음.
DB/라우트 변경 없이 auto_schedule_config JSON에 플래그 추가 +
관리자 다이얼로그에 토글.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 18:12:26 +09:00
8a8548e2d7 docs: 보류(유지보수 부채) 항목 카테고리별 정리 + 누락 항목 보강 2026-06-07 18:06:54 +09:00
805e1b5ec9 style(pc): 페이지 스크롤바 핸들 불투명 단색(primary)으로 변경 2026-06-07 17:29:04 +09:00
ec1c413ba8 style(pc): 페이지 스크롤바 핸들 끝을 둥글게 (os-handle-border-radius) 2026-06-07 17:28:32 +09:00
5aca8d97d1 feat(pc): 페이지 스크롤바를 OverlayScrollbars로 교체
#root의 min-width(1440)를 스크롤 컨테이너(main) 내부 콘텐츠로 옮기고
main에 OverlayScrollbars 적용. 1440 미만으로 줄여도 세로 스크롤바가
뷰포트 끝에 항상 보이고 가로 스크롤이 생김(maplestory 방식). 헤더 고정
모델 유지, 일정 페이지는 기존 내부 스크롤 유지. 공개/관리자 레이아웃 동일 적용.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 17:23:24 +09:00
93d7737bb0 chore(frontend): overlayscrollbars 의존성 추가 2026-06-07 17:19:28 +09:00
ac1c09a9c5 docs: 계획서 웨이브3 완료 체크 2026-06-07 16:39:53 +09:00
54e014c833 perf(schedule): 월별 일정 Redis 캐시 + 쓰기 시 무효화
캘린더(최다 호출 공개 엔드포인트) getMonthlySchedules를 getOrSet으로
캐시(TTL 60s). 무효화는 모든 쓰기가 수렴하는 3개 meili 동기화 함수
(addOrUpdateSchedule/syncScheduleById/deleteSchedule)에 redis 전달 시
schedule:monthly:* 무효화. 관리자 라우트·봇 경로에서 redis 전달(즉시
반영), festival/누락 경로는 60s TTL로 자동 치유.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 16:39:40 +09:00
f50463f394 docs: 계획서 웨이브2 완료 체크 2026-06-07 16:19:47 +09:00
1b330872f5 perf(schedule): 카드 onClick 안정화로 React.memo 복구
카드가 onClick(schedule)을 호출하도록 변경(기존 호출부는 인자 무시라
호환), 페이지는 useCallback 안정 핸들러를 전달. 매 렌더 새 인라인
함수로 memo가 깨져 필터/스크롤마다 전체 카드가 리렌더되던 문제 해결.
검색 결과 가상화 카드는 범위에서 제외(이미 최적화됨).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 16:19:33 +09:00
067618d792 perf(mobile-schedule): selectedDate 참조 안정화
storedSelectedDate가 없을 때 매 렌더 new Date()가 생성돼 다수
useMemo/useEffect가 재실행되고 날짜 스트립이 반복 scrollIntoView되던
문제를, useMemo로 참조를 안정화(값 의미는 동일).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 16:14:06 +09:00
11956a4669 docs: 계획서 웨이브1 완료 체크 2026-06-07 15:43:13 +09:00
b0a2e69711 fix(youtube): YouTube API fetch에 타임아웃 추가
업스트림 행 시 봇 동기화/요청 핸들러가 무한 대기하던 문제 방지.
fetchWithTimeout(AbortController, 10s)으로 7개 fetch 호출 일괄 래핑.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 15:42:53 +09:00
6e559a52b7 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>
2026-06-07 15:41:57 +09:00
e16d3f1230 fix(security): 공개 /api/bots에서 민감 정보 제거
무인증 공개 엔드포인트가 봇 설정(채널/계정/필터)과 에러 내부정보
(errorMessage 등)를 그대로 노출하던 것을 상태 요약 필드만 반환하도록
화이트리스트. getBots() await 누락(잠재 버그)도 함께 수정.
관리자 화면은 인증된 /api/admin/bots 사용(영향 없음).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 15:40:42 +09:00
6b66445295 refactor(frontend): 죽은 API/훅 제거
- createSchedule/updateSchedule (백엔드 라우트 없음, 미사용) 삭제
- useMemberDetail/getMemberByName (미정의 함수 호출, 미사용) 삭제
- 불필요해진 fetchFormData import 정리

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 15:38:19 +09:00
48c2a68cda fix(x-bots): X봇 생성 시 exclude_managed_channels ReferenceError 수정
POST body 스키마/구조분해에 exclude_managed_channels(default true,
컬럼 기본값과 일치)를 추가. 누락으로 INSERT 시 ReferenceError 크래시.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 15:36:19 +09:00
7828ca6399 docs: 개선 작업 계획서 추가 2026-06-07 15:35:44 +09:00
3e56670e8a feat(x-bot): TikTok 카드 썸네일 온디맨드 프록시
TikTok은 OG 이미지를 막지만 oEmbed는 thumbnail_url 제공. 단 서명·만료
URL이라 저장하지 않고, /api/schedules/x-card-thumb/:postId 엔드포인트가
요청 시 oEmbed로 현재 썸네일을 받아 302 리다이렉트(Redis 6h 캐시).
resolveCard는 TikTok 카드 이미지를 이 프록시 경로로 설정.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 09:40:32 +09:00
0b730405a6 fix(x-bot): 만료되는 트위터 카드 이미지를 원본 OG 이미지로 대체
Nitter가 주는 pbs.twimg.com/card_img 이미지는 시간이 지나면 404로
만료됨. resolveCard에서 카드 이미지가 없거나 해당 만료성 URL이면
원본 URL의 OG 이미지(i.ytimg 등 안정적)로 대체.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 09:30:59 +09:00
134f5836b7 fix(x-bot): OG 카드 엔티티 디코딩 + YouTube 이미지 보강
- 스크래퍼가 Nitter HTML 엔티티(&#x2F; &amp; 등)를 디코딩하지 않아
  본문/카드 제목에 [JP&#x2F;EN]처럼 노출되던 문제 수정 (extractCard,
  extractTextFromHtml에 decodeEntities 적용)
- resolveCard가 Nitter 카드에 제목만 있고 이미지가 없을 때 OG로 이미지를
  보강하도록 변경 (YouTube 카드 이미지 누락 복구)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 09:26:54 +09:00
3ba27c0100 style(festival-bot): 봇 관리 페이지 축제 섹션 색상 amber로 변경
typeConfig.festival 색상을 emerald → amber로 변경해 다이얼로그·축제
카테고리 색감과 통일.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 23:06:07 +09:00
de6e56047c style(festival-bot): 다이얼로그 색상 축제 카테고리 톤(amber)으로 변경
초록(emerald) → amber 계열로 변경해 행사/축제 색감과 통일.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 23:04:38 +09:00
543371db23 feat(festival-bot): 비활성 월일 때 '대기 중' 상태 표시
봇 목록 API에 active_months 노출. 봇이 켜져 있어도 현재 달이 활성
월이 아니면 카드 상태를 초록 '실행 중' 대신 amber '대기 중'으로 표시.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 23:04:38 +09:00
d24f8cabe3 docs(api): 축제 봇 active_months 필드 문서화
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 22:59:03 +09:00
2af3af9345 feat(festival-bot): 다이얼로그에 활성 월 선택 UI
축제 봇 추가/수정 다이얼로그에 1~12월 토글 그리드 + 전체 선택/해제
추가. 신규 봇은 전체 월(항상 실행) 기본. 전체 선택은 active_months
null로 저장.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 22:58:13 +09:00
8b36e9b5f7 feat(festival-bot): active_months API + 축제 봇 전용으로 한정
- botMonths 유틸(parse/serialize) 추가, 빈배열·전체선택은 NULL 정규화
- 축제 봇 라우트 조회/생성/수정에 active_months 반영 (수정 시 봇 재시작으로 즉시 적용)
- 봇별 설정이 이미 분리돼 있어 x/youtube에는 미적용 — DB 컬럼/스케줄러 매퍼에서 제외
- 스케줄러 isActiveMonth 게이트는 범용 유지(미설정 봇은 항상 실행)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 22:56:26 +09:00
fa37891ab3 feat(bots): 스케줄러에 활성 월 게이트 추가
봇 매퍼에 activeMonths 파싱 추가. cron 콜백·즉시 실행 시 현재 KST
월이 활성 월에 포함될 때만 동기화 실행(매 실행 시점 재평가). NULL/
빈배열/12개 전체는 항상 실행. date.js에 monthKST() 헬퍼 추가.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 22:52:26 +09:00
604142672e feat(bots): active_months 컬럼 추가 (시즌성 봇 실행 월 제한)
bot_festival/bot_x/bot_youtube에 active_months JSON 컬럼 추가.
NULL=전체 월(항상 실행), 정수 배열이면 해당 월에만 동기화.
대학 축제 봇처럼 시즌에만 도는 봇의 불필요한 API 호출 절약.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 22:49:35 +09:00
39a6225897 feat(schedule-mobile): 날짜 미정 일정 렌더링 (B안)
모바일 일정 페이지에 date_precision='month' 일정을 점선 "N월 중"
카드(UndatedScheduleListCard)로 표시. 선택 날짜와 무관하게 해당
달이면 확정 일정 아래 "날짜 미정" 구분선과 함께 배치.
캘린더/날짜 점은 1일에 찍지 않도록 PC·모바일 dot 목록에서 제외.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 20:27:07 +09:00
39aadf50e6 feat(schedule-pc): 날짜 미정 일정 렌더링 (B안)
date_precision='month' 일정을 점선 "N월 중" 카드(UndatedScheduleCard)로
표시. 선택 날짜와 무관하게 해당 월이면 확정 일정 아래에 배치하고
사이에 "날짜 미정" 구분선 추가. 카운트에도 포함.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 20:10:26 +09:00
44d30d48f6 feat(schedule): 조회 응답에 datePrecision 포함
목록(SCHEDULE_LIST_SQL)·상세(getScheduleDetail) 쿼리/포맷터가
date_precision을 반환하도록 추가. 기본값 'day'. 공개 페이지에서
'month'인 일정을 날짜 미정으로 렌더링하기 위한 읽기 지원.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 18:44:08 +09:00
17746581e1 feat(schedule): date_precision 컬럼 추가 (날짜 미정 일정 지원)
월만 확정된 일정을 위해 schedules에 date_precision ENUM('day','month')
추가. 기본값 'day'로 기존 일정/쿼리에 영향 없음. month인 경우
date는 해당 월 1일로 저장하고 확정 시 수정에서 정확한 날짜 입력.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 18:34:24 +09:00
01cf1cfe9a fix(mobile-schedule): 카테고리 칩 자동 숨김 제거
스크롤 방향 감지 자동 숨김이 sticky 바의 transform 전환과
충돌해 리스트 상단에서 위로 스크롤 시 떨림 발생.
항상 보이는 단순 sticky 칩 바로 변경.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 15:04:45 +09:00
f6ba4c8183 feat(mobile-members): 매거진 풀블리드 스티키 스택 디자인
- 멤버를 화면 폭 전체 화보 카드로 (한글명 + 생일/나이 + 인스타)
- 스티키 스택: 스크롤 시 다음 카드가 이전 카드를 덮으며 흐려짐/축소
- 스크롤 멈추면 가장 가까운 카드를 상단에 자석처럼 스냅
- 마지막 카드도 상단까지 올라오도록 동적 하단 여유 공간

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 14:47:37 +09:00
9b2e4e190d feat(mobile-schedule): 카테고리 필터 추가 + 리스트 카드 플랫화
- 일정 리스트 위에 카테고리 필터 칩 추가 (해당 달 전체 카테고리)
- 스크롤 방향 감지 자동 숨김 (내리면 숨고 올리면 보임), 상단 여백 일관
- 카테고리 선택 시 일정 목록 + 날짜 점 필터링 (공개 PC와 store 공유)
- 일정 리스트 카드: 그림자 제거, 1.5px 테두리로 플랫하게

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 14:10:16 +09:00
381461c25e fix(event): 모바일 축제 포스터 클릭 시 라이트박스로 열기
새 창(target=_blank) → MobileLightbox로 변경 (메인/추가 포스터 모두).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 13:48:57 +09:00
1a50f93d65 feat(event): 축제 상세 페이지 디자인 개편
- 골드 그라데이션 단일 히어로로 통합 (타입/학교/제목/날짜/장소/멤버/링크)
- 장소는 히어로에서 클릭 → 지도 다이얼로그(PC 모달/모바일 바텀시트)
- 칩(타입·학교·멤버·링크)을 흰 배경 + 앰버 글자로, 텍스트 그림자 추가
- 모바일: 포스터를 카드와 분리해 크게 표시 + 스크롤 패럴랙스/페이드

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 13:46:20 +09:00
c0ac18060d fix(admin-schedule): 카테고리 필터를 공개 페이지와 통일
- categoryCounts를 선택 날짜와 무관하게 해당 달 전체 기준으로 변경
- 달력 점도 카테고리 필터 반영

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 12:54:47 +09:00
70fb6527fe fix(event): 학교 축제 상세 날짜 옆 배지를 학교명 → 축제 타입으로
학교명은 이미 제목에 포함되어 중복이므로 '대학 축제' 타입 표시로 변경.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 12:50:32 +09:00
2ddc16d532 feat(concert): 콘서트 상세 페이지 추가 (몰입형 디자인)
기존 DefaultSection으로 빈약하게 표시되던 콘서트를 전용 상세 페이지로.

백엔드:
- getScheduleDetail 콘서트 분기: 포스터/장소/세트리스트(곡별 멤버)/굿즈/다른 회차

프론트엔드:
- ConcertSection(PC) / MobileConcertSection(모바일) 신규
- 블러 포스터 히어로 + 회차 드롭다운 + 장소 지도 다이얼로그
- 세트리스트 펼치기/접기 애니메이션, 유닛/솔로만 멤버 태그
- 굿즈 masonry 갤러리(라이트박스), 회차 전환은 history replace
- 모바일은 세로 중앙형 전용 히어로 + 바텀시트
- Lightbox/MobileLightbox를 createPortal로 변경 (오버레이 전체화면 보장)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 23:20:48 +09:00
dd5ef48592 feat(schedule): 검색 결과 없음 UI 추가 및 빈 상태 위치 개선
- PC: 검색 모드에서 결과 0개일 때 아무것도 안 나오던 문제 수정
  (돋보기 아이콘 + 검색어 표시), 검색 중 로딩 구분
- 모바일: 검색/날짜별 빈 상태를 아이콘 포함 디자인으로 통일
- 빈 상태/로딩을 일정 영역 기준으로 배치
  (PC: 상단 30% 지점, 모바일: 중앙)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 22:15:10 +09:00
8b8b9a7f53 feat(x-schedule): 링크 미리보기(OG) 카드 추가 - 하이브리드
트윗의 외부 링크에 대해 미리보기 카드 표시.
- Nitter가 렌더링한 카드 우선 사용 (extractCard)
- Nitter 카드가 비어있으면 본문 URL로 OG 직접 추출 (og.js)
  - YouTube/Instagram 등 복구, HTML 엔티티 디코딩 포함
  - TikTok 등 봇 차단 사이트는 Nitter 카드로 커버
- schedule_x.card_data 컬럼 + getScheduleDetail 응답에 card 포함
- 가로 레이아웃 카드 (왼쪽 이미지 + 오른쪽 텍스트)
- CardImage: 이미지 로드 실패 시 fallback 아이콘 (인스타 CDN 만료 대비)
- 자체 영상/이미지가 있으면 OG 카드 숨김 (중복 방지)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 19:34:42 +09:00
d3d3c9cf75 feat(x-schedule): 트윗 네이티브 영상 썸네일 표시
Nitter는 영상 파일을 제공하지 않으므로 썸네일만 추출해 표시하고,
재생 시 원본 트윗으로 이동.

- scraper: extractVideoThumbnails 추가 (amplify_video_thumb 등)
- schedule_x.video_thumbnails 컬럼 + saveTweet 저장
- getScheduleDetail 응답에 videoThumbnails 포함
- PC/모바일 X 상세: 썸네일 + 재생버튼 + 'X에서 재생' 배지

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 19:02:29 +09:00
4cfa4ffc00 fix(x-bot): 리트윗 이중 저장 방지
같은 리트윗이 타임라인에서 래퍼 id / 원본 id 두 형태로 번갈아 나타나
post_id 중복 체크를 통과해 두 번 저장되던 문제 수정.

리트윗 저장 시 동일 내용 + (원작자 또는 봇계정) username이 이미 있으면
중복으로 간주해 건너뜀. hydration으로 양쪽 모두 전체 내용을 갖추므로
내용 기반 매칭이 안정적으로 동작.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 18:02:22 +09:00
2cfc580283 fix(x-bot): 잘린 긴 리트윗 전체 내용 복원 (hydration)
X long tweet(280자 초과)을 Nitter 타임라인이 간헐적으로 …로 잘라서
주는 경우, 개별 상태 페이지에서 재요청해 전체 내용으로 교체.

- parseTweets: …로 끝나는 트윗에 truncated 플래그 부여
- hydrateTruncatedTweets: 잘린 트윗 status 페이지 재요청 후 교체 (best-effort)
- fetchTweets/fetchAllTweets에 적용
- fetchSingleTweet을 timeout-safe하게 변경

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 18:00:14 +09:00
6a24b997dd feat(logs): 활동 로그 다이얼로그 상세 정보 표시 + 에러 정보 보강
- logs API: longtext로 저장된 details를 JSON 객체로 파싱해 반환
- 응답 스키마에 additionalProperties: true 추가 (fast-json-stringify가
  스키마 미정의 키를 제거하던 문제 해결)
- scheduler 에러 로그: err.cause / err.code / err.causeCode 함께 기록
  (fetch failed 등 모호한 메시지의 진짜 원인 식별 가능)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:02:53 +09:00
9e3bff9762 feat(schedule): 카테고리 필터 개선 및 달력 오늘 버튼 추가
- 카테고리 카운트를 선택 날짜와 무관하게 해당 달 전체 기준으로 변경
- 카테고리 섹션이 길어지면 카드 내부 스크롤 (평소엔 콘텐츠 크기 유지)
- 달력 하단에 오늘 날짜로 이동하는 버튼 추가
- 달력 하단 여백 24px → 20px

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:47:13 +09:00
51e8f17cc1 feat(schedule): 카테고리 선택 시 달력 점도 필터링
카테고리 필터가 일정 목록만 필터링하던 것을 달력 점에도 적용.
날짜 필터는 제외하여 달력은 한 달 전체 점을 유지.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:35:12 +09:00
d530822a68 feat(festival-bot): 대학 축제 크롤러 봇 구현 (3단계)
검색 페이지(memogipost)를 크롤링하여 프로미스나인 출연 대학 축제를
Gemini url_context로 추출, 행사 일정을 자동 생성하는 봇.

백엔드:
- services/event.js: 이벤트 생성 로직 공유화 (upsertVenue, createEventSchedule, 카카오 검색)
- services/festival/: scraper(검색 페이지 크롤) + gemini(추출) + index(봇 플러그인)
- routes/admin/festival-bots.js: 축제 봇 CRUD API
- scheduler.js: festival 타입 지원, 시간 단위 cron(0 */H * * *) 변환
- 처리한 글 URL은 festival_crawl_log에 기록, 새 글 없으면 Gemini 미호출
- 학교명 부분일치 중복 감지, 활동 멤버 전체 자동 등록
- Gemini 503/500/429 재시도 로직

기타 수정:
- 행사 상세 페이지 관련 링크 줄바꿈 (truncate → break-all)
- 대학 축제 아이콘 변경 (GraduationCap → PartyPopper)
- docs/api.md, CLAUDE.md 환경변수 문서화

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:28:24 +09:00