블로그 · 포트폴리오 · 가족 전용 공간을 하나의 서비스로 운영하는 풀스택 웹 애플리케이션.
Live: namubal78.github.io
API: coking-cooding-api.onrender.com
| Layer | Stack |
|---|---|
| Frontend | Next.js 15 · React 19 · TypeScript · Tailwind CSS v4 |
| Backend | Spring Boot 3.4.4 · Java 17 · Spring Security · JWT · OAuth2 |
| Database | PostgreSQL (Supabase) · HikariCP · Hibernate 6 |
| Storage | Supabase Storage |
| Realtime | WebSocket · STOMP · SockJS |
| Push | Web Push VAPID · Service Worker |
| AI | |
| CI/CD | GitHub Actions → Docker (ghcr.io) → Render / GitHub Pages |
GitHub Push
│
├─ deploy-backend.yml
│ └─ Docker build → ghcr.io → Render (Spring Boot)
│ │
│ PostgreSQL (Supabase)
│ Supabase Storage
│
├─ deploy-frontend.yml
│ └─ Next.js static export → GitHub Pages
│
└─ dev-log.yml (일시 비활성화)
└─ 2분 대기 → 백엔드 웹훅 → Claude Haiku API 요약
→ dev_logs 테이블 upsert → Slack 알림
FAMILY_EMAILS)로 접근 제어SameSite=None; Secure 쿠키에 OAuth state 저장하는 CookieOAuth2AuthorizationRequestRepository 구현MvcRequestMatcher로 공개/보호 엔드포인트 분리Render 배포 초기 반복 실패 원인:
ddl-auto: update → Hibernate 스키마 동기화 중 DB 타임아웃 → 290초 포트 스캔 타임아웃해결:
jpa:
hibernate:
ddl-auto: none
datasource:
hikari:
keepalive-time: 60000 # 유휴 커넥션 유지 (Supabase 풀러 timeout 대응)
connection-timeout: 60000
max-lifetime: 1800000
lazy-init + MvcRequestMatcher 충돌도 함께 해결.
/api/messenger/unread 폴링으로 미읽음 카운트 → Navbar 뱃지 표시 (PC/모바일)message_reads 테이블: 사용자별 lastReadId 저장 → 읽음 처리 즉시 반영운영 비용 절감을 위해 현재 비활성화 상태입니다.
GitHub Push
→ GitHub Actions (deploy-backend.yml 완료 후)
→ dev-log.yml 워크플로 실행 (2분 대기)
→ POST /api/dev-log/webhook (HMAC-SHA256 서명 검증)
→ Claude Haiku API: 커밋 diff 기술 요약 생성
→ dev_logs 테이블 upsert (commit_sha UNIQUE)
→ Slack Bot 알림 (성공/실패 분기)
Claude API 요청은 커밋 SHA 기반 멱등 처리 — 워크플로 재실행 시 중복 생성 없음.
파일 업로드 · 조회 · 삭제를 Spring RestTemplate으로 Supabase Storage REST API와 직접 통신:
// 업로드
restTemplate.exchange(
supabaseUrl + "/storage/v1/object/" + bucket + "/" + storagePath,
HttpMethod.POST, new HttpEntity<>(file.getBytes(), headers), String.class
);
// 스토리지 사용량: Storage list API로 실제 파일 크기 합산
// POST /storage/v1/object/list/{bucket} → metadata.size 합산
// DB 컬럼 대신 Storage API 직접 조회 → 기존 파일도 정확히 반영
CookieOAuth2AuthorizationRequestRepository로 CSRF 방어/api/auth/** JWT 발급
/oauth2/** Google · Kakao OAuth2 콜백
/api/blog/posts 블로그 CRUD
/api/blog/comments 댓글 · 대댓글 (비로그인 BCrypt 패스워드)
/api/photos/** 사진 업로드 · 삭제 · 스토리지 용량
/api/workout/** 운동 CRUD · 타이머 · 통계 · 음성인식 NLP
/api/planner/** 플래너 CRUD
/api/messenger/** WebSocket STOMP · 읽음 처리 · 미읽음 카운트
/api/push/** Web Push 구독 · 발송
/api/dev-log/webhook GitHub Actions 개발일지 웹훅
/api/demo/** AI 챗봇 · 파일 업로드 · 결제 데모 (공개)
| 테이블 | 설명 |
|---|---|
users |
OAuth2 이메일 기반 사용자 |
posts |
블로그 게시글 |
comments |
댓글 · 대댓글 (parent_id self-ref) |
dev_logs |
AI 자동 개발 일지 (commit_sha UNIQUE) |
photos |
Supabase Storage 파일 메타데이터 |
planner_items |
플래너 일정 |
exercises |
운동 종목 |
workout_logs |
날짜별 세트 완료 기록 |
messages |
WebSocket 채팅 메시지 |
message_reads |
사용자별 마지막 읽은 메시지 ID |
push_subscriptions |
Web Push VAPID 구독 정보 |
deploy-backend.yml — backend/** 변경 시Docker multi-stage build (gradle → jre-slim)
→ ghcr.io push
→ Render Deploy Hook
→ Slack 성공/실패 알림
deploy-frontend.yml — frontend/** 변경 시npm ci && next build (static export)
→ GitHub Pages (gh-pages branch)
dev-log.yml — 일시 비활성화 (수동 실행만 가능)120초 대기 (Render 재시작 완료 대기)
→ curl POST /api/dev-log/webhook (HMAC 서명)
# Backend
cd backend
./gradlew bootRun
# Frontend
cd frontend
npm install
npm run dev
환경변수: backend/.env.example, frontend/.env.local.example 참고.