from __future__ import annotations import datetime from typing import Literal, Self from pydantic import BaseModel, Field, TypeAdapter, field_validator from app.constants import UNKNOWN_COUNTRY_ISO2 class BasePresaveEvent(BaseModel): task_id: str = Field(alias="taskId") group_id: str = Field(alias="groupId") album_id: int = Field(alias="albumId") country: str | None = None product_upc: str | None = Field(None, alias="productUpc") store_name: str = Field(alias="storeName") store_id: int | None = Field(None, alias="storeId") client_id: str | None = Field(None, alias="clientId") client_ip: str | None = Field(None, alias="clientIp") created_at: datetime.datetime = Field(alias="createdAt") city: str | None = None region: str | None = None region_code: str | None = Field(None, alias="regionCode") latitude: float | None = None longitude: float | None = None @field_validator("country", mode="before") def _prepare_country(cls, value: str | None) -> str | None: if not value: return None return value if (value := value.upper()) not in UNKNOWN_COUNTRY_ISO2 else None class EmailPresaveEvent(BasePresaveEvent): event_type: Literal["presave"] = Field(alias="eventType") email: str class NativePresaveEvent(BasePresaveEvent): event_type: Literal["native_presave"] = Field(alias="eventType") token: str email: str | None = None full_name: str | None = Field(None, alias="fullName") # Common type for all Songwhip events PresaveEvent = EmailPresaveEvent | NativePresaveEvent PresaveEventValidator: TypeAdapter[PresaveEvent] = TypeAdapter(PresaveEvent) class EventRecord(BaseModel): task_id: str = Field(alias="taskId") group_id: str = Field(alias="groupId") album_id: int = Field(alias="albumId") country: str | None product_upc: str | None = Field(None, alias="productUpc") store_name: str = Field(alias="storeName") store_id: int | None = Field(alias="storeId") client_id: str | None = Field(alias="clientId") client_ip: str | None = Field(alias="clientIp") email: str | None token: str | None full_name: str | None = Field(alias="fullName") city: str | None region: str | None region_code: str | None = Field(alias="regionCode") latitude: float | None longitude: float | None global_participant_id: str | None = Field(alias="globalParticipantId") vendor_id: int | None = Field(alias="vendorId") subaccount_id: int | None = Field(alias="subaccountId") created_at: datetime.datetime = Field(alias="createdAt") @property def key(self) -> str: return f"{self.group_id}:{self.task_id}" @classmethod def from_presave_event(cls, event: PresaveEvent) -> list[Self]: return [cls._from_presave_event(event)] @classmethod def _from_presave_event(cls, event: PresaveEvent) -> Self: if isinstance(event, EmailPresaveEvent): return cls._from_email_presave_event(event) return cls._from_native_presave_event(event) @classmethod def _from_email_presave_event(cls, event: EmailPresaveEvent) -> Self: return cls.model_construct( task_id=event.task_id, group_id=event.group_id, album_id=event.album_id, country=event.country, product_upc=event.product_upc, store_name=event.store_name, store_id=event.store_id, client_id=event.client_id, client_ip=event.client_ip, email=event.email, token=None, full_name=None, city=event.city, region=event.region, region_code=event.region_code, latitude=event.latitude, longitude=event.longitude, global_participant_id=None, vendor_id=None, subaccount_id=None, created_at=event.created_at, ) @classmethod def _from_native_presave_event( cls, event: NativePresaveEvent, ) -> Self: return cls.model_construct( task_id=event.task_id, group_id=event.group_id, album_id=event.album_id, country=event.country, product_upc=event.product_upc, store_name=event.store_name, store_id=event.store_id, client_id=event.client_id, client_ip=event.client_ip, email=event.email, token=event.token, full_name=event.full_name, city=event.city, region=event.region, region_code=event.region_code, latitude=event.latitude, longitude=event.longitude, global_participant_id=None, vendor_id=None, subaccount_id=None, created_at=event.created_at, )