Files
reloop-v2/docs/admin-guide.md
reloop 78371cacf8 feat(phase10C): 온보딩 + 프론트 재구성 — 수학 수능 4단원 + PS 트랙
- lib/api.ts: GoalType('math-suneung'|'ps'|...), MathUnit, MATH_UNIT_LABEL,
  MeUser{focusUnits,bojHandle}, ps.* helper (search/bookmark/sync/getProblem)
- onboarding/page.tsx: 5개 GoalType 옵션, math-suneung 단원 멀티셀렉트,
  ps 인풋 + 즉시 동기화 토글, focusUnits/bojHandle PATCH
- profile/page.tsx: 동일 컨트롤 노출 + 검증
- ps/{page,bookmarks/page,sync/page}.tsx: 검색·필터·북마크·동기화 신규 섹션
- components/ps/PsTabs.tsx, layout/{SideNav,BottomNav}.tsx, ui/Icon.tsx:
  PS 진입점 + Phosphor code/bookmark-simple 아이콘
- exam/shared.ts: getExamDurationSeconds → 수학 100분 고정
- 비수학 하드코딩 전면 제거: dashboard/exams/study/subjects/school/assignment
- 카피 갱신: landing/pricing/layout metadata + README + docs(user/admin/pipeline)
- .eslintrc.json: Next core lint 결정성 확보

Phase 10C DoD 통과 — pnpm build + lint 0 에러, /ps /ps/bookmarks /ps/sync 라우트 생성,
grep -rE '국어|영어|한국사|생활과 윤리|사회탐구' frontend/src docs = 0
2026-04-14 19:38:16 +09:00

5.4 KiB

ReLoop 운영 및 배포 가이드

서버 환경

항목
OS Debian 12 (VM on Proxmox)
Node.js 20+
pnpm 9.12+
DB MariaDB (Docker, Port 33006)
프로세스 매니저 PM2
리버스 프록시 Nginx
도메인 reloop.nabomhalang.co.kr (프론트), reloop-api.nabomhalang.co.kr (API)

배포 절차

1. 코드 동기화

# Gitea에서 pull
cd /home/dev/reloop-v2
git fetch origin && git reset --hard origin/master

2. 백엔드 배포

cd backend
pnpm install --no-frozen-lockfile
pnpm prisma:deploy                   # 마이그레이션 적용 (안전, 데이터 보존)
rm -f tsconfig.build.tsbuildinfo     # 빌드 캐시 초기화
pnpm exec tsc -p tsconfig.build.json # 컴파일
pm2 restart reloop-api

pnpm build (nest build) 대신 tsc -p tsconfig.build.json을 직접 사용합니다. nest CLI의 빌드 래퍼가 간헐적으로 dist/ 를 비우는 버그가 있어서 직접 tsc를 실행하는 것이 안정적입니다.

3. 프론트엔드 배포

cd frontend
pnpm install --no-frozen-lockfile
pnpm build                           # Next.js 프로덕션 빌드
cp -r .next/static .next/standalone/.next/
cp -r public .next/standalone/ 2>/dev/null || true
pm2 restart reloop-web

4. 상태 확인

pm2 list | grep reloop
# reloop-api    online
# reloop-web    online

# 헬스체크
curl -s -o /dev/null -w "%{http_code}\n" https://reloop-api.nabomhalang.co.kr/api/health
curl -s -o /dev/null -w "%{http_code}\n" https://reloop.nabomhalang.co.kr/

KICE 기출 데이터 관리

PDF 원본 배치

data/kice/
├── 2025/
│   └── 수학/
│       ├── (25)수학_공통_문제지.pdf
│       ├── (25)수학_확통_문제지.pdf
│       ├── (25)수학_미적분_문제지.pdf
│       ├── (25)수학_기하_문제지.pdf
│       └── (25)수학_정답표.pdf
└── 2026/
    └── (같은 구조)

임포트 실행

cd backend

# 특정 단원 (subject=math)
pnpm cli:kice-import --year=2026 --subject=math

