블로그 글쓰기 스터디 자동화 플랫폼. 웹 대시보드(관리+유저) + Discord 봇(스케줄러+이벤트).
packages/
├── bot/ # Discord 봇 (스케줄러 + 이벤트 핸들러만, 슬래시 커맨드 없음) → AWS EC2 (Docker)
├── web/ # Next.js 16 대시보드 → Vercel 배포
└── shared/ # 공유 코드 (DB 스키마, 타입, 유틸)
deploy/
└── bot/ # EC2 배포 스크립트 (deploy.sh) — 리포에 커밋하지 않음, EC2에 직접 배치
모노레포: pnpm workspace (pnpm-workspace.yaml)
| 영역 | 기술 |
|---|---|
| Runtime | Node.js 22, TypeScript 5.x |
| Bot | discord.js v14, feedsmith (RSS 파서), pg-boss (PostgreSQL 잡 큐), Sentry (에러 모니터링) |
| Web | Next.js 16 App Router, React 19, shadcn/ui, Tailwind CSS v4, Tiptap (리치 에디터), sonner (토스트), Framer Motion (랜딩 애니메이션), Firebase (FCM 푸시 알림), Sentry (에러 모니터링), Cloudflare R2 (이미지 스토리지) |
| DB | Supabase PostgreSQL + Drizzle ORM (Transaction Pooler, prepare: false) |
| Auth | Supabase Auth (Discord OAuth) + @supabase/ssr |
| 배포 | AWS EC2 Docker (bot), Vercel (web), Supabase (DB + Auth) |
| CI/CD | GitHub Actions → ECR → SSH deploy (bot), Vercel Git Integration (web) |
# 개발
pnpm dev:bot # 봇 로컬 실행
pnpm dev:web # 웹 로컬 실행 (localhost:3300)
# 빌드/테스트
pnpm build # 전체 빌드 (shared → bot/web)
pnpm test # 전체 테스트
pnpm lint # 전체 린트
pnpm typecheck # 타입 체크
# shared 패키지 변경 시
pnpm --filter @blog-study/shared build # 반드시 리빌드
# 봇 전용
pnpm --filter @blog-study/bot rss-collect # 수동 RSS 수집 (봇 없이)- 언어: 모든 코드는 TypeScript strict 모드
- 스타일: Prettier + ESLint (설정 파일 참조)
- 폰트: Pretendard (한국어 최적화)
- 네이밍: camelCase (변수/함수), PascalCase (컴포넌트/타입), kebab-case (파일명)
- DB 컬럼: snake_case (Drizzle ORM이 자동 매핑)
- 커밋: 기존 git log 스타일 따름, Co-Authored-By 포함
- Drizzle SQL:
packages/shared/drizzle/*.sql마이그레이션 파일은 로컬 전용 (.gitignore에 등록됨, 커밋 금지) - 다이얼로그:
window.confirm(),window.alert(),window.prompt()사용 금지 → 커스텀 다이얼로그 컴포넌트 사용 (기존DeletePostDialog패턴 참고) - 토스트:
sonner라이브러리 사용 (toast.success(),toast.error()) — inline 상태 관리 토스트 금지 - API 응답: 모든 API 라우트는
Errors.*()+successResponse()+errorResponse()패턴 사용 (직접NextResponse.json금지) - 캐시: 읽기 전용 API에
withCache(response, maxAge)적용 (members: 60s, ranking: 30s) - 보안: Tiptap content는 저장 전
sanitizeTiptapContent()적용, 댓글 content는sanitizeDescription()적용, 외부 URL fetch/저장 시isSafeUrl()SSRF 체크 (blogUrl, profileImageUrl, 소셜 URL 등 사용자 입력 URL 포함) - 블로그 URL 수정: 프로필 수정 시 blogUrl 변경 가능, 변경 시 rssUrl을 null 초기화 후
after()로 RSS 비동기 재감지 - Discord 알림: 웹에서 직접 Discord REST API 호출 시
discord-notify.ts유틸 사용, 사용자 입력은escapeDiscordMarkdown()적용,allowed_mentions: { parse: [] }필수 - 댓글 길이: 최대 5000자 제한 (API에서 검증)
- 이미지 업로드: Cloudflare R2 (
board-images/{userId}/{uuid}.{ext}), 5MB 제한, rate limit 20회/분/유저 - 포스트 수동등록: 2단계 UX (URL→미리보기→편집→등록), OG HTML 엔티티 자동 디코딩, Discord 알림 토글, 푸시 알림은 Discord 토글과 무관하게 항상 발송
- 새 글 푸시 알림: 수동 등록 + 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자) + 웹 딥링크 버튼 - 벌금 납부: 웹
/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로 유저가 끌 수 없음. FCM 푸시 body는 plain text (**bold**등 마크다운 렌더 안 됨 — 강조 시 따옴표'X'사용) - D-Day 계산: KST 캘린더 날짜 기준 (midnight 비교, 당일=D-Day=0), 제출률은 active 유저만 카운트
- 알림 로그:
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만 부여
- Discord 버튼:
discord-notify.ts에components(Link Button) +allowEveryone옵션 지원 - 백그라운드 작업: API route에서 푸시 알림/점수 부여 등 fire-and-forget 작업은
after()fromnext/server사용 (Vercel 서버리스 종료 방지) - 비밀댓글 알림: 비밀댓글(
isSecret)의 푸시 알림은 내용 마스킹 ('비밀 댓글이 달렸습니다.'), 포스트/게시판 댓글 모두 적용 - 비밀댓글 isSecret 토글: PATCH 시 본인만 변경 가능 (관리자도 타인 비밀 상태 변경 불가)
- 포스트 삭제: 본인 또는 관리자만 가능, 트랜잭션으로 댓글/조회기록/활동점수(blog_post) 일괄 삭제
- 이모지 리액션: 게시판 글 + 포스트에 고정 6종 이모지 (👍👀🔥💡😂✅) 토글,
ReactionBar공용 컴포넌트 (apiPathprop으로 board/posts 구분), 호버(PC)/클릭(모바일) 시 닉네임 팝오버, 복수 선택 가능, 활동 점수/알림 없음 - 인기글 점수:
댓글×3 + 조회수×2 + 리액션×1, 인기순 상위 5개 메달 테두리 (금/은/동/스카이블루/라벤더) - 인기 포스트 알림: 화 08:05 KST 자동 + 수동 트리거, 이전 회차 TOP 5 Discord Embed (이모지별 카운트, 썸네일, 링크 버튼),
popular_posts_channel_id설정 필요, grace period 종료 후 4일 이내만 자동 발송 (중복 방지) - 포스트 회차 필터: 전체/회차별 셀렉트 드롭다운,
/api/rounds에서 동적 조회, 인기순도 선택 회차 기준 - 스터디원 목록: active + dormant + ob 모두 표시, 상태 칩으로 구분 (OB: 황금 파스텔, 휴면: secondary)
| 파일 | 설명 |
|---|---|
packages/shared/src/db/schema.ts |
전체 DB 스키마 (Drizzle) |
packages/shared/src/db/index.ts |
DB 연결 (Transaction Pooler, prepare: false) |
packages/web/src/lib/supabase/client.ts |
브라우저용 Supabase 클라이언트 |
packages/web/src/lib/supabase/server.ts |
서버용 Supabase 클라이언트 (cookies) |
packages/web/src/lib/supabase/middleware.ts |
미들웨어용 세션 갱신 |
packages/web/src/app/(user)/layout.tsx |
사용자 레이아웃 (인증 체크 + 상태별 리다이렉트) |
packages/web/src/app/auth/callback/route.ts |
OAuth 콜백 + 상태별 리다이렉트 |
packages/web/src/lib/admin.ts |
관리자 권한 체크 (withAdminAuth) |
packages/web/src/lib/member-config.ts |
멤버 상태별 라벨/뱃지 설정 |
packages/web/src/lib/rss-detect.ts |
블로그 URL → RSS URL 자동 감지 |
packages/web/src/app/(user)/layout.tsx |
사용자 레이아웃 (상태 체크 + 리다이렉트) |
packages/web/src/app/ |
Next.js 페이지/라우트 |
packages/bot/src/lib/sentry.ts |
봇 Sentry SDK 초기화 (PII 스크러빙, DB URL/토큰 마스킹) |
packages/bot/src/bot.ts |
Discord 클라이언트 초기화 (이벤트 핸들러만) |
packages/bot/src/job-queue.ts |
pg-boss 싱글톤 (시작/종료/조회) |
packages/bot/src/scheduler-registry.ts |
잡 등록 + RSS→Post→Notification→Push 파이프라인 |
packages/bot/src/schedulers/popular-posts.ts |
인기 포스트 TOP 5 Discord 알림 (화 08:01 KST + 수동) |
packages/bot/src/services/score.service.ts |
활동 점수 계산/부여 (봇: blog_post만) |
packages/web/src/lib/score.ts |
웹 활동 점수 부여 (board_post, post_comment, board_comment, post_view) |
packages/web/src/lib/score-config.ts |
활동 점수 타입별 메타데이터 (Single Source of Truth: 라벨, 이모지, 배점, 뱃지 컬러) |
packages/web/src/app/(user)/profile/activity/page.tsx |
활동 내역 페이지 (타입별 필터, 무한 로드) |
packages/web/src/components/board/reaction-bar.tsx |
이모지 리액션 바 공용 컴포넌트 (apiPath prop) |
packages/bot/src/lib/notification-logger.ts |
봇 알림 로그 DB 헬퍼 (logNotification) |
packages/web/src/lib/notification-log.ts |
웹 알림 로그 DB 헬퍼 (logNotification) |
packages/web/src/lib/notification-log-config.ts |
알림 로그 타입별 메타데이터 (라벨, 색상, DM 여부) |
packages/web/src/app/api/admin/bot-logs/route.ts |
관리자 알림 로그 조회 API |
packages/web/src/app/(admin)/admin/bot-operations/notification-logs.tsx |
관리자 알림 로그 UI 컴포넌트 |
packages/web/src/lib/board-auth.ts |
게시판 인증 헬퍼 (getBoardAuth) |
packages/web/src/lib/board-config.ts |
게시판 카테고리/뱃지 설정 |
packages/web/src/lib/api-error.ts |
API 표준 응답/에러 헬퍼 (successResponse, Errors, withCache) |
packages/web/src/lib/sanitize.ts |
입력 새니타이즈 (sanitizeDescription, sanitizeTiptapContent, decodeHtmlEntities, getTodayKST) |
packages/web/src/app/not-found.tsx |
커스텀 404 페이지 |
packages/web/src/app/(user)/error.tsx |
사용자 에러 바운더리 |
packages/web/src/app/(admin)/error.tsx |
관리자 에러 바운더리 |
packages/web/src/components/ui/member-avatar.tsx |
재사용 아바타 컴포넌트 (링크+관리자뱃지) |
packages/web/src/components/board/tiptap-editor.tsx |
Tiptap 리치 에디터 (H1-H3, 구분선, 코드블록, 링크, 한글 IME 대응) |
packages/web/src/components/layout/bottom-nav.tsx |
모바일 하단 탭 바 (사용자: 랭킹/포스트/홈/게시판/스터디원, 관리자: 멤버/회차/출석/벌금/점수/봇) |
packages/web/src/components/layout/notice-banner.tsx |
글로벌 공지 배너 (제목+내용 미리보기, 접기/닫기) |
packages/web/src/components/layout/pull-to-refresh.tsx |
Pull-to-Refresh 컴포넌트 (PWA 터치 제스처) |
packages/web/src/hooks/use-pull-to-refresh.ts |
Pull-to-Refresh 훅 (window.location.reload() 기반) |
packages/web/src/app/api/notice-banner/route.ts |
활성 공지 배너 조회 API |
packages/web/src/app/(admin)/admin/bot-operations/page.tsx |
봇 수동 실행 대시보드 (관리자 전용) |
packages/web/src/app/api/admin/bot-operations/[operationId]/route.ts |
봇 작업 트리거 프록시 (web → bot HTTP API, 30s 타임아웃) |
packages/web/src/app/(admin)/admin/rounds/page.tsx |
회차 관리 페이지 (CRUD + 현재 회차 설정) |
packages/web/src/app/api/profile/edit/route.ts |
프로필 수정 API (blogUrl 변경 시 RSS 재감지, 소셜 URL SSRF 체크) |
packages/web/src/app/api/posts/[id]/route.ts |
포스트 삭제 API (본인/관리자, 댓글+조회+점수 일괄 삭제) |
packages/web/src/app/api/profile/withdraw/route.ts |
유저 자체 탈퇴 API |
packages/web/src/lib/firebase/admin.ts |
Firebase Admin SDK (lazy 초기화, getAdminMessaging()) |
packages/web/src/lib/firebase/client.ts |
Firebase 클라이언트 (FCM 토큰 요청, 포그라운드 메시지) |
packages/web/src/lib/push.ts |
FCM 푸시 전송 (sendPushToMember, sendPushToMembers) |
packages/web/src/hooks/use-push-notification.ts |
푸시 알림 훅 (권한 관리, 토큰 복원, 구독/해제) |
packages/web/src/components/settings/push-notification-settings.tsx |
알림 설정 UI (타입별 토글 + 테스트 전송) |
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 |
디스코드 채널 일괄 생성 스크립트 |
packages/bot/src/scripts/list-channels.ts |
서버 채널 구조 조회 스크립트 |
packages/bot/src/api-server.ts |
봇 HTTP API 서버 (Express, 수동 트리거 엔드포인트, rate limit 10/min) |
packages/web/src/lib/discord-notify.ts |
Discord REST API 채널 메시지 전송 유틸 (웹→Discord 직접 알림, 버튼/embed/allowEveryone 지원) |
packages/web/src/lib/r2.ts |
Cloudflare R2 업로드/삭제 유틸 (uploadToR2, deleteFromR2) |
packages/web/src/app/api/board/image/route.ts |
게시판 이미지 업로드 API (R2, 5MB, JPEG/PNG/GIF/WebP) |
packages/web/src/app/api/posts/preview/route.ts |
포스트 URL OG 미리보기 API (제목/설명/썸네일 추출) |
packages/web/src/components/board/image-block.tsx |
Tiptap ImageBlock 커스텀 노드 (리사이즈, 삭제, 캡션) |
packages/web/src/components/board/image-drop-plugin.ts |
Tiptap 이미지 드래그앤드롭 + 붙여넣기 플러그인 |
packages/bot/src/services/round.service.ts |
회차 관리 + ConfigKeys (announcement/notice/curation/admin_notification 채널) |
packages/web/src/components/landing/landing-client.tsx |
랜딩 페이지 클라이언트 (7섹션: Hero, Stats, Bento, HowItWorks, Marquee, CTA, Footer) |
packages/web/src/components/landing/motion.tsx |
랜딩 애니메이션 컴포넌트 (FadeUp, StaggerContainer, CountUp, DrawLine) |
packages/web/src/app/opengraph-image.tsx |
OG 이미지 동적 생성 (Edge Runtime, next/og ImageResponse, 1200×630) |
packages/web/public/logo.svg |
풀 로고 SVG (픽토그램 + 텍스트) |
packages/bot/Dockerfile |
봇 Docker 이미지 (multi-stage, node:22-alpine) |
.github/workflows/bot-deploy.yml |
봇 CI/CD (CI Gate → ECR 빌드/푸시 → SSH 배포) |
.github/workflows/ci.yml |
PR/push CI (lint, typecheck, test, build) |
packages/web/next.config.ts |
Next.js 설정 + Sentry withSentryConfig 래핑 |
packages/web/sentry.client.config.ts |
Sentry 클라이언트 SDK 초기화 (DSN 가드, PII 스크러빙) |
packages/web/sentry.server.config.ts |
Sentry 서버 SDK 초기화 |
packages/web/sentry.edge.config.ts |
Sentry Edge SDK 초기화 |
packages/web/instrumentation.ts |
Next.js instrumentation hook (Sentry 서버/엣지 등록) |
packages/web/src/app/global-error.tsx |
전역 에러 바운더리 (Sentry 전송 + 다크모드 대응) |
- 웹: Supabase Auth → Discord OAuth →
user.identities[].id(Discord ID) →members.discord_id매칭 - 봇:
service_rolekey로 직접 DB 접근 (스케줄러/이벤트 핸들러 전용, 슬래시 커맨드 없음) - 미들웨어: 없음 (Next.js 16 + Sentry withSentryConfig 비호환). 인증 체크는 각 layout에서 처리
- 관리자:
(admin)/layout.tsx에서/api/admin/check호출 → 미인증 시 로그인, 비관리자 시 접근 거부 다이얼로그. API는withAdminAuth래퍼로 서버사이드 권한 체크 - API Route:
createClient()→getUser()→identities배열에서 Discord ID 추출 - 상태 리다이렉트:
auth/callback+(user)/layout.tsx에서 이중 체크 → 상태별 차단 페이지로 리다이렉트 - 랜딩 페이지: 서버 사이드
getUser()체크 → 인증 유저는/dashboard로 redirect
| 상태 | 접근 | 출석/벌금 | 비고 |
|---|---|---|---|
pending_approval |
차단 (/pending) |
제외 | 온보딩 후 기본 상태 |
active |
허용 | 대상 | 관리자 승인 시 전환 |
inactive |
차단 (/inactive) |
제외 | |
dormant |
허용 | 제외 | 관리자가 반복 전환 가능 (1회 제한 해제됨) |
ob |
허용 | 제외 | 관리자 승인 시 선택 가능 |
withdrawn |
차단 | 제외 | soft delete |
- 스타일: Vercel 화이트/블랙 미니멀 + 스카이블루 포인트
- 컴포넌트: shadcn/ui + Radix UI
- 아이콘: lucide-react
- 다크모드: next-themes (시스템 연동)
- 폰트: Pretendard Variable
- 기본 아바타: DiceBear
fun-emoji스타일 (getDefaultAvatar()inutils.ts) - 아바타 리소스: DiceBear - 30+ 스타일, seed 기반 결정적 아바타 생성, API:
https://api.dicebear.com/9.x/{style}/svg?seed={seed} - 레이아웃: 데스크톱 사이드바 + 모바일 하단 탭 바 (사용자/관리자 모드별 탭 자동 전환)
- 사이드바: 모드 전환은 헤더 프로필 드롭다운에서만 가능 (사이드바에 토글 없음)
- 큐레이션: 현재 네비게이션에서 숨김 (TODO: 나중에 활성화 예정), 페이지/로직은 유지
- 포스트: 최신순/인기순 탭, 무한 스크롤, 썸네일(OG 이미지)+그라디언트 폴백, 인기순 상위 3개 금/은/동 메달, 검색(제목/작성자) + 분야 필터(복수 선택)
- 공지 배너: 글로벌 상단 배너 (제목+내용 미리보기, 접기→제목만, 닫기→숨김, 관리자 페이지 미표시)
- 다이얼로그: Safari PWA 스크롤 대응 (flex 레이아웃,
inset-y-0 my-auto센터링,overflow-y-auto,data-scroll-locked가드) - Pull-to-Refresh: 커스텀 터치 제스처 →
window.location.reload()(Safari PWA 최적화, 다이얼로그 열림 시 비활성화) - PWA: 홈 화면 추가 지원 (manifest.json, 서비스 워커 없음)
- 랜딩 페이지: Linear 스타일 다크 모드 원페이지 (큐시즘 블루 그라디언트
#0091FF→#004DFF, Framer Motion 풀 애니메이션, DB 스탯 ISR 60s, 인증 유저/dashboard리다이렉트) - 로고: 커스텀 SVG 픽토그램 (펜촉+화살표, 큐시즘 블루 그라디언트),
icon.svg/icon-192.png/icon-512.png - OG 이미지:
opengraph-image.tsx동적 생성 (Edge Runtime, 1200×630, 다크 테마 + Mock UI 카드),layout.tsx에openGraph/twitter메타데이터 - 토스트: 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참조
| 에이전트 | 용도 |
|---|---|
code-reviewer |
PR 리뷰, 코드 품질 체크 |
qa-expert |
테스트 작성, QA 전략 |
security-auditor |
보안 취약점 검토 |
frontend-developer |
UI 컴포넌트 구현, 반응형 |
devops-engineer |
배포, CI/CD, 인프라 |
ui-designer |
디자인 시스템, 컴포넌트 설계 |
accessibility-tester |
접근성 검증 |
| 커맨드 | 설명 |
|---|---|
/safe-commit |
변경 파일만 명시적 추가 후 커밋 |
/pr |
PR 생성 |
/review |
코드 리뷰 |
/test |
테스트 작성 |
/security |
보안 검토 |
/devops |
DevOps 작업 |
- 프레임워크: Vitest + fast-check (Property-Based Testing)
- 구조:
*.property.test.ts(속성 테스트),*.test.ts(단위 테스트) - 최소 100회 반복: Property-Based Test
- 주석 형식:
Property {N}: {설명}→ Correctness Property 매핑
.env.example 참조. 필수:
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY(Supabase)SUPABASE_SERVICE_KEY,DATABASE_URL,DATABASE_URL_DIRECT(DB)DISCORD_TOKEN,DISCORD_CLIENT_ID,DISCORD_CLIENT_SECRET,DISCORD_GUILD_IDADMIN_DISCORD_IDS(관리자 Discord ID, 쉼표 구분)NEXT_PUBLIC_SENTRY_DSN(Sentry 에러 모니터링, web 전용)SENTRY_DSN(Sentry 에러 모니터링, bot 전용)SENTRY_AUTH_TOKEN(소스맵 업로드, Vercel/CI에서만 설정)FIREBASE_PROJECT_ID,FIREBASE_PRIVATE_KEY,FIREBASE_CLIENT_EMAIL등 (Firebase Admin, 서버용)NEXT_PUBLIC_FIREBASE_*(Firebase 클라이언트,API_KEY/AUTH_DOMAIN/PROJECT_ID/MESSAGING_SENDER_ID/APP_ID/VAPID_KEY)INTERNAL_API_KEY(봇→웹 내부 API 인증, 웹+봇 공유)WEB_URL(봇에서 웹 API 호출 시 base URL, 봇 전용)
env 파일 위치 (2곳):
.env.local— 루트 (shared/bot용)packages/web/.env.local— Next.js용
주의: packages/web/.env.local에도 동일 환경변수 필요 (Next.js는 패키지 디렉토리 기준)
스키마 변경 시 drizzle-kit push까지 직접 실행:
cd packages/shared
export $(grep DATABASE_URL ../../.env.local | head -1 | xargs)
npx drizzle-kit push --force| 문서 | 설명 |
|---|---|
docs/ARCHITECTURE.md |
시스템 아키텍처 (Mermaid 다이어그램) |
docs/26-03-06-tech-decisions.md |
기술 선택 근거 (ADR) |
docs/26-03-06-ui-design-system.md |
UI 디자인 시스템 스펙 |
docs/26-03-06-development.md |
개발 환경 설정 |
docs/26-03-06-checklist.md |
구현 체크리스트 |
docs/26-03-06-schema-summary.md |
DB 스키마 요약 (테이블/Enum/FK) |
docs/26-03-06-patterns.md |
API 패턴 & 코드 규칙 |
docs/ONBOARDING.md |
팀 온보딩 가이드 |
docs/26-03-08-discord-channel-setup.md |
디스코드 채널 세팅 가이드 (큐스팅) |
docs/plans/26-03-08-landing-page-redesign-design.md |
랜딩 페이지 리디자인 디자인 문서 |
docs/plans/26-03-08-landing-page-redesign.md |
랜딩 페이지 구현 플랜 |
- 파이프라인:
devpush → CI Gate (lint+typecheck+test) → ECR 빌드(ARM64) → SSH 배포 - 트리거:
packages/bot/**,packages/shared/**변경 시 +workflow_dispatch - EC2: illdan-mgmt (t4g ARM64),
~/study-admin-bot/deploy.sh+.env - ECR:
699475955307.dkr.ecr.ap-northeast-2.amazonaws.com/study-admin-bot - deploy.sh: ECR 로그인 → pull → 컨테이너 교체 → health check → Discord 웹훅 알림
- 주의:
deploy/bot/deploy.sh는 커밋하지 않음 (EC2에 직접 배치)
yy-mm-dd-{설명}.md — 예: 26-03-03-system-architecture.md
- 설명은 다른 문서와 구분될 정도로 구체적으로 작명
docs/plans/하위도 동일 컨벤션 적용- 단,
docs/ARCHITECTURE.md는 제외하며 업데이트 시에도 네이밍을 그대로 유지