phase 3.2 & 4.1

This commit is contained in:
Danilo Reyes
2025-11-02 00:36:32 -06:00
parent cac1db0ed7
commit d40139822d
21 changed files with 2230 additions and 123 deletions

View File

@@ -1,35 +1,52 @@
"""Image model for uploaded images."""
"""Image database model."""
import uuid
from datetime import datetime
from typing import TYPE_CHECKING
from uuid import UUID, uuid4
from sqlalchemy import BigInteger, Column, DateTime, ForeignKey, Integer, String
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import relationship
from sqlalchemy import BigInteger, DateTime, ForeignKey, Integer, String
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database.base import Base
if TYPE_CHECKING:
from app.database.models.board_image import BoardImage
from app.database.models.user import User
class Image(Base):
"""Image model representing uploaded image files."""
"""
Image model representing uploaded image files.
Images are stored in MinIO and can be reused across multiple boards.
Reference counting tracks how many boards use each image.
"""
__tablename__ = "images"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
filename = Column(String(255), nullable=False, index=True)
storage_path = Column(String(512), nullable=False)
file_size = Column(BigInteger, nullable=False)
mime_type = Column(String(100), nullable=False)
width = Column(Integer, nullable=False)
height = Column(Integer, nullable=False)
image_metadata = Column(JSONB, nullable=False)
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
reference_count = Column(Integer, nullable=False, default=0)
id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True, default=uuid4)
user_id: Mapped[UUID] = mapped_column(
PGUUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
filename: Mapped[str] = mapped_column(String(255), nullable=False)
storage_path: Mapped[str] = mapped_column(String(512), nullable=False)
file_size: Mapped[int] = mapped_column(BigInteger, nullable=False)
mime_type: Mapped[str] = mapped_column(String(100), nullable=False)
width: Mapped[int] = mapped_column(Integer, nullable=False)
height: Mapped[int] = mapped_column(Integer, nullable=False)
metadata: Mapped[dict] = mapped_column(JSONB, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=datetime.utcnow)
reference_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
# Relationships
user = relationship("User", back_populates="images")
board_images = relationship("BoardImage", back_populates="image", cascade="all, delete-orphan")
user: Mapped["User"] = relationship("User", back_populates="images")
board_images: Mapped[list["BoardImage"]] = relationship(
"BoardImage", back_populates="image", cascade="all, delete-orphan"
)
def __repr__(self) -> str:
return f"<Image(id={self.id}, filename={self.filename})>"
"""String representation of Image."""
return f"<Image(id={self.id}, filename='{self.filename}', user_id={self.user_id})>"