import os from pathlib import Path from dotenv import load_dotenv from sqlalchemy import Boolean, ForeignKey, Integer, LargeBinary, String, Text, create_engine, text from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship load_dotenv(Path(__file__).with_name(".evn"), override=True) DATABASE_URL = os.getenv("DATABASE_URL") if not DATABASE_URL: raise RuntimeError("DATABASE_URL is not configured") engine = create_engine(DATABASE_URL, pool_pre_ping=True) class Base(DeclarativeBase): pass class Service(Base): __tablename__ = "services" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(100)) description: Mapped[str] = mapped_column(Text, default="") category: Mapped[str] = mapped_column(String(50)) author: Mapped[str] = mapped_column(String(100), default="anonymous") selected: Mapped[bool] = mapped_column(Boolean, default=False, server_default="false") archive_key: Mapped[str | None] = mapped_column(String(500), nullable=True) files: Mapped[list["ServiceFile"]] = relationship( back_populates="service", cascade="all, delete-orphan" ) class ServiceFile(Base): __tablename__ = "service_files" id: Mapped[int] = mapped_column(primary_key=True) service_id: Mapped[int] = mapped_column(ForeignKey("services.id")) path: Mapped[str] = mapped_column(String(500)) content: Mapped[bytes] = mapped_column(LargeBinary) service: Mapped[Service] = relationship(back_populates="files") def init_db() -> None: Base.metadata.create_all(engine) # Short MVP migration: existing hackathon databases may lack this new field. with engine.begin() as connection: connection.execute( text( "ALTER TABLE services " "ADD COLUMN IF NOT EXISTS selected BOOLEAN NOT NULL DEFAULT FALSE" ) ) connection.execute( text("ALTER TABLE services ADD COLUMN IF NOT EXISTS archive_key VARCHAR(500)") )