- architecture.md: routes/ 폴더 구조 추가 - development.md: API 클라이언트 헬퍼 사용법 추가 - code-review.md: 모든 작업 완료로 삭제 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.9 KiB
5.9 KiB
개발/배포 가이드
개발 모드
실행
cd /docker/fromis_9
docker compose up -d --build
컨테이너 구성
| 컨테이너 | 포트 | 설명 |
|---|---|---|
fromis9-frontend |
80 | Vite 개발 서버, HMR 지원 |
fromis9-backend |
80 | Fastify API, --watch 모드 |
fromis9-meilisearch |
7700 | 검색 엔진 |
fromis9-redis |
6379 | 캐시 |
- Vite가
/api,/docs요청을 백엔드로 프록시
로그 확인
# 전체 로그
docker compose logs -f
# 백엔드만
docker compose logs -f fromis9-backend
# 프론트엔드만
docker compose logs -f fromis9-frontend
코드 수정
frontend/,backend/폴더가 컨테이너에 마운트됨node_modules도 호스트 폴더에 직접 설치됨- 코드 수정 시 자동 반영 (HMR, watch)
재시작
# 백엔드만 재시작
docker compose restart fromis9-backend
# 프론트엔드만 재시작
docker compose restart fromis9-frontend
# 전체 재시작
docker compose restart
배포 모드 전환
1. Dockerfile 수정
backend/Dockerfile:
# 개발 모드 주석처리
# FROM node:20-alpine
# ...
# 배포 모드 주석해제
FROM node:20-alpine
WORKDIR /app
RUN apk add --no-cache ffmpeg
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
frontend/Dockerfile:
# 개발 모드 주석처리
# FROM node:20-alpine
# ...
# 배포 모드 주석해제
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
2. 빌드 및 실행
docker compose up -d --build
환경 변수 (.env)
# 서버
PORT=80
# 데이터베이스
DB_HOST=mariadb
DB_PORT=3306
DB_USER=...
DB_PASSWORD=...
DB_NAME=fromis9
# Redis
REDIS_HOST=fromis9-redis
REDIS_PORT=6379
# Meilisearch
MEILI_HOST=http://fromis9-meilisearch:7700
MEILI_MASTER_KEY=...
# JWT
JWT_SECRET=...
# AWS S3
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=...
S3_BUCKET=...
# YouTube API
YOUTUBE_API_KEY=...
Caddy 설정
위치: /docker/caddy/Caddyfile
fromis_9 사이트 설정
fromis9.caadiq.co.kr {
import custom_errors
reverse_proxy fromis9-frontend:80
}
설정 설명
import custom_errors: 공통 에러 페이지 (403, 404, 500, 502, 503)reverse_proxy fromis9-frontend:80: Docker 네트워크로 프론트엔드 컨테이너에 연결- 업로드 크기 제한 없음 (Caddy 기본값)
Caddy 재시작
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
네트워크 구조
인터넷 → Caddy (:443) → fromis9-frontend (:80) → fromis9-backend (:80)
↓
MariaDB, Redis, Meilisearch (내부 네트워크)
프론트엔드 개발 가이드
API 클라이언트 구조
src/api/
├── index.js # 전체 export
├── client.js # api, authApi 헬퍼 (에러 처리, 토큰 주입)
├── public/ # 공개 API (인증 불필요)
│ ├── albums.js # getAlbums, getAlbumByName, getTrack
│ ├── members.js # getMembers
│ └── schedules.js # getSchedules, getSchedule, getCategories
└── admin/ # 관리자 API (인증 필요)
├── auth.js # login, verifyToken
├── albums.js # createAlbum, updateAlbum, deleteAlbum, ...
├── bots.js # getBots, startBot, stopBot, syncBot
├── categories.js # getCategories, createCategory, updateCategory, ...
├── members.js # updateMember
├── schedules.js # getYoutubeInfo, saveYoutube, getXInfo, saveX, ...
├── stats.js # getStats
└── suggestions.js # getDict, saveDict
client.js 헬퍼:
// 공개 API 헬퍼 (인증 불필요)
import { api } from '@/api/client';
api.get('/albums');
api.post('/schedules/suggestions/save', { query: '검색어' });
// 인증 API 헬퍼 (토큰 자동 주입)
import { authApi } from '@/api/client';
authApi.get('/admin/stats');
authApi.post('/admin/schedules', data);
authApi.put('/admin/albums/1', data);
authApi.del('/admin/schedules/1');
사용 예시:
// 공개 API
import { getSchedules, getSchedule } from '@/api/public/schedules';
// 관리자 API
import * as botsApi from '@/api/admin/bots';
React Query 사용 (데이터 페칭)
데이터 페칭 시 useEffect 대신 useQuery를 사용합니다.
이유:
useEffect는 React StrictMode에서 2번 실행됨 (개발 모드)useQuery는 자동 캐싱, 중복 요청 방지, 에러/로딩 상태 관리 제공
예시:
// ❌ Bad - useEffect 사용
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(data => setData(data))
.finally(() => setLoading(false));
}, []);
// ✅ Good - useQuery 사용
import { useQuery } from '@tanstack/react-query';
const { data, isLoading } = useQuery({
queryKey: ['data'],
queryFn: () => fetch('/api/data').then(res => res.json()),
});
캐시 무효화:
import { useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
// 특정 쿼리 무효화
queryClient.invalidateQueries({ queryKey: ['schedules'] });
// 모든 쿼리 무효화
queryClient.invalidateQueries();
유용한 명령어
# 컨테이너 상태 확인
docker compose ps
# 완전 재시작
docker compose down && docker compose up -d --build
# Meilisearch 동기화
curl -X POST https://fromis9.caadiq.co.kr/api/schedules/sync-search \
-H "Authorization: Bearer <token>"
# Redis 확인
docker exec fromis9-redis redis-cli KEYS "*"