Skip to content

refactor: RSS/스케줄러 마이그레이션 + 큐레이션 시스템 구축#6

Merged
bbbang105 merged 9 commits intodevfrom
refactor/rss-scheduler-migration
Feb 25, 2026
Merged

refactor: RSS/스케줄러 마이그레이션 + 큐레이션 시스템 구축#6
bbbang105 merged 9 commits intodevfrom
refactor/rss-scheduler-migration

Conversation

@bbbang105
Copy link
Copy Markdown
Owner

@bbbang105 bbbang105 commented Feb 24, 2026

Summary

RSS/스케줄러 인프라를 현대적인 스택으로 마이그레이션하고, 큐레이션 시스템을 전면 구축한 PR입니다.

1. RSS/스케줄러 마이그레이션

  • rss-parser(deprecated) → feedsmith(TypeScript 네이티브) RSS 파서 교체
  • node-cron(인메모리) → pg-boss(PostgreSQL 기반 잡 큐) 스케줄러 교체
  • 5개 스케줄러를 index.ts에서 실제 연결하여 RSS 폴링, 출석체크, 벌금알림, 회차리포트, 큐레이션 기능 활성화

2. 큐레이션 시스템

  • 소스 관리: 16개 외부 소스(GeekNews, Disquiet, 요즘IT 등) 등록 및 CRUD
  • 크롤링: SSE 스트리밍 기반 실시간 크롤링 진행 모달 (Progress Bar + 소스별 성공/실패 상세)
  • 기간 필터: 크롤링 시 수집 기간 지정 (1일/3일/7일/30일/전체)
  • 아이템 관리: 관리자 카드 그리드 뷰 + 필터(카테고리/소스) + 삭제 + 페이지네이션
  • 사용자 큐레이션 페이지: 카드 UI, 카테고리/태그 필터, 12개씩 번호 페이지네이션
  • 태그 시스템: INTEREST_OPTIONS 공유 상수 기반, 프라이머리 단일 색상 통일

3. 기타 개선

  • Velog URL 패턴 수정, RSS 자동 감지 개선
  • 대시보드/멤버 API 개선
  • 모노레포 환경 .env.local 로딩 경로 수정
  • Next.js 16 + React 19 + Tailwind v4 프레임워크 업그레이드

Changes

Bot (RSS/스케줄러)

