3 Commits

Author SHA1 Message Date
d794d3f9c6 refactor: add clear cast 2026-05-29 13:34:40 +03:00
d2e8eb9e4d refactor: delete serializers 2026-05-28 20:52:06 +03:00
b6ffdd9553 refactor: delete me_user 2026-05-28 13:41:39 +03:00
8 changed files with 90 additions and 19 deletions

View File

@@ -7,6 +7,8 @@ from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.security import HTTPBasic, HTTPBasicCredentials
from starlette.middleware.cors import CORSMiddleware from starlette.middleware.cors import CORSMiddleware
from starlette.exceptions import HTTPException
from fastapi.exceptions import RequestValidationError
from src.application.domain.exceptions import ApplicationException, UnauthorizedException from src.application.domain.exceptions import ApplicationException, UnauthorizedException
from src.infrastructure.cache import create_redis_client from src.infrastructure.cache import create_redis_client
from src.infrastructure.vault import JwtKeyStore, start_jwt_keys_scheduler from src.infrastructure.vault import JwtKeyStore, start_jwt_keys_scheduler
@@ -15,7 +17,9 @@ from src.infrastructure.logger import logger
from src.infrastructure.config import settings from src.infrastructure.config import settings
from src.presentation.handlers import ( from src.presentation.handlers import (
application_exception_handler, application_exception_handler,
http_exception_handler,
unhandled_exception_handler, unhandled_exception_handler,
validation_exception_handler,
) )
from src.presentation.middleware import TraceIDMiddleware, SecurityHeadersMiddleware from src.presentation.middleware import TraceIDMiddleware, SecurityHeadersMiddleware
from src.presentation.routing import me_router from src.presentation.routing import me_router
@@ -80,6 +84,8 @@ app: FastAPI = FastAPI(
}, },
) )
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(ApplicationException, application_exception_handler) app.add_exception_handler(ApplicationException, application_exception_handler)
app.add_exception_handler(Exception, unhandled_exception_handler) app.add_exception_handler(Exception, unhandled_exception_handler)

View File

@@ -1,2 +1,4 @@
from src.presentation.handlers.unhandled_handler import unhandled_exception_handler from src.presentation.handlers.unhandled_handler import unhandled_exception_handler
from src.presentation.handlers.application_handler import application_exception_handler from src.presentation.handlers.application_handler import application_exception_handler
from src.presentation.handlers.http_exception_handler import http_exception_handler
from src.presentation.handlers.validation_handler import validation_exception_handler

View File

@@ -0,0 +1,11 @@
from fastapi import Request
from fastapi.responses import ORJSONResponse
from starlette.exceptions import HTTPException
async def http_exception_handler(_request: Request, exc: HTTPException) -> ORJSONResponse:
return ORJSONResponse(
status_code=exc.status_code,
content={'detail': exc.detail},
headers=dict(exc.headers) if exc.headers else None,
)

View File

@@ -0,0 +1,10 @@
from fastapi import Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import ORJSONResponse
async def validation_exception_handler(_request: Request, exc: RequestValidationError) -> ORJSONResponse:
return ORJSONResponse(
status_code=422,
content={'detail': exc.errors()},
)

View File

