Skip to content

feat: 글 조회 점수 시스템 + RSS 동의 설정 + 수동 글 등록#9

Merged
bbbang105 merged 1 commit intodevfrom
feat/post-view-scoring-rss-consent
Feb 26, 2026
Merged

feat: 글 조회 점수 시스템 + RSS 동의 설정 + 수동 글 등록#9
bbbang105 merged 1 commit intodevfrom
feat/post-view-scoring-rss-consent

Conversation

@bbbang105
Copy link
Copy Markdown
Owner

@bbbang105 bbbang105 commented Feb 26, 2026

Summary

  • 글 조회 시 활동 점수 부여 시스템 구축 (2점/회, 일 최대 5회)
  • RSS 자동 수집 동의/비동의 토글 및 수동 글 등록 기능 추가
  • 관리자 점수 내역 삭제 기능 추가

Changes

DB 스키마 (packages/shared)

  • ActivityScoreTypePOST_VIEW 추가
  • members 테이블에 rss_consent 컬럼 추가 (default: true)
  • post_views 테이블 신규 (member_id + post_id UNIQUE)

봇 (packages/bot)

  • score.service.ts: POST_VIEW 점수 설정 (2점, 일 상한 10점)
  • rss-poller.ts: rssConsent !== false 필터 추가

웹 프론트 (packages/web)

파일 변경 내용
posts/page.tsx 글 클릭 시 조회 점수 API 호출 (fire-and-forget) + 수동 글 등록 모달
dashboard/page.tsx 대시보드 최근 포스트 클릭에도 조회 점수 부여
profile/onboarding/page.tsx RSS 동의 토글 추가 (Step 1, default: true)
profile/edit/page.tsx RSS 설정 카드 추가
admin/members/page.tsx RSS ON/OFF 배지 표시
admin/scores/page.tsx 점수 삭제 버튼 + 커스텀 확인 다이얼로그 + post_view 라벨/배지

웹 API (packages/web/src/app/api)

엔드포인트 설명
POST /api/posts/[id]/view 글 조회 점수 부여 (본인 글/중복/일 상한 체크)
POST /api/posts/manual 수동 글 등록 (OG 크롤링 → 실패 시 제목 직접 입력)
DELETE /api/admin/scores 관리자 점수 내역 삭제
GET /api/profile rssConsent 응답 누락 수정

기타

  • CLAUDE.md: confirm()/alert() 사용 금지 → 커스텀 다이얼로그 규칙 추가
  • .serena/: 프로젝트 설정 추가

Design Decisions

결정 이유
조회 점수 2점 리액션(1점)과 스레드 댓글(3점) 사이 밸런스
fire-and-forget 방식 점수 API 실패해도 UX에 영향 없음
CTE 원자적 쿼리 동시 요청 시 일일 상한 race condition 방지
OG 크롤링 + 422 폴백 자동 제목 추출 시도 → 실패 시 사용자 직접 입력
post_views UNIQUE 제약 DB 레벨 중복 방지 (ON CONFLICT DO NOTHING)

Test Plan

  • 다른 사람 글 클릭 → 점수 +2 확인
  • 같은 글 재클릭 → 점수 미부여 확인
  • 본인 글 클릭 → 점수 미부여 확인
  • 하루 5회 초과 → 점수 미부여 확인
  • RSS 동의 OFF → 봇 RSS 폴링에서 제외 확인
  • 수동 글 등록 → OG 제목 자동 추출 확인
  • OG 실패 시 → 제목 입력 UI 노출 확인
  • 관리자 점수 삭제 → 커스텀 다이얼로그 확인
  • 프로필 수정에서 RSS false 값 정상 로드 확인

DB Migration (수동 적용 필요)

ALTER TABLE members ADD COLUMN rss_consent BOOLEAN DEFAULT true;
CREATE TABLE post_views (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), member_id UUID NOT NULL REFERENCES members(id), post_id UUID NOT NULL REFERENCES posts(id), viewed_at TIMESTAMPTZ DEFAULT now());
CREATE UNIQUE INDEX post_views_member_post_unique ON post_views (member_id, post_id);
CREATE INDEX idx_post_views_member_id ON post_views (member_id);

🤖 Generated with Claude Code

- 글 조회 시 활동 점수 부여 (2점, 일 5회, 중복/본인 글 제외)
- post_views 테이블 + POST_VIEW 점수 타입 추가
- RSS 자동 수집 동의 토글 (온보딩/프로필 편집)
- RSS 비동의 시 수동 글 등록 (URL → OG 크롤링, 실패 시 제목 직접 입력)
- 관리자 점수 내역 삭제 기능 (커스텀 확인 다이얼로그)
- 관리자 멤버 목록에 RSS ON/OFF 배지 표시
- 대시보드 최근 포스트 클릭 시에도 조회 점수 부여
- Serena 설정 추가 (.serena/)

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

