# 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에서 이관) ```sql 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 라우팅 변경 ```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 호환) --- ## 배포 순서 (안전 우선) 1. **코드 개발** — personal 서비스 + lotto-backend 정리 + 인프라 변경 2. **git push** — 자동 배포 트리거 3. **NAS에서 데이터 디렉토리 준비** — `mkdir -p data/personal` 4. **기존 portfolio.db 이동** — `cp data/portfolio/portfolio.db data/personal/personal.db` 5. **lotto.db에서 Blog/Todo 데이터 복사**: ```bash sqlite3 data/lotto.db ".dump todos" | sqlite3 data/personal/personal.db sqlite3 data/lotto.db ".dump blog_posts" | sqlite3 data/personal/personal.db ``` 6. **컨테이너 재시작** — `docker compose restart personal` 7. **검증** — API 호출로 데이터 건수 대조 8. **lotto.db 원본 테이블** — 삭제하지 않고 당분간 유지 --- ## 프론트엔드 변경 없음. 모든 API 호출이 상대경로(`/api/todos`, `/api/blog/posts`, `/api/profile/`)이므로 nginx 라우팅 변경만으로 자동 적용. --- ## 리스크 - **낮음**: Blog/Todo는 lotto 테이블과 FK/공유 쿼리 없음 - **롤백**: lotto.db 원본 테이블 유지 + nginx 라우팅 원복으로 즉시 롤백 가능 - **다운타임**: nginx reload 순간 (~1초)