Files
Hyungi Ahn 68fa86ea52 feat(markdown): persist extracted images with auth routes
Markdown Canonical Phase 1B.5 — marker 가 추출하던 이미지를 NAS 에 영구 저장하고
DB 메타 + 인증 라우트 + 프론트 swap 까지 wiring.

핵심 변경:
- marker-service /convert 응답에 base64 image 리스트 포함 (stateless 유지, NAS write 권한 X)
- marker_worker 가 NAS `/documents/extracted_images/{doc_id}/` 에 persist + UPSERT +
  고아 row DELETE + md_content ref 를 `docimg:img_NNN` stable scheme 으로 정규화
- /api/documents/{id}/images/{key}/raw 인증 라우트 (Cache-Control private + ETag = content_hash)
- frontend MarkdownDoc 가 placeholder card 안의 docimg ref 를 실제 <img> 로 swap

원칙:
- 이미지 binary = NAS, metadata = Postgres (학습 섹션 패턴 동일)
- image_key sequence 기반 결정적 → 재변환 idempotent
- MARKDOWN_IMAGE_PERSIST=false env 로 rollback 가능 (placeholder card 폴백 자연 유지)

기존 28건 marker success 문서는 본 PR 에서 건드리지 않음 — deploy + 신규 업로드 1건 +
sample 5건 검증 후 scripts/marker_reprocess_existing_success.py 로 targeted reprocess.

plan: ~/.claude/plans/piped-humming-crystal.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:05:41 +09:00

39 lines
1.8 KiB
SQL

-- 236_document_images.sql (1/3) — Markdown Canonical Phase 1B.5 ImgAuth
--
-- marker-service 가 PDF 변환 시 추출한 이미지를 NAS 에 영구 저장하고 메타를 DB 에 기록.
--
-- 저장 위치: NAS `/documents/extracted_images/{document_id}/{image_key}.{ext}`
-- (file_watcher 가 보는 PKM 경로와 분리 — 자동 인덱싱 안 됨).
--
-- 표시: GET /api/documents/{doc_id}/images/{image_key}/raw (인증 필요)
--
-- md_content 의 ref 형식: `![alt](docimg:img_001)` — image_key 가 sequence 기반 결정적
-- 이라 재변환 시 idempotent.
--
-- soft delete 미사용. 재변환 시 UPSERT + 고아 row DELETE + NAS 파일 unlink.
-- ON DELETE CASCADE — 문서 삭제 시 이미지 row 도 정리 (NAS 파일 unlink 는 worker 책임).
--
-- 참고 패턴: study_question_images (PR-8, migration 198). 동일 NFS trail.
--
-- single statement per file (asyncpg exec_driver_sql 제약, feedback_migration_runner_single_statement.md).
CREATE TABLE IF NOT EXISTS document_images (
id BIGSERIAL PRIMARY KEY,
document_id BIGINT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
image_key VARCHAR(32) NOT NULL,
relative_path TEXT NOT NULL,
file_path TEXT NOT NULL,
mime_type TEXT NOT NULL,
file_size BIGINT NOT NULL,
content_hash VARCHAR(64) NOT NULL,
width INTEGER,
height INTEGER,
page_index INTEGER,
alt_text TEXT,
source_slug TEXT,
extraction_engine VARCHAR(32) NOT NULL DEFAULT 'marker',
extraction_engine_version VARCHAR(32),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (document_id, image_key)
);