from anydi import singleton from fansifter_common.adapters.graphql_router import GlobalParticipant from fansifter_common.artist.exceptions import InvalidGlobalParticipantIdError from fansifter_common.artist.validators import ArtistValidator from ows_text_campaigns.artist.exceptions import ( ArtistNotConfiguredError, ArtistNotFoundError, ArtistSettingsNotFoundError, ) from ows_text_campaigns.artist.models import ArtistSettings from ows_text_campaigns.artist.repositories import ArtistSettingsRepository from ows_text_campaigns.artist.types import PhoneNumberStr from ows_text_campaigns.artist.vcard import ArtistVCard from ows_text_campaigns.assets.image import ImageProcessor from ows_text_campaigns.assets.models import Asset from ows_text_campaigns.assets.services import AssetService from ows_text_campaigns.assets.types import BinaryAsset from ows_text_campaigns.assets.utils import create_asset_filename from ows_text_campaigns.config import Settings @singleton class ArtistSettingsService: object_type = "artist" def __init__( self, artist_settings_repository: ArtistSettingsRepository, artist_validator: ArtistValidator, image_processor: ImageProcessor, asset_service: AssetService, settings: Settings, ) -> None: self.artist_settings_repository = artist_settings_repository self.artist_validator = artist_validator self.asset_service = asset_service self.image_processor = image_processor self.settings = settings def get_settings(self, global_participant_id: str) -> ArtistSettings: artist_settings = self.artist_settings_repository.get_by_global_participant_id( global_participant_id ) if artist_settings is None: raise ArtistSettingsNotFoundError return artist_settings def get_settings_by_phone_number(self, phone_number: str) -> ArtistSettings: artist_settings = self.artist_settings_repository.get_by_phone_number( phone_number ) if artist_settings is None: raise ArtistSettingsNotFoundError return artist_settings def get_configured_settings(self, global_participant_id: str) -> ArtistSettings: artist_settings = self.artist_settings_repository.get_by_global_participant_id( global_participant_id ) if artist_settings is None: raise ArtistNotConfiguredError( "Artist settings is not created for the specified global participant ID." ) if not artist_settings.phone_numbers: raise ArtistNotConfiguredError( "Artist does not have any phone numbers configured." ) return artist_settings def validate_global_participant_id( self, global_participant_id: str ) -> GlobalParticipant: return self.artist_validator.validate_global_participant_id( global_participant_id ) def get_global_participant(self, global_participant_id: str) -> GlobalParticipant: try: return self.validate_global_participant_id(global_participant_id) except InvalidGlobalParticipantIdError as exc: raise ArtistNotFoundError from exc def create_asset_key(self, global_participant_id: str, *, filename: str) -> str: """Create a new image key for the artist.""" filename = create_asset_filename(filename) return f"{self.settings.assets_path}/artist/{global_participant_id}/{filename}" def create_and_upload_artist_image( self, *, global_participant_id: str, asset: BinaryAsset, key: str, identity_id: str | None = None, ) -> Asset: image = self.image_processor.validate_and_process( asset, max_file_size=self.settings.artist_image_max_file_size, min_width=self.settings.artist_image_min_width, min_height=self.settings.artist_image_min_height, allowed_content_types=self.settings.artist_image_allowed_content_types, square_crop=True, ) return self.asset_service.create_and_upload_image_to_s3( image, key=key, object_type=self.object_type, object_id=global_participant_id, identity_id=identity_id, ) def get_artist_image(self, *, global_participant_id: str, asset_id: str) -> Asset: return self.asset_service.get_asset_for( asset_id=asset_id, object_type=self.object_type, object_id=global_participant_id, ) def create_and_upload_vcard( self, artist_settings: ArtistSettings, photo: BinaryAsset | None = None, sync_artist_name: bool = False, ) -> list[Asset]: """Create and upload vCard for the artist.""" # Sync artist name if needed if sync_artist_name: global_participant = self.get_global_participant( artist_settings.global_participant_id ) if global_participant.name != artist_settings.artist_name: artist_settings.artist_name = global_participant.name # Load photo asset if photo is None and artist_settings.asset: photo = self.asset_service.get_asset_from_s3(artist_settings.asset.key) # Create vCard for each phone number result: list[Asset] = [] for phone_number in artist_settings.phone_numbers: asset = self.create_and_upload_vcard_for_phone_number( global_participant_id=artist_settings.global_participant_id, artist_name=artist_settings.artist_name, phone_number=phone_number.phone_number, photo=photo, ) phone_number.vcard_asset_id = asset.id result.append(asset) self.artist_settings_repository.save(artist_settings) return result def create_and_upload_vcard_for_phone_number( self, *, global_participant_id: str, artist_name: str, phone_number: PhoneNumberStr, photo: BinaryAsset | None = None, ) -> Asset: vcard = ArtistVCard( name=artist_name, phone=phone_number, photo=photo, ) filename = "vcard.vcf" vcard_bin = vcard.as_binary(filename=filename) key = self.create_asset_key(global_participant_id, filename=filename) asset = Asset( key=key, object_type=self.object_type, object_id=global_participant_id, original_filename=filename, content_type=vcard_bin.content_type, extension=vcard_bin.extension, file_size=vcard_bin.file_size, ) self.asset_service.upload_asset_to_s3( vcard_bin, key=asset.key, original_filename=filename, ) self.asset_service.save_asset(asset) return asset