feat: add docs and custom exc
This commit is contained in:
@@ -3,7 +3,7 @@ from datetime import datetime,timedelta,timezone
|
||||
from src.application.abstractions import IUnitOfWork
|
||||
from src.application.contracts import IBeorgService,ILogger
|
||||
from src.application.domain.dto import BeorgKycCreateResponse,BeorgKycResultResponse,KycSessionResponse
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.application.domain.exceptions import ApplicationException,KycFailedException,KycNotCompletedException,KycSessionExpiredException,KycSessionMissingUserTokenException
|
||||
from src.application.services import ensure_adult,extract_personal_data,parse_birth_date
|
||||
|
||||
|
||||
@@ -58,11 +58,11 @@ class CompleteKycCommand:
|
||||
async def __call__(self,user_id: str) -> BeorgKycResultResponse:
|
||||
session = await self._get_session(user_id)
|
||||
if not session.user_token:
|
||||
raise ApplicationException(status_code=409,message='KYC session has no user token')
|
||||
raise KycSessionMissingUserTokenException()
|
||||
|
||||
result = await self._beorg_service.get_result(user_token=session.user_token)
|
||||
if result.done_state is None:
|
||||
raise ApplicationException(status_code=409,message='KYC is not completed yet')
|
||||
raise KycNotCompletedException()
|
||||
if result.done_state is False:
|
||||
async with self._unit_of_work as unit_of_work:
|
||||
await unit_of_work.kyc_repository.update_session_result(
|
||||
@@ -74,7 +74,7 @@ class CompleteKycCommand:
|
||||
result_data=result.data,
|
||||
error='KYC failed',
|
||||
)
|
||||
raise ApplicationException(status_code=400,message='KYC failed')
|
||||
raise KycFailedException()
|
||||
|
||||
personal_data = extract_personal_data(result.data)
|
||||
birth_date = parse_birth_date(personal_data.birth_date)
|
||||
@@ -115,7 +115,7 @@ class CompleteKycCommand:
|
||||
client_user_token=session.client_user_token,
|
||||
qr_code=session.qr_code,
|
||||
)
|
||||
raise ApplicationException(status_code=404,message='KYC session expired')
|
||||
raise KycSessionExpiredException()
|
||||
|
||||
|
||||
class GetKycSessionCommand:
|
||||
@@ -134,7 +134,7 @@ class GetKycSessionCommand:
|
||||
await unit_of_work.kyc_repository.expire_started_sessions(user_id=user_id,now=now)
|
||||
session = await unit_of_work.kyc_repository.get_latest_session(user_id=user_id)
|
||||
if session is None or session.expires_at is None:
|
||||
raise ApplicationException(status_code=404,message='KYC session expired')
|
||||
raise KycSessionExpiredException()
|
||||
|
||||
expires_in = max(int((session.expires_at - now).total_seconds()),0)
|
||||
return KycSessionResponse(
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from src.application.domain.exceptions.application_exceptions import ApplicationException
|
||||
from src.application.domain.exceptions.kyc_exceptions import BeorgConfigException,BeorgRejectedException,BeorgUnavailableException,CsrfRequestRequiredException,InvalidTokenException,JwtDecodeFailedException,KycAgeRestrictedException,KycBirthDateInvalidException,KycFailedException,KycNotCompletedException,KycPersonalDataIncompleteException,KycSessionExpiredException,KycSessionMissingUserTokenException,NotAuthenticatedException,UnauthorizedException,UserNotFoundException
|
||||
@@ -7,11 +7,13 @@ class ApplicationException(Exception):
|
||||
self,
|
||||
status_code: int,
|
||||
message: str,
|
||||
error_code: str = 'application_error',
|
||||
headers: Mapping[str, str] | None = None,
|
||||
):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
self.message = message
|
||||
self.error_code = error_code
|
||||
self.headers = headers
|
||||
|
||||
def __str__(self):
|
||||
|
||||
98
src/application/domain/exceptions/kyc_exceptions.py
Normal file
98
src/application/domain/exceptions/kyc_exceptions.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from __future__ import annotations
|
||||
from src.application.domain.exceptions.application_exceptions import ApplicationException
|
||||
|
||||
|
||||
class UnauthorizedException(ApplicationException):
|
||||
|
||||
def __init__(self,message: str = 'Unauthorized') -> None:
|
||||
super().__init__(status_code=401,message=message,error_code='unauthorized')
|
||||
|
||||
|
||||
class NotAuthenticatedException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=401,message='Not authenticated',error_code='not_authenticated')
|
||||
|
||||
|
||||
class InvalidTokenException(ApplicationException):
|
||||
|
||||
def __init__(self,message: str = 'Invalid token') -> None:
|
||||
super().__init__(status_code=401,message=message,error_code='invalid_token')
|
||||
|
||||
|
||||
class JwtDecodeFailedException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=500,message='JWT decode failed',error_code='jwt_decode_failed')
|
||||
|
||||
|
||||
class CsrfRequestRequiredException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=500,message='Request is required for CSRF protection',error_code='csrf_request_required')
|
||||
|
||||
|
||||
class UserNotFoundException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=404,message='User not found',error_code='user_not_found')
|
||||
|
||||
|
||||
class KycSessionMissingUserTokenException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=409,message='KYC session has no user token',error_code='kyc_session_missing_user_token')
|
||||
|
||||
|
||||
class KycNotCompletedException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=409,message='KYC is not completed yet',error_code='kyc_not_completed')
|
||||
|
||||
|
||||
class KycFailedException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=400,message='KYC failed',error_code='kyc_failed')
|
||||
|
||||
|
||||
class KycSessionExpiredException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=404,message='KYC session expired',error_code='kyc_session_expired')
|
||||
|
||||
|
||||
class KycPersonalDataIncompleteException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=422,message='KYC personal data is incomplete',error_code='kyc_personal_data_incomplete')
|
||||
|
||||
|
||||
class KycBirthDateInvalidException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=422,message='KYC birth date has invalid format',error_code='kyc_birth_date_invalid')
|
||||
|
||||
|
||||
class KycAgeRestrictedException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=403,message='KYC is unavailable for users under 18',error_code='kyc_age_restricted')
|
||||
|
||||
|
||||
class BeorgUnavailableException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=502,message='Beorg service unavailable',error_code='beorg_unavailable')
|
||||
|
||||
|
||||
class BeorgRejectedException(ApplicationException):
|
||||
|
||||
def __init__(self,message: str = 'Beorg rejected kyc request') -> None:
|
||||
super().__init__(status_code=400,message=message,error_code='beorg_rejected')
|
||||
|
||||
|
||||
class BeorgConfigException(ApplicationException):
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(status_code=500,message='Beorg service is not configured',error_code='beorg_not_configured')
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
from datetime import date,datetime
|
||||
from typing import Any
|
||||
from src.application.domain.dto import KycPersonalData
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.application.domain.exceptions import KycAgeRestrictedException,KycBirthDateInvalidException,KycPersonalDataIncompleteException
|
||||
|
||||
|
||||
FIELD_ALIASES = {
|
||||
@@ -33,7 +33,7 @@ def extract_personal_data(data: Any) -> KycPersonalData:
|
||||
|
||||
missing = [field for field in ('first_name','last_name','birth_date') if not values.get(field)]
|
||||
if missing:
|
||||
raise ApplicationException(status_code=422,message='KYC personal data is incomplete')
|
||||
raise KycPersonalDataIncompleteException()
|
||||
|
||||
return KycPersonalData(
|
||||
first_name=values['first_name'],
|
||||
@@ -51,7 +51,7 @@ def ensure_adult(birth_date: date) -> None:
|
||||
except ValueError:
|
||||
adult_from = date(today.year - 18,2,28)
|
||||
if birth_date > adult_from:
|
||||
raise ApplicationException(status_code=403,message='KYC is unavailable for users under 18')
|
||||
raise KycAgeRestrictedException()
|
||||
|
||||
|
||||
def parse_birth_date(value: str) -> date:
|
||||
@@ -84,4 +84,4 @@ def _parse_date(value: str) -> date:
|
||||
return datetime.strptime(clean,date_format).date()
|
||||
except ValueError:
|
||||
continue
|
||||
raise ApplicationException(status_code=422,message='KYC birth date has invalid format')
|
||||
raise KycBirthDateInvalidException()
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Any
|
||||
import aiohttp
|
||||
from src.application.contracts import IBeorgService
|
||||
from src.application.domain.dto import BeorgKycCreateResponse,BeorgKycResultResponse
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.application.domain.exceptions import BeorgConfigException,BeorgRejectedException,BeorgUnavailableException
|
||||
|
||||
|
||||
class BeorgService(IBeorgService):
|
||||
@@ -49,11 +49,11 @@ class BeorgService(IBeorgService):
|
||||
data = await response.json(content_type=None)
|
||||
|
||||
if response.status >= 500:
|
||||
raise ApplicationException(status_code=502,message='Beorg service unavailable')
|
||||
raise BeorgUnavailableException()
|
||||
|
||||
result = BeorgKycCreateResponse.model_validate(data)
|
||||
if not result.status:
|
||||
raise ApplicationException(status_code=400,message=result.error or 'Beorg rejected kyc request')
|
||||
raise BeorgRejectedException(result.error or 'Beorg rejected kyc request')
|
||||
|
||||
return result
|
||||
|
||||
@@ -73,11 +73,11 @@ class BeorgService(IBeorgService):
|
||||
data = await response.json(content_type=None)
|
||||
|
||||
if response.status >= 500:
|
||||
raise ApplicationException(status_code=502,message='Beorg service unavailable')
|
||||
raise BeorgUnavailableException()
|
||||
|
||||
return BeorgKycResultResponse.model_validate(data)
|
||||
|
||||
|
||||
def _ensure_configured(self) -> None:
|
||||
if not self._project_id or not self._machine_uid or not self._token or not self._process_info:
|
||||
raise ApplicationException(status_code=500,message='Beorg service is not configured')
|
||||
raise BeorgConfigException()
|
||||
|
||||
@@ -4,7 +4,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from src.application.abstractions.repositories import IUserRepository
|
||||
from src.application.domain.entities import UserEntity
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.application.domain.exceptions import UserNotFoundException
|
||||
from src.infrastructure.database.models.user import UserModel
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class UserRepository(IUserRepository):
|
||||
result = await self._session.execute(select(UserModel).where(UserModel.email == email))
|
||||
user = result.scalar_one_or_none()
|
||||
if user is None:
|
||||
raise ApplicationException(status_code=404,message='User not found')
|
||||
raise UserNotFoundException()
|
||||
return self._to_entity(user)
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class UserRepository(IUserRepository):
|
||||
) -> UserEntity:
|
||||
user = await self._session.get(UserModel,user_id)
|
||||
if user is None:
|
||||
raise ApplicationException(status_code=404,message='User not found')
|
||||
raise UserNotFoundException()
|
||||
|
||||
user.first_name = first_name
|
||||
user.last_name = last_name
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
from jose import jwt, ExpiredSignatureError, JWTError
|
||||
from src.application.contracts import ILogger, IJwtService
|
||||
from src.application.domain.dto import AccessTokenPayload
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.application.domain.exceptions import ApplicationException,InvalidTokenException,JwtDecodeFailedException
|
||||
from src.infrastructure.config.settings import settings
|
||||
from src.infrastructure.vault import JwtKeyStore
|
||||
|
||||
@@ -17,7 +17,7 @@ class JwtService(IJwtService):
|
||||
|
||||
if payload.get('type') != 'access':
|
||||
self._logger.warning(f'Access token invalid type received_type={payload.get("type")}')
|
||||
raise ApplicationException(status_code=401, message='Invalid token type')
|
||||
raise InvalidTokenException('Invalid token type')
|
||||
|
||||
try:
|
||||
return AccessTokenPayload(
|
||||
@@ -32,7 +32,7 @@ class JwtService(IJwtService):
|
||||
)
|
||||
except KeyError as exception:
|
||||
self._logger.warning(f'Access token missing claim error={str(exception)}')
|
||||
raise ApplicationException(status_code=401, message=f'Missing token claim: {exception}')
|
||||
raise InvalidTokenException(f'Missing token claim: {exception}')
|
||||
|
||||
async def _decode_and_verify(self, token: str) -> dict:
|
||||
kid: str | None = None
|
||||
@@ -42,12 +42,12 @@ class JwtService(IJwtService):
|
||||
kid = header.get('kid')
|
||||
if not kid:
|
||||
self._logger.warning(f'JWT header missing kid header={header}')
|
||||
raise ApplicationException(status_code=401, message='Missing token header: kid')
|
||||
raise InvalidTokenException('Missing token header: kid')
|
||||
|
||||
received_alg = header.get('alg')
|
||||
if received_alg != settings.JWT_ALGORITHM:
|
||||
self._logger.warning(f'JWT invalid algorithm kid={kid} received_alg={received_alg} expected_alg={settings.JWT_ALGORITHM}')
|
||||
raise ApplicationException(status_code=401, message='Invalid token algorithm')
|
||||
raise InvalidTokenException('Invalid token algorithm')
|
||||
|
||||
public_pem = await self._key_store.get_public_key_for_kid(str(kid))
|
||||
|
||||
@@ -58,7 +58,7 @@ class JwtService(IJwtService):
|
||||
|
||||
if not public_pem:
|
||||
self._logger.warning(f'JWT unknown kid kid={kid}')
|
||||
raise ApplicationException(status_code=401, message='Unknown token kid')
|
||||
raise InvalidTokenException('Unknown token kid')
|
||||
|
||||
options = {
|
||||
'verify_signature': True,
|
||||
@@ -85,25 +85,25 @@ class JwtService(IJwtService):
|
||||
|
||||
if 'sid' not in payload:
|
||||
self._logger.warning(f'JWT missing sid claim kid={kid}')
|
||||
raise ApplicationException(status_code=401, message='Missing token claim: sid')
|
||||
raise InvalidTokenException('Missing token claim: sid')
|
||||
|
||||
if 'type' not in payload:
|
||||
self._logger.warning(f'JWT missing type claim kid={kid}')
|
||||
raise ApplicationException(status_code=401, message='Missing token claim: type')
|
||||
raise InvalidTokenException('Missing token claim: type')
|
||||
|
||||
return payload
|
||||
|
||||
except ExpiredSignatureError as exception:
|
||||
self._logger.info(f'JWT expired kid={kid} error={str(exception)}')
|
||||
raise ApplicationException(status_code=401, message='Token expired')
|
||||
raise InvalidTokenException('Token expired')
|
||||
|
||||
except ApplicationException:
|
||||
raise
|
||||
|
||||
except JWTError as exception:
|
||||
self._logger.warning(f'JWT decode failed kid={kid} error={str(exception)}')
|
||||
raise ApplicationException(status_code=401, message='Invalid token')
|
||||
raise InvalidTokenException()
|
||||
|
||||
except Exception as exception:
|
||||
self._logger.error(f'Unexpected JWT decode error kid={kid} error={str(exception)}')
|
||||
raise ApplicationException(status_code=500, message='JWT decode failed')
|
||||
raise JwtDecodeFailedException()
|
||||
16
src/main.py
16
src/main.py
@@ -3,13 +3,14 @@ from contextlib import asynccontextmanager
|
||||
import secrets
|
||||
from typing import AsyncGenerator
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from fastapi import Depends, FastAPI, status
|
||||
from fastapi import Depends,FastAPI
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||
from src.application.commands import PollKycSessionsCommand
|
||||
from src.application.domain.enums import LogFormat,LogLevel
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.application.domain.exceptions import ApplicationException,UnauthorizedException
|
||||
from src.infrastructure.beorg import BeorgService
|
||||
from src.infrastructure.config.settings import get_settings
|
||||
from src.infrastructure.database.context import async_session_maker
|
||||
@@ -18,7 +19,7 @@ from src.infrastructure.vault import JwtKeyStore, start_jwt_keys_scheduler
|
||||
from src.infrastructure.utils import generate_instance_id
|
||||
from src.infrastructure.logger import logger
|
||||
from src.infrastructure.config import settings
|
||||
from src.presentation.handlers import application_exception_handler, unhandled_exception_handler
|
||||
from src.presentation.handlers import application_exception_handler,unhandled_exception_handler,validation_exception_handler
|
||||
from src.presentation.middleware import TraceIDMiddleware, SecurityHeadersMiddleware
|
||||
from src.presentation.routing import kyc_router
|
||||
|
||||
@@ -29,11 +30,9 @@ async def verify_credentials(credentials: HTTPBasicCredentials = Depends(securit
|
||||
user_ok = secrets.compare_digest(credentials.username, settings.DOCS_USERNAME)
|
||||
pass_ok = secrets.compare_digest(credentials.password, settings.DOCS_PASSWORD)
|
||||
if not (user_ok and pass_ok):
|
||||
raise ApplicationException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
message='Unauthorized',
|
||||
headers={'WWW-Authenticate': 'Basic'},
|
||||
)
|
||||
exception = UnauthorizedException()
|
||||
exception.headers = {'WWW-Authenticate': 'Basic'}
|
||||
raise exception
|
||||
return credentials
|
||||
|
||||
|
||||
@@ -97,6 +96,7 @@ app: FastAPI = FastAPI(
|
||||
)
|
||||
|
||||
app.add_exception_handler(ApplicationException, application_exception_handler)
|
||||
app.add_exception_handler(RequestValidationError, validation_exception_handler)
|
||||
app.add_exception_handler(Exception, unhandled_exception_handler)
|
||||
|
||||
app.include_router(kyc_router)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi import Depends, Request
|
||||
from fastapi.security.utils import get_authorization_scheme_param
|
||||
from src.application.contracts import IJwtService
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.application.domain.exceptions import InvalidTokenException,NotAuthenticatedException
|
||||
from src.application.domain.dto import AccessTokenPayload, AuthContext
|
||||
from src.presentation.dependencies import get_jwt_service
|
||||
|
||||
@@ -27,10 +27,10 @@ async def require_access_token(
|
||||
) -> AuthContext:
|
||||
token = _extract_access_token(request)
|
||||
if not token:
|
||||
raise ApplicationException(status_code=401, message='Not authenticated')
|
||||
raise NotAuthenticatedException()
|
||||
|
||||
payload: AccessTokenPayload = await jwt_service.decode_access_token(token)
|
||||
if payload.type != 'access':
|
||||
raise ApplicationException(status_code=401, message='Invalid token type')
|
||||
raise InvalidTokenException('Invalid token type')
|
||||
|
||||
return AuthContext(user_id=payload.sub, sid=payload.sid, token=payload)
|
||||
|
||||
@@ -3,7 +3,7 @@ import inspect
|
||||
from functools import wraps
|
||||
from typing import Callable, Awaitable, Any, Optional, Annotated
|
||||
from fastapi import Request, Header
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
from src.application.domain.exceptions import CsrfRequestRequiredException
|
||||
from src.infrastructure.security import CsrfService
|
||||
|
||||
|
||||
@@ -39,10 +39,7 @@ def csrf_protect(
|
||||
break
|
||||
|
||||
if request is None:
|
||||
raise ApplicationException(
|
||||
status_code=500,
|
||||
message='Request is required for CSRF protection',
|
||||
)
|
||||
raise CsrfRequestRequiredException()
|
||||
|
||||
csrf = CsrfService()
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from fastapi import Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import ORJSONResponse
|
||||
from src.application.domain.exceptions import ApplicationException
|
||||
|
||||
@@ -6,13 +7,35 @@ from src.application.domain.exceptions import ApplicationException
|
||||
async def application_exception_handler(request: Request,exception: ApplicationException) -> ORJSONResponse:
|
||||
return ORJSONResponse(
|
||||
status_code=exception.status_code,
|
||||
content={'detail': exception.message},
|
||||
content={
|
||||
'error': {
|
||||
'code': exception.error_code,
|
||||
'message': exception.message,
|
||||
},
|
||||
},
|
||||
headers=exception.headers,
|
||||
)
|
||||
|
||||
|
||||
async def validation_exception_handler(request: Request,exception: RequestValidationError) -> ORJSONResponse:
|
||||
return ORJSONResponse(
|
||||
status_code=422,
|
||||
content={
|
||||
'error': {
|
||||
'code': 'request_validation_error',
|
||||
'message': 'Request validation error',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def unhandled_exception_handler(request: Request,exception: Exception) -> ORJSONResponse:
|
||||
return ORJSONResponse(
|
||||
status_code=500,
|
||||
content={'detail': 'Internal server error'},
|
||||
content={
|
||||
'error': {
|
||||
'code': 'internal_server_error',
|
||||
'message': 'Internal server error',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,27 +1,81 @@
|
||||
from fastapi import APIRouter,Depends
|
||||
from fastapi.responses import ORJSONResponse
|
||||
from src.application.commands import GetKycSessionCommand,PassKycCommand
|
||||
from src.application.domain.dto import AuthContext
|
||||
from src.application.domain.dto import AuthContext,BeorgKycCreateResponse,KycSessionResponse
|
||||
from src.presentation.decorators.auth import require_access_token
|
||||
from src.presentation.dependencies.commands import get_kyc_session_command,get_pass_kyc_command
|
||||
from src.presentation.schemas import ErrorResponse
|
||||
|
||||
|
||||
kyc_router = APIRouter(prefix='/kyc', tags=['Kyc'])
|
||||
|
||||
CREATE_KYC_RESPONSES = {
|
||||
400: {
|
||||
'model': ErrorResponse,
|
||||
'description': 'Beorg rejected request. error.code: beorg_rejected',
|
||||
},
|
||||
401: {
|
||||
'model': ErrorResponse,
|
||||
'description': 'Authentication error. error.code: not_authenticated, invalid_token',
|
||||
},
|
||||
500: {
|
||||
'model': ErrorResponse,
|
||||
'description': 'Configuration or internal error. error.code: beorg_not_configured, internal_server_error',
|
||||
},
|
||||
502: {
|
||||
'model': ErrorResponse,
|
||||
'description': 'Beorg is unavailable. error.code: beorg_unavailable',
|
||||
},
|
||||
422: {
|
||||
'model': ErrorResponse,
|
||||
'description': 'Request validation error. error.code: request_validation_error',
|
||||
},
|
||||
}
|
||||
|
||||
@kyc_router.post('/create')
|
||||
GET_KYC_SESSION_RESPONSES = {
|
||||
401: {
|
||||
'model': ErrorResponse,
|
||||
'description': 'Authentication error. error.code: not_authenticated, invalid_token',
|
||||
},
|
||||
404: {
|
||||
'model': ErrorResponse,
|
||||
'description': 'Active KYC session was not found. error.code: kyc_session_expired',
|
||||
},
|
||||
500: {
|
||||
'model': ErrorResponse,
|
||||
'description': 'Internal error. error.code: internal_server_error',
|
||||
},
|
||||
422: {
|
||||
'model': ErrorResponse,
|
||||
'description': 'Request validation error. error.code: request_validation_error',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@kyc_router.post(
|
||||
'/create',
|
||||
response_model=BeorgKycCreateResponse,
|
||||
responses=CREATE_KYC_RESPONSES,
|
||||
summary='Start KYC session',
|
||||
description='Creates a Beorg KYC session for one hour and returns link, user token and QR code.',
|
||||
)
|
||||
async def create_kyc(
|
||||
#auth: AuthContext = Depends(require_access_token),
|
||||
command: PassKycCommand = Depends(get_pass_kyc_command),
|
||||
) -> ORJSONResponse:
|
||||
) -> BeorgKycCreateResponse:
|
||||
result = await command(user_id='01KPKAFN6J1NJBY15DX8JE2QYB')
|
||||
return ORJSONResponse(result.model_dump())
|
||||
return result
|
||||
|
||||
|
||||
@kyc_router.get('/session')
|
||||
@kyc_router.get(
|
||||
'/session',
|
||||
response_model=KycSessionResponse,
|
||||
responses=GET_KYC_SESSION_RESPONSES,
|
||||
summary='Get KYC session',
|
||||
description='Returns latest KYC session status, link, QR code and expiration data.',
|
||||
)
|
||||
async def get_kyc_session(
|
||||
#auth: AuthContext = Depends(require_access_token),
|
||||
command: GetKycSessionCommand = Depends(get_kyc_session_command),
|
||||
) -> ORJSONResponse:
|
||||
) -> KycSessionResponse:
|
||||
result = await command(user_id='01KPKAFN6J1NJBY15DX8JE2QYB')
|
||||
return ORJSONResponse(result.model_dump())
|
||||
return result
|
||||
@@ -0,0 +1 @@
|
||||
from src.presentation.schemas.error import ErrorDetail,ErrorResponse
|
||||
|
||||
10
src/presentation/schemas/error.py
Normal file
10
src/presentation/schemas/error.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ErrorDetail(BaseModel):
|
||||
code: str
|
||||
message: str
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
error: ErrorDetail
|
||||
Reference in New Issue
Block a user