feat(study): study_sessions backend (Phase 1) — 자격증/어학 일반 학습 세션 + assets 연결

iPad 손글씨 필사 / 모바일 암기노트 / 모바일 퀴즈가 같은 데이터를 공유하는
일반 학습 세션 backend. study_type 으로 certification/language 분기.

- migrations/164: study_sessions + study_session_assets DDL + 5 partial indexes
- app/models/study_session.py: StudySession + StudySessionAsset ORM (cascade)
- app/api/study_sessions.py: CRUD + snapshot(PNG) + assets + filter + groups
  - ownership: 모든 endpoint user_id 검증, mismatch 도 404 (정보 누설 방지)
  - 409 중복: UNIQUE(session, document, asset_type, role) 사전 SELECT + IntegrityError 폴백
  - enum 422: study_type / mode / asset_type / role / review_state / order
  - filter: 11개 (study_type, certification, language_code, learning_level,
    subject, topic, review_state, document_id, asset_type, mode, due_before)
  - groups: certification 트리 + language 트리 + has_audio/has_video
  - snapshot: documents.py atomic rename + error_code 패턴 차용
- app/main.py: /api/study-sessions router 등록

plan: ~/.claude/plans/scalable-chasing-stonebraker.md
Phase 1 미사용 필드 (review_state/quiz/ocr/ai_summary/prompt) 는 NULL 허용,
자동 로직은 Phase 2~4 별도 PR 에서 활성.
This commit is contained in:
Hyungi Ahn
2026-04-27 08:15:28 +09:00
parent c6335c9a1e
commit 7804f22dce
4 changed files with 1174 additions and 0 deletions
+2
View File
@@ -17,6 +17,7 @@ from api.memos import router as memos_router
from api.news import router as news_router
from api.search import router as search_router
from api.setup import router as setup_router
from api.study_sessions import router as study_sessions_router
from api.video import router as video_router
from core.config import settings
from core.database import async_session, engine, init_db
@@ -108,6 +109,7 @@ app.include_router(news_router, prefix="/api/news", tags=["news"])
app.include_router(digest_router, prefix="/api/digest", tags=["digest"])
app.include_router(audio_router, prefix="/api/audio", tags=["audio"])
app.include_router(video_router, prefix="/api/video", tags=["video"])
app.include_router(study_sessions_router, prefix="/api/study-sessions", tags=["study-sessions"])
# TODO: Phase 5에서 추가
# app.include_router(tasks.router, prefix="/api/tasks", tags=["tasks"])