feat: 소설 분기 시스템 및 트리 메모장 구현
🌟 주요 기능: - 트리 구조 메모장 시스템 - 소설 분기 관리 (정사 경로 설정) - 중앙 배치 트리 다이어그램 - 정사 경로 목차 뷰 - 인라인 편집 기능 📚 백엔드: - MemoTree, MemoNode 모델 추가 - 정사 경로 자동 순서 관리 - 분기점에서 하나만 선택 가능한 로직 - RESTful API 엔드포인트 🎨 프론트엔드: - memo-tree.html: 트리 다이어그램 에디터 - story-view.html: 정사 경로 목차 뷰 - SVG 연결선으로 시각적 트리 표현 - Alpine.js 기반 반응형 UI - Monaco Editor 통합 ✨ 특별 기능: - 정사 경로 황금색 배지 표시 - 확대/축소 및 패닝 지원 - 드래그 앤 드롭 준비 - 내보내기 및 인쇄 기능 - 인라인 편집 모달
This commit is contained in:
153
database/init/005_create_memo_tree_tables.sql
Normal file
153
database/init/005_create_memo_tree_tables.sql
Normal file
@@ -0,0 +1,153 @@
|
||||
-- 트리 구조 메모장 테이블 생성
|
||||
-- 005_create_memo_tree_tables.sql
|
||||
|
||||
-- 메모 트리 (프로젝트/워크스페이스)
|
||||
CREATE TABLE memo_trees (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
tree_type VARCHAR(50) DEFAULT 'general', -- 'novel', 'research', 'project', 'general'
|
||||
template_data JSONB, -- 템플릿별 메타데이터
|
||||
settings JSONB DEFAULT '{}', -- 트리별 설정
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
is_public BOOLEAN DEFAULT FALSE,
|
||||
is_archived BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
|
||||
-- 메모 노드 (트리의 각 노드)
|
||||
CREATE TABLE memo_nodes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tree_id UUID NOT NULL REFERENCES memo_trees(id) ON DELETE CASCADE,
|
||||
parent_id UUID REFERENCES memo_nodes(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
||||
-- 기본 정보
|
||||
title VARCHAR(500) NOT NULL,
|
||||
content TEXT, -- 실제 메모 내용 (Markdown)
|
||||
node_type VARCHAR(50) DEFAULT 'memo', -- 'folder', 'memo', 'chapter', 'character', 'plot'
|
||||
|
||||
-- 트리 구조 관리
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
depth_level INTEGER DEFAULT 0,
|
||||
path TEXT, -- 경로 저장 (예: /1/3/7)
|
||||
|
||||
-- 메타데이터
|
||||
tags TEXT[], -- 태그 배열
|
||||
node_metadata JSONB DEFAULT '{}', -- 노드별 메타데이터 (캐릭터 정보, 플롯 정보 등)
|
||||
|
||||
-- 상태 관리
|
||||
status VARCHAR(50) DEFAULT 'draft', -- 'draft', 'writing', 'review', 'complete'
|
||||
word_count INTEGER DEFAULT 0,
|
||||
|
||||
-- 시간 정보
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- 제약 조건
|
||||
CONSTRAINT no_self_reference CHECK (id != parent_id)
|
||||
);
|
||||
|
||||
-- 메모 노드 버전 관리 (선택적)
|
||||
CREATE TABLE memo_node_versions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
node_id UUID NOT NULL REFERENCES memo_nodes(id) ON DELETE CASCADE,
|
||||
version_number INTEGER NOT NULL,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
content TEXT,
|
||||
node_metadata JSONB DEFAULT '{}',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
||||
UNIQUE(node_id, version_number)
|
||||
);
|
||||
|
||||
-- 메모 트리 공유 (협업 기능)
|
||||
CREATE TABLE memo_tree_shares (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tree_id UUID NOT NULL REFERENCES memo_trees(id) ON DELETE CASCADE,
|
||||
shared_with_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
permission_level VARCHAR(20) DEFAULT 'read', -- 'read', 'write', 'admin'
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
||||
UNIQUE(tree_id, shared_with_user_id)
|
||||
);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX idx_memo_trees_user_id ON memo_trees(user_id);
|
||||
CREATE INDEX idx_memo_trees_type ON memo_trees(tree_type);
|
||||
CREATE INDEX idx_memo_nodes_tree_id ON memo_nodes(tree_id);
|
||||
CREATE INDEX idx_memo_nodes_parent_id ON memo_nodes(parent_id);
|
||||
CREATE INDEX idx_memo_nodes_user_id ON memo_nodes(user_id);
|
||||
CREATE INDEX idx_memo_nodes_path ON memo_nodes USING GIN(string_to_array(path, '/'));
|
||||
CREATE INDEX idx_memo_nodes_tags ON memo_nodes USING GIN(tags);
|
||||
CREATE INDEX idx_memo_nodes_type ON memo_nodes(node_type);
|
||||
CREATE INDEX idx_memo_node_versions_node_id ON memo_node_versions(node_id);
|
||||
CREATE INDEX idx_memo_tree_shares_tree_id ON memo_tree_shares(tree_id);
|
||||
|
||||
-- 트리거 함수: updated_at 자동 업데이트
|
||||
CREATE OR REPLACE FUNCTION update_memo_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 트리거 생성
|
||||
CREATE TRIGGER memo_trees_updated_at
|
||||
BEFORE UPDATE ON memo_trees
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_memo_updated_at();
|
||||
|
||||
CREATE TRIGGER memo_nodes_updated_at
|
||||
BEFORE UPDATE ON memo_nodes
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_memo_updated_at();
|
||||
|
||||
-- 트리거 함수: 경로 자동 업데이트
|
||||
CREATE OR REPLACE FUNCTION update_memo_node_path()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
-- 루트 노드인 경우
|
||||
IF NEW.parent_id IS NULL THEN
|
||||
NEW.path = '/' || NEW.id::text;
|
||||
NEW.depth_level = 0;
|
||||
ELSE
|
||||
-- 부모 노드의 경로를 가져와서 확장
|
||||
SELECT path || '/' || NEW.id::text, depth_level + 1
|
||||
INTO NEW.path, NEW.depth_level
|
||||
FROM memo_nodes
|
||||
WHERE id = NEW.parent_id;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 경로 업데이트 트리거
|
||||
CREATE TRIGGER memo_nodes_path_update
|
||||
BEFORE INSERT OR UPDATE OF parent_id ON memo_nodes
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_memo_node_path();
|
||||
|
||||
-- 샘플 데이터 (개발용)
|
||||
-- 소설 템플릿 예시
|
||||
INSERT INTO memo_trees (user_id, title, description, tree_type, template_data)
|
||||
SELECT
|
||||
u.id,
|
||||
'내 첫 번째 소설',
|
||||
'판타지 소설 프로젝트',
|
||||
'novel',
|
||||
'{
|
||||
"genre": "fantasy",
|
||||
"target_length": 100000,
|
||||
"chapters_planned": 20,
|
||||
"main_characters": [],
|
||||
"world_building": {}
|
||||
}'::jsonb
|
||||
FROM users u
|
||||
WHERE u.email = 'admin@test.com'
|
||||
LIMIT 1;
|
||||
Reference in New Issue
Block a user