diff --git a/src/application/abstractions/repositories/i_user_repository.py b/src/application/abstractions/repositories/i_user_repository.py index d400a15..964b9f9 100644 --- a/src/application/abstractions/repositories/i_user_repository.py +++ b/src/application/abstractions/repositories/i_user_repository.py @@ -19,7 +19,7 @@ class IUserRepository(ABC): raise NotImplementedError @abstractmethod - async def set_crypto_wallet(self, user_id: str, wallet_address: str) -> UserEntity: + async def set_encrypted_mnemonic(self, user_id: str, encrypted_mnemonic: str) -> UserEntity: raise NotImplementedError @abstractmethod diff --git a/src/application/commands/__init__.py b/src/application/commands/__init__.py index 4d2ec64..358b55a 100644 --- a/src/application/commands/__init__.py +++ b/src/application/commands/__init__.py @@ -1,7 +1,7 @@ from src.application.commands.get_me import GetMeCommand from src.application.commands.set_phone import SetPhoneCommand -from src.application.commands.set_crypto_wallet_start import SetCryptoWalletStartCommand -from src.application.commands.set_crypto_wallet_complete import SetCryptoWalletCompleteCommand +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 from src.application.commands.update_bank_details_complete import UpdateBankDetailsCompleteCommand from src.application.commands.change_password_start import ChangePasswordStartCommand diff --git a/src/application/commands/set_crypto_wallet_complete.py b/src/application/commands/set_encrypted_mnemonic_complete.py similarity index 58% rename from src/application/commands/set_crypto_wallet_complete.py rename to src/application/commands/set_encrypted_mnemonic_complete.py index f8757d6..0112421 100644 --- a/src/application/commands/set_crypto_wallet_complete.py +++ b/src/application/commands/set_encrypted_mnemonic_complete.py @@ -5,7 +5,7 @@ from src.application.domain.exceptions import ApplicationException from src.infrastructure.database.decorators import transactional -class SetCryptoWalletCompleteCommand: +class SetEncryptedMnemonicCompleteCommand: def __init__( self, unit_of_work: IUnitOfWork, @@ -19,42 +19,42 @@ class SetCryptoWalletCompleteCommand: self._logger = logger @transactional - async def __call__(self, *, user_id: str, code: str, wallet_address: str) -> UserEntity: + async def __call__(self, *, user_id: str, code: str, encrypted_mnemonic: str) -> UserEntity: code = (code or '').strip() - USER_PREFIX = 'crypto_wallet:user:' - CODE_PREFIX = 'crypto_wallet:code:' + USER_PREFIX = 'encrypted_mnemonic:user:' + CODE_PREFIX = 'encrypted_mnemonic:code:' user_key = f'{USER_PREFIX}{user_id}' code_key = f'{CODE_PREFIX}{code}' user = await self._unit_of_work.user_repository.get_user_by_id(user_id=user_id) - if user.crypto_wallet is not None: - self._logger.info(f'Crypto wallet already set for user_id={user_id}') - raise ApplicationException(409, 'Crypto wallet already set and cannot be changed') + if user.encrypted_mnemonic is not None: + self._logger.info(f'Encrypted mnemonic already set for user_id={user_id}') + raise ApplicationException(409, 'Encrypted mnemonic already set and cannot be changed') cached_user_id = await self._cache.get(code_key) if not cached_user_id: - self._logger.info(f'Crypto wallet set failed: code not found (user_id={user_id})') + self._logger.info(f'Encrypted mnemonic set failed: code not found (user_id={user_id})') raise ApplicationException(400, 'Invalid or expired code') if cached_user_id != user_id: - self._logger.info(f'Crypto wallet set failed: code-user mismatch (user_id={user_id})') + self._logger.info(f'Encrypted mnemonic set failed: code-user mismatch (user_id={user_id})') raise ApplicationException(400, 'Invalid or expired code') code_hash = await self._cache.get(user_key) if not code_hash: - self._logger.info(f'Crypto wallet set failed: user key missing (user_id={user_id})') + self._logger.info(f'Encrypted mnemonic set failed: user key missing (user_id={user_id})') raise ApplicationException(400, 'Invalid or expired code') ok = await self._hash_service.verify(hashed_value=code_hash, plain_value=code) if not ok: - self._logger.info(f'Crypto wallet set failed: code hash mismatch (user_id={user_id})') + self._logger.info(f'Encrypted mnemonic set failed: code hash mismatch (user_id={user_id})') raise ApplicationException(400, 'Invalid or expired code') - user = await self._unit_of_work.user_repository.set_crypto_wallet( + user = await self._unit_of_work.user_repository.set_encrypted_mnemonic( user_id=user_id, - wallet_address=wallet_address, + encrypted_mnemonic=encrypted_mnemonic, ) await self._cache.set_user(user_id, user) @@ -62,7 +62,7 @@ class SetCryptoWalletCompleteCommand: await self._cache.delete(code_key) await self._cache.delete(user_key) except Exception as e: - self._logger.warning(f'Crypto wallet set cleanup failed (user_id={user_id}): {e}') + self._logger.warning(f'Encrypted mnemonic set cleanup failed (user_id={user_id}): {e}') - self._logger.info(f'Crypto wallet set for user_id={user_id}') + self._logger.info(f'Encrypted mnemonic set for user_id={user_id}') return user diff --git a/src/application/commands/set_crypto_wallet_start.py b/src/application/commands/set_encrypted_mnemonic_start.py similarity index 78% rename from src/application/commands/set_crypto_wallet_start.py rename to src/application/commands/set_encrypted_mnemonic_start.py index ac4d952..2b115fe 100644 --- a/src/application/commands/set_crypto_wallet_start.py +++ b/src/application/commands/set_encrypted_mnemonic_start.py @@ -9,7 +9,7 @@ from src.infrastructure.context_vars import trace_id_var from src.infrastructure.database.decorators import transactional -class SetCryptoWalletStartCommand: +class SetEncryptedMnemonicStartCommand: def __init__( self, hash_service: IHashService, @@ -30,15 +30,15 @@ class SetCryptoWalletStartCommand: LOCK_TTL = 30 MAX_ATTEMPTS = 20 - USER_PREFIX = 'crypto_wallet:user:' - CODE_PREFIX = 'crypto_wallet:code:' - LOCK_PREFIX = 'crypto_wallet:lock:' + USER_PREFIX = 'encrypted_mnemonic:user:' + CODE_PREFIX = 'encrypted_mnemonic:code:' + LOCK_PREFIX = 'encrypted_mnemonic:lock:' user = await self._unit_of_work.user_repository.get_user_by_id(user_id=user_id) - if user.crypto_wallet is not None: - self._logger.info(f'Crypto wallet already set for user_id={user_id}') - raise ApplicationException(409, 'Crypto wallet already set and cannot be changed') + if user.encrypted_mnemonic is not None: + self._logger.info(f'Encrypted mnemonic already set for user_id={user_id}') + raise ApplicationException(409, 'Encrypted mnemonic already set and cannot be changed') if not user.email: self._logger.warning(f'User {user_id} does not have an email address') @@ -51,7 +51,7 @@ class SetCryptoWalletStartCommand: lock_key = f'{LOCK_PREFIX}{user_id}' locked = await self._cache.set_nx(lock_key, '1', ttl=LOCK_TTL) if not locked: - self._logger.info(f'Crypto wallet set throttled by lock (user_id={user_id})') + self._logger.info(f'Encrypted mnemonic set throttled by lock (user_id={user_id})') raise ApplicationException(429, 'Too many requests. Please wait.') try: @@ -59,7 +59,7 @@ class SetCryptoWalletStartCommand: existing = await self._cache.get(user_key) if existing: - self._logger.info(f'Crypto wallet set denied: code already exists for user_id={user_id}') + self._logger.info(f'Encrypted mnemonic set denied: code already exists for user_id={user_id}') raise ApplicationException(429, 'Code already sent. Please wait before retrying.') for _ in range(MAX_ATTEMPTS): @@ -75,7 +75,7 @@ class SetCryptoWalletStartCommand: saved = await self._cache.set(user_key, code_hash, ttl=TTL) if not saved: await self._cache.delete(code_key) - self._logger.error(f'Crypto wallet set failed: cannot save code hash for user_id={user_id}') + self._logger.error(f'Encrypted mnemonic set failed: cannot save code hash for user_id={user_id}') raise ApplicationException(503, 'Temporary error. Please try again.') message_id = str(ULID()) @@ -95,12 +95,12 @@ class SetCryptoWalletStartCommand: } message = { - 'event': 'crypto_wallet_set', + 'event': 'encrypted_mnemonic_set', 'payload': payload, 'metadata': metadata, } - self._logger.info(f'Crypto wallet set code created for user_id={user_id}') + self._logger.info(f'Encrypted mnemonic set code created for user_id={user_id}') try: await self._messanger.publish_to_queue( @@ -118,12 +118,12 @@ class SetCryptoWalletStartCommand: except Exception as rollback_err: self._logger.error(f'Publish failed and rollback cache failed for user_id={user_id}: {str(rollback_err)}') - self._logger.error(f'Failed to publish crypto wallet set email for user_id={user_id}: {str(exception)}') + self._logger.error(f'Failed to publish encrypted mnemonic set email for user_id={user_id}: {str(exception)}') raise ApplicationException(503, 'Temporary error. Please try again.') return True - self._logger.error(f'Crypto wallet set failed: code space exhausted for user_id={user_id}') + self._logger.error(f'Encrypted mnemonic set failed: code space exhausted for user_id={user_id}') raise ApplicationException(503, 'Temporary error. Please try again.') finally: diff --git a/src/application/domain/entities/user.py b/src/application/domain/entities/user.py index b7c2634..5247505 100644 --- a/src/application/domain/entities/user.py +++ b/src/application/domain/entities/user.py @@ -14,7 +14,7 @@ class UserEntity: last_name: str | None = None birth_date: date | None = None - crypto_wallet: str | None = None + encrypted_mnemonic: str | None = None phone: str | None = None passport_data: str | None = None diff --git a/src/infrastructure/cache/keydb_client.py b/src/infrastructure/cache/keydb_client.py index afa7d32..520158f 100644 --- a/src/infrastructure/cache/keydb_client.py +++ b/src/infrastructure/cache/keydb_client.py @@ -37,7 +37,7 @@ class KeydbCache(ICache): 'middle_name': user.middle_name, 'last_name': user.last_name, 'birth_date': str(user.birth_date) if user.birth_date else None, - 'crypto_wallet': user.crypto_wallet, + 'encrypted_mnemonic': user.encrypted_mnemonic, 'phone': user.phone, 'passport_data': user.passport_data, 'inn': user.inn, diff --git a/src/infrastructure/database/models/user.py b/src/infrastructure/database/models/user.py index ecdb681..7ad1d0a 100644 --- a/src/infrastructure/database/models/user.py +++ b/src/infrastructure/database/models/user.py @@ -1,9 +1,9 @@ from __future__ import annotations -from sqlalchemy import Boolean,Date,String,DateTime -from sqlalchemy.orm import Mapped,mapped_column +from sqlalchemy import Boolean, Date, DateTime, String, Text +from sqlalchemy.orm import Mapped, mapped_column from src.infrastructure.database.models.base import Base -from src.infrastructure.database.models.mixins import UlidPrimaryKeyMixin,AuditTimestampsMixin,SoftDeleteMixin +from src.infrastructure.database.models.mixins import UlidPrimaryKeyMixin, AuditTimestampsMixin, SoftDeleteMixin class UserModel(Base, UlidPrimaryKeyMixin, AuditTimestampsMixin, SoftDeleteMixin): @@ -17,7 +17,7 @@ class UserModel(Base, UlidPrimaryKeyMixin, AuditTimestampsMixin, SoftDeleteMixin middle_name: Mapped[str | None] = mapped_column(String(128), nullable=True) birth_date: Mapped[Date | None] = mapped_column(Date, nullable=True) - crypto_wallet: Mapped[str | None] = mapped_column(String(255), nullable=True) + encrypted_mnemonic: Mapped[str | None] = mapped_column(Text, nullable=True) phone: Mapped[str | None] = mapped_column(String(16), nullable=True) passport_data: Mapped[str | None] = mapped_column(String(255), nullable=True) diff --git a/src/infrastructure/database/repositories/user_repository.py b/src/infrastructure/database/repositories/user_repository.py index 522c739..6f8c4ee 100644 --- a/src/infrastructure/database/repositories/user_repository.py +++ b/src/infrastructure/database/repositories/user_repository.py @@ -40,7 +40,7 @@ class UserRepository(IUserRepository): middle_name=user.middle_name, last_name=user.last_name, birth_date=user.birth_date, - crypto_wallet=user.crypto_wallet, + encrypted_mnemonic=user.encrypted_mnemonic, phone=user.phone, passport_data=user.passport_data, inn=user.inn, @@ -89,8 +89,8 @@ class UserRepository(IUserRepository): ) return await self._update_field(user_id, **payload) - async def set_crypto_wallet(self, user_id: str, wallet_address: str) -> UserEntity: - return await self._update_field(user_id, crypto_wallet=wallet_address) + 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 get_password_hash(self, user_id: str) -> str: try: diff --git a/src/presentation/dependencies/__init__.py b/src/presentation/dependencies/__init__.py index 615c62b..c05cd41 100644 --- a/src/presentation/dependencies/__init__.py +++ b/src/presentation/dependencies/__init__.py @@ -1,8 +1,8 @@ from src.presentation.dependencies.commands import ( get_get_me_command, get_set_phone_command, - get_set_crypto_wallet_start_command, - get_set_crypto_wallet_complete_command, + get_set_encrypted_mnemonic_start_command, + get_set_encrypted_mnemonic_complete_command, get_update_bank_details_start_command, get_update_bank_details_complete_command, get_change_password_start_command, diff --git a/src/presentation/dependencies/commands.py b/src/presentation/dependencies/commands.py index 1c139b1..1aa4216 100644 --- a/src/presentation/dependencies/commands.py +++ b/src/presentation/dependencies/commands.py @@ -1,6 +1,6 @@ from fastapi import Depends from src.application.abstractions import IUnitOfWork -from src.application.commands import GetMeCommand, SetPhoneCommand, SetCryptoWalletStartCommand, SetCryptoWalletCompleteCommand, UpdateBankDetailsStartCommand, UpdateBankDetailsCompleteCommand, ChangePasswordStartCommand, ChangePasswordCompleteCommand, ChangeEmailStartCommand, ChangeEmailConfirmOldCommand, ChangeEmailCompleteCommand +from src.application.commands import GetMeCommand, SetPhoneCommand, SetEncryptedMnemonicStartCommand, SetEncryptedMnemonicCompleteCommand, UpdateBankDetailsStartCommand, UpdateBankDetailsCompleteCommand, ChangePasswordStartCommand, ChangePasswordCompleteCommand, ChangeEmailStartCommand, ChangeEmailConfirmOldCommand, ChangeEmailCompleteCommand from src.application.contracts import ILogger, ICache, IQueueMessanger, IHashService from src.presentation.dependencies.cache import get_cache from src.presentation.dependencies.logger import get_logger @@ -25,14 +25,14 @@ def get_set_phone_command( return SetPhoneCommand(logger=logger, unit_of_work=unit_of_work, cache=cache) -def get_set_crypto_wallet_start_command( +def get_set_encrypted_mnemonic_start_command( logger: ILogger = Depends(get_logger), unit_of_work: IUnitOfWork = Depends(get_unit_of_work), cache: ICache = Depends(get_cache), messanger: IQueueMessanger = Depends(get_rabbit), hash_service: IHashService = Depends(get_hash_service), -) -> SetCryptoWalletStartCommand: - return SetCryptoWalletStartCommand( +) -> SetEncryptedMnemonicStartCommand: + return SetEncryptedMnemonicStartCommand( logger=logger, unit_of_work=unit_of_work, cache=cache, @@ -41,13 +41,13 @@ def get_set_crypto_wallet_start_command( ) -def get_set_crypto_wallet_complete_command( +def get_set_encrypted_mnemonic_complete_command( logger: ILogger = Depends(get_logger), unit_of_work: IUnitOfWork = Depends(get_unit_of_work), cache: ICache = Depends(get_cache), hash_service: IHashService = Depends(get_hash_service), -) -> SetCryptoWalletCompleteCommand: - return SetCryptoWalletCompleteCommand( +) -> SetEncryptedMnemonicCompleteCommand: + return SetEncryptedMnemonicCompleteCommand( logger=logger, unit_of_work=unit_of_work, cache=cache, diff --git a/src/presentation/routing/account.py b/src/presentation/routing/account.py index abd463b..6a7e507 100644 --- a/src/presentation/routing/account.py +++ b/src/presentation/routing/account.py @@ -30,7 +30,7 @@ async def me( 'middle_name': user.middle_name, 'last_name': user.last_name, 'birth_date': str(user.birth_date) if user.birth_date else None, - 'crypto_wallet': user.crypto_wallet, + 'encrypted_mnemonic': user.encrypted_mnemonic, 'phone': user.phone, 'passport_data': user.passport_data, 'inn': user.inn, diff --git a/src/presentation/routing/account_settings.py b/src/presentation/routing/account_settings.py index 2ad6179..44897d3 100644 --- a/src/presentation/routing/account_settings.py +++ b/src/presentation/routing/account_settings.py @@ -1,13 +1,13 @@ from fastapi import APIRouter, Request, Depends from fastapi.responses import ORJSONResponse from starlette import status -from src.application.commands import SetPhoneCommand, SetCryptoWalletStartCommand, SetCryptoWalletCompleteCommand, UpdateBankDetailsStartCommand, UpdateBankDetailsCompleteCommand, ChangePasswordStartCommand, ChangePasswordCompleteCommand, ChangeEmailStartCommand, ChangeEmailConfirmOldCommand, ChangeEmailCompleteCommand +from src.application.commands import SetPhoneCommand, SetEncryptedMnemonicStartCommand, SetEncryptedMnemonicCompleteCommand, UpdateBankDetailsStartCommand, UpdateBankDetailsCompleteCommand, ChangePasswordStartCommand, ChangePasswordCompleteCommand, ChangeEmailStartCommand, ChangeEmailConfirmOldCommand, ChangeEmailCompleteCommand from src.application.domain.dto import AuthContext from src.presentation.decorators import require_access_token from src.presentation.dependencies import ( get_set_phone_command, - get_set_crypto_wallet_start_command, - get_set_crypto_wallet_complete_command, + get_set_encrypted_mnemonic_start_command, + get_set_encrypted_mnemonic_complete_command, get_update_bank_details_start_command, get_update_bank_details_complete_command, get_change_password_start_command, @@ -16,7 +16,7 @@ from src.presentation.dependencies import ( get_change_email_confirm_old_command, get_change_email_complete_command, ) -from src.presentation.schemas import SetPhoneRequest, CryptoWalletConfirmRequest, BankConfirmRequest, ChangePasswordConfirmRequest, ChangeEmailConfirmOldRequest, ChangeEmailCompleteRequest +from src.presentation.schemas import SetPhoneRequest, EncryptedMnemonicConfirmRequest, BankConfirmRequest, ChangePasswordConfirmRequest, ChangeEmailConfirmOldRequest, ChangeEmailCompleteRequest account_settings_router = APIRouter(prefix='/settings') @@ -33,29 +33,29 @@ async def set_phone( return ORJSONResponse(status_code=status.HTTP_200_OK, content={'phone': user.phone}) -@account_settings_router.post(path='/crypto-wallet/start', response_class=ORJSONResponse, status_code=status.HTTP_200_OK) -async def crypto_wallet_start( +@account_settings_router.post(path='/encrypted-mnemonic/start', response_class=ORJSONResponse, status_code=status.HTTP_200_OK) +async def encrypted_mnemonic_start( request: Request, auth: AuthContext = Depends(require_access_token), - command: SetCryptoWalletStartCommand = Depends(get_set_crypto_wallet_start_command), + command: SetEncryptedMnemonicStartCommand = Depends(get_set_encrypted_mnemonic_start_command), ): result = await command(user_id=auth.user_id) return {'success': result} -@account_settings_router.post(path='/crypto-wallet/complete', response_class=ORJSONResponse, status_code=status.HTTP_200_OK) -async def crypto_wallet_complete( +@account_settings_router.post(path='/encrypted-mnemonic/complete', response_class=ORJSONResponse, status_code=status.HTTP_200_OK) +async def encrypted_mnemonic_complete( request: Request, - body: CryptoWalletConfirmRequest, + body: EncryptedMnemonicConfirmRequest, auth: AuthContext = Depends(require_access_token), - command: SetCryptoWalletCompleteCommand = Depends(get_set_crypto_wallet_complete_command), + command: SetEncryptedMnemonicCompleteCommand = Depends(get_set_encrypted_mnemonic_complete_command), ): user = await command( user_id=auth.user_id, code=body.code, - wallet_address=body.wallet_address, + encrypted_mnemonic=body.encrypted_mnemonic, ) - return {'crypto_wallet': user.crypto_wallet} + return {'encrypted_mnemonic': user.encrypted_mnemonic} @account_settings_router.post(path='/email/start', response_class=ORJSONResponse, status_code=status.HTTP_200_OK) diff --git a/src/presentation/schemas/__init__.py b/src/presentation/schemas/__init__.py index b3a1cc2..f629d3b 100644 --- a/src/presentation/schemas/__init__.py +++ b/src/presentation/schemas/__init__.py @@ -1,5 +1,5 @@ from src.presentation.schemas.phone import SetPhoneRequest from src.presentation.schemas.bank import BankUpdateRequest, BankConfirmRequest -from src.presentation.schemas.crypto_wallet import CryptoWalletConfirmRequest +from src.presentation.schemas.encrypted_mnemonic import EncryptedMnemonicConfirmRequest from src.presentation.schemas.password import ChangePasswordConfirmRequest from src.presentation.schemas.email import ChangeEmailConfirmOldRequest, ChangeEmailCompleteRequest \ No newline at end of file diff --git a/src/presentation/schemas/crypto_wallet.py b/src/presentation/schemas/encrypted_mnemonic.py similarity index 56% rename from src/presentation/schemas/crypto_wallet.py rename to src/presentation/schemas/encrypted_mnemonic.py index e50d133..54a6049 100644 --- a/src/presentation/schemas/crypto_wallet.py +++ b/src/presentation/schemas/encrypted_mnemonic.py @@ -2,9 +2,9 @@ import re from pydantic import BaseModel, field_validator -class CryptoWalletConfirmRequest(BaseModel): +class EncryptedMnemonicConfirmRequest(BaseModel): code: str - wallet_address: str + encrypted_mnemonic: str @field_validator('code') @classmethod @@ -14,10 +14,10 @@ class CryptoWalletConfirmRequest(BaseModel): raise ValueError('Code must be exactly 6 digits') return v - @field_validator('wallet_address') + @field_validator('encrypted_mnemonic') @classmethod - def validate_tron_address(cls, v: str) -> str: + def validate_encrypted_mnemonic(cls, v: str) -> str: v = v.strip() - if not re.match(r'^T[1-9A-HJ-NP-Za-km-z]{33}$', v): - raise ValueError('Invalid TRON wallet address') + if not v: + raise ValueError('encrypted_mnemonic must not be empty') return v