import logging from collections.abc import Sequence from typing import Any from anydi import singleton from fansifter_common.encrypter import Encrypter from fansifter_common.identifiers.exceptions import InvalidProfileTokenError from fansifter_common.identifiers.types import ( FansifterProfileIdentifier, ProfileIdentifier, ProfileToken, ) from fansifter_common.identifiers.utils import ( decrypt_profile_identifier, encrypt_profile_token, ) from preference_center.profile.cache import ProfileCache from preference_center.profile.exceptions import ( ProfileNotFoundError, UnsupportedProfileIdentifierError, ) from preference_center.profile.models import Profile, Subscription from preference_center.profile.repositories import ( ProfileRepository, SubscriptionRepository, ) logger = logging.getLogger(__name__) @singleton class ProfileService: def __init__( self, encrypter: Encrypter, cache: ProfileCache, profile_repository: ProfileRepository, subscription_repository: SubscriptionRepository, ) -> None: self.encrypter = encrypter self.cache = cache self.profile_repository = profile_repository self.subscription_repository = subscription_repository def get_profile_by_email(self, email: str) -> Profile: """ Get a profile by its email address. """ profile = self.profile_repository.get_by_email(email) if profile is None: raise ProfileNotFoundError( context={ "profile_email": email, } ) return profile def get_profile_by_identifier(self, identifier: ProfileIdentifier) -> Profile: """ Get a profile using a profile identifier. """ context: dict[str, Any] = {} if isinstance(identifier, FansifterProfileIdentifier): profile = self.profile_repository.get_by_id(identifier.profile_id) context["profile_id"] = identifier.profile_id else: profile = self.profile_repository.get_by_crm_id(identifier.crm_id) context["profile_crm_id"] = identifier.crm_id if profile is None: raise ProfileNotFoundError(context=context) return profile def get_profile_by_identifier_cached( self, identifier: ProfileIdentifier ) -> Profile: """ Get a profile using an identifier, consulting cache first. """ profile = self.cache.get_profile(identifier) if profile is None: profile = self.get_profile_by_identifier(identifier) self.cache.set_profile(identifier, profile=profile) elif profile.deleted: raise ProfileNotFoundError( context={ "profile_id": profile.id, "profile_deleted": True, } ) return profile def decode_profile_identifier(self, token: ProfileToken) -> ProfileIdentifier: """ Decode a profile token into a profile identifier. """ try: return decrypt_profile_identifier(self.encrypter, token=token) except (InvalidProfileTokenError, UnsupportedProfileIdentifierError) as exc: raise ProfileNotFoundError from exc def encode_profile_identifier(self, identifier: ProfileIdentifier) -> ProfileToken: """ Encode a profile identifier into an encrypted profile token. """ return encrypt_profile_token(self.encrypter, identifier=identifier) def get_subscriptions_cached(self, profile_id: str) -> Sequence[Subscription]: subscriptions = self.cache.get_subscriptions(profile_id=profile_id) if subscriptions is None: subscriptions = self.subscription_repository.find_by_profile_id(profile_id) self.cache.set_subscriptions( profile_id=profile_id, subscriptions=subscriptions ) return subscriptions