Skip to content

feat: 멤버 승인 시스템 + 6단계 상태 관리 구축#10

Merged
bbbang105 merged 14 commits intodevfrom
feat/member-approval-system
Feb 27, 2026
Merged

feat: 멤버 승인 시스템 + 6단계 상태 관리 구축#10
bbbang105 merged 14 commits intodevfrom
feat/member-approval-system

Conversation

@bbbang105
Copy link
Copy Markdown
Owner

@bbbang105 bbbang105 commented Feb 27, 2026

Summary

온보딩 완료 후 관리자 승인이 필요한 멤버 승인 워크플로우와 6단계 상태 관리 시스템을 구축합니다.

  • 신규 가입자는 온보딩 후 pending_approval 상태로 설정, 관리자 승인 전까지 플랫폼 접근 차단
  • 관리자 멤버 관리 페이지를 탭 기반 UI로 전면 개편, 승인/거절/상태변경 기능 추가
  • 6개 상태: pending_approval · active · inactive · dormant · ob · withdrawn

Changes

파일 변경 내용
packages/shared/src/db/schema.ts MemberStatus enum에 pending_approval, inactive, ob 추가
packages/web/src/lib/member-config.ts 6개 상태 설정 (label, badge variant)
packages/web/src/app/api/auth/me/route.ts 응답에 status 필드 추가
packages/web/src/app/api/profile/onboarding/route.ts 신규 유저 INSERT 시 pending_approval 설정
packages/web/src/app/auth/callback/route.ts 상태별 리다이렉트 (pending→/pending, inactive→/inactive)
packages/web/src/app/(user)/layout.tsx UserLayout에 상태 기반 차단 리다이렉트 추가
packages/web/src/app/(user)/pending/page.tsx 신규 승인대기 전용 페이지
packages/web/src/app/(user)/inactive/page.tsx 신규 비활성 전용 페이지
packages/web/src/app/api/admin/members/route.ts GET 그룹핑/카운트 6개 상태 확장 + 프로필 필드 추가
packages/web/src/app/api/admin/members/[id]/route.ts PUT validStatuses 6개로 확장
packages/web/src/app/(admin)/admin/members/page.tsx 탭 기반 필터 UI + 승인/거절 핸들러 + Member 인터페이스 확장
packages/web/src/app/(admin)/admin/members/pending-member-card.tsx 신규 승인대기 멤버 카드 (2단계 승인: active/ob 선택)
packages/web/src/app/(admin)/admin/members/member-form-dialog.tsx 편집 모드에서 상태 변경 select 추가

Design Decisions

결정 이유
pending_approval 상태 시 완전 차단 승인 전 데이터 접근 방지, UX 명확성
2단계 승인 UX (승인 클릭 → active/ob 선택) OB 유저를 바로 배정할 수 있도록
탭 기반 필터 (카드 대신 pill 버튼) 6개 상태를 깔끔하게 표시, 모바일 대응
inactive도 완전 차단 비활성 유저의 불필요한 접근 방지
dormant, ob는 모든 기능 허용 출석/벌금만 제외, 플랫폼 참여는 유지

Test Plan

  • 온보딩 완료 시 pending_approval 상태 확인
  • pending_approval 유저 로그인 → /pending 페이지 표시 확인
  • inactive 유저 로그인 → /inactive 페이지 표시 확인
  • 관리자 멤버 페이지에서 탭 필터 동작 확인
  • 승인대기 탭에서 카드 UI + 승인(active/ob)/거절 동작 확인
  • 멤버 편집 다이얼로그에서 상태 변경 동작 확인
  • active/dormant/ob 유저는 정상 접근 확인
  • pnpm build 성공 확인

🤖 Generated with Claude Code

bbbang105 and others added 11 commits February 27, 2026 09:47
회원 승인 시스템 기반 작업. 기존 active/dormant/withdrawn에
pending_approval(승인대기), inactive(비활성), ob(OB) 상태를 추가.

Co-Authored-By: Claude <noreply@anthropic.com>
pending_approval(승인대기/warning), inactive(비활성/destructive),
ob(OB/outline) 상태 및 Badge variant 매핑 추가.

Co-Authored-By: Claude <noreply@anthropic.com>
프론트엔드에서 멤버 상태(pending_approval, active, inactive 등)에 따라
리다이렉트 처리를 할 수 있도록 status 필드를 응답에 포함

Co-Authored-By: Claude <noreply@anthropic.com>
신규 유저가 온보딩을 완료하면 즉시 active가 아닌 pending_approval 상태로
설정하여 관리자 승인 후에만 대시보드에 접근할 수 있도록 함
기존 유저의 UPDATE 케이스는 변경하지 않음

Co-Authored-By: Claude <noreply@anthropic.com>
로그인 시 멤버 status를 조회하여 상태에 따라 적절한 페이지로 리다이렉트:
- pending_approval → /pending (승인 대기 페이지)
- inactive → /inactive (비활성 안내 페이지)
- active → /dashboard (기존 동작 유지)

Co-Authored-By: Claude <noreply@anthropic.com>
pending_approval, inactive 상태의 유저를 각각 /pending, /inactive
페이지로 리다이렉트. 차단 페이지 자체는 예외 처리하여 무한 루프 방지.

Co-Authored-By: Claude <noreply@anthropic.com>
pending_approval 상태 유저가 보는 대기 화면.
스카이블루 Clock 아이콘, 안내 메시지, 로그아웃 버튼 포함.

Co-Authored-By: Claude <noreply@anthropic.com>
inactive 상태 유저가 보는 차단 화면.
레드 ShieldX 아이콘, 안내 메시지, 로그아웃 버튼 포함.

