from __future__ import annotations from datetime import datetime import sqlalchemy as sa from fansifter_common.adapters.db.types import ChoiceType from fansifter_common.auth.account import Account from fansifter_common.utils import timezone from fansifter_common.utils.uuid import uuid_string from sqlalchemy.orm import Mapped, mapped_column, relationship from dmp.adapters.db import Model from dmp.adapters.db.sqltypes import EncryptedTextType from dmp.app_connections.enums import AppConnectionStatus from dmp.audiences.models import AudienceShare from dmp.core.types import EncryptedToken class TikTokAdAccount(Model, kw_only=True): __tablename__ = "tiktok_ad_account" id: Mapped[str] = mapped_column(primary_key=True, default_factory=uuid_string) external_id: Mapped[str] = mapped_column(unique=True) name: Mapped[str | None] = mapped_column(default=None) business_center_id: Mapped[str | None] = mapped_column(default=None) business_center_name: Mapped[str | None] = mapped_column(default=None) campaigns_count: Mapped[int] = mapped_column(default=0) created_at: Mapped[datetime] = mapped_column(default_factory=timezone.now) updated_at: Mapped[datetime] = mapped_column( default_factory=timezone.now, onupdate=timezone.now ) class TikTokUserConnectionAdAccount(Model, kw_only=True): __tablename__ = "tiktok_user_connection_ad_account" user_connection_id: Mapped[str] = mapped_column( sa.ForeignKey("tiktok_user_connection.id"), primary_key=True ) ad_account_id: Mapped[str] = mapped_column( sa.ForeignKey("tiktok_ad_account.id"), primary_key=True ) class TikTokUserConnection(Model, kw_only=True): __tablename__ = "tiktok_user_connection" __table_args__ = (sa.UniqueConstraint("identity_id", "user_id"),) id: Mapped[str] = mapped_column(primary_key=True, default_factory=uuid_string) identity_id: Mapped[str] user_id: Mapped[str] user_name: Mapped[str] = mapped_column(default="") user_picture: Mapped[str | None] = mapped_column(default=None) token: Mapped[EncryptedToken] = mapped_column(EncryptedTextType) is_valid: Mapped[bool] = mapped_column(default=True) created_at: Mapped[datetime] = mapped_column(default_factory=timezone.now) updated_at: Mapped[datetime] = mapped_column( default_factory=timezone.now, onupdate=timezone.now ) # relations ad_accounts: Mapped[list[TikTokAdAccount]] = relationship( secondary=TikTokUserConnectionAdAccount.__table__, lazy="raise", default_factory=list, repr=False, ) @property def ad_accounts_count(self) -> int: return len(self.ad_accounts) def remove_ad_account(self, ad_account: TikTokAdAccount) -> bool: try: self.ad_accounts.remove(ad_account) except ValueError: return False return True class TikTokUserAdAccount(Model, kw_only=True): __tablename__ = "tiktok_user_ad_account" __table_args__ = (sa.UniqueConstraint("identity_id", "ad_account_id"),) id: Mapped[str] = mapped_column(primary_key=True, default_factory=uuid_string) identity_id: Mapped[str] ad_account_id: Mapped[str] = mapped_column(sa.ForeignKey("tiktok_ad_account.id")) vendor_id: Mapped[int] subaccount_id: Mapped[int] created_at: Mapped[datetime] = mapped_column(default_factory=timezone.now) updated_at: Mapped[datetime] = mapped_column( default_factory=timezone.now, onupdate=timezone.now ) # relations ad_account: Mapped[TikTokAdAccount] = relationship(lazy="raise", init=False) @property def account(self) -> Account: return Account(vendor_id=self.vendor_id, subaccount_id=self.subaccount_id) class _TikTokAdReportingConnectionAdAccount(Model, kw_only=True): __tablename__ = "tiktok_ad_reporting_connection_ad_account" connection_id: Mapped[str] = mapped_column( sa.ForeignKey("tiktok_ad_reporting_connection.id"), primary_key=True ) ad_account_id: Mapped[str] = mapped_column( sa.ForeignKey("tiktok_ad_account.id"), primary_key=True ) class TikTokAdReportingConnection(Model, kw_only=True): __tablename__ = "tiktok_ad_reporting_connection" id: Mapped[str] = mapped_column(primary_key=True, default_factory=uuid_string) user_id: Mapped[str] fivetran_connector_id: Mapped[str] identity_id: Mapped[str] fivetran_schema: Mapped[str] = mapped_column(unique=True) status: Mapped[AppConnectionStatus] = mapped_column( ChoiceType(AppConnectionStatus), default=AppConnectionStatus.PENDING ) is_paused: Mapped[bool] = mapped_column(default=False) initial_email_sent: Mapped[bool] = mapped_column(default=False) synced_at: Mapped[datetime | None] = mapped_column(default=None) created_at: Mapped[datetime] = mapped_column(default_factory=timezone.now) updated_at: Mapped[datetime] = mapped_column( default_factory=timezone.now, onupdate=timezone.now ) # relations ad_accounts: Mapped[list[TikTokAdAccount]] = relationship( secondary=_TikTokAdReportingConnectionAdAccount.__table__, lazy="raise", default_factory=list, repr=False, ) def __str__(self) -> str: return self.fivetran_schema @property def ad_accounts_count(self) -> int: return len(self.ad_accounts) def remove_ad_account(self, ad_account: TikTokAdAccount) -> bool: try: self.ad_accounts.remove(ad_account) except ValueError: return False return True class TikTokAudience(Model, kw_only=True): __tablename__ = "tiktok_audience" id: Mapped[str] = mapped_column(primary_key=True, default_factory=uuid_string) name: Mapped[str] external_id: Mapped[str | None] = mapped_column(unique=True, default=None) ad_account_id: Mapped[str] = mapped_column( sa.ForeignKey("tiktok_ad_account.id"), init=False ) fans_count: Mapped[int] = mapped_column(init=False) audience_id: Mapped[str] = mapped_column(sa.ForeignKey("audience.id"), init=False) snapshot_id: Mapped[str] = mapped_column( sa.ForeignKey("audience_snapshot.id"), init=False ) share_id: Mapped[str] = mapped_column( sa.ForeignKey("audience_share.id"), unique=True, init=False ) created_at: Mapped[datetime] = mapped_column(default_factory=timezone.now) # relations ad_account: Mapped[TikTokAdAccount] = relationship(lazy="raise") share: Mapped[AudienceShare] = relationship(lazy="raise") def __post_init__(self) -> None: self.audience_id = self.share.audience_id self.snapshot_id = self.share.snapshot_id self.share_id = self.share.id self.fans_count = self.share.fans_count self.ad_account_id = self.ad_account.id @property def ad_account_external_id(self) -> str: return self.ad_account.external_id @property def url(self) -> str: return ( "https://ads.tiktok.com/i18n/dmp/adver/main" f"?aadvid={self.ad_account_external_id}" )