vercel bot commented Feb 26, 2026

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

Project Deployment Actions Updated (UTC)
study-admin-web Building Building Preview, Comment Feb 26, 2026 11:45am

@bbbang105 bbbang105 added the 🚀 feat 새로운 기능 추가 / 일부 코드 추가 / 일부 코드 수정 (리팩토링과 구분) / 디자인 요소 수정 label Feb 26, 2026
@bbbang105 bbbang105 merged commit b65e986 into dev Feb 26, 2026
8 checks passed
@bbbang105 bbbang105 deleted the feat/post-view-scoring-rss-consent branch February 26, 2026 11:52
choihooo added a commit that referenced this pull request Mar 10, 2026
P0 #9 문제 해결:
- 인메모리 Map → DB pendingConfirmation 컬럼으로 변경
- 봇 재시작 시 대기 중인 확인 내역 소실 문제 해결

변경 사항:
- schema.ts: fines 테이블에 pendingConfirmation 컬럼 추가
- dm-handler.ts: addPendingConfirmation, removePendingConfirmation을 DB로 변경
- isPendingConfirmation: DB 조회로 변경
- 불필요한 Map 관리 함수 제거

DB 마이그레이션:
- ALTER TABLE fines ADD COLUMN pending_confirmation boolean DEFAULT true
- CREATE INDEX idx_fines_pending_confirmation

테스트 완료:
- ✅ DB에 벌금 생성
- ✅ DM 버튼 발송
- ✅ 버튼 클릭 시 DB에서 pending_confirmation false로 변경
- ✅ 벌금 상태 PAID로 업데이트

장점:
- 봇 재시작 후에도 대기 중인 벌금 확인 유지
- DB 조회로 실시간 동시성 문제 해결
- 인메모리 상태 관리 불필요

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
choihooo added a commit that referenced this pull request Mar 10, 2026
* feat: 벌금 납부 확인을 버튼 기반으로 변경

MessageContent Intent (100+ 서버 요구사항) 없이 동작하도록
텍스트 파싱 방식에서 Discord 버튼/인터랙션 방식으로 변경

변경 사항:
- dm-handler.ts: MessageCreate → InteractionCreate 이벤트 핸들러 변경
- sendFineNotification/sendFineReminder: "납부 완료" 버튼 추가
- handleButtonInteraction: 버튼 클릭 시 벌금 상태 PAID로 변경
- bot.ts: 주석 업데이트 (Intent 활성화 불가능 명시)
- fine.service.ts: isPaymentConfirmation, PaymentConfirmationWords @deprecated

P0 #2 MessageContent Intent 비활성화 문제 해결:
- 봇이 1개 서버에만 있어 Message Content Intent 활성화 불가
- 버튼/컴포넌트 기반으로 변경하여 Intent 없이 동작

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

* refactor: ChannelType.DM enum 사용으로 매직 넘버 제거

* feat: 인메모리 pendingConfirmations를 DB 영속화로 변경

P0 #9 문제 해결:
- 인메모리 Map → DB pendingConfirmation 컬럼으로 변경
- 봇 재시작 시 대기 중인 확인 내역 소실 문제 해결

변경 사항:
- schema.ts: fines 테이블에 pendingConfirmation 컬럼 추가
- dm-handler.ts: addPendingConfirmation, removePendingConfirmation을 DB로 변경
- isPendingConfirmation: DB 조회로 변경
- 불필요한 Map 관리 함수 제거

DB 마이그레이션:
- ALTER TABLE fines ADD COLUMN pending_confirmation boolean DEFAULT true
- CREATE INDEX idx_fines_pending_confirmation

테스트 완료:
- ✅ DB에 벌금 생성
- ✅ DM 버튼 발송
- ✅ 버튼 클릭 시 DB에서 pending_confirmation false로 변경
- ✅ 벌금 상태 PAID로 업데이트

장점:
- 봇 재시작 후에도 대기 중인 벌금 확인 유지
- DB 조회로 실시간 동시성 문제 해결
- 인메모리 상태 관리 불필요

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

Refs: docs/26-03-11-web-accessibility-audit.md P1 #9
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

🚀 feat 새로운 기능 추가 / 일부 코드 추가 / 일부 코드 수정 (리팩토링과 구분) / 디자인 요소 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant