init
This commit is contained in:
3
src/application/domain/dto/__init__.py
Normal file
3
src/application/domain/dto/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from src.application.domain.dto.admin_auth import AdminLoginDto
|
||||
from src.application.domain.dto.token import AccessTokenPayload, AdminAuthContext
|
||||
from src.application.domain.dto.keys import JwtKeySet, JwtKeyPair
|
||||
15
src/application/domain/dto/admin_auth.py
Normal file
15
src/application/domain/dto/admin_auth.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdminLoginDto:
|
||||
id: str
|
||||
email: str
|
||||
first_name: str | None
|
||||
last_name: str | None
|
||||
role: str
|
||||
access_token: str
|
||||
last_login_at: datetime | None = None
|
||||
21
src/application/domain/dto/keys.py
Normal file
21
src/application/domain/dto/keys.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Dict
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class JwtKeyPair:
|
||||
kid: str
|
||||
private_key_pem: str
|
||||
public_key_pem: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class JwtKeySet:
|
||||
active: JwtKeyPair
|
||||
previous: Optional[JwtKeyPair] = None
|
||||
|
||||
def public_keys_by_kid(self) -> Dict[str, str]:
|
||||
out = {self.active.kid: self.active.public_key_pem}
|
||||
if self.previous:
|
||||
out[self.previous.kid] = self.previous.public_key_pem
|
||||
return out
|
||||
17
src/application/domain/dto/token.py
Normal file
17
src/application/domain/dto/token.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AccessTokenPayload(BaseModel):
|
||||
sub: str
|
||||
type: str
|
||||
role: str | None = None
|
||||
iat: int
|
||||
nbf: int
|
||||
exp: int
|
||||
iss: str | None = None
|
||||
aud: str | None = None
|
||||
|
||||
|
||||
class AdminAuthContext(BaseModel):
|
||||
admin_user_id: str
|
||||
role: str
|
||||
33
src/application/domain/dto/user.py
Normal file
33
src/application/domain/dto/user.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, date
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class UserCreatedDto:
|
||||
id: str
|
||||
email: str
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class UserLoginDto:
|
||||
id: str | None = None
|
||||
email: str | None = None
|
||||
first_name: str | None = None
|
||||
middle_name: str | None = None
|
||||
last_name: str | None = None
|
||||
birth_date: date | None = None
|
||||
encrypted_mnemonic: str | None = None
|
||||
phone: str | None = None
|
||||
passport_data: str | None = None
|
||||
inn: str | None = None
|
||||
erc20: str | None = None
|
||||
avatar_link: str | None = None
|
||||
kyc_verified: bool | None = None
|
||||
access_token: str | None = None
|
||||
refresh_token: str | None = None
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
kyc_verified_at: datetime | None = None
|
||||
|
||||
5
src/application/domain/entities/__init__.py
Normal file
5
src/application/domain/entities/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from src.application.domain.entities.user import UserEntity
|
||||
from src.application.domain.entities.admin_user import AdminUserEntity
|
||||
from src.application.domain.entities.admin_session import AdminSessionEntity
|
||||
|
||||
__all__ = ['UserEntity', 'AdminUserEntity', 'AdminSessionEntity']
|
||||
18
src/application/domain/entities/admin_session.py
Normal file
18
src/application/domain/entities/admin_session.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdminSessionEntity:
|
||||
sid: str
|
||||
admin_user_id: str
|
||||
device_id: str
|
||||
revoked_at: datetime | None
|
||||
last_seen_at: datetime
|
||||
refresh_jti_hash: str | None
|
||||
refresh_expires_at: datetime | None
|
||||
user_agent: str | None = None
|
||||
first_ip: str | None = None
|
||||
last_ip: str | None = None
|
||||
18
src/application/domain/entities/admin_user.py
Normal file
18
src/application/domain/entities/admin_user.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdminUserEntity:
|
||||
id: str
|
||||
email: str
|
||||
password_hash: str
|
||||
first_name: str | None
|
||||
last_name: str | None
|
||||
role: str
|
||||
is_active: bool
|
||||
last_login_at: datetime | None
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
72
src/application/domain/entities/organization.py
Normal file
72
src/application/domain/entities/organization.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class LegalEntityEntity:
|
||||
id: str
|
||||
user_id: str
|
||||
name: str
|
||||
short_name: str | None
|
||||
inn: str
|
||||
ogrn: str | None
|
||||
kpp: str | None
|
||||
legal_address: str | None
|
||||
actual_address: str | None
|
||||
bank_details: dict[str, Any] | None
|
||||
contact_person: str | None
|
||||
contact_phone: str | None
|
||||
status: str
|
||||
kyc_verified: bool
|
||||
kyc_verified_at: datetime | None
|
||||
encrypted_mnemonic: str | None
|
||||
created_by: str | None
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class OrganizationWalletEntity:
|
||||
id: str
|
||||
organization_id: str
|
||||
chain: str
|
||||
address: str
|
||||
derivation_path: str
|
||||
created_at: datetime | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class OrganizationDocumentEntity:
|
||||
id: str
|
||||
organization_id: str
|
||||
document_type: str
|
||||
file_name: str
|
||||
s3_key: str
|
||||
content_type: str
|
||||
file_size_bytes: int
|
||||
uploaded_by: str | None
|
||||
created_at: datetime | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class PurchaseRequestEntity:
|
||||
id: str
|
||||
organization_id: str
|
||||
status: str
|
||||
usdt_amount: Decimal
|
||||
rub_amount: Decimal | None
|
||||
exchange_rate: Decimal | None
|
||||
service_fee_percent: Decimal | None
|
||||
comment: str | None
|
||||
admin_comment: str | None
|
||||
target_wallet_chain: str | None
|
||||
target_wallet_address: str | None
|
||||
tx_hash: str | None
|
||||
assigned_to: str | None
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
completed_at: datetime | None = None
|
||||
34
src/application/domain/entities/user.py
Normal file
34
src/application/domain/entities/user.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, datetime
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class UserEntity:
|
||||
id: str | None = None
|
||||
email: str | None = None
|
||||
password_hash: str | None = None
|
||||
|
||||
first_name: str | None = None
|
||||
middle_name: str | None = None
|
||||
last_name: str | None = None
|
||||
birth_date: date | None = None
|
||||
|
||||
encrypted_mnemonic: str | None = None
|
||||
phone: str | None = None
|
||||
|
||||
passport_data: str | None = None
|
||||
inn: str | None = None
|
||||
erc20: str | None = None
|
||||
|
||||
avatar_link: str | None = None
|
||||
|
||||
kyc_verified: bool | None = None
|
||||
is_deleted: bool | None = None
|
||||
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
kyc_verified_at: datetime | None = None
|
||||
account_type: str | None = None
|
||||
provisioned_by: str | None = None
|
||||
provisioned_at: datetime | None = None
|
||||
2
src/application/domain/enums/__init__.py
Normal file
2
src/application/domain/enums/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from src.application.domain.enums.log_level import LogLevel
|
||||
from src.application.domain.enums.log_format import LogFormat
|
||||
6
src/application/domain/enums/account_type.py
Normal file
6
src/application/domain/enums/account_type.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class AccountType(StrEnum):
|
||||
INDIVIDUAL = 'individual'
|
||||
LEGAL_ENTITY = 'legal_entity'
|
||||
7
src/application/domain/enums/admin_role.py
Normal file
7
src/application/domain/enums/admin_role.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class AdminRole(StrEnum):
|
||||
OPERATOR = 'operator'
|
||||
COMPLIANCE = 'compliance'
|
||||
SUPERADMIN = 'superadmin'
|
||||
7
src/application/domain/enums/log_format.py
Normal file
7
src/application/domain/enums/log_format.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class LogFormat(Enum):
|
||||
"""Enum for supported log formats"""
|
||||
TEXT = 'text'
|
||||
JSON = 'json'
|
||||
54
src/application/domain/enums/log_level.py
Normal file
54
src/application/domain/enums/log_level.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class LogLevel(Enum):
|
||||
DEBUG = 10
|
||||
INFO = 20
|
||||
WARNING = 30
|
||||
ERROR = 40
|
||||
CRITICAL = 50
|
||||
EXCEPTION = 60
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"[{self.value}, '{self.name}']"
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, LogLevel):
|
||||
return self.value == other.value
|
||||
if isinstance(other, int):
|
||||
return self.value == other
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __lt__(self, other: object) -> bool:
|
||||
if isinstance(other, LogLevel):
|
||||
return self.value < other.value
|
||||
if isinstance(other, int):
|
||||
return self.value < other
|
||||
return NotImplemented
|
||||
|
||||
def __le__(self, other: object) -> bool:
|
||||
if isinstance(other, LogLevel):
|
||||
return self.value <= other.value
|
||||
if isinstance(other, int):
|
||||
return self.value <= other
|
||||
return NotImplemented
|
||||
|
||||
def __gt__(self, other: object) -> bool:
|
||||
if isinstance(other, LogLevel):
|
||||
return self.value > other.value
|
||||
if isinstance(other, int):
|
||||
return self.value > other
|
||||
return NotImplemented
|
||||
|
||||
def __ge__(self, other: object) -> bool:
|
||||
if isinstance(other, LogLevel):
|
||||
return self.value >= other.value
|
||||
if isinstance(other, int):
|
||||
return self.value >= other
|
||||
return NotImplemented
|
||||
10
src/application/domain/exceptions/__init__.py
Normal file
10
src/application/domain/exceptions/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
from src.application.domain.exceptions.bad_request_exception import BadRequestException
|
||||
from src.application.domain.exceptions.conflict_exception import ConflictException
|
||||
from src.application.domain.exceptions.forbidden_exception import ForbiddenException
|
||||
from src.application.domain.exceptions.internal_server_exception import InternalServerException
|
||||
from src.application.domain.exceptions.not_found_exception import NotFoundException
|
||||
from src.application.domain.exceptions.service_unavailable_exception import ServiceUnavailableException
|
||||
from src.application.domain.exceptions.too_many_requests_exception import TooManyRequestsException
|
||||
from src.application.domain.exceptions.unauthorized_exception import UnauthorizedException
|
||||
from src.application.domain.exceptions.refresh_concurrent_exception import RefreshConcurrentException
|
||||
18
src/application/domain/exceptions/application_exception.py
Normal file
18
src/application/domain/exceptions/application_exception.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
from typing import Mapping
|
||||
|
||||
|
||||
class ApplicationException(Exception):
|
||||
def __init__(
|
||||
self,
|
||||
status_code: int,
|
||||
message: str,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
self.message = message
|
||||
self.headers = headers
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.status_code}: {self.message}'
|
||||
16
src/application/domain/exceptions/bad_request_exception.py
Normal file
16
src/application/domain/exceptions/bad_request_exception.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from typing import Mapping
|
||||
from starlette import status
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
|
||||
|
||||
class BadRequestException(ApplicationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = 'Bad Request',
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
message=message,
|
||||
headers=headers,
|
||||
)
|
||||
16
src/application/domain/exceptions/conflict_exception.py
Normal file
16
src/application/domain/exceptions/conflict_exception.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from typing import Mapping
|
||||
from starlette import status
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
|
||||
|
||||
class ConflictException(ApplicationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = 'Conflict',
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
message=message,
|
||||
headers=headers,
|
||||
)
|
||||
16
src/application/domain/exceptions/forbidden_exception.py
Normal file
16
src/application/domain/exceptions/forbidden_exception.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from typing import Mapping
|
||||
from starlette import status
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
|
||||
|
||||
class ForbiddenException(ApplicationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = 'Forbidden',
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
message=message,
|
||||
headers=headers,
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
from typing import Mapping
|
||||
from starlette import status
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
|
||||
|
||||
class InternalServerException(ApplicationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = 'Internal Server Error',
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
message=message,
|
||||
headers=headers,
|
||||
)
|
||||
16
src/application/domain/exceptions/not_found_exception.py
Normal file
16
src/application/domain/exceptions/not_found_exception.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from typing import Mapping
|
||||
from starlette import status
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
|
||||
|
||||
class NotFoundException(ApplicationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = 'Not Found',
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
message=message,
|
||||
headers=headers,
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
from starlette import status
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
|
||||
|
||||
class RefreshConcurrentException(ApplicationException):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
status_code=status.HTTP_200_OK,
|
||||
message='Refresh already handled',
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
from typing import Mapping
|
||||
from starlette import status
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
|
||||
|
||||
class ServiceUnavailableException(ApplicationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = 'Service Unavailable',
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
message=message,
|
||||
headers=headers,
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
from typing import Mapping
|
||||
from starlette import status
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
|
||||
|
||||
class TooManyRequestsException(ApplicationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = 'Too Many Requests',
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
message=message,
|
||||
headers=headers,
|
||||
)
|
||||
16
src/application/domain/exceptions/unauthorized_exception.py
Normal file
16
src/application/domain/exceptions/unauthorized_exception.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from typing import Mapping
|
||||
from starlette import status
|
||||
from src.application.domain.exceptions.application_exception import ApplicationException
|
||||
|
||||
|
||||
class UnauthorizedException(ApplicationException):
|
||||
def __init__(
|
||||
self,
|
||||
message: str = 'Unauthorized',
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
message=message,
|
||||
headers=headers,
|
||||
)
|
||||
Reference in New Issue
Block a user