from __future__ import annotations from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.exc import SQLAlchemyError from src.application.contracts import ILogger from src.application.domain.exceptions import ApplicationException, BadRequestException, DataBaseErrorException, NotFoundException from src.application.abstractions.repositories import IUserRepository from src.application.domain.entities import UserEntity from src.infrastructure.database.models import UserModel class UserRepository(IUserRepository): def __init__(self, session: AsyncSession, logger: ILogger): self._session = session self._logger = logger async def _get_active_user(self, user_id: str) -> UserModel: stmt = ( select(UserModel) .where( UserModel.id == user_id, UserModel.is_deleted.is_(False), ) ) result = await self._session.execute(stmt) user: UserModel | None = result.scalar_one_or_none() if user is None: self._logger.warning(f'User not found with user_id {user_id}') raise NotFoundException(message='User not found') return user @staticmethod def _to_entity(user: UserModel) -> UserEntity: return UserEntity( id=user.id, email=user.email, password_hash=None, first_name=user.first_name, middle_name=user.middle_name, last_name=user.last_name, birth_date=user.birth_date, encrypted_mnemonic=user.encrypted_mnemonic, phone=user.phone, passport_data=user.passport_data, inn=user.inn, erc20=user.erc20, avatar_link=user.avatar_link, kyc_verified_at=user.kyc_verified_at, kyc_verified=user.kyc_verified, is_deleted=user.is_deleted, created_at=user.created_at, updated_at=user.updated_at, ) async def get_user_by_id(self, user_id: str) -> UserEntity: try: user = await self._get_active_user(user_id) return self._to_entity(user) except ApplicationException: raise except SQLAlchemyError as exception: self._logger.exception(str(exception)) raise DataBaseErrorException(message=f'Database error: {str(exception)}') async def _update_field(self, user_id: str, **fields: object) -> UserEntity: try: user = await self._get_active_user(user_id) for key, value in fields.items(): setattr(user, key, value) await self._session.flush() await self._session.refresh(user) return self._to_entity(user) except ApplicationException: raise except SQLAlchemyError as exception: self._logger.exception(str(exception)) raise DataBaseErrorException(message=f'Database error: {str(exception)}') async def set_phone(self, user_id: str, phone: str) -> UserEntity: return await self._update_field(user_id, phone=phone) async def set_bank_details(self, user_id: str, **fields: str) -> UserEntity: allowed = {'passport_data', 'inn', 'erc20'} payload = {k: v for k, v in fields.items() if k in allowed and v is not None} if not payload: raise BadRequestException(message='No identity fields to update') return await self._update_field(user_id, **payload) async def set_encrypted_mnemonic(self, user_id: str, encrypted_mnemonic: str) -> UserEntity: return await self._update_field(user_id, encrypted_mnemonic=encrypted_mnemonic) async def set_avatar_link(self, user_id: str, avatar_link: str | None) -> UserEntity: return await self._update_field(user_id, avatar_link=avatar_link) async def get_password_hash(self, user_id: str) -> str: try: user = await self._get_active_user(user_id) return user.password_hash except ApplicationException: raise except SQLAlchemyError as exception: self._logger.exception(str(exception)) raise DataBaseErrorException(message=f'Database error: {str(exception)}') async def set_password(self, user_id: str, password_hash: str) -> UserEntity: return await self._update_field(user_id, password_hash=password_hash) async def set_email(self, user_id: str, email: str) -> UserEntity: return await self._update_field(user_id, email=email) async def email_exists(self, email: str) -> bool: try: stmt = ( select(UserModel) .where( UserModel.email == email, UserModel.is_deleted.is_(False), ) ) result = await self._session.execute(stmt) return result.scalar_one_or_none() is not None except SQLAlchemyError as exception: self._logger.exception(str(exception)) raise DataBaseErrorException(message=f'Database error: {str(exception)}') async def get_user_by_email(self, email: str) -> UserEntity | None: try: stmt = ( select(UserModel) .where( UserModel.email == email, UserModel.is_deleted.is_(False), ) ) result = await self._session.execute(stmt) user: UserModel | None = result.scalar_one_or_none() if user is None: return None return self._to_entity(user) except SQLAlchemyError as exception: self._logger.exception(str(exception)) raise DataBaseErrorException(message=f'Database error: {str(exception)}')