- portfolio/ 디렉토리를 personal/로 리네이밍 - lotto-backend의 Blog/Todo 라우트·CRUD를 personal 서비스로 이전 - lotto-backend에서 Blog/Todo 코드 제거 (DB 테이블 스키마는 유지) - nginx: /api/todos, /api/blog/ 라우팅을 personal로 추가 - docker-compose: portfolio → personal 서비스 변�� - deploy 스크립트: portfolio → personal 반영 데이터 마이그레이션은 배포 후 NAS에서 별도 수행 필요: 1. cp data/portfolio/portfolio.db data/personal/personal.db 2. sqlite3 data/lotto.db ".dump todos" | sqlite3 data/personal/personal.db 3. sqlite3 data/lotto.db ".dump blog_posts" | sqlite3 data/personal/personal.db Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7.6 KiB
7.6 KiB
Personal 서비스 마이그레이션 설계
개요
기존 portfolio 서비스를 personal로 리네이밍하고, lotto-backend에 있던 Blog/Todo 기능을 personal 서비스로 통합한다.
목표: 신규 컨테이너 없이, 개인 콘텐츠(포트폴리오 + 블로그 + 투두)를 하나의 서비스로 통합
제약: 기존 데이터 무손실 이전 필수
아키텍처
변경 전
lotto-backend (lotto.db)
├── 로또 API (/api/lotto/*)
├── 블로그 API (/api/blog/posts) ← 이전 대상
└── 투두 API (/api/todos) ← 이전 대상
portfolio (portfolio.db)
└── 포트폴리오 API (/api/profile/*)
변경 후
lotto-backend (lotto.db)
└── 로또 API (/api/lotto/*) ← Blog/Todo 라우트 제거
personal (personal.db)
├── 포트폴리오 API (/api/profile/*)
├── 블로그 API (/api/blog/posts) ← 통합
└── 투두 API (/api/todos) ← 통합
서비스 속성
| 항목 | 현재 (portfolio) | 변경 후 (personal) |
|---|---|---|
| 디렉토리 | portfolio/ |
personal/ |
| 컨테이너명 | portfolio |
personal |
| 포트 | 18850 | 18850 (유지) |
| DB 파일 | data/portfolio/portfolio.db |
data/personal/personal.db |
| API prefix | /api/profile/ |
/api/profile/ + /api/todos + /api/blog/ |
DB 스키마
personal.db에 기존 5테이블 + 신규 2테이블:
기존 테이블 (portfolio에서 이관)
profile— 프로필 (id=1 싱글턴)careers— 경력projects— 프로젝트skills— 기술스택introductions— 자기소개
신규 추가 테이블 (lotto-backend에서 이관)
CREATE TABLE IF NOT EXISTS todos (
id TEXT PRIMARY KEY
DEFAULT (lower(hex(randomblob(4))) || '-' || lower(hex(randomblob(2)))),
title TEXT NOT NULL,
description TEXT,
status TEXT NOT NULL DEFAULT 'todo'
CHECK(status IN ('todo','in_progress','done')),
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
CREATE INDEX IF NOT EXISTS idx_todos_created ON todos(created_at DESC);
CREATE TABLE IF NOT EXISTS blog_posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL DEFAULT '',
excerpt TEXT NOT NULL DEFAULT '',
tags TEXT NOT NULL DEFAULT '[]',
date TEXT NOT NULL DEFAULT (date('now','localtime')),
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
CREATE INDEX IF NOT EXISTS idx_blog_date ON blog_posts(date DESC);
API 엔드포인트 (personal 서비스 전체)
포트폴리오 (기존 유지)
| 메서드 | 경로 | 인증 | 설명 |
|---|---|---|---|
| GET | /api/profile/public |
- | 공개 데이터 일괄 조회 |
| POST | /api/profile/auth |
- | 비밀번호 인증 → 토큰 |
| GET/PUT | /api/profile/profile |
Bearer | 프로필 조회/수정 |
| GET/POST | /api/profile/careers |
Bearer | 경력 목록/추가 |
| PUT/DELETE | /api/profile/careers/{id} |
Bearer | 경력 수정/삭제 |
| GET/POST | /api/profile/projects |
Bearer | 프로젝트 목록/추가 |
| PUT/DELETE | /api/profile/projects/{id} |
Bearer | 프로젝트 수정/삭제 |
| GET/POST | /api/profile/skills |
Bearer | 기술 목록/추가 |
| PUT/DELETE | /api/profile/skills/{id} |
Bearer | 기술 수정/삭제 |
| GET/POST | /api/profile/introductions |
Bearer | 자기소개 목록/추가 |
| PUT/DELETE | /api/profile/introductions/{id} |
Bearer | 자기소개 수정/삭제 |
| PATCH | /api/profile/introductions/{id}/main |
Bearer | 메인 자기소개 지정 |
투두 (lotto-backend에서 이전, 인증 없음)
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/todos |
전체 목록 |
| POST | /api/todos |
생성 |
| DELETE | /api/todos/done |
완료 일괄 삭제 |
| PUT | /api/todos/{id} |
수정 |
| DELETE | /api/todos/{id} |
삭제 |
블로그 (lotto-backend에서 이전, 인증 없음)
| 메서드 | 경로 | 설명 |
|---|---|---|
| GET | /api/blog/posts |
목록 ({"posts": [...]}) |
| POST | /api/blog/posts |
생성 |
| PUT | /api/blog/posts/{id} |
수정 |
| DELETE | /api/blog/posts/{id} |
삭제 |
Nginx 라우팅 변경
# 추가: /api/todos → personal
location /api/todos {
resolver 127.0.0.11 valid=10s;
set $personal_backend personal:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://$personal_backend$request_uri;
}
# 추가: /api/blog/ → personal
location /api/blog/ {
resolver 127.0.0.11 valid=10s;
set $personal_backend personal:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://$personal_backend$request_uri;
}
# 변경: portfolio → personal
location /api/profile/ {
resolver 127.0.0.11 valid=10s;
set $personal_backend personal:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://$personal_backend$request_uri;
}
기존 /api/ catch-all은 lotto-backend로 유지 (todos/blog 요청은 위의 더 구체적인 location에서 먼저 매칭).
인프라 변경
docker-compose.yml
portfolio서비스 →personal로 리네이밍- 볼륨:
${RUNTIME_PATH}/data/personal:/app/data - 환경변수 동일 (PORTFOLIO_EDIT_PASSWORD 등)
deploy.sh / deploy-nas.sh
- SERVICES, BUILD_TARGETS, CONTAINER_NAMES 등에서
portfolio→personal변경 - DATA_DIRS에서
portfolio→personal변경
lotto-backend 정리
main.py에서 Blog/Todo 라우트 + Pydantic 모델 제거 (약 100줄)db.py에서 Blog/Todo CRUD 함수 제거 (약 130줄)db.py의init_db()에서 todos/blog_posts 테이블 생성 코드는 유지 (기존 DB 호환)
배포 순서 (안전 우선)
- 코드 개발 — personal 서비스 + lotto-backend 정리 + 인프라 변경
- git push — 자동 배포 트리거
- NAS에서 데이터 디렉토리 준비 —
mkdir -p data/personal - 기존 portfolio.db 이동 —
cp data/portfolio/portfolio.db data/personal/personal.db - lotto.db에서 Blog/Todo 데이터 복사:
sqlite3 data/lotto.db ".dump todos" | sqlite3 data/personal/personal.db sqlite3 data/lotto.db ".dump blog_posts" | sqlite3 data/personal/personal.db - 컨테이너 재시작 —
docker compose restart personal - 검증 — API 호출로 데이터 건수 대조
- lotto.db 원본 테이블 — 삭제하지 않고 당분간 유지
프론트엔드
변경 없음. 모든 API 호출이 상대경로(/api/todos, /api/blog/posts, /api/profile/)이므로 nginx 라우팅 변경만으로 자동 적용.
리스크
- 낮음: Blog/Todo는 lotto 테이블과 FK/공유 쿼리 없음
- 롤백: lotto.db 원본 테이블 유지 + nginx 라우팅 원복으로 즉시 롤백 가능
- 다운타임: nginx reload 순간 (~1초)