Files
document-server/backend/src/models/memo_tree.py
Hyungi Ahn f95f67364a feat: 소설 분기 시스템 및 트리 메모장 구현
🌟 주요 기능:
- 트리 구조 메모장 시스템
- 소설 분기 관리 (정사 경로 설정)
- 중앙 배치 트리 다이어그램
- 정사 경로 목차 뷰
- 인라인 편집 기능

📚 백엔드:
- MemoTree, MemoNode 모델 추가
- 정사 경로 자동 순서 관리
- 분기점에서 하나만 선택 가능한 로직
- RESTful API 엔드포인트

🎨 프론트엔드:
- memo-tree.html: 트리 다이어그램 에디터
- story-view.html: 정사 경로 목차 뷰
- SVG 연결선으로 시각적 트리 표현
- Alpine.js 기반 반응형 UI
- Monaco Editor 통합

 특별 기능:
- 정사 경로 황금색 배지 표시
- 확대/축소 및 패닝 지원
- 드래그 앤 드롭 준비
- 내보내기 및 인쇄 기능
- 인라인 편집 모달
2025-08-25 10:25:10 +09:00

112 lines
5.0 KiB
Python

"""
트리 구조 메모장 모델
"""
from sqlalchemy import Column, String, Text, Integer, Boolean, DateTime, ForeignKey, ARRAY, JSON
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
import uuid
from ..core.database import Base
class MemoTree(Base):
"""메모 트리 (프로젝트/워크스페이스)"""
__tablename__ = "memo_trees"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
title = Column(String(255), nullable=False)
description = Column(Text)
tree_type = Column(String(50), default="general") # 'novel', 'research', 'project', 'general'
template_data = Column(JSON) # 템플릿별 메타데이터
settings = Column(JSON, default={}) # 트리별 설정
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
is_public = Column(Boolean, default=False)
is_archived = Column(Boolean, default=False)
# 관계
user = relationship("User", back_populates="memo_trees")
nodes = relationship("MemoNode", back_populates="tree", cascade="all, delete-orphan")
shares = relationship("MemoTreeShare", back_populates="tree", cascade="all, delete-orphan")
class MemoNode(Base):
"""메모 노드 (트리의 각 노드)"""
__tablename__ = "memo_nodes"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tree_id = Column(UUID(as_uuid=True), ForeignKey("memo_trees.id", ondelete="CASCADE"), nullable=False)
parent_id = Column(UUID(as_uuid=True), ForeignKey("memo_nodes.id", ondelete="CASCADE"))
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
# 기본 정보
title = Column(String(500), nullable=False)
content = Column(Text) # Markdown 형식
node_type = Column(String(50), default="memo") # 'folder', 'memo', 'chapter', 'character', 'plot'
# 트리 구조 관리
sort_order = Column(Integer, default=0)
depth_level = Column(Integer, default=0)
path = Column(Text) # 경로 저장 (예: /1/3/7)
# 메타데이터
tags = Column(ARRAY(String)) # 태그 배열
node_metadata = Column(JSON, default={}) # 노드별 메타데이터
# 상태 관리
status = Column(String(50), default="draft") # 'draft', 'writing', 'review', 'complete'
word_count = Column(Integer, default=0)
# 정사 경로 관련 필드
is_canonical = Column(Boolean, default=False) # 정사 경로 여부
canonical_order = Column(Integer, nullable=True) # 정사 경로 순서
story_path = Column(Text, nullable=True) # 정사 경로 문자열
# 시간 정보
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
# 관계
tree = relationship("MemoTree", back_populates="nodes")
user = relationship("User", back_populates="memo_nodes")
parent = relationship("MemoNode", remote_side=[id], back_populates="children")
children = relationship("MemoNode", back_populates="parent", cascade="all, delete-orphan")
versions = relationship("MemoNodeVersion", back_populates="node", cascade="all, delete-orphan")
class MemoNodeVersion(Base):
"""메모 노드 버전 관리"""
__tablename__ = "memo_node_versions"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
node_id = Column(UUID(as_uuid=True), ForeignKey("memo_nodes.id", ondelete="CASCADE"), nullable=False)
version_number = Column(Integer, nullable=False)
title = Column(String(500), nullable=False)
content = Column(Text)
node_metadata = Column(JSON, default={})
created_at = Column(DateTime(timezone=True), server_default=func.now())
created_by = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
# 관계
node = relationship("MemoNode", back_populates="versions")
creator = relationship("User")
class MemoTreeShare(Base):
"""메모 트리 공유 (협업 기능)"""
__tablename__ = "memo_tree_shares"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tree_id = Column(UUID(as_uuid=True), ForeignKey("memo_trees.id", ondelete="CASCADE"), nullable=False)
shared_with_user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
permission_level = Column(String(20), default="read") # 'read', 'write', 'admin'
created_at = Column(DateTime(timezone=True), server_default=func.now())
created_by = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
# 관계
tree = relationship("MemoTree", back_populates="shares")
shared_with_user = relationship("User", foreign_keys=[shared_with_user_id])
creator = relationship("User", foreign_keys=[created_by])