49d8f68986
자료실 자료를 사용자가 명시적으로 "1회독 완료" 클릭 시 +1 누적. detail 진입 자동 카운트 ❌. append-only 로그. 데이터: - migrations 174~176: document_reads 테이블 + 인덱스 2개 (단일 statement 분할) ORM: - app/models/document_read.py: DocumentRead (user_id, document_id, read_at) API (app/api/document_reads.py, /api/documents prefix): - POST /api/documents/{id}/read — 회독 +1 - GET /api/documents/{id}/read-stats — {read_count, last_read_at} - DELETE /api/documents/{id}/read/last — 현재 사용자의 그 문서 마지막 1건만 · ownership: WHERE user_id=current_user.id AND document_id=:doc_id · documents 에 user_id 부재 (single-user). multi-user 전환 시 ownership check 추가 필요 — 코드 주석 명시. 응답 확장: - DocumentResponse: read_count(default 0), last_read_at(default None) - /api/documents/library: 페이지 N건 한정 LEFT JOIN 으로 read 통계 매핑 (N+1 회피) - /api/library/tree CategoryTreeNode: unread_count 추가 · 기존 path_docs 가 ancestor 누적 구조라 그대로 활용 — 하위 경로 합산 자동 규칙 (사용자 명시 — 변경 금지): · 같은 날 여러 번 클릭 → 각각 별개 회독 · 실수 클릭 취소 = DELETE /read/last · documents 에 read_count 컬럼 추가 ❌, 로그 기반 COUNT(*) 만 plan: ~/.claude/plans/scalable-chasing-stonebraker.md 브랜치: feature/library-reads (손글씨 트랙과 분리)
23 lines
1.0 KiB
SQL
23 lines
1.0 KiB
SQL
-- 174_document_reads.sql
|
|
-- 자료실 회독 추적 — append-only 로그 (1/3)
|
|
-- plan: ~/.claude/plans/scalable-chasing-stonebraker.md
|
|
--
|
|
-- 단일 statement (asyncpg 제약). 인덱스는 175, 176 으로 분리.
|
|
--
|
|
-- 동작 규칙 (사용자 명시):
|
|
-- - detail 페이지 진입만으로 자동 +1 금지. 명시 클릭 시에만 row insert.
|
|
-- - 같은 날 여러 번 클릭 가능 (각 row).
|
|
-- - 회독 횟수 = COUNT(*), 마지막 시각 = MAX(read_at).
|
|
-- - documents 에 read_count 컬럼 추가하지 않음. 본 로그만으로 집계.
|
|
--
|
|
-- ownership:
|
|
-- - 현재 documents 테이블에 user_id 없음 (single-user). document_reads.user_id 만으로
|
|
-- 사용자 분리. multi-user 전환 시 documents.user_id 추가 후 별도 ownership check 필요.
|
|
|
|
CREATE TABLE IF NOT EXISTS document_reads (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
document_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
read_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|