파일 변경
packages/bot/package.json rss-parser/node-cron 제거, feedsmith/pg-boss 추가
packages/bot/src/services/rss.service.ts axios fetch + parseFeed 분리, RSS/Atom/JSON/RDF 전체 포맷 지원
packages/bot/src/schedulers/*.ts (5개) node-cron 제거, 비즈니스 로직만 유지
packages/bot/src/job-queue.ts 신규 pg-boss 싱글톤
packages/bot/src/scheduler-registry.ts 신규 7개 잡 등록 + RSS→Post→Notification 파이프라인
packages/bot/src/index.ts pg-boss 시작 + registerAllJobs 연결

Web (큐레이션)

파일 변경
packages/web/src/app/api/admin/curation/crawl/route.ts SSE 스트리밍 크롤링 + since 기간 필터
packages/web/src/app/api/admin/curation/items/route.ts 신규 아이템 목록 API (페이지네이션, 필터)
packages/web/src/app/api/admin/curation/items/[id]/route.ts 신규 아이템 삭제 API
packages/web/src/app/api/curation/route.ts 페이지네이션(page/limit), 소스명 JOIN, 태그 AND 필터
packages/web/src/app/(admin)/admin/curation/page.tsx 크롤링 진행 모달, 기간 선택, 태그 Badge 통일
packages/web/src/app/(admin)/admin/curation/items/page.tsx 신규 관리자 아이템 카드 그리드
packages/web/src/app/(user)/curation/page.tsx INTEREST_OPTIONS 태그 필터, 소스명 표시, 번호 페이지네이션
packages/web/src/components/ui/dialog.tsx 신규 shadcn Dialog
packages/web/src/components/ui/progress.tsx 신규 shadcn Progress

Shared

파일 변경
packages/shared/src/config/tag-colors.ts 신규 36개 태그별 색상 매핑 (관리용, 현재 미사용)
packages/shared/src/config/index.ts tag-colors export 추가
packages/shared/src/config/interest-options.ts AI/ML → AI 변경

Design Decisions

결정 이유
feedsmith parseFeed() + extractFeedItems() RSS/Atom/JSON/RDF 전체 포맷을 단일 인터페이스로 정규화
pg-boss schedule() + work() 패턴 비즈니스 로직 유지, cron 관리만 pg-boss 위임
DATABASE_URL_DIRECT 별도 환경변수 pg-boss LISTEN/NOTIFY에 Direct Connection 필요
크롤링 SSE 스트리밍 긴 작업의 실시간 진행률 표시, 소스별 성공/실패 즉시 피드백
태그 프라이머리 단일 색상 사용자 페이지 일관성, 온보딩과 동일한 Badge 스타일
INTEREST_OPTIONS 공유 상수 기반 필터 DB 동적 태그 대신 미리 정의된 관심 태그로 통일

Commits

커밋 설명
05cbdfd 큐레이션 아이템 관리 뷰, 크롤링 진행 모달, 페이지네이션
177b01a Velog URL 패턴 수정, RSS 감지, 대시보드/멤버 API 개선
c2df533 큐레이션 시스템 개선 — RSS 크롤링, 태그, 수동 크롤 지원
1902aa3 모노레포 환경에서 .env.local 로딩 경로 수정
fd0eda6 rss-poller 테스트에서 제거된 stop() 호출 수정
794ef07 CLAUDE.md 기술 스택 및 핵심 파일 최신화
a5d3782 스케줄러 연결 (pg-boss 잡 큐 → index.ts 통합)
8bbc21b node-cron → pg-boss 마이그레이션
bc0982c rss-parser → feedsmith 마이그레이션

Test Plan

  • pnpm --filter @blog-study/shared build 통과
  • pnpm --filter @blog-study/bot typecheck 통과
  • pnpm --filter @blog-study/bot build 통과
  • pnpm --filter @blog-study/web build 통과
  • 소스에서 rss-parser, node-cron import 완전 제거 확인
  • 크롤링 실행 → 16개 소스 중 15개 성공, 266개 아이템 수집
  • 사용자 큐레이션 페이지 카드/필터/페이지네이션 동작 확인
  • 관리자 아이템 뷰 카드 그리드/필터/삭제 동작 확인
  • .envDATABASE_URL_DIRECT 설정 후 pnpm dev:bot 실행하여 pg-boss 잡 등록 확인

🤖 Generated with Claude Code

bbbang105 and others added 4 commits February 24, 2026 16:03
- rss-parser(deprecated) 제거, feedsmith(TypeScript 네이티브) 추가
- RssService: axios fetch + parseFeed 분리 구조로 변경
- RSS/Atom/JSON/RDF 전체 피드 포맷 지원 (extractFeedItems 헬퍼)

Co-Authored-By: Claude <noreply@anthropic.com>
- node-cron 제거, pg-boss(PostgreSQL 기반 잡 큐) 추가
- 5개 스케줄러에서 cron 의존성 제거, 비즈니스 로직만 유지
- pg-boss 싱글톤 모듈 (job-queue.ts) 생성
- 7개 잡 레지스트리 (scheduler-registry.ts) 생성
- RSS 폴링 → PostService.create → NotificationService 파이프라인 연결
- DATABASE_URL_DIRECT 환경변수 추가 (pg-boss LISTEN/NOTIFY용)

Co-Authored-By: Claude <noreply@anthropic.com>
- index.ts에서 pg-boss 시작 + 7개 잡 등록 연결
- graceful shutdown에 pg-boss 정리 추가 (onShutdown 콜백)
- RSS 폴링, 출석체크, 벌금알림, 회차리포트, 큐레이션 모두 활성화

Co-Authored-By: Claude <noreply@anthropic.com>
- Bot 기술 스택: feedsmith, pg-boss로 업데이트
- 핵심 파일에 job-queue.ts, scheduler-registry.ts 추가
- 환경변수에 DATABASE_URL_DIRECT 추가

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
study-admin-web Ready Ready Preview, Comment Feb 25, 2026 8:32am

@bbbang105 bbbang105 added 📄 docs 문서 추가 및 수정 🔄 refactor 코드 리팩토링 🚀 feat 새로운 기능 추가 / 일부 코드 추가 / 일부 코드 수정 (리팩토링과 구분) / 디자인 요소 수정 labels Feb 24, 2026
- afterEach에서 poller.stop() → vi.clearAllMocks()로 변경
- node-cron 제거 후 stop() 메서드가 없어 발생한 테스트 실패 수정

Co-Authored-By: Claude <noreply@anthropic.com>
@bbbang105 bbbang105 added the 🚨 fix 버그 수정 / 에러 해결 label Feb 24, 2026
- dotenv 로딩 시 cwd + 상위 디렉토리까지 탐색하도록 변경
- packages/* 에서 실행해도 루트 .env.local을 찾을 수 있음

Co-Authored-By: Claude <noreply@anthropic.com>
bbbang105 and others added 2 commits February 25, 2026 13:56
- curation_sources에 tags, rss_url 컬럼 추가
- INTEREST_OPTIONS 상수를 shared 패키지로 추출
- POST/PATCH API에 tags, rssUrl 필드 지원 + RSS URL 자동 감지
- 수동 크롤링 API 엔드포인트 추가 (POST /api/admin/curation/crawl)
- 관리자 페이지에 태그 선택기, RSS URL 입력, 크롤링 실행 버튼 추가
- 봇 curation-crawl 스케줄 KST 8시로 변경 (cron: 0 23 * * *)
- 봇 CurationCrawler에 feedsmith 기반 RSS crawlFunction 연결

Co-Authored-By: Claude <noreply@anthropic.com>
- Velog /posts 경로 지원 (url-validator, rss.service)
- 웹 RSS URL 자동 감지 유틸 추가 (rss-detect.ts)
- 대시보드 API memberName → memberNickname 수정
- 멤버 API 정렬/필터 개선
- 포스트 목록 페이지 개선
- RSS 테스트/폴링 스크립트 추가

Co-Authored-By: Claude <noreply@anthropic.com>
- 관리자 아이템 목록 API/페이지 추가 (카드 그리드, 필터, 삭제)
- 크롤링 API를 SSE 스트리밍으로 전환, 진행률 모달 구현
- 크롤링 시 기간 필터(since) 지원
- 사용자 큐레이션 페이지에 12개씩 페이지네이션 + 번호 UI
- 태그 색상을 프라이머리 단일 색상으로 통일 (admin/user 공통)
- 태그 필터를 INTEREST_OPTIONS 공유 상수 기반으로 변경
- shadcn Dialog/Progress 컴포넌트 추가

Co-Authored-By: Claude <noreply@anthropic.com>
@bbbang105 bbbang105 changed the title refactor: RSS/스케줄러 마이그레이션 (feedsmith + pg-boss) refactor: RSS/스케줄러 마이그레이션 + 큐레이션 시스템 구축 Feb 25, 2026
@bbbang105 bbbang105 merged commit a17cf4a into dev Feb 25, 2026
7 checks passed
@bbbang105 bbbang105 deleted the refactor/rss-scheduler-migration branch February 25, 2026 08:39
choihooo added a commit that referenced this pull request Mar 10, 2026
P0 #5: 주간 랭킹 날짜 필터 추가
- 문제: activityScores 테이블에서 모든 기간의 점수를 합산
- 해결: 이번 주 시작일(~ 00:00:00 KST)부터 종료일(~ 23:59:59.999 KST)까지의 점수만 필터링
- 파일: weekly-ranking.ts

P0 #6: 회차 시작 알림 타임존 수정
- 문제: UTC 기준으로 날짜 비교해서 KST에서 실행 시 날짜 불일치
- 해결: KST (UTC+9) 기준으로 오늘 날짜 구해서 비교
- 파일: round-reporter.ts (sendRoundStartAnnouncement)

P0 #7: 회차 종료 후 isCurrent 플래그 업데이트
- 문제: 회차 리포트 전송 후 isCurrent 플래그가 업데이트되지 않음
- 해결: 다음 회차가 있으면 해당 회차를 current로 설정, 없으면 현재 회차의 isCurrent를 false로 변경
- 파일: round-reporter.ts (sendRoundReport)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
choihooo added a commit that referenced this pull request Mar 10, 2026
코드 리뷰 사항 반영:
- P0 #6 타임존 처리 로직을 formatKSTDate() 헬퍼 함수로 분리
- 코드 재사용성과 명확성 향상
- KST 오프셋 계산 로직을 함수 내부로 캡슐화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
choihooo added a commit that referenced this pull request Mar 10, 2026
* fix: 회차 스케줄러 버그 수정 (P0 #5, #6, #7)

P0 #5: 주간 랭킹 날짜 필터 추가
- 문제: activityScores 테이블에서 모든 기간의 점수를 합산
- 해결: 이번 주 시작일(~ 00:00:00 KST)부터 종료일(~ 23:59:59.999 KST)까지의 점수만 필터링
- 파일: weekly-ranking.ts

P0 #6: 회차 시작 알림 타임존 수정
- 문제: UTC 기준으로 날짜 비교해서 KST에서 실행 시 날짜 불일치
- 해결: KST (UTC+9) 기준으로 오늘 날짜 구해서 비교
- 파일: round-reporter.ts (sendRoundStartAnnouncement)

P0 #7: 회차 종료 후 isCurrent 플래그 업데이트
- 문제: 회차 리포트 전송 후 isCurrent 플래그가 업데이트되지 않음
- 해결: 다음 회차가 있으면 해당 회차를 current로 설정, 없으면 현재 회차의 isCurrent를 false로 변경
- 파일: round-reporter.ts (sendRoundReport)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: 타임존 처리를 formatKSTDate 헬퍼 함수로 분리

코드 리뷰 사항 반영:
- P0 #6 타임존 처리 로직을 formatKSTDate() 헬퍼 함수로 분리
- 코드 재사용성과 명확성 향상
- KST 오프셋 계산 로직을 함수 내부로 캡슐화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
choihooo added a commit that referenced this pull request Mar 11, 2026
- sr-only 클래스로 화면에는 보이지 않는 라벨 추가
- htmlFor와 id로 명시적 연결
- 검색 아이콘에 aria-hidden 적용
- 큐레이션, 멤버, 벌금 페이지 검색창 개선

Refs: docs/26-03-11-web-accessibility-audit.md P1 #6
choihooo added a commit that referenced this pull request Mar 11, 2026
* feat: 큐레이션 데이터 품질 개선 (description, thumbnailUrl 추출)

P1 #8: 큐레이션 봇 크롤러 데이터 품질 개선

주요 변경사항:
- RSS 피드에서 description, thumbnailUrl 필드 추출
- feed-parser 공유 유틸리티 생성 (extractFeedItems, sanitizeDescription, extractOgImage)
- SSRF 보호 강화 (isSafeUrl를 url-validator로 이동)
- HTML 엔티티 디코딩 추가 (html-entities 패키지)
- 제어 문자/유니코드 익스플로잇 제거 로직 개선
- 웹 API와 봇 간 코드 중복 제거

보안 개선:
- extractOgImage 함수에 isSafeUrl SSRF 체크 추가
- sanitizeDescription에 제어 문자/유니코드 익스플로잇 제거 추가
- 내부 URL (localhost, 127.0.0.1, 169.254.169.254 등) 차단

테스트:
- feed-parser.property.test.ts 추가 (20개 테스트 케이스)
- SSRF 보호, XSS 방지, 한글/이모지 처리 등 검증

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* perf: OG 이미지 추출 병렬 처리로 성능 개선

- 봇 스케줄러: Promise.allSettled로 OG 이미지 동시 추출
- 웹 API: OG 이미지 병렬 추출 후 DB 삽입 (순차 유지)
- 순차 처리(최대 250초) → 병렬 처리(최대 5초)로 대기 시간 단축
- Promise.allSettled로 개별 실패시 전체 프로세스 보호

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: 타입 안전성 개선 (NormalizedFeedItem 타입 명시)

- 웹 API: NormalizedFeedItem 타입 import 추가
- filter/map 콜백에 명시적 타입 어노테이션 추가
- Promise.allSettled 결과에 타입 가드 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: PR 빌드 에러 수정 (utils export, isSafeUrl re-export)

- shared/utils: utils namespace export 추가 (import { utils } 패턴 지원)
- rss-detect.ts: isSafeUrl re-export 추가
- 타입 안전성 개선: Promise.allSettled result 변수 분리

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: P2 #15 핸들러 및 스케줄러 테스트 추가

- Handlers 통합 테스트 5개 추가 (모듈 로드, 함수 export 확인)
- Round Service 통합 테스트 15개 추가
  * isDeadlinePassed, isGracePeriod, isGracePeriodEnded 함수 테스트
  * 날짜 파싱 정확성 테스트
  * 일관성 테스트
- 총 145개 테스트 통과

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: handlers.test.ts mock export 추가로 테스트 수정

- sendFineNotification, sendFineReminder export를 mock에 추가
- 모든 테스트 통과 (153개)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: 린트 에러 수정 (unused imports 제거)

- getConfigValue import 제거 (round-integration.test.ts)
- Client import 제거 (handlers.test.ts)
- 에러 0개, warnings 56개 (console.log 관련)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: PR #29 코드 리뷰 피드백 반영

## 개선사항

### 1. 핸들러 테스트 실제 동작 검증으로 강화
- Mock만 사용하던 테스트를 실제 이벤트 리스너 등록 검증으로 개선
- setupActivityHandler: MessageCreate, MessageReactionAdd 이벤트 등록 확인
- setupDMHandler: InteractionCreate 이벤트 등록 확인
- Property-Based Testing 패턴 유지

### 2. feed-parser 보안 강화 (SSRF)
- extractOgImage()에서 OG 이미지 URL 자체도 안전한지 검증
- 이중 SSRF 보호: 페이지 URL + OG 이미지 URL 모두 검증
- 공격자가 og:image에 내부 URL을 넣는 방어

### 3. 에러 핸들링 개선
- extractOgImage() 에러를 타입별로 분류하여 로그
- timeout, network error, unexpected error 각각 다른 레벨로 처리
- 디버깅 및 운영 모니터링 개선

### 4. 테스트 커버리지 확대
- extractFeedItems() 포맷별 동작 테스트 추가 (RSS, Atom, JSON, RDF)
- Property 17-20: 각 포맷의 파싱 로직 검증
- 총 24개 테스트 통과

### 5. 타입 안정성 개선
- API 라우트에서 불필요한 타입 단언 제거
- 타입 가드를 사용하여 안전하게 필터링

### 6. next-env.d.ts 복구
- 개발 전용 변경을 원래대로 복구
- 프로덕션 빌드 호환성 유지

## 테스트 결과
- 154개 테스트 전체 통과
- 핸들러 테스트: 9개 (이벤트 리스너 등록 검증)
- feed-parser 테스트: 24개 (보안 + 포맷별 동작)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(a11y): 테이블 헤더에 scope 속성 추가

- TableHead 컴포넌트에 기본값 scope="col" 추가
- 모든 테이블 헤더의 의미 체계를 스크린 리더에 명확히 전달
- WCAG 2.1 AA 준수

Refs: docs/26-03-11-web-accessibility-audit.md P1 #9

* feat(a11y): 활성 내비게이션에 aria-current 속성 추가

- BottomNav 컴포넌트에 aria-current="page" 추가
- 현재 페이지 여부를 스크린 리더에 명확히 전달
- 모바일 하단 내비게이션 접근성 개선

Refs: docs/26-03-11-web-accessibility-audit.md P1 #4

* feat(a11y): 외부 링크에 스크린 리더용 보조 텍스트 추가

- ExternalLinkIcon 재사용 컴포넌트 생성
- 새 탭에서 열림을 나타내는 sr-only 텍스트 추가
- 랜딩 페이지, 게시판, 큐레이션 외부 링크에 적용
- aria-hidden으로 아이콘 숨김 처리

Refs: docs/26-03-11-web-accessibility-audit.md P1 #10

* feat(a11y): 검색 입력 필드에 접근 가능한 라벨 추가

- sr-only 클래스로 화면에는 보이지 않는 라벨 추가
- htmlFor와 id로 명시적 연결
- 검색 아이콘에 aria-hidden 적용
- 큐레이션, 멤버, 벌금 페이지 검색창 개선

Refs: docs/26-03-11-web-accessibility-audit.md P1 #6

* feat(a11y): 폼 에러 메시지를 입력 필드와 연결

- 에러 메시지에 role="alert", aria-live="assertive" 추가
- 입력 필드에 aria-invalid, aria-describedby 속성 추가
- 필드별 에러를 동적으로 연결하여 스크린 리더에 정확히 전달
- 게시판 작성, 멤버 폼에 적용

Refs: docs/26-03-11-web-accessibility-audit.md P1 #5

* fix(a11y): 사용하지 않는 ExternalLinkIcon import 제거

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📄 docs 문서 추가 및 수정 🚀 feat 새로운 기능 추가 / 일부 코드 추가 / 일부 코드 수정 (리팩토링과 구분) / 디자인 요소 수정 🚨 fix 버그 수정 / 에러 해결 🔄 refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant