Initial MVP skeleton with auth, chat persistence, UI and text LLM integration
This commit is contained in:
3
backend/app/db/base_class.py
Normal file
3
backend/app/db/base_class.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
37
backend/app/db/init_db.py
Normal file
37
backend/app/db/init_db.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import logging
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.security import get_password_hash
|
||||
from app.db.base_class import Base
|
||||
from app.db.session import engine
|
||||
from app.db.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def init_db(db: Session) -> None:
|
||||
# MVP: create tables if they don't exist
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
bootstrap_admin(db)
|
||||
|
||||
def bootstrap_admin(db: Session) -> None:
|
||||
admin_login = settings.admin_bootstrap_login
|
||||
admin_pass = settings.admin_bootstrap_password
|
||||
|
||||
user = db.scalar(select(User).where(User.login == admin_login))
|
||||
if not user:
|
||||
logger.info(f"Creating bootstrap admin user: {admin_login}")
|
||||
hashed_password = get_password_hash(admin_pass)
|
||||
admin_user = User(
|
||||
login=admin_login,
|
||||
hashed_password=hashed_password,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(admin_user)
|
||||
db.commit()
|
||||
db.refresh(admin_user)
|
||||
else:
|
||||
logger.info(f"Admin user {admin_login} already exists")
|
||||
70
backend/app/db/models.py
Normal file
70
backend/app/db/models.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey, Text
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.db.base_class import Base
|
||||
|
||||
def generate_uuid() -> str:
|
||||
return uuid.uuid4().hex
|
||||
|
||||
def utc_now() -> datetime:
|
||||
return datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(String, primary_key=True, index=True, default=generate_uuid)
|
||||
login = Column(String, unique=True, index=True, nullable=False)
|
||||
hashed_password = Column(String, nullable=False)
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
sessions = relationship("Session", back_populates="user", cascade="all, delete-orphan")
|
||||
chats = relationship("Chat", back_populates="user", cascade="all, delete-orphan")
|
||||
|
||||
class Session(Base):
|
||||
__tablename__ = "sessions"
|
||||
|
||||
id = Column(String, primary_key=True, index=True, default=generate_uuid)
|
||||
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
|
||||
expires_at = Column(DateTime, nullable=False)
|
||||
|
||||
user = relationship("User", back_populates="sessions")
|
||||
|
||||
class Chat(Base):
|
||||
__tablename__ = "chats"
|
||||
|
||||
id = Column(String, primary_key=True, index=True, default=generate_uuid)
|
||||
user_id = Column(String, ForeignKey("users.id"), nullable=False, index=True)
|
||||
title = Column(String, nullable=False, default="New Chat")
|
||||
model_alias = Column(String, nullable=False)
|
||||
created_at = Column(DateTime, nullable=False, default=utc_now)
|
||||
updated_at = Column(DateTime, nullable=False, default=utc_now, onupdate=utc_now)
|
||||
|
||||
user = relationship("User", back_populates="chats")
|
||||
messages = relationship("Message", back_populates="chat", cascade="all, delete-orphan", order_by="Message.created_at")
|
||||
|
||||
class Message(Base):
|
||||
__tablename__ = "messages"
|
||||
|
||||
id = Column(String, primary_key=True, index=True, default=generate_uuid)
|
||||
chat_id = Column(String, ForeignKey("chats.id"), nullable=False, index=True)
|
||||
role = Column(String, nullable=False) # system, user, assistant
|
||||
content = Column(Text, nullable=False)
|
||||
created_at = Column(DateTime, nullable=False, default=utc_now)
|
||||
|
||||
chat = relationship("Chat", back_populates="messages")
|
||||
attachments = relationship("Attachment", back_populates="message", cascade="all, delete-orphan")
|
||||
|
||||
class Attachment(Base):
|
||||
__tablename__ = "attachments"
|
||||
|
||||
id = Column(String, primary_key=True, index=True, default=generate_uuid)
|
||||
message_id = Column(String, ForeignKey("messages.id"), nullable=False, index=True)
|
||||
filename = Column(String, nullable=False)
|
||||
content_type = Column(String, nullable=False)
|
||||
file_path = Column(String, nullable=False)
|
||||
created_at = Column(DateTime, nullable=False, default=utc_now)
|
||||
|
||||
message = relationship("Message", back_populates="attachments")
|
||||
14
backend/app/db/session.py
Normal file
14
backend/app/db/session.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
engine = create_engine(settings.database_url, pool_pre_ping=True)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
Reference in New Issue
Block a user