init. project

This commit is contained in:
2026-04-13 11:34:23 +08:00
commit c7c0659a85
202 changed files with 31196 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
from .user import User
from .knowledge import KnowledgeBase, Document, DocumentChunk
from .chat import Chat, Message
from .api_key import APIKey
from .tooling import ToolJob, SRSExtraction, SRSRequirement
__all__ = [
"User",
"KnowledgeBase",
"Document",
"DocumentChunk",
"Chat",
"Message",
"APIKey",
"ToolJob",
"SRSExtraction",
"SRSRequirement",
]

View File

@@ -0,0 +1,18 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, VARCHAR
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.models.base import Base, TimestampMixin
class APIKey(Base, TimestampMixin):
__tablename__ = "api_keys"
id = Column(Integer, primary_key=True, index=True)
key = Column(VARCHAR(128), unique=True, index=True, nullable=False)
name = Column(String(255), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
is_active = Column(Boolean, default=True, nullable=False)
last_used_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
user = relationship("User", back_populates="api_keys")

View File

@@ -0,0 +1,9 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, DateTime
from datetime import datetime
Base = declarative_base()
class TimestampMixin:
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)

View File

@@ -0,0 +1,39 @@
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean, Table
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import relationship
from app.models.base import Base, TimestampMixin
# Association table for many-to-many relationship between Chat and KnowledgeBase
chat_knowledge_bases = Table(
"chat_knowledge_bases",
Base.metadata,
Column("chat_id", Integer, ForeignKey("chats.id"), primary_key=True),
Column("knowledge_base_id", Integer, ForeignKey("knowledge_bases.id"), primary_key=True),
)
class Chat(Base, TimestampMixin):
__tablename__ = "chats"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(255), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Relationships
messages = relationship("Message", back_populates="chat", cascade="all, delete-orphan")
user = relationship("User", back_populates="chats")
knowledge_bases = relationship(
"KnowledgeBase",
secondary=chat_knowledge_bases,
backref="chats"
)
class Message(Base, TimestampMixin):
__tablename__ = "messages"
id = Column(Integer, primary_key=True, index=True)
content = Column(LONGTEXT, nullable=False)
role = Column(String(50), nullable=False)
chat_id = Column(Integer, ForeignKey("chats.id"), nullable=False)
# Relationships
chat = relationship("Chat", back_populates="messages")

View File