@@ -10,7 +10,6 @@ from src.presentation.dependencies.logger import get_logger
from src.presentation.decorators import csrf_protect from src.presentation.decorators import csrf_protect
from src.presentation.schemas.api_errors import ApiErrorPayload, ApiValidationErrorsPayload from src.presentation.schemas.api_errors import ApiErrorPayload, ApiValidationErrorsPayload
from src.presentation.schemas.me_public import MeUserPublicResponse from src.presentation.schemas.me_public import MeUserPublicResponse
from src.presentation.serializers import me_user_public
account_router = APIRouter() account_router = APIRouter()
@@ -37,6 +36,10 @@ account_router = APIRouter()
'description': 'Ошибка проверки CSRF (нет пары cookie/заголовка, несовпадение или просрочен токен).', 'description': 'Ошибка проверки CSRF (нет пары cookie/заголовка, несовпадение или просрочен токен).',
'model': ApiErrorPayload, 'model': ApiErrorPayload,
}, },
status.HTTP_404_NOT_FOUND: {
'description': 'Учётная запись не найдена.',
'model': ApiErrorPayload,
},
status.HTTP_422_UNPROCESSABLE_ENTITY: { status.HTTP_422_UNPROCESSABLE_ENTITY: {
'description': 'Ошибка валидации входных данных (например, заголовков).', 'description': 'Ошибка валидации входных данных (например, заголовков).',
'model': ApiValidationErrorsPayload, 'model': ApiValidationErrorsPayload,
@@ -52,4 +55,22 @@ async def me(
) -> MeUserPublicResponse: ) -> MeUserPublicResponse:
user = await command(user_id=auth.user_id) user = await command(user_id=auth.user_id)
logger.info(f'Get user: {user.id}') logger.info(f'Get user: {user.id}')
return me_user_public(user) return MeUserPublicResponse(
id=user.id,
email=user.email,
first_name=user.first_name,
middle_name=user.middle_name,
last_name=user.last_name,
birth_date=user.birth_date,
encrypted_mnemonic=user.encrypted_mnemonic,
phone=user.phone,
passport_data=user.passport_data,
inn=user.inn,
erc20=user.erc20,
avatar_link=user.avatar_link,
kyc_verified=user.kyc_verified,
is_deleted=user.is_deleted,
created_at=user.created_at,
updated_at=user.updated_at,
kyc_verified_at=user.kyc_verified_at
)

View File

@@ -30,7 +30,6 @@ from src.presentation.schemas import (
) )
from src.presentation.schemas.api_errors import ApiErrorPayload, ApiValidationErrorsPayload from src.presentation.schemas.api_errors import ApiErrorPayload, ApiValidationErrorsPayload
from src.presentation.schemas.me_public import MeUserPublicResponse, SetAvatarPublicResponse from src.presentation.schemas.me_public import MeUserPublicResponse, SetAvatarPublicResponse
from src.presentation.serializers import me_user_public
account_settings_router = APIRouter(prefix='/settings') account_settings_router = APIRouter(prefix='/settings')
@@ -175,7 +174,25 @@ async def set_avatar(
command: SetAvatarCommand = Depends(get_set_avatar_command), command: SetAvatarCommand = Depends(get_set_avatar_command),
) -> SetAvatarPublicResponse: ) -> SetAvatarPublicResponse:
user, webp_size = await command(user_id=auth.user_id, image_bytes=body.decoded_bytes) user, webp_size = await command(user_id=auth.user_id, image_bytes=body.decoded_bytes)
pub = me_user_public(user) pub = MeUserPublicResponse(
id=user.id,
email=user.email,
first_name=user.first_name,
middle_name=user.middle_name,
last_name=user.last_name,
birth_date=user.birth_date,
encrypted_mnemonic=user.encrypted_mnemonic,
phone=user.phone,
passport_data=user.passport_data,
inn=user.inn,
erc20=user.erc20,
avatar_link=user.avatar_link,
kyc_verified=user.kyc_verified,
is_deleted=user.is_deleted,
created_at=user.created_at,
updated_at=user.updated_at,
kyc_verified_at=user.kyc_verified_at
)
return SetAvatarPublicResponse(**pub.model_dump(), webp_size_bytes=webp_size) return SetAvatarPublicResponse(**pub.model_dump(), webp_size_bytes=webp_size)
@@ -197,7 +214,25 @@ async def delete_avatar(
command: DeleteAvatarCommand = Depends(get_delete_avatar_command), command: DeleteAvatarCommand = Depends(get_delete_avatar_command),
) -> MeUserPublicResponse: ) -> MeUserPublicResponse:
user = await command(user_id=auth.user_id) user = await command(user_id=auth.user_id)
return me_user_public(user) return MeUserPublicResponse(
id=user.id,
email=user.email,
first_name=user.first_name,
middle_name=user.middle_name,
last_name=user.last_name,
birth_date=user.birth_date,
encrypted_mnemonic=user.encrypted_mnemonic,
phone=user.phone,
passport_data=user.passport_data,
inn=user.inn,
erc20=user.erc20,
avatar_link=user.avatar_link,
kyc_verified=user.kyc_verified,
is_deleted=user.is_deleted,
created_at=user.created_at,
updated_at=user.updated_at,
kyc_verified_at=user.kyc_verified_at
)
@account_settings_router.post( @account_settings_router.post(

View File

@@ -1 +0,0 @@
from src.presentation.serializers.me_user import me_user_payload, me_user_public

View File

@@ -1,13 +0,0 @@
from __future__ import annotations
from src.application.domain.entities import UserEntity
from src.presentation.schemas.me_public import MeUserPublicResponse
def me_user_public(user: UserEntity) -> MeUserPublicResponse:
return MeUserPublicResponse.from_user(user)
def me_user_payload(user: UserEntity) -> dict:
return me_user_public(user).model_dump(mode='json')