From d9ecdd7b86d55d0b22a558b7ca1feec249076853 Mon Sep 17 00:00:00 2001 From: Noloquideus Date: Tue, 12 May 2026 20:48:06 +0300 Subject: [PATCH] feat: add get me --- docker-compose.yml | 30 +---- .../commands/update_bank_details_complete.py | 18 +-- src/application/domain/entities/user.py | 5 +- src/infrastructure/cache/keydb_client.py | 5 +- .../database/repositories/user_repository.py | 14 ++- src/presentation/routing/account.py | 5 +- src/presentation/routing/account_settings.py | 12 +- src/presentation/schemas/bank.py | 104 +++++++++--------- 8 files changed, 88 insertions(+), 105 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3835b5e..22eaff2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,12 +5,12 @@ services: context: . dockerfile: Dockerfile ports: - - "8002:8002" + - "8003:8003" environment: PYTHONUNBUFFERED: "1" APP_MODULE: "src.main:app" APP_HOST: "0.0.0.0" - APP_PORT: "8002" + APP_PORT: "8003" APP_WORKERS: "1" env_file: - .env @@ -52,32 +52,6 @@ services: timeout: 2s retries: 20 -# keydb: -# image: eqalpha/keydb -# container_name: keydb -# restart: no -# expose: -# - "6379" -# volumes: -# - keydb_data:/data -# environment: -# KEYDB_PASSWORD: keydb -# command: > -# sh -c " -# keydb-server -# --requirepass $$KEYDB_PASSWORD -# --dir /data -# --appendonly yes -# --appendfsync everysec -# --save 900 1 -# --save 300 10 -# --save 60 10000 -# " -# healthcheck: -# test: ["CMD", "redis-cli", "ping"] -# interval: 5s -# timeout: 2s -# retries: 20 volumes: keydb_data: diff --git a/src/application/commands/update_bank_details_complete.py b/src/application/commands/update_bank_details_complete.py index 02b0c49..1c5ba8a 100644 --- a/src/application/commands/update_bank_details_complete.py +++ b/src/application/commands/update_bank_details_complete.py @@ -24,9 +24,9 @@ class UpdateBankDetailsCompleteCommand: *, user_id: str, code: str, - bik: str | None = None, - account_number: str | None = None, - card_number: str | None = None, + passport_data: str | None = None, + inn: str | None = None, + erc20: str | None = None, ) -> UserEntity: code = (code or '').strip() @@ -56,12 +56,12 @@ class UpdateBankDetailsCompleteCommand: raise ApplicationException(400, 'Invalid or expired code') fields = {} - if bik is not None: - fields['bik'] = bik - if account_number is not None: - fields['account_number'] = account_number - if card_number is not None: - fields['card_number'] = card_number + if passport_data is not None: + fields['passport_data'] = passport_data + if inn is not None: + fields['inn'] = inn + if erc20 is not None: + fields['erc20'] = erc20 user = await self._unit_of_work.user_repository.set_bank_details(user_id, **fields) await self._cache.set_user(user_id, user) diff --git a/src/application/domain/entities/user.py b/src/application/domain/entities/user.py index f0a7961..b7c2634 100644 --- a/src/application/domain/entities/user.py +++ b/src/application/domain/entities/user.py @@ -17,10 +17,9 @@ class UserEntity: crypto_wallet: str | None = None phone: str | None = None - bik: str | None = None - account_number: str | None = None - card_number: str | None = None + passport_data: str | None = None inn: str | None = None + erc20: str | None = None kyc_verified: bool | None = None is_deleted: bool | None = None diff --git a/src/infrastructure/cache/keydb_client.py b/src/infrastructure/cache/keydb_client.py index 17d98be..afa7d32 100644 --- a/src/infrastructure/cache/keydb_client.py +++ b/src/infrastructure/cache/keydb_client.py @@ -39,10 +39,9 @@ class KeydbCache(ICache): 'birth_date': str(user.birth_date) if user.birth_date else None, 'crypto_wallet': user.crypto_wallet, 'phone': user.phone, - 'bik': user.bik, - 'account_number': user.account_number, - 'card_number': user.card_number, + 'passport_data': user.passport_data, 'inn': user.inn, + 'erc20': user.erc20, 'kyc_verified': user.kyc_verified, 'is_deleted': user.is_deleted, 'created_at': user.created_at.isoformat() if user.created_at else None, diff --git a/src/infrastructure/database/repositories/user_repository.py b/src/infrastructure/database/repositories/user_repository.py index d491121..522c739 100644 --- a/src/infrastructure/database/repositories/user_repository.py +++ b/src/infrastructure/database/repositories/user_repository.py @@ -42,10 +42,9 @@ class UserRepository(IUserRepository): birth_date=user.birth_date, crypto_wallet=user.crypto_wallet, phone=user.phone, - bik=user.bik, - account_number=user.account_number, - card_number=user.card_number, + passport_data=user.passport_data, inn=user.inn, + erc20=user.erc20, kyc_verified_at=user.kyc_verified_at, kyc_verified=user.kyc_verified, is_deleted=user.is_deleted, @@ -81,7 +80,14 @@ class UserRepository(IUserRepository): return await self._update_field(user_id, phone=phone) async def set_bank_details(self, user_id: str, **fields: str) -> UserEntity: - return await self._update_field(user_id, **fields) + 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 ApplicationException( + status_code=status.HTTP_400_BAD_REQUEST, + message='No identity fields to update', + ) + 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) diff --git a/src/presentation/routing/account.py b/src/presentation/routing/account.py index 61e056c..7b8f270 100644 --- a/src/presentation/routing/account.py +++ b/src/presentation/routing/account.py @@ -30,10 +30,9 @@ async def me( 'birth_date': str(user.birth_date) if user.birth_date else None, 'crypto_wallet': user.crypto_wallet, 'phone': user.phone, - 'bik': user.bik, - 'account_number': user.account_number, - 'card_number': user.card_number, + 'passport_data': user.passport_data, 'inn': user.inn, + 'erc20': user.erc20, 'kyc_verified': user.kyc_verified, 'is_deleted': user.is_deleted, 'created_at': user.created_at.isoformat() if user.created_at else None, diff --git a/src/presentation/routing/account_settings.py b/src/presentation/routing/account_settings.py index 38c4d8f..2ad6179 100644 --- a/src/presentation/routing/account_settings.py +++ b/src/presentation/routing/account_settings.py @@ -136,15 +136,15 @@ async def bank_details_complete( user = await command( user_id=auth.user_id, code=body.code, - bik=body.bik, - account_number=body.account_number, - card_number=body.card_number, + passport_data=body.passport_data, + inn=body.inn, + erc20=body.erc20, ) return ORJSONResponse( status_code=status.HTTP_200_OK, content={ - 'bik': user.bik, - 'account_number': user.account_number, - 'card_number': user.card_number, + 'passport_data': user.passport_data, + 'inn': user.inn, + 'erc20': user.erc20, }, ) diff --git a/src/presentation/schemas/bank.py b/src/presentation/schemas/bank.py index 04fa5fb..9f2a0f3 100644 --- a/src/presentation/schemas/bank.py +++ b/src/presentation/schemas/bank.py @@ -4,69 +4,67 @@ from pydantic import BaseModel, field_validator, model_validator class BankUpdateRequest(BaseModel): - bik: str | None = None - account_number: str | None = None - card_number: str | None = None + passport_data: str | None = None + inn: str | None = None + erc20: str | None = None @model_validator(mode='after') def at_least_one(self) -> Self: - if not any([self.bik, self.account_number, self.card_number]): + if not any([self.passport_data, self.inn, self.erc20]): raise ValueError('At least one field is required') return self - @field_validator('bik') + @field_validator('passport_data', 'inn', 'erc20') @classmethod - def validate_bik(cls, v: str | None) -> str | None: + def strip_optional(cls, v: str | None) -> str | None: + if v is None: + return None + s = v.strip() + return s or None + + @field_validator('inn') + @classmethod + def validate_inn(cls, v: str | None) -> str | None: if v is None: return None v = v.strip() - if not re.match(r'^\d{9}$', v): - raise ValueError('BIK must be exactly 9 digits') + if not re.match(r'^\d{10}(\d{2})?$', v): + raise ValueError('INN must be 10 or 12 digits') + if len(v) > 12: + raise ValueError('INN is too long') return v - @field_validator('account_number') + @field_validator('erc20') @classmethod - def validate_account_number(cls, v: str | None) -> str | None: + def validate_erc20(cls, v: str | None) -> str | None: if v is None: return None v = v.strip() - if not re.match(r'^\d{20}$', v): - raise ValueError('Account number must be exactly 20 digits') + if not re.match(r'^0x[a-fA-F0-9]{40}$', v): + raise ValueError('ERC20 address must be 0x followed by 40 hex characters') return v - @field_validator('card_number') + @field_validator('passport_data') @classmethod - def validate_card_number(cls, v: str | None) -> str | None: + def validate_passport_data(cls, v: str | None) -> str | None: if v is None: return None - v = re.sub(r'[\s\-]', '', v) - if not re.match(r'^\d{13,19}$', v): - raise ValueError('Card number must be 13-19 digits') - if not cls._luhn_check(v): - raise ValueError('Invalid card number (Luhn check failed)') + v = v.strip() + if len(v) > 255: + raise ValueError('Passport data is too long') return v - @staticmethod - def _luhn_check(number: str) -> bool: - digits = [int(d) for d in number] - odd_digits = digits[-1::-2] - even_digits = digits[-2::-2] - total = sum(odd_digits) - for d in even_digits: - total += sum(divmod(d * 2, 10)) - return total % 10 == 0 - class BankConfirmRequest(BaseModel): code: str - bik: str | None = None - account_number: str | None = None - card_number: str | None = None + passport_data: str | None = None + inn: str | None = None + erc20: str | None = None @model_validator(mode='after') def at_least_one_field(self) -> Self: - if not any([self.bik, self.account_number, self.card_number]): - raise ValueError('At least one bank field is required') + if not any([self.passport_data, self.inn, self.erc20]): + raise ValueError('At least one field is required') return self @field_validator('code') @@ -77,34 +75,42 @@ class BankConfirmRequest(BaseModel): raise ValueError('Code must be exactly 6 digits') return v - @field_validator('bik') + @field_validator('passport_data', 'inn', 'erc20') @classmethod - def validate_bik(cls, v: str | None) -> str | None: + def strip_optional(cls, v: str | None) -> str | None: + if v is None: + return None + s = v.strip() + return s or None + + @field_validator('inn') + @classmethod + def validate_inn(cls, v: str | None) -> str | None: if v is None: return None v = v.strip() - if not re.match(r'^\d{9}$', v): - raise ValueError('BIK must be exactly 9 digits') + if not re.match(r'^\d{10}(\d{2})?$', v): + raise ValueError('INN must be 10 or 12 digits') + if len(v) > 12: + raise ValueError('INN is too long') return v - @field_validator('account_number') + @field_validator('erc20') @classmethod - def validate_account_number(cls, v: str | None) -> str | None: + def validate_erc20(cls, v: str | None) -> str | None: if v is None: return None v = v.strip() - if not re.match(r'^\d{20}$', v): - raise ValueError('Account number must be exactly 20 digits') + if not re.match(r'^0x[a-fA-F0-9]{40}$', v): + raise ValueError('ERC20 address must be 0x followed by 40 hex characters') return v - @field_validator('card_number') + @field_validator('passport_data') @classmethod - def validate_card_number(cls, v: str | None) -> str | None: + def validate_passport_data(cls, v: str | None) -> str | None: if v is None: return None - v = re.sub(r'[\s\-]', '', v) - if not re.match(r'^\d{13,19}$', v): - raise ValueError('Card number must be 13-19 digits') - if not BankUpdateRequest._luhn_check(v): - raise ValueError('Invalid card number (Luhn check failed)') + v = v.strip() + if len(v) > 255: + raise ValueError('Passport data is too long') return v