"""Knowledge Base models. Mirrors the Project feature's shape (human-friendly *code*, creator FK, created/updated timestamps). Hierarchy is: knowledge_base └─ knowledge_topic (unique per (topic, knowledge_base_id)) ├─ knowledge_fact (category_id NULL → fact lives on the topic) └─ knowledge_category (parent NULL → top-level category in topic) ├─ knowledge_fact └─ knowledge_category (parent → nested) `project_knowledge_base` is the M2M link between projects and knowledge bases. Relationships are intentionally kept minimal (no ORM cascade on the self-referential category tree); deletion ordering is handled explicitly in the router to stay clear of FK-ordering surprises under MySQL. """ from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, UniqueConstraint from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.core.config import Base class KnowledgeBase(Base): __tablename__ = "knowledge_bases" id = Column(Integer, primary_key=True, index=True) knowledge_base_code = Column(String(16), unique=True, index=True, nullable=True) title = Column(String(200), nullable=False) description = Column(Text, nullable=True) created_by = Column(Integer, ForeignKey("users.id"), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) last_updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) creator = relationship("User", foreign_keys=[created_by]) class KnowledgeTopic(Base): __tablename__ = "knowledge_topics" __table_args__ = ( UniqueConstraint("topic", "knowledge_base_id", name="uq_knowledge_topic_kb"), ) id = Column(Integer, primary_key=True, index=True) topic = Column(String(200), nullable=False) knowledge_base_id = Column(Integer, ForeignKey("knowledge_bases.id"), nullable=False, index=True) description = Column(Text, nullable=True) created_by = Column(Integer, ForeignKey("users.id"), nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) last_updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) creator = relationship("User", foreign_keys=[created_by]) class KnowledgeCategory(Base): __tablename__ = "knowledge_categories" __table_args__ = ( # NOTE: MySQL treats NULLs as distinct in a UNIQUE index, so this only # enforces uniqueness for non-NULL `parent`. Top-level categories # (parent IS NULL) are de-duped in the router (application-level check). UniqueConstraint("topic_id", "parent", "name", name="uq_knowledge_category_triple"), ) id = Column(Integer, primary_key=True, index=True) name = Column(String(200), nullable=False) parent = Column(Integer, ForeignKey("knowledge_categories.id"), nullable=True, index=True) topic_id = Column(Integer, ForeignKey("knowledge_topics.id"), nullable=False, index=True) description = Column(Text, nullable=True) created_by = Column(Integer, ForeignKey("users.id"), nullable=True) last_updated_by = Column(Integer, ForeignKey("users.id"), nullable=True) class KnowledgeFact(Base): __tablename__ = "knowledge_facts" id = Column(Integer, primary_key=True, index=True) category_id = Column(Integer, ForeignKey("knowledge_categories.id"), nullable=True, index=True) topic_id = Column(Integer, ForeignKey("knowledge_topics.id"), nullable=False, index=True) fact = Column(Text, nullable=False) last_updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) class ProjectKnowledgeBase(Base): __tablename__ = "project_knowledge_bases" __table_args__ = ( UniqueConstraint("project_id", "knowledge_base_id", name="uq_project_knowledge_base"), ) id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id"), nullable=False, index=True) knowledge_base_id = Column(Integer, ForeignKey("knowledge_bases.id"), nullable=False, index=True) class KnowledgeBaseCodeCounter(Base): __tablename__ = "knowledge_base_code_counters" id = Column(Integer, primary_key=True, index=True) prefix = Column(String(16), unique=True, index=True, nullable=False) next_value = Column(Integer, default=0)