from datetime import datetime, timezone from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base def _utcnow() -> datetime: return datetime.now(timezone.utc) class Recipe(Base): __tablename__ = "recipes" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(255), nullable=False) slug: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True) category: Mapped[str | None] = mapped_column(String(100), nullable=True) image_filename: Mapped[str | None] = mapped_column(String(255), nullable=True) preptime: Mapped[str | None] = mapped_column(String(100), nullable=True) cooktime: Mapped[str | None] = mapped_column(String(100), nullable=True) baketime: Mapped[str | None] = mapped_column(String(100), nullable=True) cooltime: Mapped[str | None] = mapped_column(String(100), nullable=True) marinade_time: Mapped[str | None] = mapped_column(String(100), nullable=True) robot_time: Mapped[str | None] = mapped_column(String(100), nullable=True) servings: Mapped[str | None] = mapped_column(String(50), nullable=True) notes: Mapped[str | None] = mapped_column(Text, nullable=True) kcal: Mapped[str | None] = mapped_column(String(50), nullable=True) kh: Mapped[str | None] = mapped_column(String(50), nullable=True) protein: Mapped[str | None] = mapped_column(String(50), nullable=True) fett: Mapped[str | None] = mapped_column(String(50), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, onupdate=_utcnow ) ingredients: Mapped[list["Ingredient"]] = relationship( back_populates="recipe", cascade="all, delete-orphan", order_by="Ingredient.position" ) steps: Mapped[list["Step"]] = relationship( back_populates="recipe", cascade="all, delete-orphan", order_by="Step.position" ) book_links: Mapped[list["BookRecipe"]] = relationship( back_populates="recipe", cascade="all, delete-orphan" ) class Ingredient(Base): __tablename__ = "ingredients" id: Mapped[int] = mapped_column(Integer, primary_key=True) recipe_id: Mapped[int] = mapped_column(ForeignKey("recipes.id"), nullable=False) position: Mapped[int] = mapped_column(Integer, nullable=False) kind: Mapped[str] = mapped_column(String(20), nullable=False, default="item") # item | section_header quantity: Mapped[str | None] = mapped_column(String(50), nullable=True) unit: Mapped[str | None] = mapped_column(String(50), nullable=True) text: Mapped[str] = mapped_column(String(255), nullable=False) recipe: Mapped["Recipe"] = relationship(back_populates="ingredients") class Step(Base): __tablename__ = "steps" id: Mapped[int] = mapped_column(Integer, primary_key=True) recipe_id: Mapped[int] = mapped_column(ForeignKey("recipes.id"), nullable=False) position: Mapped[int] = mapped_column(Integer, nullable=False) text: Mapped[str] = mapped_column(Text, nullable=False) recipe: Mapped["Recipe"] = relationship(back_populates="steps") class Book(Base): __tablename__ = "books" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) recipe_links: Mapped[list["BookRecipe"]] = relationship( back_populates="book", cascade="all, delete-orphan" ) class BookRecipe(Base): __tablename__ = "book_recipes" __table_args__ = (UniqueConstraint("book_id", "recipe_id", name="uq_book_recipe"),) id: Mapped[int] = mapped_column(Integer, primary_key=True) book_id: Mapped[int] = mapped_column(ForeignKey("books.id"), nullable=False) recipe_id: Mapped[int] = mapped_column(ForeignKey("recipes.id"), nullable=False) chapter: Mapped[str | None] = mapped_column(String(100), nullable=True) position: Mapped[int] = mapped_column(Integer, nullable=False, default=0) book: Mapped["Book"] = relationship(back_populates="recipe_links") recipe: Mapped["Recipe"] = relationship(back_populates="book_links")