Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ firebase-service-account.json
packages/shared/drizzle/*.sql
.vercel
scripts

# Superpowers brainstorming artifacts (local only)
.superpowers
11 changes: 9 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ pnpm --filter @blog-study/bot rss-collect # 수동 RSS 수집 (봇 없이)
- **새 글 푸시 알림**: 수동 등록 + RSS 수집 모두 지원. 대상: active/OB/dormant (작성자 본인 제외), 알림 타입 `new_post`. 봇→웹 내부 API(`/api/internal/new-post-push`, Bearer 인증) 경유
- **포스트 수정**: 본인 또는 관리자만 제목/설명 수정 가능 (`PATCH /api/posts/[id]`)
- **공지 알림**: 게시판 공지 작성 시 FCM 푸시 + Discord 공지채널(`notice_channel_id`) `@everyone` + embed(제목+본문 미리보기 500자) + 웹 딥링크 버튼
- **벌금 DM**: 계좌 정보 포함 (3333333114501 카카오뱅크), 납부완료 시 관리자 채널 알림
- **벌금 납부**: 웹 `/profile/fines`에서 본인 납부 처리 (atomic update), 납부 시 관리자 Discord 채널 알림. 계좌 정보: 3333333114501 카카오뱅크
- **리마인더 푸시**: 벌금 알림(`fine_notification`)/벌금 리마인더(`fine_reminder`)/마감 리마인더(`deadline_reminder`)/지각 독촉(`grace_nudge`)/투표 리마인더(`poll_reminder`) 5종은 Discord DM 대신 FCM 푸시로 발송. 봇→웹 내부 API(`/api/internal/reminder-push`) 경유. `FORCE_SEND_TYPES`로 유저가 끌 수 없음
- **D-Day 계산**: KST 캘린더 날짜 기준 (midnight 비교, 당일=D-Day=0), 제출률은 active 유저만 카운트
- **Discord 알림 로그**: `discord_notification_logs` 테이블에 봇/웹 모든 채널+DM 알림 성공/실패 기록, `logNotification()` 헬퍼 (봇: `notification-logger.ts`, 웹: `notification-log.ts`), 관리자 페이지 "알림 로그" 탭에서 조회 (타입/소스/대상/상태 필터 + 무한 스크롤)
- **알림 로그**: `discord_notification_logs` 테이블에 봇/웹 모든 채널+DM+푸시 알림 성공/실패 기록 (target: `channel`/`dm`/`push`), `logNotification()` 헬퍼 (봇: `notification-logger.ts`, 웹: `notification-log.ts`), 관리자 페이지 "알림 로그" 탭에서 조회 (타입/소스/대상/상태 필터 + 무한 스크롤, 푸시 로그에 수신자 닉네임 표시)
- **비밀답글 가시성**: 비밀 답글은 작성자/포스트작성자/부모댓글작성자/관리자가 열람 가능
- **랭킹**: active + OB + dormant 전원 표시, 웹 페이지 4위부터 (포디움과 분리), 주간랭킹 전원 나열
- **RSS 수집**: active + OB (rssConsent=true만), 포스트 점수는 active만 부여
Expand Down Expand Up @@ -145,6 +146,11 @@ pnpm --filter @blog-study/bot rss-collect # 수동 RSS 수집 (봇 없이)
| `packages/web/src/app/api/push/test/route.ts` | 테스트 푸시 알림 API (레이트 리밋 5/min) |
| `packages/web/src/app/api/notification-preferences/route.ts` | 알림 타입별 설정 CRUD API |
| `packages/web/src/app/api/internal/new-post-push/route.ts` | 새 글 푸시 알림 내부 API (봇→웹, Bearer 인증, rate limit 20/min) |
| `packages/web/src/app/api/internal/reminder-push/route.ts` | 범용 리마인더 푸시 내부 API (봇→웹, 5종 FORCE_SEND_TYPES) |
| `packages/bot/src/lib/push-client.ts` | 봇→웹 내부 API 호출 래퍼 (reminder-push 등) |
| `packages/web/src/app/(user)/profile/fines/page.tsx` | 벌금 상세 페이지 (내 벌금 내역 + 납부 완료) |
| `packages/web/src/app/api/profile/fines/route.ts` | 내 벌금 목록 API |
| `packages/web/src/app/api/fines/[id]/pay/route.ts` | 벌금 납부 완료 API (atomic update) |
| `packages/web/src/app/api/firebase-sw/route.ts` | FCM 서비스 워커 동적 서빙 (rewrite: `/firebase-messaging-sw.js` → `/api/firebase-sw`) |
| `packages/bot/src/scripts/rss-collect.ts` | 수동 RSS 수집 스크립트 (봇 없이 독립 실행) |
| `packages/bot/src/scripts/setup-channels.ts` | 디스코드 채널 일괄 생성 스크립트 |
Expand Down Expand Up @@ -215,6 +221,7 @@ pnpm --filter @blog-study/bot rss-collect # 수동 RSS 수집 (봇 없이)
- **토스트**: sonner (`<Toaster />` in root layout, `position="bottom-center"`, `richColors`)
- **에러 바운더리**: `(user)/error.tsx`, `(admin)/error.tsx` — Sentry 전송 + 리셋 버튼, `global-error.tsx` — 전역 폴백 (다크모드 인라인 스타일)
- **404 페이지**: `not-found.tsx` — 대시보드 링크 포함
- **벌금 상세**: `/profile/fines` — 내 벌금 내역 카드 + "납부 완료" 버튼, 프로필 미납 스탯 카드 클릭 시 이동, 푸시 딥링크 대상
- **CSP**: `next.config.ts`에 Content-Security-Policy 헤더 설정
- **상세 스펙**: `docs/26-03-06-ui-design-system.md` 참조

Expand Down
8 changes: 5 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Blog Study Admin - 시스템 아키텍처

> 최종 업데이트: 2026-03-23 (v12)
> 최종 업데이트: 2026-04-15 (v13)

블로그 글쓰기 스터디 운영 자동화 플랫폼. 웹 대시보드에서 모든 관리/유저 기능을 제공하고, Discord 봇은 스케줄러(RSS 수집/출석/벌금/큐레이션)와 이벤트 핸들러만 담당한다.

Expand Down Expand Up @@ -71,6 +71,7 @@ graph TB
API -->|POST /api/trigger/*| BOT_API
BOT_API --> SCH
BOT_API -->|POST /api/internal/new-post-push| API
BOT_API -->|POST /api/internal/reminder-push| API
API -->|Discord REST API| CH_ADMIN

Web -->|HTTPS| DB
Expand Down Expand Up @@ -252,6 +253,7 @@ flowchart TD
| User | `/members` | 멤버 목록 | 로그인 필수 |
| User | `/members/[id]` | 멤버 상세 | 로그인 필수 |
| User | `/profile` | 프로필 | 로그인 필수 |
| User | `/profile/fines` | 벌금 상세 (내 벌금 내역 + 납부 완료) | 로그인 필수 |
| User | `/profile/notifications` | 알림 설정 (푸시 토글 + 타입별 설정 + 테스트) | 로그인 필수 |
| Admin | `/admin` | 관리자 대시보드 | 관리자 전용 |
| Admin | `/admin/members` | 멤버 관리 | 관리자 전용 |
Expand Down Expand Up @@ -461,7 +463,7 @@ erDiagram
|------|------|------|
| RSS Poller | 5분 | active 멤버 RSS 피드 수집 |
| Attendance Checker | 매주 화 00:00 | 지각/결석 판정 |
| Fine Reminder | 매일 10:00 | 미납 벌금 DM 리마인드 (1일 간격) |
| Fine Reminder | 매일 10:00 | 미납 벌금 FCM 푸시 리마인드 (봇→웹 내부 API, 1일 간격) |
| Curation Crawler | 매일 09:00 | 외부 컨텐츠 크롤링 |
| Daily Content | 매일 10:00 | 큐레이션 컨텐츠 공유 |
| Round Reporter | 회차 종료 시 | 회차 리포트 자동 생성 → #공지사항 |
Expand All @@ -481,7 +483,7 @@ erDiagram
| **SQL Injection** | Drizzle ORM 파라미터화 쿼리 (raw SQL 사용 안 함) | 전체 API Routes |
| **CSRF** | Supabase Auth 쿠키 `SameSite=Lax` | Supabase 기본 설정 |
| **입력 검증** | description 새니타이즈 (제어 문자/제로 너비 유니코드 제거, 300자 제한) | `lib/sanitize.ts` |
| **내부 API 인증** | Bearer 토큰 (`INTERNAL_API_KEY`, timing-safe 비교) + rate limit 20/min + UUID/길이 검증 | `api/internal/new-post-push/` |
| **내부 API 인증** | Bearer 토큰 (`INTERNAL_API_KEY`, timing-safe 비교) + rate limit 20/min + UUID/길이 검증 | `api/internal/new-post-push/`, `api/internal/reminder-push/` |

### 에러 처리

Expand Down
Loading
Loading