chaekmate 개발일지 — Phase 2: 읽기 단계 기반(user_books + 책장 UI)
chaekmate 개발일지 (2026-04-20)
오늘 한 일 요약
3단계 독서 워크플로(고르기 → 읽기 → 나누기) 중 ‘읽기’ 단계의 기반을 구축했다. 추천받은 책을 ‘읽고 싶어요 / 읽는 중 / 다 읽었어요’ 상태로 관리하는 DB·API·UI를 추가. Phase 3(독후감 자동 생성)로 넘어가기 위한 연결 다리.
PR #6 → 7d0f234 squash merge.
주요 작업
1. DB — user_books 테이블
rated_books와 분리해서 신규 테이블을 만들었다.
CREATE TABLE user_books (
user_id TEXT NOT NULL,
isbn13 TEXT NOT NULL,
status TEXT CHECK (status IN ('want','reading','done')),
added_at TEXT,
started_at TEXT,
finished_at TEXT,
PRIMARY KEY (user_id, isbn13)
);왜 분리했나 — rated_books는 ‘추천 품질 피드백’(liked/disliked/curious/skip)이고, user_books는 ‘독서 진도’(want/reading/done)이다. 의미가 다른 신호를 한 테이블에 섞으면 ‘liked이면서 want는 유효한 조합인가?’ 같은 상태 혼란이 생긴다.
이 분리 덕분에 ’👍 좋아요한 책을 📖 읽는 중’이라는 상태가 자연스럽게 표현된다.
2. 타임스탬프 라이프사이클
status 전이 시 started_at/finished_at을 자동으로 기록하게 구현:
- ’reading’으로 전이 → started_at = now() (이미 값 있으면 유지)
- ’done’으로 전이 → finished_at = now() (이미 값 있으면 유지)
유닛 테스트로 확인: want → reading → done 전이 시 started_at은 최초 reading 시점이 유지되고, finished_at은 done 시점에 새로 기록됨.
3. FastAPI 라우팅 순서 함정
책장 조회에 GET /api/books/my 엔드포인트가 필요했는데, 나중에 /{isbn13}을 추가할 수도 있어 고정 경로를 먼저 등록하는 순서를 지켰다. 반대로 하면 /my가 isbn13=’my’로 매칭되어 400 에러.
FastAPI 라우팅은 등록 순서대로 매칭되므로, 구체적인 경로부터 선언해야 한다는 원칙을 명시적으로 적용.
4. 낙관적 업데이트 (Optimistic Update)
추천 카드에서 상태 토글을 클릭하면 UI가 즉시 반영되고, 서버 응답은 백그라운드에서 기다린다. 실패 시 이전 상태로 롤백. 네트워크 지연 시 UX가 매끄러워진다.
const prevStatus = statusMap.get(isbn);
setStatusMap(prev => new Map(prev).set(isbn, newStatus)); // 즉시 반영
try {
await setBookStatus(isbn, newStatus);
} catch (err) {
setStatusMap(prev => new Map(prev).set(isbn, prevStatus)); // 롤백
showError(err);
}5. Phase 3 준비 — 딥링크
/my-shelf 카드 클릭 시 /chat?isbn=... URL로 이동한다. Phase 3(독후감 자동 생성)에서 이 쿼리를 받아서 ’이 책에 대한 대화 기반 독후감 초안’을 생성할 예정.
Phase 2에서는 이 쿼리를 받기만 하고 실제 처리는 Phase 3에서. 인터페이스만 미리 잡아두면 Phase 간 연결이 매끄러워짐.
구현 프로파일
| 영역 | 변경 |
|---|---|
src/db.py |
user_books 스키마 + 헬퍼 4종 (set_book_status, remove_book_from_shelf, load_user_books, merge_guest_user_books) |
backend/routers/books.py |
신규 라우터 (POST /api/books/status, GET /api/books/my) |
backend/main.py |
books 라우터 등록 |
frontend/src/app/chat/page.tsx |
추천 카드에 📚/📖/✅ 토글 추가 |
frontend/src/app/my-shelf/page.tsx |
3-tab 책장 페이지 (신규) |
frontend/src/lib/types.ts, api.ts |
BookStatus 타입 + API 헬퍼 |
next build → 7 static pages. 유닛 테스트 통과.
배포 상태
- Vercel: main merge 자동 배포됨
- Fly.io: 수동
fly deploy필요 (스키마 + 신규 엔드포인트 반영)
회고
잘된 것 - src/ 기존 모듈 건드리지 않고 헬퍼만 추가 (재작성 없음) - 유닛 테스트로 타임스탬프 라이프사이클 검증 - 낙관적 업데이트로 UX 매끄러움 확보 - Phase 3을 위한 /chat?isbn= 딥링크를 미리 심어둡
배운 것 - rated_books vs user_books 분리 설계의 가치 — 신호의 의미를 분리하면 미래의 복잡도가 줄어든다 - FastAPI 라우팅 순서는 명시적으로 지켜야 함 (암묵적으로 잘 되기를 기대하면 안 됨)
다음: Phase 3 — 독후감 자동 생성
repo: sigolyori/chaekmate
PR: #6