import uuid from datetime import UTC, datetime from sqlalchemy import BigInteger, DateTime, ForeignKey, Index, Integer, String, UniqueConstraint from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base def _utcnow() -> datetime: return datetime.now(UTC) class Image(Base): __tablename__ = "images" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) hash: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True) filename: Mapped[str] = mapped_column(String, nullable=False) mime_type: Mapped[str] = mapped_column(String(20), nullable=False) size_bytes: Mapped[int] = mapped_column(BigInteger, nullable=False) width: Mapped[int] = mapped_column(Integer, nullable=False) height: Mapped[int] = mapped_column(Integer, nullable=False) storage_key: Mapped[str] = mapped_column(String(64), nullable=False) thumbnail_key: Mapped[str | None] = mapped_column(String(70), nullable=True, default=None) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, nullable=False ) image_tags: Mapped[list["ImageTag"]] = relationship( back_populates="image", cascade="all, delete-orphan" ) @property def tags(self) -> list[str]: return [it.tag.name for it in self.image_tags if it.tag] class Tag(Base): __tablename__ = "tags" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name: Mapped[str] = mapped_column(String(64), unique=True, nullable=False, index=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, nullable=False ) image_tags: Mapped[list["ImageTag"]] = relationship(back_populates="tag") class ImageTag(Base): __tablename__ = "image_tags" __table_args__ = ( UniqueConstraint("image_id", "tag_id", name="uq_image_tag"), Index("ix_image_tags_image_id", "image_id"), Index("ix_image_tags_tag_id", "tag_id"), ) image_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("images.id", ondelete="CASCADE"), primary_key=True ) tag_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("tags.id", ondelete="RESTRICT"), primary_key=True ) image: Mapped["Image"] = relationship(back_populates="image_tags") tag: Mapped["Tag"] = relationship(back_populates="image_tags")