# 특정 연도 전체
pnpm cli:kice-import --year=2026

# 전체 (2025 + 2026)
pnpm cli:kice-import

# 드라이 런 (DB 변경 없이 파일 스캔만)
pnpm cli:kice-import --dry-run

임포트가 하는 일:

  1. PDF 페이지를 문제별로 PNG 크롭 → uploads/problems/{year}/{subject}/{number}.png
  2. 정답표 파싱 (텍스트 또는 OCR)
  3. ProblemSet / Problem / Passage DB upsert (멱등)

OCR 보정 (2025 정답표 등)

2025 정답표는 이미지 기반 PDF라 텍스트 추출이 안 됩니다. Codex Vision OCR로 보정:

pnpm cli:ocr-fix --year=2025 --answers-only    # 정답만 채움
pnpm cli:ocr-fix --year=2025                    # 문항 본문까지 보정
pnpm cli:ocr-fix --dry-run                      # 미리보기

codex CLI가 설치되어 있어야 하고 ChatGPT 로그인이 되어 있어야 합니다.

문제 이미지 서버 전송

임포트는 로컬에서 실행하고 이미지는 서버로 전송:

# 로컬에서
cd backend
tar -czf /tmp/problem-images.tar.gz -C uploads problems/

# 서버에서
cd /home/dev/reloop-v2/backend/uploads
tar -xzf /tmp/problem-images.tar.gz

PM2 ecosystem 설정

ecosystem.config.js 예시:

module.exports = {
  apps: [
    {
      name: 'reloop-api',
      script: 'dist/main.js',
      cwd: '/home/dev/reloop-v2/backend',
      env: { NODE_ENV: 'production', PORT: 3001 },
    },
    {
      name: 'reloop-web',
      script: '.next/standalone/server.js',
      cwd: '/home/dev/reloop-v2/frontend',
      env: { NODE_ENV: 'production', PORT: 3000, HOSTNAME: '0.0.0.0' },
    },
  ],
};

모니터링

pm2 logs reloop-api --lines 50     # API 로그
pm2 logs reloop-web --lines 50     # 프론트 로그
pm2 monit                          # 실시간 CPU/메모리

DB 관리

# Prisma Studio (로컬에서)
cd backend && pnpm exec prisma studio

# 직접 SQL
mysql -h 10.10.10.146 -P 33006 -u reloop_v2 -p reloop_v2

# 마이그레이션 상태 확인
pnpm exec prisma migrate status

# 데이터 초기화 (주의!)
pnpm prisma:reset    # 전체 드롭 + 재생성 + 시드

보안 체크리스트

  • JWT_SECRET이 32자 이상 랜덤 문자열인지 확인
  • CORS_ORIGINS에 프론트엔드 도메인만 허용되어 있는지 확인
  • .env 파일이 git에 커밋되지 않았는지 확인
  • rate limit 동작 확인: for i in {1..70}; do curl -s -o /dev/null -w "%{http_code} " https://reloop-api.nabomhalang.co.kr/api/auth/login; done
  • HTTPS가 nginx에서 강제되는지 확인
  • uploads/ 디렉토리 권한이 644 (파일) / 755 (디렉토리)인지 확인

트러블슈팅

증상 원인 해결
reloop-api 즉시 크래시 dist/main.js 없음 tsc -p tsconfig.build.json 실행
express 모듈 못 찾음 pnpm strict 모드 pnpm install 재실행 (express가 deps에 명시됨)
프론트 빌드 실패 (@phosphor-icons) 패키지 미설치 pnpm install --no-frozen-lockfile
문제 이미지 404 uploads 디렉토리 없음 mkdir -p uploads/problems + 이미지 전송
2025 정답 null 이미지 PDF + OCR 미실행 pnpm cli:ocr-fix --year=2025 --answers-only
마이그레이션 실패 (shadow DB) INDEX 권한 없음 MariaDB root로 GRANT INDEX ON *.* TO 'reloop_v2'@'%'