- 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>
221 lines
7.6 KiB
Markdown
221 lines
7.6 KiB
Markdown
# 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초)
|