feat: add get me
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
5
src/infrastructure/cache/keydb_client.py
vendored
5
src/infrastructure/cache/keydb_client.py
vendored
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user