Files
hyungi_document_server/app/models/document_note.py
T
Hyungi Ahn 24bd363beb feat(library): 자료별 손글씨 노트 (PR-D) — iPad 학습 시 옆에 필기
자료실 자료 detail 에 "필기" 버튼 → 본문 아래에 HandwriteCanvas 띄움.
자료당 사용자별 1개 캔버스 (UNIQUE user×document). upsert 방식.

Backend:
- migrations 177~178: document_notes (user_id, document_id, strokes_json,
  canvas 크기) + UNIQUE(user_id, document_id) + 인덱스
- app/models/document_note.py: DocumentNote ORM
- app/api/document_notes.py:
  · GET    /api/documents/{id}/note  — 단건 조회 (없으면 strokes_json=null)
  · PUT    /api/documents/{id}/note  — upsert (PostgreSQL ON CONFLICT)
  · DELETE /api/documents/{id}/note
  · ownership: WHERE user_id=current_user.id (single-user 가정)
- app/main.py: document_notes_router 등록 (/api/documents prefix)

Frontend:
- routes/documents/[id]/+page.svelte:
  · 자료실 자료 (category='library') 의 affordance row 에 "필기" 토글 추가
  · 클릭 시 GET /note 로 strokes 로드 → HandwriteCanvas 본문 카드 아래 마운트
  · 캔버스 onChange → PUT /note 자동 저장 (HandwriteCanvas 내부 3초 idle 디바운스 활용)
  · 60vh / min-h-[400px] 분할. 모바일에선 본문 아래 스크롤로 자연스럽게.
- HandwriteCanvas 재사용 — sessionId prop 에 documentId 전달.
  localStorage 키도 그대로 사용 (자료별로 namespacing).
2026-04-27 12:38:03 +09:00

45 lines
1.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""document_notes 테이블 ORM — 자료별 손글씨 노트 (자료 1:1).
설계:
- user×document UNIQUE — 자료당 사용자별 한 캔버스.
- upsert 방식. PUT /api/documents/{id}/note 로 strokes_json 전체 갱신.
- 회독 (document_reads, append-only log) 와 별개.
NOTE: documents 에 user_id 부재 (single-user). document_notes.user_id 로
ownership. multi-user 전환 시 documents.user_id 추가 후 별도 check 필요.
"""
from datetime import datetime
from typing import Any
from sqlalchemy import BigInteger, DateTime, ForeignKey, Integer, UniqueConstraint
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
class DocumentNote(Base):
__tablename__ = "document_notes"
__table_args__ = (
UniqueConstraint("user_id", "document_id", name="document_notes_user_id_document_id_key"),
)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
user_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
document_id: Mapped[int] = mapped_column(
BigInteger, ForeignKey("documents.id", ondelete="CASCADE"), nullable=False
)
strokes_json: Mapped[dict[str, Any] | None] = mapped_column(JSONB)
canvas_width: Mapped[int | None] = mapped_column(Integer)
canvas_height: Mapped[int | None] = mapped_column(Integer)
schema_version: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.now, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=datetime.now, onupdate=datetime.now, nullable=False
)