from __future__ import annotations from botocore.exceptions import ClientError from src.application.abstractions import IUnitOfWork from src.application.contracts import ICache, ILogger, IS3 from src.application.domain.entities import UserEntity from src.infrastructure.database.decorators import transactional class DeleteAvatarCommand: def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger, cache: ICache, s3: IS3): self._unit_of_work = unit_of_work self._logger = logger self._cache = cache self._s3 = s3 @transactional async def _load_user(self, user_id: str) -> UserEntity: user = await self._unit_of_work.user_repository.get_user_by_id(user_id) self._logger.debug(f'DeleteAvatar _load_user user_id={user_id} has_avatar_link={bool(user.avatar_link)}') return user async def __call__(self, user_id: str) -> UserEntity: prior = await self._load_user(user_id) link = prior.avatar_link self._logger.info(f'DeleteAvatar start user_id={user_id} had_link={bool(link)}') if link: key = self._s3.object_key_from_public_url(link) self._logger.debug(f'DeleteAvatar parsed_object_key user_id={user_id} has_key={bool(key)}') if not key: self._logger.warning( f'DeleteAvatar could not parse avatar URL for S3 user_id={user_id} link_len={len(link)}' ) if key: self._logger.info(f'DeleteAvatar S3 delete start user_id={user_id} key={key}') try: await self._s3.delete_object(key=key) self._logger.info(f'DeleteAvatar S3 delete done user_id={user_id} key={key}') except ClientError as exc: code = exc.response.get('Error', {}).get('Code', '') if code not in ('NoSuchKey', '404'): self._logger.warning(f'DeleteAvatar S3 delete failed user_id={user_id} code={code}: {exc}') else: self._logger.debug(f'DeleteAvatar S3 object already absent user_id={user_id} code={code}') user = await self._clear_avatar_link(user_id) self._logger.debug(f'DeleteAvatar DB cleared user_id={user_id} entity_has_link={bool(user.avatar_link)}') await self._cache.set_user(user_id, user) self._logger.debug(f'DeleteAvatar cache updated user_id={user_id}') self._logger.info(f'Avatar removed user_id={user_id}') return user @transactional async def _clear_avatar_link(self, user_id: str) -> UserEntity: self._logger.debug(f'DeleteAvatar DB transaction set_avatar_link user_id={user_id} link=None') return await self._unit_of_work.user_repository.set_avatar_link(user_id, None)