63ed4d81e5
필기 세션과 자료(library document)를 한 학습 주제(예: 가스기사) 아래로 묶는
1차 컨테이너. 향후 단어장/오디오/문제세트 등 학습 자산이 같은 묶음으로 들어올 수
있도록 응답 구조(sections + stats)를 dict 기반으로 설계.
데이터 모델 (migrations 179~185):
- study_topics: user_id × name partial unique (active 행만), soft delete
- study_sessions.study_topic_id: 1:N nullable FK (ON DELETE SET NULL)
- study_topic_documents: 자료 N:M 매핑 (user_id 반정규화로 권한 격리)
설계 원칙:
- documents.category(자료실 UI 축)와 직교 → 자료실 facet/카테고리 미터치
- StudySession.certification/subject/topic 보존 (세부 메타로 계속 사용)
- study_type은 느슨한 분류 (강한 enum 미사용, jlpt_n3 등 확장 여지)
- polymorphic study_topic_items 영구 금지 → 자산 타입별 조인 테이블 추가 방식
API: /api/study-topics CRUD + /by-document/{id} + 자료/세션 매핑 엔드포인트.
프론트: /study/topics 목록 + /study/topics/[id] 통합 뷰(필기·자료 두 트랙) +
write 폼에 워크스페이스 드롭다운 + study hub 진입 카드.
후속 PR-2 어학 UX, PR-3 오디오 자산, PR-4 AI retrieval scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
29 lines
1.5 KiB
SQL
29 lines
1.5 KiB
SQL
-- 179_study_topics.sql (1/6)
|
|
-- 학습 주제(study_topic) — 학습 워크스페이스 1차 컨테이너.
|
|
-- 필기 세션 + 자료(library document)를 한 주제(예: 가스기사) 아래로 묶는다.
|
|
--
|
|
-- 핵심 개념:
|
|
-- - 단순 폴더/태그가 아닌 학습 워크스페이스 컨테이너 (향후 단어장/오디오/문제세트 등 자산이 같은 컨테이너로 들어옴).
|
|
-- - documents.category(자료실 UI 축) 와 직교. 자료실 facet/카테고리는 건드리지 않는다.
|
|
-- - study_sessions.certification/subject/topic 컬럼은 보존, 본 컨테이너 와 직교한 세부 메타로 계속 사용.
|
|
--
|
|
-- study_type 권장값: certification / language / school / work / general.
|
|
-- 백엔드는 강한 enum 으로 강제하지 않음 (DB 제약 / Pydantic Literal 모두 사용 금지).
|
|
-- 어학 세부(jlpt_n3 등) 같은 분류 확장 여지 확보.
|
|
--
|
|
-- soft delete (deleted_at): 묶음 해제 시 매핑·세션 보호.
|
|
-- partial unique index (180) 가 active 행끼리만 중복 방지 → 삭제된 주제명 재사용 가능.
|
|
|
|
CREATE TABLE IF NOT EXISTS study_topics (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name VARCHAR(120) NOT NULL,
|
|
description TEXT,
|
|
color VARCHAR(20),
|
|
study_type VARCHAR(40),
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
deleted_at TIMESTAMPTZ
|
|
);
|