- music-lab/ 신규 서비스 (포트 18600) - POST /api/music/generate 비동기 음악 생성 (task_id 반환) - GET /api/music/status/:id 폴링 (queued→processing→succeeded/failed) - GET /api/music/library 라이브러리 조회 - POST /api/music/library 트랙 수동 추가 - DELETE /api/music/library/:id 트랙 삭제 (파일 포함) - SQLite: music_tasks + music_library 테이블 - 생성 완료 시 라이브러리 자동 등록 - AI 서버 응답: binary audio / JSON audio_url 모두 지원 - nginx: /api/music/ 프록시 + /media/music/ 오디오 파일 직접 서빙 - docker-compose: music-lab 서비스 + frontend 볼륨 마운트 추가 - CLAUDE.md 업데이트 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
13 KiB
13 KiB
CLAUDE.md — web-backend 프로젝트 가이드
Claude Code가 이 프로젝트를 작업할 때 참조하는 설정 및 구조 문서.
1. 프로젝트 개요
Synology NAS 기반의 개인 웹 플랫폼 백엔드 모노레포.
- 서비스: lotto-lab, stock-lab, travel-album, music-lab, deployer
- 프론트엔드: 별도 레포 (React + Vite SPA), 빌드 산출물만 NAS에 배포
- 인프라: Docker Compose + Nginx(리버스 프록시) + Gitea Webhook 자동 배포
2. NAS 환경
| 항목 | 값 |
|---|---|
| 장비 | Synology NAS |
| CPU | Intel Celeron J4025 (2 Core, 2.0 GHz) |
| 메모리 | 18 GB |
| Docker | Synology Container Manager |
| Git 서버 | Gitea (self-hosted, NAS 내부) |
| AI 서버 | Windows PC (192.168.45.59:8000) — NVIDIA 3070 Ti + Ollama |
3. NAS 디렉토리 구조
/volume1
├── docker/webpage/ # 운영 런타임 (Docker Compose 실행 위치)
│ ├── backend/ # lotto-backend 소스 (rsync 동기화)
│ ├── stock-lab/ # stock-lab 소스 (rsync 동기화)
│ ├── travel-proxy/ # travel-proxy 소스 (rsync 동기화)
│ ├── deployer/ # deployer 소스 (rsync 동기화)
│ ├── nginx/default.conf # Nginx 설정
│ ├── scripts/deploy.sh # Webhook 트리거 배포 스크립트
│ ├── docker-compose.yml
│ ├── .env # 운영 환경변수
│ ├── data/lotto.db # SQLite DB
│ └── data/music/ # 생성된 오디오 파일 (music-lab)
│
├── workspace/web-page-backend/ # Git 레포 클론 위치 (REPO_PATH)
│
└── web/images/webPage/travel/ # 원본 여행 사진 (RO 마운트)
4. Docker 서비스 & 포트
| 컨테이너 | 포트 | 역할 |
|---|---|---|
lotto-backend |
18000 | 로또 데이터 수집·분석·추천 API |
stock-lab |
18500 | 주식 뉴스·AI 분석·KIS API 연동 |
music-lab |
18600 | AI 음악 생성·라이브러리 관리 API |
travel-proxy |
19000 | 여행 사진 API + 썸네일 생성 |
lotto-frontend (nginx) |
8080 | 정적 SPA 서빙 + API 리버스 프록시 |
webpage-deployer |
19010 | Gitea Webhook 수신 → 자동 배포 |
5. Nginx 라우팅 규칙
| 경로 | 프록시 대상 | 비고 |
|---|---|---|
/api/ |
lotto-backend:8000 |
lotto API (기본) |
/api/travel/ |
travel-proxy:8000 |
travel API |
/api/stock/ |
stock-lab:8000 |
stock API |
/api/trade/ |
stock-lab:8000 |
KIS 실계좌 API |
/api/portfolio |
stock-lab:8000 |
trailing slash 유무 모두 매칭 |
/api/music/ |
music-lab:8000 |
AI 음악 생성·라이브러리 API |
/webhook, /webhook/ |
deployer:9000 |
Gitea Webhook |
/media/music/ |
/data/music/ (파일 직접 서빙) |
생성된 오디오 파일 |
/media/travel/.thumb/ |
/data/thumbs/ (파일 직접 서빙) |
썸네일 캐시 |
/media/travel/ |
/data/travel/ (파일 직접 서빙) |
원본 사진 |
/assets/ |
정적 파일 (장기 캐시) | Vite 해시 파일 |
/ |
SPA fallback (try_files → index.html) |
6. 기술 스택
| 레이어 | 기술 |
|---|---|
| Backend 언어 | Python 3.12 |
| API 프레임워크 | FastAPI |
| DB | SQLite (/app/data/*.db) |
| 스케줄러 | APScheduler |
| 컨테이너 | Docker (python:3.12-slim 기반) |
| AI 연동 | Ollama (Llama 3.1) — Windows PC (192.168.45.59) |
| 주식 API | KIS (한국투자증권) Open API |
7. 자동 배포 흐름
개발자 git push → Gitea → Webhook (HMAC SHA256 검증)
→ deployer 컨테이너 → /scripts/deploy.sh
→ rsync(REPO→RUNTIME) → docker compose up -d --build
- 배포 스크립트 위치:
scripts/deploy-nas.sh(레포) /scripts/deploy.sh(런타임) - 환경변수 파일:
.env(RUNTIME_PATH, REPO_PATH, PHOTO_PATH, PUID, PGID 등) - 백업:
.releases/디렉토리에 자동 백업
8. 로컬 개발 환경
# .env 기본값으로 즉시 실행 가능 (RUNTIME_PATH=., PHOTO_PATH=./mock_data/photos)
docker compose up -d
| 서비스 | 로컬 URL |
|---|---|
| Frontend + API | http://localhost:8080 |
| Lotto Backend | http://localhost:18000 |
| Travel API | http://localhost:19000 |
| Stock Lab | http://localhost:18500 |
9. 서비스별 핵심 정보
lotto-lab (backend/)
- DB:
/app/data/lotto.db - 데이터 소스:
smok95.github.io/lotto/results/ - 파일 구조:
main.py,db.py,recommender.py,collector.py,checker.py,generator.py,analyzer.py,utils.py
lotto.db 테이블
| 테이블 | 설명 |
|---|---|
draws |
로또 당첨번호 |
recommendations |
추천 이력 (즐겨찾기·태그·채점 포함) |
simulation_runs |
시뮬레이션 실행 기록 |
simulation_candidates |
시뮬레이션 후보 (점수 5종) |
best_picks |
현재 활성 최적 번호 20개 (is_active 플래그로 교체) |
todos |
투두리스트 (UUID PK) |
blog_posts |
블로그 글 (tags: JSON 배열) |
스케줄러 job
- 09:10 / 21:10 매일 — 당첨번호 동기화 + 채점 (
sync_latest→check_results_for_draw) - 00:05, 04:05, 08:05, 12:05, 16:05, 20:05 — 몬테카를로 시뮬레이션 (20,000후보 → 상위100 → best_picks 20개 교체)
lotto-lab API 목록
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/lotto/latest |
최신 당첨번호 |
| GET | /api/lotto/{drw_no} |
특정 회차 |
| GET | /api/lotto/stats |
번호 빈도 통계 |
| GET | /api/lotto/analysis |
5가지 통계 분석 리포트 |
| GET | /api/lotto/best |
시뮬레이션 최적 번호 (기본 20쌍) |
| GET | /api/lotto/simulation |
시뮬레이션 상세 결과 |
| GET | /api/lotto/recommend |
통계 기반 추천 |
| GET | /api/lotto/recommend/heatmap |
히트맵 기반 추천 |
| GET | /api/lotto/recommend/batch |
배치 추천 |
| POST | /api/lotto/recommend/batch |
배치 추천 저장 |
| POST | /api/admin/simulate |
시뮬레이션 수동 실행 |
| POST | /api/admin/sync_latest |
당첨번호 수동 동기화 |
| GET | /api/history |
추천 이력 (limit, offset, favorite, tag, sort) |
| PATCH | /api/history/{id} |
즐겨찾기·메모·태그 수정 |
| DELETE | /api/history/{id} |
삭제 |
| GET | /api/todos |
투두 전체 목록 |
| POST | /api/todos |
투두 생성 (status: todo|in_progress|done) |
| PUT | /api/todos/{id} |
투두 수정 |
| DELETE | /api/todos/done |
완료 항목 일괄 삭제 |
| DELETE | /api/todos/{id} |
투두 개별 삭제 |
| GET | /api/blog/posts |
블로그 글 목록 ({"posts": [...]}, date DESC) |
| POST | /api/blog/posts |
블로그 글 생성 (date 미입력 시 오늘) |
| PUT | /api/blog/posts/{id} |
블로그 글 수정 |
| DELETE | /api/blog/posts/{id} |
블로그 글 삭제 |
stock-lab (stock-lab/)
- Windows AI 서버 연동:
WINDOWS_AI_SERVER_URL=http://192.168.45.59:8000 - KIS API 연동으로 실계좌 잔고·거래 조회
- 뉴스 스크래핑: 네이버 증권 + 해외 사이트
- DB:
/app/data/stock.db(articles, portfolio, broker_cash, asset_snapshots, sell_history 테이블) - 파일 구조:
main.py,db.py,scraper.py,price_fetcher.py,holidays.json
stock-lab API 목록
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/stock/news |
뉴스 조회 (limit, category 파라미터) |
| GET | /api/stock/indices |
주요 지표 실시간 조회 |
| POST | /api/stock/scrap |
수동 뉴스 스크랩 트리거 |
| GET | /api/trade/balance |
실계좌 잔고 조회 (Windows AI 서버 프록시) |
| POST | /api/trade/order |
주식 주문 (Windows AI 서버 프록시) |
| GET | /api/portfolio |
포트폴리오 전체 조회 (현재가·손익·예수금 포함) |
| POST | /api/portfolio |
종목 추가 |
| PUT | /api/portfolio/{id} |
종목 수정 |
| DELETE | /api/portfolio/{id} |
종목 삭제 |
| GET | /api/portfolio/cash |
예수금 전체 조회 |
| PUT | /api/portfolio/cash |
예수금 등록·수정 (upsert) |
| DELETE | /api/portfolio/cash/{broker} |
예수금 삭제 |
| POST | /api/portfolio/snapshot |
총 자산 스냅샷 수동 저장 |
| GET | /api/portfolio/snapshot/history |
스냅샷 이력 조회 (days=0: 전체, days=N: 최근 N건) |
| GET | /api/portfolio/sell-history |
매도 내역 조회 (broker, days 필터 선택) |
| POST | /api/portfolio/sell-history |
매도 기록 저장 (id 포함 레코드 반환) |
| PUT | /api/portfolio/sell-history/{id} |
매도 기록 수정 (수정된 레코드 반환) |
| DELETE | /api/portfolio/sell-history/{id} |
매도 기록 삭제 |
매도 히스토리 (sell_history)
- 독립 테이블 —
portfolio테이블과 별개로 관리 sold_at: UTC ISO8601 형식 (new Date().toISOString())realized_profit/realized_rate: 프론트 계산값 저장 (백엔드 재계산 무방)- 응답 정렬:
sold_at DESC(최신순)
총 자산 스냅샷 (asset_snapshots)
- 평일 15:40 APScheduler 자동 실행 (
save_daily_snapshot) - 공휴일 판별:
holidays.json(매년 수동 갱신, KRX 기준) →is_market_open()함수 - 같은 날 중복 저장 시 upsert (date UNIQUE 제약)
- 수동 저장:
POST /api/portfolio/snapshot - 이력 조회:
GET /api/portfolio/snapshot/history?days=30(ASC 정렬, 차트용)
스케줄러 job
- 08:00 매일 — 뉴스 스크랩 (
run_scraping_job) - 15:40 평일 — 총 자산 스냅샷 저장 (
save_daily_snapshot)
music-lab (music-lab/)
- AI 음악 생성 서비스. Windows AI 서버(
MUSIC_AI_SERVER_URL)에 생성 요청 프록시 - 생성된 오디오 파일:
/app/data/music/(Nginx가/media/music/로 직접 서빙) - DB:
/app/data/music.db(music_tasks, music_library 테이블) - 파일 구조:
main.py,db.py - 생성 흐름: POST generate → task_id 반환 → BackgroundTask가 AI 서버 호출 → 파일 저장 → 라이브러리 자동 등록
music-lab API 목록
| 메서드 | 경로 | 설명 |
|---|---|---|
| POST | /api/music/generate |
음악 생성 시작 (task_id 반환, 비동기) |
| GET | /api/music/status/{task_id} |
생성 상태 폴링 (queued→processing→succeeded/failed) |
| GET | /api/music/library |
라이브러리 전체 조회 |
| POST | /api/music/library |
트랙 수동 추가 (201) |
| DELETE | /api/music/library/{id} |
트랙 삭제 (로컬 파일 포함) |
환경변수
MUSIC_AI_SERVER_URL: AI 음악 생성 서버 URL (미설정 시 생성 요청 실패)MUSIC_MEDIA_BASE: 오디오 파일 공개 URL prefix (기본/media/music)MUSIC_DATA_PATH: NAS 오디오 파일 저장 경로 (기본./data/music)
AI 서버 응답 형식 (2가지 모두 지원)
- binary audio (Content-Type: audio/*) → 직접 저장
- JSON
{"audio_url": "..."}→ 해당 URL에서 다운로드 후 저장
travel-proxy (travel-proxy/)
- 원본 사진:
/data/travel/(RO) - 썸네일 캐시:
/data/thumbs/(RW) - 메타:
/data/travel/_meta/region_map.json,regions.geojson - 썸네일: 480×480 리사이징 (Pillow), 온디맨드 생성 후 영구 캐시
- 메모리 캐시: TTL 300초 (앨범 스캔 결과)
travel-proxy API 목록
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/travel/regions |
지역 GeoJSON |
| GET | /api/travel/photos |
사진 목록 (region, page=1, size=20) |
| POST | /api/travel/reload |
메모리 캐시 초기화 |
deployer (deployer/)
- Webhook 검증:
X-Gitea-Signature(HMAC SHA256,compare_digest사용) WEBHOOK_SECRET환경변수로 시크릿 관리- Webhook 수신 즉시
{"ok": True}응답 후 BackgroundTask로 배포 실행 - 배포 타임아웃: 10분 (
scripts/deploy.sh)
10. 주의사항
- Nginx trailing slash:
/api/portfolio는 trailing slash 없이도 매칭되도록 두 location 블록으로 처리 - 라우트 순서:
DELETE /api/todos/done은DELETE /api/todos/{id}보다 반드시 먼저 등록 (FastAPI prefix 매칭 순서) - PUID/PGID: travel-proxy는 NAS 파일 권한을 위해 PUID/PGID를 환경변수로 주입
- 캐시 전략:
index.html은no-store,assets/는 1년 장기 캐시(immutable) - Frontend 배포: git push로 자동 배포되지 않음. 로컬 빌드 후 NAS에 수동 업로드
- .env 파일: 절대 커밋 금지.
.env.example만 레포에 포함 - 공휴일 목록:
stock-lab/app/holidays.json매년 수동 갱신 필요 (KRX 기준) - Windows AI 서버 IP:
192.168.45.59— 공유기 DHCP 고정 예약으로 고정. Tailscale은 Synology에서 TCP 불가(userspace 모드)라 로컬 IP 사용 - 현재가 조회: 네이버 모바일 API → HTML 파싱 폴백, 3분 TTL 캐시 (
price_fetcher.py) - 시뮬레이션 교체 방식:
best_picks는 교체형 — 새 시뮬레이션 실행 시is_active=0으로 비활성화 후 신규 입력