import urllib.parse from collections.abc import Mapping from datetime import datetime from typing import Any import sqlalchemy as sa from audience_common import datetimeutc from sqlalchemy import event from sqlalchemy.orm import Mapped, mapped_column from campaigns.assets.enums import AssetStatus from campaigns.config import settings from campaigns.connectors.db.orm import Base from campaigns.connectors.db.sqltypes import ChoiceType from campaigns.utils.uuid import uuid_string class Asset(Base): __tablename__ = "asset" RAW_UPLOAD_FOLDER = "raw" THUMBNAIL_UPLOAD_FOLDER = "thumbnails" id: Mapped[str] = mapped_column(primary_key=True, default=uuid_string) status: Mapped[AssetStatus] = mapped_column( ChoiceType(AssetStatus, impl=sa.Text()), default=AssetStatus.PENDING ) filename: Mapped[str] key: Mapped[str] = mapped_column(unique=True) thumbnail_key: Mapped[str | None] = mapped_column(unique=True) bucket: Mapped[str] size: Mapped[int | None] = mapped_column(sa.BigInteger) filetype: Mapped[str | None] content_type: Mapped[str | None] meta: Mapped[dict[str, Any]] = mapped_column(sa.JSON, default=dict) errors: Mapped[list[Mapping[str, Any]] | None] = mapped_column(sa.JSON) created_at: Mapped[datetime] = mapped_column(default=datetimeutc.now) updated_at: Mapped[datetime] = mapped_column( default=datetimeutc.now, onupdate=datetimeutc.now ) uploaded_at: Mapped[datetime | None] def __str__(self) -> str: return self.filename @classmethod def make_key(cls, asset_id: str, filename: str) -> str: return f"{cls.RAW_UPLOAD_FOLDER}/{asset_id}/{filename}" def set_thumbnail_key(self, extension: str | None = None) -> None: extension = extension or "" self.thumbnail_key = ( f"{self.THUMBNAIL_UPLOAD_FOLDER}/{self.id}/{self.filename}{extension}" ) @property def is_completed(self) -> bool: return self.status == AssetStatus.COMPLETED @property def url(self) -> str | None: if not self.is_completed: return None return urllib.parse.urljoin( f"https://{settings.assets_cdn_domain}/", urllib.parse.quote_plus(self.key, safe="/"), ) @property def thumbnail_url(self) -> str | None: if not self.is_completed or not self.thumbnail_key: return None return urllib.parse.urljoin( f"https://{settings.assets_cdn_domain}/", urllib.parse.quote_plus(self.thumbnail_key, safe="/"), ) @event.listens_for(Asset, "init") def receive_init(target: Asset, args: Any, kwargs: Any) -> Asset: if kwargs.get("key") is None and "id" in kwargs and "filename" in kwargs: target.key = Asset.make_key( asset_id=kwargs["id"], filename=kwargs["filename"], ) return target