You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
graph LR
SHARED["@blog-study/shared<br/>DB 스키마 · 타입 · 유틸"]
BOT["@blog-study/bot<br/>Discord 봇"]
WEB["@blog-study/web<br/>웹 대시보드"]
BOT -->|workspace:*| SHARED
WEB -->|workspace:*| SHARED
Loading
패키지
설명
배포
packages/shared
Drizzle 스키마, 타입, 유틸
npm (workspace 내부)
packages/bot
Discord 봇 (스케줄러 + 이벤트 핸들러, 슬래시 커맨드 없음)
AWS EC2 (Docker)
packages/web
Next.js 대시보드, API Routes
Vercel
인증 아키텍처
sequenceDiagram
actor User
participant Browser
participant Middleware
participant NextAPI as API Route
participant SupaAuth as Supabase Auth
participant Discord as Discord OAuth
participant DB as PostgreSQL
User->>Browser: "Discord로 로그인" 클릭
Browser->>SupaAuth: signInWithOAuth({ provider: 'discord' })
SupaAuth->>Discord: OAuth2 Authorization
Discord-->>User: 권한 동의 화면
User->>Discord: 승인
Discord-->>SupaAuth: Authorization Code
SupaAuth-->>Browser: Redirect → /auth/callback?code=xxx
Browser->>NextAPI: GET /auth/callback?code=xxx
NextAPI->>SupaAuth: exchangeCodeForSession(code)
SupaAuth-->>NextAPI: Session + Cookies
NextAPI-->>Browser: Redirect → /dashboard (Set-Cookie)
Note over Browser,Middleware: 이후 모든 요청
Browser->>Middleware: Request + Session Cookie
Middleware->>SupaAuth: getUser() (세션 갱신)
SupaAuth-->>Middleware: User + Discord Identity
Middleware->>Middleware: 라우트 보호 체크
Middleware-->>Browser: Response (갱신된 Cookie)
Browser->>NextAPI: API 요청 + Session Cookie
NextAPI->>SupaAuth: getUser()
SupaAuth-->>NextAPI: User (Discord ID)
NextAPI->>DB: members WHERE discord_id = ?
DB-->>NextAPI: Member Data
NextAPI-->>Browser: JSON Response
Loading
인증 레이어 분리
컨텍스트
인증 방식
Discord ID 추출
웹 브라우저
Supabase Auth (Discord OAuth PKCE)
user.identities[].id where provider === 'discord'
웹 미들웨어
@supabase/ssr 쿠키 기반 세션
동일
웹 API Route
createClient() → getUser()
동일
Discord 봇
service_role key로 직접 DB 접근
스케줄러/이벤트 핸들러 전용 (슬래시 커맨드 없음)
데이터 흐름
RSS 글 수집 → 알림
flowchart TD
A["pg-boss 스케줄러<br/>5분마다 실행"] --> B["feedsmith로<br/>RSS 피드 파싱"]
B --> C{"새 글<br/>감지?"}
C -->|No| A
C -->|Yes| D["posts 테이블 저장"]
D --> E["출석 상태 업데이트"]
E --> F["활동 점수 부여 (봇: blog_post)"]
F --> G["Discord 채널 알림"]
G --> H["FCM 푸시 알림<br/>(웹 내부 API 호출, fire-and-forget)"]
Loading
큐레이션 추천 흐름
flowchart TD
A["pg-boss 스케줄러<br/>매일 09:00"] --> B["활성 소스<br/>RSS 크롤링"]
B --> C["curation_items 저장<br/>키워드 기반 relevance_score 계산"]
C --> D["매일 10:00<br/>최고 점수 아이템 선택"]
D --> E["Discord #큐레이션 공유"]
F["웹 /curation 페이지"] --> G{"정렬 모드"}
G -->|recommended| H["user.interests ∩ item.tags<br/>오버랩 점수 정렬"]
G -->|latest| I["publishedAt DESC"]
Loading
라우트 구조
웹 페이지
그룹
경로
설명
보호
Public
/
랜딩 페이지 (다크 모드, Framer Motion, DB 스탯 ISR 60s, 인증 시 → /dashboard)
erDiagram
members ||--o{ posts : "작성"
members ||--o{ attendance : "출석"
members ||--o{ fines : "벌금"
members ||--o{ activity_scores : "활동점수"
members ||--o{ post_views : "조회"
members ||--o{ post_comments : "포스트댓글"
members ||--o{ board_posts : "게시글"
members ||--o{ board_comments : "댓글"
members ||--o{ fcm_tokens : "FCM토큰"
members ||--o{ notification_preferences : "알림설정"
rounds ||--o{ posts : "회차"
rounds ||--o{ attendance : "회차"
rounds ||--o{ fines : "회차"
posts ||--o{ post_comments : "포스트댓글"
posts ||--o{ post_views : "조회기록"
curation_sources ||--o{ curation_items : "수집"
board_posts ||--o{ board_comments : "댓글"
members {
uuid id PK
varchar discord_id UK
varchar name
varchar part
varchar blog_url
varchar status
text[] interests
boolean onboarding_completed
}
rounds {
serial id PK
integer round_number UK
date start_date
date end_date
boolean is_current
}
posts {
uuid id PK
uuid member_id FK
integer round_id FK
varchar title
varchar url UK
timestamp published_at
text description
varchar thumbnail_url
integer comment_count
}
attendance {
uuid id PK
uuid member_id FK
integer round_id FK
varchar status
}
fines {
uuid id PK
uuid member_id FK
integer round_id FK
varchar type
integer amount
varchar status
}
activity_scores {
uuid id PK
uuid member_id FK
varchar type
integer points
date date
}
post_views {
uuid id PK
uuid member_id FK
uuid post_id FK
}
post_comments {
uuid id PK
uuid post_id FK
uuid member_id FK
uuid parent_id FK
text content
boolean is_secret
}
board_posts {
uuid id PK
uuid member_id FK
varchar category
varchar title
jsonb content
boolean is_notice_banner
integer comment_count
}
board_comments {
uuid id PK
uuid post_id FK
uuid member_id FK
uuid parent_id FK
text content
}
curation_sources {
uuid id PK
varchar url UK
varchar name
varchar category
text[] tags
}
fcm_tokens {
uuid id PK
uuid member_id FK
text token
text device_info
timestamp last_used_at
}
notification_preferences {
uuid id PK
uuid member_id FK
varchar type
boolean enabled
}
curation_items {
uuid id PK
uuid source_id FK
varchar title
varchar url UK
varchar category
text[] tags
real relevance_score
}
랜딩 페이지: Linear 스타일 다크 모드 원페이지 (7섹션: Nav/Hero/Stats/Bento/HowItWorks/Marquee/CTA). 큐시즘 블루 그라디언트 (#0091FF→#004DFF), Framer Motion 애니메이션, DB 스탯 ISR 60s
공지 배너: 전역 상단 스카이블루 배너 (NoticeBanner), 관리자가 공지 글에서 활성화 (1개만), 접기/닫기 localStorage 유지
Dialog/AlertDialog: Safari PWA 스크롤 대응 — flex flex-col + inset-y-0 my-auto 센터링 + overflow-y-auto (grid/transform 방식은 Safari에서 클리핑 발생)
Pull-to-Refresh: 커스텀 터치 제스처 기반 새로고침 (PullToRefresh + usePullToRefresh), Safari PWA 최적화, 다이얼로그 열림 시 data-scroll-locked 가드로 비활성화
PWA: manifest.json + 커스텀 로고 아이콘 (SVG/192/512, maskable) → 홈 화면 추가 지원
OG 이미지: opengraph-image.tsx Edge Runtime 동적 생성 (1200×630, next/og ImageResponse). 다크 테마 + K 로고 + 히어로 카피 + Mock UI 카드 (랭킹/포스트). layout.tsx에 openGraph/twitter 메타데이터 + og:url
FCM 푸시: Firebase Cloud Messaging 서비스 워커 (API route /api/firebase-sw → rewrite /firebase-messaging-sw.js) → 백그라운드 알림. 타입별(댓글/답글/공지/새글) 개별 설정, 테스트 알림 전송 지원. 새 글 알림은 수동 등록(after()) + RSS 수집(봇→웹 내부 API) 모두 지원. 푸시/점수 등 백그라운드 작업은 after() from next/server 사용