@@ -0,0 +1,97 @@
from sqlalchemy import Column, Integer, String, ForeignKey, Text, DateTime, JSON, BigInteger, TIMESTAMP, text
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import relationship
from app.models.base import Base, TimestampMixin
from datetime import datetime
import sqlalchemy as sa
class KnowledgeBase(Base, TimestampMixin):
__tablename__ = "knowledge_bases"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(255), nullable=False)
description = Column(LONGTEXT)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
documents = relationship("Document", back_populates="knowledge_base", cascade="all, delete-orphan")
user = relationship("User", back_populates="knowledge_bases")
processing_tasks = relationship("ProcessingTask", back_populates="knowledge_base")
chunks = relationship("DocumentChunk", back_populates="knowledge_base", cascade="all, delete-orphan")
document_uploads = relationship("DocumentUpload", back_populates="knowledge_base", cascade="all, delete-orphan")
class Document(Base, TimestampMixin):
__tablename__ = "documents"
id = Column(Integer, primary_key=True, index=True)
file_path = Column(String(255), nullable=False) # Path in MinIO
file_name = Column(String(255), nullable=False) # Actual file name
file_size = Column(BigInteger, nullable=False) # File size in bytes
content_type = Column(String(100), nullable=False) # MIME type
file_hash = Column(String(64), index=True) # SHA-256 hash of file content
knowledge_base_id = Column(Integer, ForeignKey("knowledge_bases.id"), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
knowledge_base = relationship("KnowledgeBase", back_populates="documents")
processing_tasks = relationship("ProcessingTask", back_populates="document")
chunks = relationship("DocumentChunk", back_populates="document", cascade="all, delete-orphan")
__table_args__ = (
# Ensure file_name is unique within each knowledge base
sa.UniqueConstraint('knowledge_base_id', 'file_name', name='uq_kb_file_name'),
)
class DocumentUpload(Base):
__tablename__ = "document_uploads"
id = Column(Integer, primary_key=True, index=True)
knowledge_base_id = Column(Integer, ForeignKey("knowledge_bases.id", ondelete="CASCADE"), nullable=False)
file_name = Column(String, nullable=False)
file_hash = Column(String, nullable=False)
file_size = Column(BigInteger, nullable=False)
content_type = Column(String, nullable=False)
temp_path = Column(String, nullable=False)
created_at = Column(TIMESTAMP, nullable=False, server_default=text("now()"))
status = Column(String, nullable=False, server_default="pending")
error_message = Column(Text)
# Relationships
knowledge_base = relationship("KnowledgeBase", back_populates="document_uploads")
class ProcessingTask(Base):
__tablename__ = "processing_tasks"
id = Column(Integer, primary_key=True, index=True)
knowledge_base_id = Column(Integer, ForeignKey("knowledge_bases.id"))
document_id = Column(Integer, ForeignKey("documents.id"), nullable=True)
document_upload_id = Column(Integer, ForeignKey("document_uploads.id"), nullable=True)
status = Column(String(50), default="pending") # pending, processing, completed, failed
error_message = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
knowledge_base = relationship("KnowledgeBase", back_populates="processing_tasks")
document = relationship("Document", back_populates="processing_tasks")
document_upload = relationship("DocumentUpload", backref="processing_tasks")
class DocumentChunk(Base, TimestampMixin):
__tablename__ = "document_chunks"
id = Column(String(64), primary_key=True) # SHA-256 hash as ID
kb_id = Column(Integer, ForeignKey("knowledge_bases.id"), nullable=False)
document_id = Column(Integer, ForeignKey("documents.id"), nullable=False)
file_name = Column(String(255), nullable=False)
chunk_metadata = Column(JSON, nullable=True)
hash = Column(String(64), nullable=False, index=True) # Content hash for change detection
# Relationships
knowledge_base = relationship("KnowledgeBase", back_populates="chunks")
document = relationship("Document", back_populates="chunks")
__table_args__ = (
sa.Index('idx_kb_file_name', 'kb_id', 'file_name'),
)

View File

@@ -0,0 +1,76 @@
from datetime import datetime
import sqlalchemy as sa
from sqlalchemy import Column, DateTime, ForeignKey, Integer, JSON, String, Text
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import relationship
from app.models.base import Base, TimestampMixin
class ToolJob(Base, TimestampMixin):
__tablename__ = "tool_jobs"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
tool_name = Column(String(128), nullable=False, index=True)
status = Column(String(32), nullable=False, default="pending")
input_file_name = Column(String(255), nullable=False)
input_file_path = Column(String(512), nullable=False)
error_message = Column(Text, nullable=True)
started_at = Column(DateTime, nullable=True)
completed_at = Column(DateTime, nullable=True)
output_summary = Column(JSON, nullable=True)
user = relationship("User")
srs_extraction = relationship(
"SRSExtraction",
back_populates="job",
uselist=False,
cascade="all, delete-orphan",
)
class SRSExtraction(Base, TimestampMixin):
__tablename__ = "srs_extractions"
id = Column(Integer, primary_key=True, index=True)
job_id = Column(Integer, ForeignKey("tool_jobs.id", ondelete="CASCADE"), nullable=False, unique=True)
document_name = Column(String(255), nullable=False)
document_title = Column(String(255), nullable=False)
generated_at = Column(DateTime, default=datetime.utcnow, nullable=False)
total_requirements = Column(Integer, nullable=False, default=0)
statistics = Column(JSON, nullable=True)
raw_output = Column(JSON, nullable=True)
job = relationship("ToolJob", back_populates="srs_extraction")
requirements = relationship(
"SRSRequirement",
back_populates="extraction",
cascade="all, delete-orphan",
order_by="SRSRequirement.sort_order",
)
class SRSRequirement(Base, TimestampMixin):
__tablename__ = "srs_requirements"
id = Column(Integer, primary_key=True, index=True)
extraction_id = Column(Integer, ForeignKey("srs_extractions.id", ondelete="CASCADE"), nullable=False)
requirement_uid = Column(String(64), nullable=False)
title = Column(String(255), nullable=False)
description = Column(LONGTEXT, nullable=False)
priority = Column(String(16), nullable=False, default="")
acceptance_criteria = Column(JSON, nullable=False)
source_field = Column(String(255), nullable=False)
section_number = Column(String(64), nullable=True)
section_title = Column(String(255), nullable=True)
requirement_type = Column(String(64), nullable=True)
sort_order = Column(Integer, nullable=False, default=0)
extraction = relationship("SRSExtraction", back_populates="requirements")
__table_args__ = (
sa.UniqueConstraint("extraction_id", "requirement_uid", name="uq_srs_extraction_requirement_uid"),
sa.Index("idx_srs_requirements_extraction_sort", "extraction_id", "sort_order"),
)

View File

@@ -0,0 +1,18 @@
from sqlalchemy import Boolean, Column, Integer, String
from sqlalchemy.orm import relationship
from app.models.base import Base, TimestampMixin
class User(Base, TimestampMixin):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
email = Column(String(255), unique=True, index=True, nullable=False)
username = Column(String(255), unique=True, index=True, nullable=False)
hashed_password = Column(String(255), nullable=False)
is_active = Column(Boolean, default=True)
is_superuser = Column(Boolean, default=False)
# Relationships
knowledge_bases = relationship("KnowledgeBase", back_populates="user")
chats = relationship("Chat", back_populates="user")
api_keys = relationship("APIKey", back_populates="user", cascade="all, delete-orphan")