Initial commit

This commit is contained in:
2026-04-22 09:57:24 +03:00
commit 00e601c21a
81 changed files with 3552 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
from src.application.contracts.i_logger import ILogger
from src.application.contracts.i_jwt_service import IJwtService
from src.application.contracts.i_csrf_service import ICsrfService
from src.application.contracts.i_cache import ICache
from src.application.contracts.i_hash_service import IHashService
from src.application.contracts.i_queue_messanger import IQueueMessanger

View File

@@ -0,0 +1,30 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from src.application.domain.entities.user import UserEntity
class ICache(ABC):
@abstractmethod
async def set(self, key: str, value: str, ttl: int) -> bool:
raise NotImplementedError
@abstractmethod
async def set_nx(self, key: str, value: str, ttl: int) -> bool:
raise NotImplementedError
@abstractmethod
async def get(self, key: str) -> str | None:
raise NotImplementedError
@abstractmethod
async def delete(self, key: str) -> bool:
raise NotImplementedError
@abstractmethod
async def get_user(self, user_id: str) -> dict | None:
raise NotImplementedError
@abstractmethod
async def set_user(self, user_id: str, user: UserEntity, ttl: int = 300) -> None:
raise NotImplementedError

View File

@@ -0,0 +1,26 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, Optional, Mapping
class ICsrfService(ABC):
@abstractmethod
def issue(self, subject: Optional[str] = None) -> str:
raise NotImplementedError
@abstractmethod
def verify(self, token: str, expected_subject: Optional[str] = None) -> dict[str, Any]:
raise NotImplementedError
@abstractmethod
def extract(self, cookies: Mapping[str, str], headers: Mapping[str, str]) -> tuple[Optional[str], Optional[str]]:
raise NotImplementedError
@abstractmethod
def verify_pair(
self,
cookie_token: Optional[str],
header_token: Optional[str],
expected_subject: Optional[str] = None,
) -> None:
raise NotImplementedError

View File

@@ -0,0 +1,12 @@
from abc import ABC, abstractmethod
class IHashService(ABC):
@abstractmethod
async def hash(self, value: str) -> str:
raise NotImplementedError
@abstractmethod
async def verify(self, hashed_value: str, plain_value: str) -> bool:
raise NotImplementedError

View File

@@ -0,0 +1,10 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from src.application.domain.dto import AccessTokenPayload
class IJwtService(ABC):
@abstractmethod
async def decode_access_token(self, token: str) -> AccessTokenPayload:
raise NotImplementedError

View File

@@ -0,0 +1,68 @@
from typing import Protocol, Optional, Callable
from src.application.domain.enums.log_format import LogFormat
from src.application.domain.enums.log_level import LogLevel
class ILogger(Protocol):
"""Interface for synchronous logger with ContextVar support for trace_id."""
log_format: LogFormat
min_level: LogLevel
id_generator: Optional[Callable[[], str]]
instance_id: str
def set_format(self, log_format: LogFormat) -> None:
"""Set log format using LogFormat enum"""
...
def set_min_level(self, level: LogLevel) -> None:
"""Set minimum log level"""
...
def new_trace_id(self) -> str:
"""Create and set new trace_id in context"""
...
def set_trace_id(self, trace_id: str) -> None:
"""Set existing trace_id in context"""
...
def get_trace_id(self) -> str:
"""Get current trace_id from context"""
...
def clear_trace_id(self) -> None:
"""Clear the trace_id in the context"""
...
def set_instance_id(self, instance_id: str) -> None:
"""Set service instance id (ULID recommended)"""
...
def get_instance_id(self) -> str:
"""Get current service instance id"""
...
def debug(self, message: str) -> None:
"""Log debug message"""
...
def info(self, message: str) -> None:
"""Log info message"""
...
def warning(self, message: str) -> None:
"""Log warning message"""
...
def error(self, message: str) -> None:
"""Log error message"""
...
def critical(self, message: str) -> None:
"""Log critical message"""
...
def exception(self, message: str) -> None:
"""Log exception with traceback"""
...

View File

@@ -0,0 +1,40 @@
from abc import ABC, abstractmethod
from typing import Mapping, Any
class IQueueMessanger(ABC):
@abstractmethod
async def connect(self) -> None:
raise NotImplementedError
@abstractmethod
async def close(self) -> None:
raise NotImplementedError
@abstractmethod
async def publish_to_queue(
self,
queue: str,
message: Any,
*,
persist: bool = True,
headers: Mapping[str, Any] | None = None,
correlation_id: str | None = None,
message_id: str | None = None,
) -> None:
raise NotImplementedError
@abstractmethod
async def publish(
self,
message: Any,
*,
exchange: str,
routing_key: str,
persist: bool = True,
headers: Mapping[str, Any] | None = None,
correlation_id: str | None = None,
message_id: str | None = None,
) -> None:
raise NotImplementedError

View File

@@ -0,0 +1,2 @@
from src.application.domain.dto.token import AccessTokenPayload, AuthContext
from src.application.domain.dto.keys import JwtPublicKey, JwtPublicKeySet

View File

@@ -0,0 +1,20 @@
from dataclasses import dataclass
from typing import Optional, Dict
@dataclass(frozen=True)
class JwtPublicKey:
kid: str
public_key_pem: str
@dataclass(frozen=True)
class JwtPublicKeySet:
active: JwtPublicKey
previous: Optional[JwtPublicKey] = 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

View File

@@ -0,0 +1,18 @@
from pydantic import BaseModel
class AccessTokenPayload(BaseModel):
sub: str
type: str
sid: str
iat: int
nbf: int
exp: int
iss: str | None = None
aud: str | None = None
class AuthContext(BaseModel):
user_id: str
sid: str
token: AccessTokenPayload

View File

@@ -0,0 +1,5 @@
from src.application.domain.entities.user import UserEntity
from src.application.domain.entities.session import SessionEntity
__all__ = ['UserEntity', 'SessionEntity']

View File

@@ -0,0 +1,20 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
@dataclass(slots=True)
class SessionEntity:
sid: str
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

View File

@@ -0,0 +1,30 @@
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
crypto_wallet: str | None = None
phone: str | None = None
bik: str | None = None
account_number: str | None = None
card_number: str | None = None
inn: 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

View File

@@ -0,0 +1,2 @@
from src.application.domain.enums.log_level import LogLevel
from src.application.domain.enums.log_format import LogFormat

View File

@@ -0,0 +1,7 @@
from enum import Enum
class LogFormat(Enum):
"""Enum for supported log formats"""
TEXT = 'text'
JSON = 'json'

View 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

View File

@@ -0,0 +1 @@
from src.application.domain.exceptions.application_exceptions import ApplicationException

View 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}"