Co-Authored-By: Claude <noreply@anthropic.com>
- GET /api/admin/members: grouped/counts에 pending_approval, inactive, ob 추가
- GET /api/admin/members: 응답에 nickname, interests, resolution, onboardingCompleted 추가
- PUT /api/admin/members/[id]: validStatuses에 6개 상태 전체 포함
- 그룹핑 로직을 동적 키 방식으로 리팩토링

Co-Authored-By: Claude <noreply@anthropic.com>
- Task 11: 4개 통계 카드 → 6개 상태 필터 탭 (pill 버튼) 교체
  - pending_approval, active, ob, dormant, inactive 탭 추가
  - 승인대기 탭 선택 시 카드 그리드 뷰로 전환
  - Member 인터페이스에 nickname, bio, interests, resolution,
    onboardingCompleted 필드 추가
  - MemberCounts/MembersData에 6개 상태 모두 포함
  - handleApproveMember / handleRejectMember 핸들러 추가

- Task 12: PendingMemberCard 컴포넌트 신규 생성
  - 프로필 이미지, 이름, 닉네임, 파트, 블로그 링크 표시
  - bio, interests, resolution 선택적 렌더링
  - 승인(활성/OB 선택) / 거절 2단계 액션

- Task 14: MemberFormDialog에 상태 편집 필드 추가
  - 편집 모드에서만 상태 select 표시
  - MEMBER_STATUS_CONFIG 기반 6개 상태 옵션
  - PUT 요청 body에 status 포함

Co-Authored-By: Claude <noreply@anthropic.com>
Record<string, ...> 대신 개별 키를 as 타입 단언으로 선언하여
TypeScript strict 모드에서 undefined 가능성 제거.

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

vercel bot commented Feb 27, 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 27, 2026 2:52am

@bbbang105 bbbang105 added 🚀 feat 새로운 기능 추가 / 일부 코드 추가 / 일부 코드 수정 (리팩토링과 구분) / 디자인 요소 수정 🚨 fix 버그 수정 / 에러 해결 labels Feb 27, 2026
리다이렉트 중에도 finally에서 onboardingChecked가 true로 설정되어
children이 잠깐 렌더링되던 문제 수정. 리다이렉트 시에는 로딩 화면을
유지하도록 setOnboardingChecked(true)를 성공 경로에서만 호출.

Co-Authored-By: Claude <noreply@anthropic.com>
pathname 변경 시 onboardingChecked를 false로 리셋하여
API 체크 완료 전까지 로딩 화면 유지.

Co-Authored-By: Claude <noreply@anthropic.com>
onboardingChecked boolean 대신 checkedPathname으로 변경하여
pathname 변경 시 자동으로 로딩 상태가 되도록 개선.
react-hooks/set-state-in-effect 룰 위반 해결.

Co-Authored-By: Claude <noreply@anthropic.com>
@bbbang105 bbbang105 merged commit 2288732 into dev Feb 27, 2026
7 checks passed
@bbbang105 bbbang105 deleted the feat/member-approval-system branch February 27, 2026 02:52
choihooo added a commit that referenced this pull request Mar 10, 2026
P1 #10 문제 해결:
- 문제: 매일 10:00에 실행되므로 daysSinceCreation % 3 === 0 로직이 불안정
- 3일째 10:00에 3일 경과 (3 % 3 = 0) → 리마인드 발송
- 4일째 10:00에도 여전히 리마인드 발송 가능

해결 방법:
1. fines 테이블에 lastReminderAt 컬럼 추가
2. 리마인드 발송 후 lastReminderAt 업데이트
3. 마지막 리마인드로부터 정확히 3일 경과 시만 재발송

변경 사항:
- schema.ts: fines 테이블에 lastReminderAt 컬럼 및 인덱스 추가
- fine-reminder.ts: 필터 로직을 lastReminderAt 기반으로 변경
- fine.service.ts: updateLastReminderAt() 메서드 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
choihooo added a commit that referenced this pull request Mar 10, 2026
* fix: Fine Reminder 3일 간격 로직 수정 (P1 #10)

P1 #10 문제 해결:
- 문제: 매일 10:00에 실행되므로 daysSinceCreation % 3 === 0 로직이 불안정
- 3일째 10:00에 3일 경과 (3 % 3 = 0) → 리마인드 발송
- 4일째 10:00에도 여전히 리마인드 발송 가능

해결 방법:
1. fines 테이블에 lastReminderAt 컬럼 추가
2. 리마인드 발송 후 lastReminderAt 업데이트
3. 마지막 리마인드로부터 정확히 3일 경과 시만 재발송

변경 사항:
- schema.ts: fines 테이블에 lastReminderAt 컬럼 및 인덱스 추가
- fine-reminder.ts: 필터 로직을 lastReminderAt 기반으로 변경
- fine.service.ts: updateLastReminderAt() 메서드 추가

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

* fix: 필터 로직 버그 수정 (코드 리뷰 사항 반영)

Priority 1 문제 해결:
1. 필터 로직 버그 수정
   - 이전: createdAt < threeDaysAgo (3일 이전 체크 - 로직 반대)
   - 수정: now < threeDaysSinceCreation (3일 미경과 제외)

2. NULL 처리 로직 개선
   - 이전: !lastReminderAt → 무조건 true (3일 미경과해도 발송)
   - 수정: !lastReminderAt → 3일 경과 확인 후 return true

변경 사항:
- threeDaysAgo 변수 제거
- threeDaysSinceCreation 변수 추가 (createdAt + 3일)
- 주석 명확화

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

Refs: docs/26-03-11-web-accessibility-audit.md P1 #10
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 새로운 기능 추가 / 일부 코드 추가 / 일부 코드 수정 (리팩토링과 구분) / 디자인 요소 수정 🚨 fix 버그 수정 / 에러 해결

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant