feat: add avatars
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from src.application.commands.get_me import GetMeCommand
|
||||
from src.application.commands.set_phone import SetPhoneCommand
|
||||
from src.application.commands.set_avatar import SetAvatarCommand
|
||||
from src.application.commands.set_encrypted_mnemonic_start import SetEncryptedMnemonicStartCommand
|
||||
from src.application.commands.set_encrypted_mnemonic_complete import SetEncryptedMnemonicCompleteCommand
|
||||
from src.application.commands.update_bank_details_start import UpdateBankDetailsStartCommand
|
||||
|
||||
54
src/application/commands/set_avatar.py
Normal file
54
src/application/commands/set_avatar.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from PIL import UnidentifiedImageError
|
||||
from ulid import ULID
|
||||
|
||||
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.application.domain.exceptions import BadRequestException, ServiceUnavailableException
|
||||
from src.infrastructure.config import settings
|
||||
from src.infrastructure.database.decorators import transactional
|
||||
from src.infrastructure.media.webp import image_bytes_to_webp
|
||||
|
||||
|
||||
class SetAvatarCommand:
|
||||
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
|
||||
|
||||
async def __call__(self, user_id: str, image_bytes: bytes) -> tuple[UserEntity, int]:
|
||||
try:
|
||||
webp_bytes = image_bytes_to_webp(image_bytes)
|
||||
except UnidentifiedImageError as exc:
|
||||
raise BadRequestException(message='Unsupported or corrupt image') from exc
|
||||
except Exception as exc:
|
||||
self._logger.exception(str(exc))
|
||||
raise BadRequestException(message='Could not process image') from exc
|
||||
|
||||
pid = user_id.replace('/', '').replace('.', '_')
|
||||
name_id = str(ULID())
|
||||
ts = int(datetime.now(timezone.utc).timestamp() * 1000)
|
||||
prefix = settings.S3_AVATAR_KEY_PREFIX.strip().strip('/')
|
||||
fname = f'{name_id}_{pid}_{ts}.webp'
|
||||
object_key = f'{prefix}/{fname}' if prefix else fname
|
||||
|
||||
try:
|
||||
url = await self._s3.upload_bytes(key=object_key, body=webp_bytes, content_type='image/webp')
|
||||
except ClientError as exc:
|
||||
self._logger.exception(str(exc))
|
||||
raise ServiceUnavailableException(message='S3 upload failed') from exc
|
||||
|
||||
user = await self._save_avatar_link(user_id, url)
|
||||
await self._cache.set_user(user_id, user)
|
||||
self._logger.info(f'Avatar set for user_id={user_id} key={object_key}')
|
||||
return user, len(webp_bytes)
|
||||
|
||||
@transactional
|
||||
async def _save_avatar_link(self, user_id: str, avatar_link: str) -> UserEntity:
|
||||
return await self._unit_of_work.user_repository.set_avatar_link(user_id, avatar_link)
|
||||
Reference in New Issue
Block a user