events auth repair

This commit is contained in:
berkay 2025-01-16 22:35:49 +03:00
parent 426b69b33c
commit 61229cb761
23 changed files with 945 additions and 754 deletions

View File

@ -0,0 +1,9 @@
"""
Authentication package initialization.
"""
from .endpoints import AUTH_CONFIG
__all__ = [
"AUTH_CONFIG",
]

View File

@ -2,103 +2,37 @@
Authentication related API endpoints.
"""
from typing import TYPE_CHECKING, Union, Optional, Dict, Any
from typing import TYPE_CHECKING, Union, Dict, Any
# Regular imports (non-TYPE_CHECKING)
from ApiEvents.abstract_class import (
MethodToEvent,
RouteFactoryConfig,
EndpointFactoryConfig,
)
from ApiEvents.base_request_model import (
BaseRequestModel,
DictRequestModel,
SuccessResponse,
from ApiEvents.abstract_class import MethodToEvent
from ApiEvents.base_request_model import DictRequestModel, SuccessResponse
from ApiLibrary.common.line_number import get_line_number_for_error
from ApiServices.Login.user_login_handler import UserLoginModule
from ApiServices.Token.token_handler import AccessToken, TokenService
from ApiValidations.Request.authentication import EmployeeSelectionValidation, Login, OccupantSelectionValidation
from ErrorHandlers import HTTPExceptionApi
from .models import (
LoginData,
LoginRequestModel,
LogoutRequestModel,
RememberRequestModel,
ForgotRequestModel,
ChangePasswordRequestModel,
CreatePasswordRequestModel,
SelectionDataEmployee,
SelectionDataOccupant,
)
if TYPE_CHECKING:
from fastapi import Request
from ApiServices.Token.token_handler import OccupantTokenObject, EmployeeTokenObject
from Schemas import (
Login,
ChangePassword,
CreatePassword,
Forgot,
Logout,
Remember,
EmployeeSelection,
OccupantSelection,
)
from ApiLibrary.common.line_number import get_line_number_for_error
from ApiServices.Login.user_login_handler import UserLoginModule
from ApiValidations.Request import (
Login,
Logout,
Remember,
Forgot,
ChangePassword,
CreatePassword,
EmployeeSelection,
OccupantSelection,
)
from Services.PostgresDb.Models.alchemy_response import AlchemyJsonResponse
from ApiValidations.Response import AccountRecordResponse
from ApiServices.Token.token_handler import AccessToken, TokenService
from ErrorHandlers import HTTPExceptionApi
# Type aliases for common types
TokenDictType = Union["EmployeeTokenObject", "OccupantTokenObject"]
class LoginRequestModel(BaseRequestModel[Login]):
"""Request model for login endpoint."""
pass
class LogoutRequestModel(BaseRequestModel[Logout]):
"""Request model for logout endpoint."""
pass
class RememberRequestModel(BaseRequestModel[Remember]):
"""Request model for remember token endpoint."""
pass
class ForgotRequestModel(BaseRequestModel[Forgot]):
"""Request model for forgot password endpoint."""
pass
class ChangePasswordRequestModel(BaseRequestModel[ChangePassword]):
"""Request model for change password endpoint."""
pass
class CreatePasswordRequestModel(BaseRequestModel[CreatePassword]):
"""Request model for create password endpoint."""
pass
class EmployeeSelectionRequestModel(BaseRequestModel[EmployeeSelection]):
"""Request model for employee selection endpoint."""
pass
class OccupantSelectionRequestModel(BaseRequestModel[OccupantSelection]):
"""Request model for occupant selection endpoint."""
pass
class AuthenticationLoginEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Login via domain and access key : [email] | [phone]"
@ -112,7 +46,7 @@ class AuthenticationLoginEventMethods(MethodToEvent):
@classmethod
async def authentication_login_with_domain_and_creds(
cls, request: "Request", data: LoginRequestModel
cls, request: "Request", data: Login
):
"""
Authenticate user with domain and credentials.
@ -129,11 +63,9 @@ class AuthenticationLoginEventMethods(MethodToEvent):
Returns:
SuccessResponse containing authentication token and user info
"""
# Create login module instance
login_module = UserLoginModule(request=request)
# Get token from login module
token = await login_module.login_user_via_credentials(access_data=data)
user_login_module = UserLoginModule(request=request)
token = await user_login_module.login_user_via_credentials(access_data=data)
# Return response with token and headers
return {
@ -157,135 +89,53 @@ class AuthenticationSelectEventMethods(MethodToEvent):
@classmethod
def _handle_employee_selection(
cls,
data: EmployeeSelectionRequestModel,
data: SelectionDataEmployee,
token_dict: TokenDictType,
request: "Request",
):
return
# """Handle employee company selection"""
# Users.client_arrow = DateTimeLocal(is_client=True, timezone=token_dict.timezone)
# if data.company_uu_id not in token_dict.companies_uu_id_list:
# return ResponseHandler.unauthorized(
# "Company not found in user's company list"
# )
# selected_company = Companies.filter_one(
# Companies.uu_id == data.company_uu_id
# ).data
# if not selected_company:
# return ResponseHandler.not_found("Company not found")
# # Get department IDs for the company
# department_ids = [
# dept.id
# for dept in Departments.filter_all(
# Departments.company_id == selected_company.id
# ).data
# ]
# # Get duties IDs for the company
# duties_ids = [
# duty.id
# for duty in Duties.filter_all(Duties.company_id == selected_company.id).data
# ]
# # Get staff IDs
# staff_ids = [
# staff.id for staff in Staff.filter_all(Staff.duties_id.in_(duties_ids)).data
# ]
# # Get employee
# employee = Employees.filter_one(
# Employees.people_id == token_dict.person_id,
# Employees.staff_id.in_(staff_ids),
# ).data
# if not employee:
# return ResponseHandler.not_found("Employee not found")
# # Get reachable events
# reachable_event_list_id = Event2Employee.get_event_id_by_employee_id(
# employee_id=employee.id
# )
# # Get staff and duties
# staff = Staff.filter_one(Staff.id == employee.staff_id).data
# duties = Duties.filter_one(Duties.id == staff.duties_id).data
# department = Departments.filter_one(Departments.id == duties.department_id).data
# # Get bulk duty
# bulk_id = Duty.filter_by_one(system=True, duty_code="BULK").data
# bulk_duty_id = Duties.filter_by_one(
# company_id=selected_company.id,
# duties_id=bulk_id.id,
# **Duties.valid_record_dict,
# ).data
# # Create company token
# company_token = CompanyToken(
# company_uu_id=selected_company.uu_id.__str__(),
# company_id=selected_company.id,
# department_id=department.id,
# department_uu_id=department.uu_id.__str__(),
# duty_id=duties.id,
# duty_uu_id=duties.uu_id.__str__(),
# bulk_duties_id=bulk_duty_id.id,
# staff_id=staff.id,
# staff_uu_id=staff.uu_id.__str__(),
# employee_id=employee.id,
# employee_uu_id=employee.uu_id.__str__(),
# reachable_event_list_id=reachable_event_list_id,
# )
# # Update Redis
# AuthActions.update_selected_to_redis(request=request, add_payload=company_token)
# return ResponseHandler.success("Company selected successfully")
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
)
@classmethod
def _handle_occupant_selection(
cls,
data: OccupantSelectionRequestModel,
data: SelectionDataOccupant,
token_dict: TokenDictType,
request: "Request",
):
"""Handle occupant type selection"""
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang=token_dict.lang,
loc=get_line_number_for_error(),
sys_msg="Occupant selection not implemented",
)
@classmethod
async def authentication_select_company_or_occupant_type(
cls,
request: "Request",
data: Union[EmployeeSelectionValidation, OccupantSelectionValidation],
token_dict: TokenDictType,
):
"""Handle selection of company or occupant type"""
try:
if isinstance(token_dict, EmployeeTokenObject):
return cls._handle_employee_selection(data, token_dict, request)
elif isinstance(token_dict, OccupantTokenObject):
return cls._handle_occupant_selection(data, token_dict, request)
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang=token_dict.lang,
loc=get_line_number_for_error(),
print(
dict(
data=data,
token_dict=token_dict.model_dump(),
request=dict(request.headers)
)
)
except Exception as e:
return ResponseHandler.error(
str(e), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
raise HTTPExceptionApi(
error_code="HTTP_500_INTERNAL_SERVER_ERROR",
lang="en",
loc=get_line_number_for_error(),
sys_msg=str(e),
)
@classmethod
def authentication_select_company_or_occupant_type(
cls,
request: "Request",
data: Union[EmployeeSelectionRequestModel, OccupantSelectionRequestModel],
token_dict: TokenDictType,
):
"""Handle selection of company or occupant type"""
# try:
# if isinstance(token_dict, EmployeeTokenObject):
# return cls._handle_employee_selection(data, token_dict, request)
# elif isinstance(token_dict, OccupantTokenObject):
# return cls._handle_occupant_selection(data, token_dict, request)
# return ResponseHandler.error(
# "Invalid token type", status_code=status.HTTP_400_BAD_REQUEST
# )
# except Exception as e:
# return ResponseHandler.error(
# str(e), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
# )
return
class AuthenticationCheckTokenEventMethods(MethodToEvent):
event_type = "LOGIN"
@ -300,7 +150,7 @@ class AuthenticationCheckTokenEventMethods(MethodToEvent):
# }
@classmethod
def authentication_check_token_is_valid(
async def authentication_check_token_is_valid(
cls, request: "Request", data: DictRequestModel
):
# try:
@ -324,7 +174,7 @@ class AuthenticationRefreshEventMethods(MethodToEvent):
# }
@classmethod
def authentication_refresh_user_info(
async def authentication_refresh_user_info(
cls,
request: "Request",
token_dict: TokenDictType,
@ -371,7 +221,7 @@ class AuthenticationChangePasswordEventMethods(MethodToEvent):
# }
@classmethod
def authentication_change_password(
async def authentication_change_password(
cls,
request: "Request",
data: ChangePasswordRequestModel,
@ -429,7 +279,7 @@ class AuthenticationCreatePasswordEventMethods(MethodToEvent):
# }
@classmethod
def authentication_create_password(
async def authentication_create_password(
cls, request: "Request", data: CreatePasswordRequestModel
):
@ -475,7 +325,7 @@ class AuthenticationDisconnectUserEventMethods(MethodToEvent):
# }
@classmethod
def authentication_disconnect_user(
async def authentication_disconnect_user(
cls, request: "Request", data: LogoutRequestModel, token_dict: TokenDictType
):
# found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data
@ -514,11 +364,11 @@ class AuthenticationLogoutEventMethods(MethodToEvent):
# }
@classmethod
def authentication_logout_user(
async def authentication_logout_user(
cls,
request: "Request",
data: LogoutRequestModel,
token_dict: TokenDictType = None,
token_dict: TokenDictType,
):
# token_user = None
# if already_tokens := RedisActions.get_object_via_access_key(request=request):
@ -553,7 +403,7 @@ class AuthenticationRefreshTokenEventMethods(MethodToEvent):
# }
@classmethod
def authentication_refresher_token(
async def authentication_refresher_token(
cls, request: "Request", data: RememberRequestModel, token_dict: TokenDictType
):
# token_refresher = UsersTokens.filter_by_one(
@ -602,7 +452,7 @@ class AuthenticationForgotPasswordEventMethods(MethodToEvent):
# }
@classmethod
def authentication_forgot_password(
async def authentication_forgot_password(
cls,
request: "Request",
data: ForgotRequestModel,
@ -644,7 +494,7 @@ class AuthenticationResetPasswordEventMethods(MethodToEvent):
}
@classmethod
def authentication_reset_password(
async def authentication_reset_password(
cls, request: "Request", data: ForgotRequestModel
):
# from sqlalchemy import or_
@ -695,11 +545,11 @@ class AuthenticationDownloadAvatarEventMethods(MethodToEvent):
# }
@classmethod
def authentication_download_avatar(
async def authentication_download_avatar(
cls,
token_dict: TokenDictType,
request: "Request",
data: DictRequestModel,
token_dict: TokenDictType,
):
# if found_user := Users.filter_one(Users.id == token_dict.user_id).data:
# expired_starts = str(
@ -725,253 +575,3 @@ class AuthenticationDownloadAvatarEventMethods(MethodToEvent):
# return ResponseHandler.not_found("Invalid data")
return
async def authentication_select_company_or_occupant_type(
request: "Request",
data: Union[EmployeeSelectionRequestModel, OccupantSelectionRequestModel],
) -> Dict[str, Any]:
return await AuthenticationSelectEventMethods.authentication_select_company_or_occupant_type(
request=request, data=data
)
async def authentication_login_with_domain_and_creds(
request: "Request", data: LoginRequestModel
) -> SuccessResponse:
"""
Authenticate user with domain and credentials.
Args:
request: FastAPI request object
data: Request body containing login credentials
{
"domain": str,
"username": str,
"password": str
}
Returns:
SuccessResponse containing authentication token and user info
"""
return await AuthenticationLoginEventMethods.authentication_login_with_domain_and_creds(
request=request, data=data
)
async def authentication_check_token_is_valid(
request: "Request", data: DictRequestModel
) -> Dict[str, Any]:
return (
await AuthenticationCheckTokenEventMethods.authentication_check_token_is_valid(
request=request, data=data
)
)
def authentication_refresh_user_info(
request: "Request", data: DictRequestModel
) -> Dict[str, Any]:
return
def authentication_change_password(
request: "Request", data: ChangePasswordRequestModel
) -> Dict[str, Any]:
return
def authentication_create_password(
request: "Request", data: CreatePasswordRequestModel
) -> Dict[str, Any]:
return
def authentication_forgot_password(
request: "Request", data: ForgotRequestModel
) -> Dict[str, Any]:
return
def authentication_reset_password(
request: "Request", data: ForgotRequestModel
) -> Dict[str, Any]:
return
def authentication_disconnect_user(
request: "Request", data: LogoutRequestModel
) -> Dict[str, Any]:
return
def authentication_logout_user(
request: "Request", data: LogoutRequestModel
) -> Dict[str, Any]:
return
def authentication_refresher_token(
request: "Request", data: RememberRequestModel
) -> Dict[str, Any]:
return
def authentication_download_avatar(
request: "Request", data: DictRequestModel
) -> Dict[str, Any]:
return
AUTH_CONFIG = RouteFactoryConfig(
name="authentication",
prefix="/authentication",
tags=["Authentication"],
include_in_schema=True,
endpoints=[
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/select",
url_of_endpoint="/authentication/select",
endpoint="/select",
method="POST",
summary="Select company or occupant type",
description="Select company or occupant type",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_select_company_or_occupant_type,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/login",
url_of_endpoint="/authentication/login",
endpoint="/login",
method="POST",
summary="Login user with domain and password",
description="Login user with domain and password",
is_auth_required=False, # Public endpoint
is_event_required=False,
endpoint_function=authentication_login_with_domain_and_creds,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/valid",
url_of_endpoint="/authentication/valid",
endpoint="/valid",
method="GET",
summary="Check access token is valid",
description="Check access token is valid",
is_auth_required=True, # Needs token validation
is_event_required=False,
endpoint_function=authentication_check_token_is_valid,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/refresh",
url_of_endpoint="/authentication/refresh",
endpoint="/refresh",
method="GET",
summary="Refresh credentials with access token",
description="Refresh credentials with access token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_refresh_user_info,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/change_password",
url_of_endpoint="/authentication/change_password",
endpoint="/change_password",
method="POST",
summary="Change password with access token",
description="Change password with access token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_change_password,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/create_password",
url_of_endpoint="/authentication/create_password",
endpoint="/create_password",
method="POST",
summary="Create password with password token",
description="Create password with password token",
is_auth_required=False, # Public endpoint
is_event_required=False,
endpoint_function=authentication_create_password,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/reset_password",
url_of_endpoint="/authentication/reset_password",
endpoint="/reset_password",
method="POST",
summary="Create password with password token",
description="Create password with password token",
is_auth_required=False, # Public endpoint
is_event_required=False,
endpoint_function=authentication_reset_password,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/disconnect",
url_of_endpoint="/authentication/disconnect",
endpoint="/disconnect",
method="POST",
summary="Disconnect user with access token",
description="Disconnect user with access token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_disconnect_user,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/logout",
url_of_endpoint="/authentication/logout",
endpoint="/logout",
method="POST",
summary="Logout user with access token",
description="Logout user with access token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_logout_user,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/refresher",
url_of_endpoint="/authentication/refresher",
endpoint="/refresher",
method="POST",
summary="Refresh token with refresh token",
description="Refresh token with refresh token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_refresher_token,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/forgot",
url_of_endpoint="/authentication/forgot",
endpoint="/forgot",
method="POST",
summary="Forgot password with email or phone number",
description="Forgot password with email or phone number",
is_auth_required=False, # Public endpoint
is_event_required=False,
endpoint_function=authentication_forgot_password,
),
EndpointFactoryConfig(
url_prefix="/authentication",
url_endpoint="/avatar",
url_of_endpoint="/authentication/avatar",
endpoint="/avatar",
method="POST",
summary="Get link of avatar with credentials",
description="Get link of avatar with credentials",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_download_avatar,
),
],
).as_dict()

View File

@ -0,0 +1,371 @@
"""
Authentication endpoint configurations.
"""
from typing import TYPE_CHECKING, Dict, Any, Union, Annotated
from fastapi import HTTPException, status, Body
from ApiValidations.Request.authentication import Login
from .auth import (
AuthenticationChangePasswordEventMethods,
AuthenticationCheckTokenEventMethods,
AuthenticationCreatePasswordEventMethods,
AuthenticationDisconnectUserEventMethods,
AuthenticationDownloadAvatarEventMethods,
AuthenticationForgotPasswordEventMethods,
AuthenticationLoginEventMethods,
AuthenticationLogoutEventMethods,
AuthenticationRefreshEventMethods,
AuthenticationRefreshTokenEventMethods,
AuthenticationResetPasswordEventMethods,
AuthenticationSelectEventMethods,
)
from .models import (
ChangePasswordRequestModel,
CreatePasswordRequestModel,
ForgotRequestModel,
LoginData,
LoginRequestModel,
LogoutRequestModel,
SelectionDataEmployee,
SelectionDataOccupant,
RememberRequestModel,
)
from ApiEvents.base_request_model import DictRequestModel
from ApiEvents.abstract_class import RouteFactoryConfig, EndpointFactoryConfig, endpoint_wrapper
if TYPE_CHECKING:
from fastapi import Request
from ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTokenObject
# Type aliases for common types
TokenDictType = Union[EmployeeTokenObject, OccupantTokenObject]
@endpoint_wrapper("/authentication/select")
async def authentication_select_company_or_occupant_type(
request: "Request",
data: Union[SelectionDataEmployee, SelectionDataOccupant],
token_dict: TokenDictType = None
) -> Dict[str, Any]:
"""
Handle selection of company or occupant type.
Args:
request: The FastAPI request object
data: Selection request data
Returns:
Dict containing the response data
"""
return {
"headers": dict(request.headers),
"data": data,
"token": token_dict
}
@endpoint_wrapper("/authentication/login")
async def authentication_login_with_domain_and_creds(
request: "Request",
data: Login,
) -> Dict[str, Any]:
"""
Authenticate user with domain and credentials.
"""
return AuthenticationLoginEventMethods.authentication_login_with_domain_and_creds(
request=request, data=data
)
@endpoint_wrapper("/authentication/check")
async def authentication_check_token_is_valid(
request: "Request",
data: DictRequestModel,
) -> Dict[str, Any]:
"""
Check if a token is valid.
"""
return {
"status": "OK",
}
@endpoint_wrapper("/authentication/refresh")
async def authentication_refresh_user_info(
request: "Request",
data: DictRequestModel,
token_dict: TokenDictType = None,
) -> Dict[str, Any]:
"""
Refresh user information.
"""
return {
"status": "OK",
}
@endpoint_wrapper("/authentication/change-password")
async def authentication_change_password(
request: "Request",
data: ChangePasswordRequestModel,
token_dict: TokenDictType = None,
) -> Dict[str, Any]:
"""
Change user password.
"""
return {
"status": "OK",
}
@endpoint_wrapper("/authentication/create-password")
async def authentication_create_password(
request: "Request",
data: CreatePasswordRequestModel,
) -> Dict[str, Any]:
"""
Create new password.
"""
return {
"status": "OK",
}
@endpoint_wrapper("/authentication/forgot-password")
async def authentication_forgot_password(
request: "Request",
data: ForgotRequestModel,
) -> Dict[str, Any]:
"""
Handle forgot password request.
"""
return {
"status": "OK",
}
@endpoint_wrapper("/authentication/reset-password")
async def authentication_reset_password(
request: "Request",
data: ForgotRequestModel,
) -> Dict[str, Any]:
"""
Reset password.
"""
return {
"status": "OK",
}
@endpoint_wrapper("/authentication/disconnect")
async def authentication_disconnect_user(
request: "Request",
data: LogoutRequestModel,
token_dict: TokenDictType = None,
) -> Dict[str, Any]:
"""
Disconnect user.
"""
return {
"status": "OK",
}
@endpoint_wrapper("/authentication/logout")
async def authentication_logout_user(
request: "Request",
data: LogoutRequestModel,
token_dict: TokenDictType = None,
) -> Dict[str, Any]:
"""
Logout user.
"""
return {
"status": "OK",
}
@endpoint_wrapper("/authentication/remember")
async def authentication_refresher_token(
request: "Request",
data: RememberRequestModel,
token_dict: TokenDictType = None,
) -> Dict[str, Any]:
"""
Refresh remember token.
"""
return {
"status": "OK",
}
@endpoint_wrapper("/authentication/avatar")
async def authentication_download_avatar(
request: "Request",
data: DictRequestModel,
token_dict: TokenDictType = None,
) -> Dict[str, Any]:
"""
Download user avatar.
"""
return {
"status": "OK",
}
prefix = "/authentication"
AUTH_CONFIG = RouteFactoryConfig(
name="authentication",
prefix=prefix,
tags=["Authentication"],
include_in_schema=True,
endpoints=[
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/select",
url_of_endpoint="/authentication/select",
endpoint="/select",
method="POST",
summary="Select company or occupant type",
description="Select company or occupant type",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_select_company_or_occupant_type,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/login",
url_of_endpoint="/authentication/login",
endpoint="/login",
method="POST",
summary="Login user with domain and password",
description="Login user with domain and password",
is_auth_required=False, # Public endpoint
is_event_required=False,
endpoint_function=authentication_login_with_domain_and_creds,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/check",
url_of_endpoint="/authentication/check",
endpoint="/check",
method="GET",
summary="Check access token is valid",
description="Check access token is valid",
is_auth_required=True, # Needs token validation
is_event_required=False,
endpoint_function=authentication_check_token_is_valid,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/refresh",
url_of_endpoint="/authentication/refresh",
endpoint="/refresh",
method="GET",
summary="Refresh credentials with access token",
description="Refresh credentials with access token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_refresh_user_info,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/change-password",
url_of_endpoint="/authentication/change-password",
endpoint="/change-password",
method="POST",
summary="Change password with access token",
description="Change password with access token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_change_password,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/create-password",
url_of_endpoint="/authentication/create-password",
endpoint="/create-password",
method="POST",
summary="Create password with password token",
description="Create password with password token",
is_auth_required=False, # Public endpoint
is_event_required=False,
endpoint_function=authentication_create_password,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/reset-password",
url_of_endpoint="/authentication/reset-password",
endpoint="/reset-password",
method="POST",
summary="Reset password with token",
description="Reset password with token",
is_auth_required=False, # Public endpoint
is_event_required=False,
endpoint_function=authentication_reset_password,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/disconnect",
url_of_endpoint="/authentication/disconnect",
endpoint="/disconnect",
method="POST",
summary="Disconnect user with access token",
description="Disconnect user with access token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_disconnect_user,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/logout",
url_of_endpoint="/authentication/logout",
endpoint="/logout",
method="POST",
summary="Logout user with access token",
description="Logout user with access token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_logout_user,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/remember",
url_of_endpoint="/authentication/remember",
endpoint="/remember",
method="POST",
summary="Refresh token with refresh token",
description="Refresh token with refresh token",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_refresher_token,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/forgot-password",
url_of_endpoint="/authentication/forgot-password",
endpoint="/forgot-password",
method="POST",
summary="Request password reset via email",
description="Request password reset via email",
is_auth_required=False, # Public endpoint
is_event_required=False,
endpoint_function=authentication_forgot_password,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/avatar",
url_of_endpoint="/authentication/avatar",
endpoint="/avatar",
method="POST",
summary="Get user avatar with credentials",
description="Get user avatar with credentials",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=authentication_download_avatar,
),
],
).as_dict()

View File

@ -0,0 +1,138 @@
"""
Authentication request models.
"""
from typing import TYPE_CHECKING, Dict, Any, Literal, Optional, TypedDict
from pydantic import BaseModel, Field, model_validator, RootModel, ConfigDict
from ApiEvents.base_request_model import BaseRequestModel, DictRequestModel
if TYPE_CHECKING:
from fastapi import Request
class TokenObjectBase(BaseModel):
"""Base model for token objects."""
user_type: str = Field(..., description="Type of user")
user_id: str = Field(..., description="User ID")
token: str = Field(..., description="Authentication token")
permissions: Dict[str, Any] = Field(default_factory=dict, description="User permissions")
class LoginData(TypedDict):
"""Type for login data."""
domain: str
access_key: str
password: str
remember_me: bool
class LoginRequestModel(BaseRequestModel[LoginData]):
"""Request model for login endpoint."""
model_config = ConfigDict(
json_schema_extra={
"example": {
"domain": "example.com",
"access_key": "user@example",
"password": "password",
"remember_me": False
}
}
)
class LogoutData(TypedDict):
"""Type for logout data."""
token: str
class LogoutRequestModel(BaseRequestModel[LogoutData]):
"""Request model for logout endpoint."""
model_config = ConfigDict(
json_schema_extra={
"example": {
"token": "your-token-here"
}
}
)
class RememberData(TypedDict):
"""Type for remember token data."""
remember_token: str
class RememberRequestModel(BaseRequestModel[RememberData]):
"""Request model for remember token endpoint."""
model_config = ConfigDict(
json_schema_extra={
"example": {
"remember_token": "your-remember-token-here"
}
}
)
class ForgotData(TypedDict):
"""Type for forgot password data."""
email: str
domain: str
class ForgotRequestModel(BaseRequestModel[ForgotData]):
"""Request model for forgot password endpoint."""
model_config = ConfigDict(
json_schema_extra={
"example": {
"email": "user@example.com",
"domain": "example.com"
}
}
)
class ChangePasswordData(TypedDict):
"""Type for change password data."""
old_password: str
new_password: str
class ChangePasswordRequestModel(BaseRequestModel[ChangePasswordData]):
"""Request model for change password endpoint."""
model_config = ConfigDict(
json_schema_extra={
"example": {
"old_password": "old-pass",
"new_password": "new-pass"
}
}
)
class CreatePasswordData(TypedDict):
"""Type for create password data."""
token: str
password: str
class CreatePasswordRequestModel(BaseRequestModel[CreatePasswordData]):
"""Request model for create password endpoint."""
model_config = ConfigDict(
json_schema_extra={
"example": {
"token": "password-creation-token",
"password": "new-password"
}
}
)
class SelectionDataOccupant(TypedDict):
"""Type for selection data."""
build_living_space_uu_id: Optional[str]
class SelectionDataEmployee(TypedDict):
"""Type for selection data."""
company_uu_id: Optional[str]

View File

@ -6,7 +6,7 @@ to be used by the dynamic route creation system.
"""
from typing import Dict, List, Any
from .auth.auth import AUTH_CONFIG
from .auth.endpoints import AUTH_CONFIG
# Registry of all route configurations

View File

@ -0,0 +1,9 @@
"""
Account records package initialization.
"""
from .endpoints import ACCOUNT_RECORDS_CONFIG
__all__ = [
"ACCOUNT_RECORDS_CONFIG",
]

View File

@ -1,24 +1,22 @@
import typing
"""
Account records service implementation.
"""
from ApiEvents.abstract_class import (
MethodToEvent,
RouteFactoryConfig,
EndpointFactoryConfig,
endpoint_wrapper,
)
from ApiEvents.base_request_model import BaseRequestModel, DictRequestModel
from typing import TYPE_CHECKING, Dict, Any
from fastapi import Request, Path, Body, Depends, APIRouter
from pydantic import BaseModel, Field
if TYPE_CHECKING:
from fastapi import Request
from typing import Dict, Any, Union
from pydantic import Field
from ApiEvents.abstract_class import MethodToEvent, endpoint_wrapper
from ApiEvents.base_request_model import DictRequestModel
from ApiValidations.Custom.token_objects import (
OccupantTokenObject,
EmployeeTokenObject,
)
from ApiLibrary import system_arrow
from ApiValidations.Request.account_records import (
InsertAccountRecord,
UpdateAccountRecord,
)
from ApiValidations.Request.base_validations import ListOptions
from Schemas import (
BuildLivingSpace,
AccountRecords,
@ -26,49 +24,16 @@ from Schemas import (
BuildDecisionBookPayments,
ApiEnumDropdown,
)
from ApiLibrary import system_arrow
from ApiValidations.Request import (
InsertAccountRecord,
UpdateAccountRecord,
ListOptions,
)
from Services.PostgresDb.Models.alchemy_response import (
AlchemyJsonResponse,
DictJsonResponse,
)
from ApiValidations.Response import AccountRecordResponse
class AddressUpdateRequest(BaseModel):
"""Request model for address update."""
data: Dict[str, Any] = Field(..., description="Updated address data")
class AddressUpdateResponse(BaseModel):
"""Response model for address update."""
address_uu_id: str = Field(..., description="UUID of the updated address")
data: Dict[str, Any] = Field(..., description="Updated address data")
function_code: str = Field(..., description="Function code for the endpoint")
class InsertAccountRecordRequestModel(BaseRequestModel[InsertAccountRecord]):
"""Request model for inserting account records."""
pass
class UpdateAccountRecordRequestModel(BaseRequestModel[UpdateAccountRecord]):
"""Request model for updating account records."""
pass
class ListOptionsRequestModel(BaseRequestModel[ListOptions]):
"""Request model for list options."""
pass
from .models import (
InsertAccountRecordRequestModel,
UpdateAccountRecordRequestModel,
ListOptionsRequestModel,
)
class AccountRecordsListEventMethods(MethodToEvent):
@ -90,7 +55,7 @@ class AccountRecordsListEventMethods(MethodToEvent):
def account_records_list(
cls,
list_options: ListOptionsRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
):
db_session = AccountRecords.new_session()
if isinstance(token_dict, OccupantTokenObject):
@ -119,7 +84,7 @@ class AccountRecordsListEventMethods(MethodToEvent):
def account_records_list_flt_res(
cls,
list_options: ListOptionsRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
):
db_session = AccountRecords.new_session()
if not isinstance(token_dict, OccupantTokenObject):
@ -269,7 +234,7 @@ class AccountRecordsCreateEventMethods(MethodToEvent):
def account_records_create(
cls,
data: InsertAccountRecordRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
):
data_dict = data.excluded_dump()
if isinstance(token_dict, OccupantTokenObject):
@ -358,7 +323,7 @@ class AccountRecordsUpdateEventMethods(MethodToEvent):
cls,
build_uu_id: str,
data: UpdateAccountRecordRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
):
if isinstance(token_dict, OccupantTokenObject):
pass
@ -373,123 +338,3 @@ class AccountRecordsUpdateEventMethods(MethodToEvent):
cls_object=AccountRecords,
response_model=UpdateAccountRecord,
)
@endpoint_wrapper("/account/records/address/list")
async def address_list(request: "Request", data: ListOptionsRequestModel):
"""Handle address list endpoint."""
return {
"data": data,
"request": str(request.headers),
"request_url": str(request.url),
"request_base_url": str(request.base_url),
}
@endpoint_wrapper("/account/records/address/create")
async def address_create(request: "Request", data: DictRequestModel):
"""Handle address creation endpoint."""
return {
"data": data,
"request": str(request.headers),
"request_url": str(request.url),
"request_base_url": str(request.base_url),
}
@endpoint_wrapper("/account/records/address/search")
async def address_search(request: "Request", data: DictRequestModel):
"""Handle address search endpoint."""
return {"data": data}
router = APIRouter()
@endpoint_wrapper("/account/records/address/{address_uu_id}")
async def address_update(
request: Request,
address_uu_id: str = Path(..., description="UUID of the address to update"),
request_data: DictRequestModel = Body(..., description="Request body"),
):
"""
Handle address update endpoint.
Args:
request: FastAPI request object
address_uu_id: UUID of the address to update
request_data: Request body containing updated address data
Returns:
DictJsonResponse: Response containing updated address info
"""
return DictJsonResponse(
data={
"address_uu_id": address_uu_id,
"data": request_data.root,
"request": str(request.headers),
"request_url": str(request.url),
"request_base_url": str(request.base_url),
}
)
prefix = "/account/records"
# Account Records Router Configuration
ACCOUNT_RECORDS_CONFIG = RouteFactoryConfig(
name="account_records",
prefix=prefix,
tags=["Account Records"],
include_in_schema=True,
endpoints=[
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/address/list",
url_of_endpoint="/account/records/address/list",
endpoint="/address/list",
method="POST",
summary="List Active/Delete/Confirm Address",
description="List Active/Delete/Confirm Address",
is_auth_required=True,
is_event_required=True,
endpoint_function=address_list,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/address/create",
url_of_endpoint="/account/records/address/create",
endpoint="/address/create",
method="POST",
summary="Create Address with given auth levels",
description="Create Address with given auth levels",
is_auth_required=False,
is_event_required=False,
endpoint_function=address_create,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/address/search",
url_of_endpoint="/account/records/address/search",
endpoint="/address/search",
method="POST",
summary="Search Address with given auth levels",
description="Search Address with given auth levels",
is_auth_required=True,
is_event_required=True,
endpoint_function=address_search,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/address/{address_uu_id}",
url_of_endpoint="/account/records/address/{address_uu_id}",
endpoint="/address/{address_uu_id}",
method="PUT",
summary="Update Address with given auth levels",
description="Update Address with given auth levels",
is_auth_required=True,
is_event_required=True,
endpoint_function=address_update,
),
],
).as_dict()

View File

@ -0,0 +1,129 @@
"""
Account records endpoint configurations.
"""
from ApiEvents.abstract_class import RouteFactoryConfig, EndpointFactoryConfig, endpoint_wrapper
from ApiEvents.base_request_model import DictRequestModel
from Services.PostgresDb.Models.alchemy_response import DictJsonResponse
from fastapi import Request, Path, Body
from .models import ListOptionsRequestModel
@endpoint_wrapper("/account/records/address/list")
async def address_list(request: "Request", data: ListOptionsRequestModel):
"""Handle address list endpoint."""
return {
"data": data,
"request": str(request.headers),
"request_url": str(request.url),
"request_base_url": str(request.base_url),
}
@endpoint_wrapper("/account/records/address/create")
async def address_create(request: "Request", data: DictRequestModel):
"""Handle address creation endpoint."""
return {
"data": data,
"request": str(request.headers),
"request_url": str(request.url),
"request_base_url": str(request.base_url),
}
@endpoint_wrapper("/account/records/address/search")
async def address_search(request: "Request", data: DictRequestModel):
"""Handle address search endpoint."""
return {"data": data}
@endpoint_wrapper("/account/records/address/{address_uu_id}")
async def address_update(
request: Request,
address_uu_id: str = Path(..., description="UUID of the address to update"),
request_data: DictRequestModel = Body(..., description="Request body"),
):
"""
Handle address update endpoint.
Args:
request: FastAPI request object
address_uu_id: UUID of the address to update
request_data: Request body containing updated address data
Returns:
DictJsonResponse: Response containing updated address info
"""
return DictJsonResponse(
data={
"address_uu_id": address_uu_id,
"data": request_data.root,
"request": str(request.headers),
"request_url": str(request.url),
"request_base_url": str(request.base_url),
}
)
prefix = "/account/records"
# Account Records Router Configuration
ACCOUNT_RECORDS_CONFIG = RouteFactoryConfig(
name="account_records",
prefix=prefix,
tags=["Account Records"],
include_in_schema=True,
endpoints=[
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/address/list",
url_of_endpoint="/account/records/address/list",
endpoint="/address/list",
method="POST",
summary="List Active/Delete/Confirm Address",
description="List Active/Delete/Confirm Address",
is_auth_required=True,
is_event_required=True,
endpoint_function=address_list,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/address/create",
url_of_endpoint="/account/records/address/create",
endpoint="/address/create",
method="POST",
summary="Create Address with given auth levels",
description="Create Address with given auth levels",
is_auth_required=False,
is_event_required=False,
endpoint_function=address_create,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/address/search",
url_of_endpoint="/account/records/address/search",
endpoint="/address/search",
method="POST",
summary="Search Address with given auth levels",
description="Search Address with given auth levels",
is_auth_required=True,
is_event_required=True,
endpoint_function=address_search,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/address/{address_uu_id}",
url_of_endpoint="/account/records/address/{address_uu_id}",
endpoint="/address/{address_uu_id}",
method="PUT",
summary="Update Address with given auth levels",
description="Update Address with given auth levels",
is_auth_required=True,
is_event_required=True,
endpoint_function=address_update,
),
],
).as_dict()

View File

@ -0,0 +1,49 @@
"""
Account records request and response models.
"""
from typing import TYPE_CHECKING, Dict, Any
from pydantic import BaseModel, Field, RootModel
from ApiEvents.base_request_model import BaseRequestModel
if TYPE_CHECKING:
from ApiValidations.Request import (
InsertAccountRecord,
UpdateAccountRecord,
ListOptions,
)
class AddressUpdateRequest(RootModel[Dict[str, Any]]):
"""Request model for address update."""
model_config = {
"json_schema_extra": {
"example": {
"street": "123 Main St",
"city": "Example City",
"country": "Example Country"
}
}
}
class AddressUpdateResponse(BaseModel):
"""Response model for address update."""
address_uu_id: str = Field(..., description="UUID of the updated address")
data: Dict[str, Any] = Field(..., description="Updated address data")
function_code: str = Field(..., description="Function code for the endpoint")
class InsertAccountRecordRequestModel(BaseRequestModel["InsertAccountRecord"]):
"""Request model for inserting account records."""
pass
class UpdateAccountRecordRequestModel(BaseRequestModel["UpdateAccountRecord"]):
"""Request model for updating account records."""
pass
class ListOptionsRequestModel(BaseRequestModel["ListOptions"]):
"""Request model for list options."""
pass

View File

@ -6,7 +6,7 @@ to be used by the dynamic route creation system.
"""
from typing import Dict, List, Any
from .account.account_records import ACCOUNT_RECORDS_CONFIG
from .account.endpoints import ACCOUNT_RECORDS_CONFIG
# Registry of all route configurations

View File

@ -5,54 +5,22 @@ This module provides base request models that can be used across different endpo
to ensure consistent request handling and validation.
"""
from typing import Dict, Any, Generic, TypeVar, Optional, get_args, get_origin
from pydantic import RootModel, BaseModel, Field, ConfigDict
from typing import Dict, Any, Generic, TypeVar, Optional, Union, get_args
from pydantic import BaseModel, Field, ConfigDict, RootModel
T = TypeVar("T")
class BaseRequestModel(RootModel[T]):
"""Base model for all API requests.
This model can be extended to create specific request models for different endpoints.
"""
class BaseRequestModel(RootModel[T], Generic[T]):
"""Base model for all API requests."""
model_config = ConfigDict(
json_schema_extra={"example": {}} # Will be populated by subclasses
)
@classmethod
def model_json_schema(cls, *args, **kwargs):
schema = super().model_json_schema(*args, **kwargs)
if hasattr(cls, "__orig_bases__"):
generic_type = get_args(cls.__orig_bases__[0])[0]
if generic_type and hasattr(generic_type, "model_json_schema"):
type_schema = generic_type.model_json_schema()
if "properties" in type_schema:
schema["properties"] = type_schema["properties"]
if "required" in type_schema:
schema["required"] = type_schema["required"]
if "title" in type_schema:
schema["title"] = type_schema["title"]
if "example" in type_schema:
schema["example"] = type_schema["example"]
elif "properties" in type_schema:
schema["example"] = {
key: prop.get("example", "string")
for key, prop in type_schema["properties"].items()
}
schema["type"] = "object"
return schema
@property
def data(self) -> T:
return self.root
class DictRequestModel(RootModel[Dict[str, Any]]):
"""Request model for endpoints that accept dictionary data."""
model_config = ConfigDict(
json_schema_extra={
"example": {
@ -63,40 +31,9 @@ class DictRequestModel(RootModel[Dict[str, Any]]):
}
)
@classmethod
def model_json_schema(cls, *args, **kwargs):
schema = super().model_json_schema(*args, **kwargs)
schema.update(
{
"title": "Dictionary Request Model",
"type": "object",
"properties": {
"key1": {"type": "string", "example": "value1"},
"key2": {"type": "string", "example": "value2"},
"nested": {
"type": "object",
"properties": {
"inner_key": {"type": "string", "example": "inner_value"}
},
},
},
"example": {
"key1": "value1",
"key2": "value2",
"nested": {"inner_key": "inner_value"},
},
}
)
return schema
@property
def data(self) -> Dict[str, Any]:
return self.root
class SuccessResponse(BaseModel):
"""Standard success response model."""
token: str = Field(..., example="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
user_info: Dict[str, Any] = Field(
...,

View File

@ -1,5 +1,14 @@
def get_line_number_for_error():
from inspect import currentframe, getframeinfo
"""Utility functions for getting line numbers and file locations."""
frameinfo = getframeinfo(currentframe())
from inspect import currentframe, getframeinfo, stack
def get_line_number_for_error() -> str:
"""Get the file name and line number of where an error occurred.
Returns:
str: A string in the format 'filename | line_number' showing where the error occurred
"""
caller = stack()[1] # Get the caller's frame
frameinfo = getframeinfo(caller[0])
return f"{frameinfo.filename} | {frameinfo.lineno}"

View File

@ -30,12 +30,14 @@ class UserLoginModule:
error_code="HTTP_400_BAD_REQUEST",
lang="en",
loc=get_line_number_for_error(),
sys_msg="User not found",
)
return found_user
async def login_user_via_credentials(self, access_data: "Login") -> Dict[str, Any]:
"""Login user via credentials."""
# Get the actual data from the BaseRequestModel if needed
print("access_data", access_data)
if hasattr(access_data, "data"):
access_data = access_data.data
@ -47,6 +49,7 @@ class UserLoginModule:
error_code="HTTP_400_BAD_REQUEST",
lang=found_user.lang,
loc=get_line_number_for_error(),
sys_msg="Invalid password create a password to user first",
)
if PasswordModule.check_password(
@ -65,4 +68,5 @@ class UserLoginModule:
error_code="HTTP_400_BAD_REQUEST",
lang=found_user.lang,
loc=get_line_number_for_error(),
sys_msg="login_user_via_credentials raised error",
)

View File

@ -67,7 +67,10 @@ class TokenService:
).data
if not living_spaces:
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="User does not have any living space",
)
occupants_selection_dict: Dict[str, Any] = {}
@ -79,7 +82,10 @@ class TokenService:
).data
if not build_parts_selection:
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="User does not have any living space",
)
build_part = build_parts_selection.get(1)
@ -211,7 +217,10 @@ class TokenService:
"companies_list": companies_list,
}
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Creating Token failed...",
)
@classmethod
@ -287,11 +296,17 @@ class TokenService:
"""Validate request has required token headers."""
if not hasattr(request, "headers"):
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Request has no headers",
)
if not request.headers.get(Auth.ACCESS_TOKEN_TAG):
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Request has no access token presented",
)
@classmethod
@ -322,7 +337,10 @@ class TokenService:
return OccupantTokenObject(**redis_object)
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Unknown user type",
)
@classmethod
@ -336,7 +354,10 @@ class TokenService:
return cls._process_redis_object(redis_object)
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Invalid access token",
)
@classmethod
@ -350,5 +371,8 @@ class TokenService:
return cls._process_redis_object(redis_object)
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Invalid access token",
)

View File

@ -15,7 +15,7 @@ from create_routes import get_all_routers
from prometheus_fastapi_instrumentator import Instrumentator
from app_handler import setup_middleware, get_uvicorn_config
from create_file import setup_security_schema, configure_route_security
from fastapi.openapi.utils import get_openapi
from open_api_creator import OpenAPISchemaCreator, create_openapi_schema
def create_app() -> FastAPI:
@ -63,12 +63,8 @@ def create_app() -> FastAPI:
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="WAG Management API",
version="4.0.0",
description="WAG Management API Service",
routes=app.routes,
)
# Create OpenAPI schema using our custom creator
openapi_schema = create_openapi_schema(app)
# Add security scheme
openapi_schema.update(setup_security_schema())

View File

@ -79,7 +79,10 @@ class MiddlewareModule:
token_context = TokenService.get_object_via_access_key(access_token=redis_token)
if not token_context:
raise HTTPExceptionApi(
error_code="USER_NOT_FOUND", lang="tr", loc=get_line_number_for_error()
error_code="USER_NOT_FOUND",
lang="tr",
loc=get_line_number_for_error(),
sys_msg="TokenService: Token Context couldnt retrieved from redis",
)
return AuthContext(token_context=token_context)

View File

@ -11,8 +11,10 @@ This module provides functionality to create and customize OpenAPI documentation
from typing import Any, Dict, List, Optional, Set
from fastapi import FastAPI, APIRouter
from fastapi.routing import APIRoute
from fastapi.openapi.utils import get_openapi
from AllConfigs.main import MainConfig as Config
from create_routes import get_all_routers
class OpenAPISchemaCreator:
@ -28,7 +30,7 @@ class OpenAPISchemaCreator:
app: FastAPI application instance
"""
self.app = app
self.protected_paths: Set[str] = set()
_, self.protected_routes = get_all_routers()
self.tags_metadata = self._create_tags_metadata()
@staticmethod
@ -60,16 +62,16 @@ class OpenAPISchemaCreator:
"""
return {
"Bearer Auth": {
"type": "apiKey",
"in": "header",
"name": "evyos-session-key",
"description": "Enter: **'Bearer <JWT>'**, where JWT is the access token",
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Enter the token with the `Bearer: ` prefix",
},
"API Key": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "API key for service authentication",
"description": "Optional API key for service authentication",
},
}
@ -82,20 +84,10 @@ class OpenAPISchemaCreator:
"""
return {
"401": {
"description": "Unauthorized - Authentication failed or not provided",
"description": "Unauthorized - Invalid or missing credentials",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"detail": {"type": "string"},
"error_code": {"type": "string"},
},
},
"example": {
"detail": "Invalid authentication credentials",
"error_code": "INVALID_CREDENTIALS",
},
"schema": {"$ref": "#/components/schemas/HTTPValidationError"}
}
},
},
@ -135,6 +127,62 @@ class OpenAPISchemaCreator:
},
}
def _process_request_body(self, path: str, method: str, schema: Dict[str, Any]) -> None:
"""
Process request body to include examples from model config.
Args:
path: Route path
method: HTTP method
schema: OpenAPI schema to modify
"""
try:
route_schema = schema["paths"][path][method]
if "requestBody" in route_schema:
request_body = route_schema["requestBody"]
if "content" in request_body:
content = request_body["content"]
if "application/json" in content:
json_content = content["application/json"]
if "schema" in json_content and "$ref" in json_content["schema"]:
ref = json_content["schema"]["$ref"]
model_name = ref.split("/")[-1]
if model_name in schema["components"]["schemas"]:
model_schema = schema["components"]["schemas"][model_name]
if "example" in model_schema:
json_content["example"] = model_schema["example"]
except KeyError:
pass
def _process_response_examples(self, path: str, method: str, schema: Dict[str, Any]) -> None:
"""
Process response body to include examples from model config.
Args:
path: Route path
method: HTTP method
schema: OpenAPI schema to modify
"""
try:
route_schema = schema["paths"][path][method]
if "responses" in route_schema:
responses = route_schema["responses"]
if "200" in responses:
response = responses["200"]
if "content" in response:
content = response["content"]
if "application/json" in content:
json_content = content["application/json"]
if "schema" in json_content and "$ref" in json_content["schema"]:
ref = json_content["schema"]["$ref"]
model_name = ref.split("/")[-1]
if model_name in schema["components"]["schemas"]:
model_schema = schema["components"]["schemas"][model_name]
if "example" in model_schema:
json_content["example"] = model_schema["example"]
except KeyError:
pass
def configure_route_security(
self, path: str, method: str, schema: Dict[str, Any]
) -> None:
@ -146,7 +194,8 @@ class OpenAPISchemaCreator:
method: HTTP method
schema: OpenAPI schema to modify
"""
if path not in Config.INSECURE_PATHS:
# Check if route is protected based on dynamic routing info
if path in self.protected_routes and method in self.protected_routes[path]:
schema["paths"][path][method]["security"] = [
{"Bearer Auth": []},
{"API Key": []},
@ -154,6 +203,11 @@ class OpenAPISchemaCreator:
schema["paths"][path][method]["responses"].update(
self._create_common_responses()
)
# Process request body examples
self._process_request_body(path, method, schema)
# Process response examples
self._process_response_examples(path, method, schema)
def create_schema(self) -> Dict[str, Any]:
"""
@ -174,9 +228,7 @@ class OpenAPISchemaCreator:
if "components" not in openapi_schema:
openapi_schema["components"] = {}
openapi_schema["components"][
"securitySchemes"
] = self._create_security_schemes()
openapi_schema["components"]["securitySchemes"] = self._create_security_schemes()
# Configure route security and responses
for route in self.app.routes:

View File

@ -1,6 +1,7 @@
class HTTPExceptionApi(Exception):
def __init__(self, error_code: str, lang: str, loc: str = ""):
def __init__(self, error_code: str, lang: str, loc: str = "", sys_msg: str = ""):
self.error_code = error_code
self.lang = lang
self.loc = loc
self.sys_msg = sys_msg

View File

@ -56,6 +56,7 @@ def handle_mongo_errors(func: Callable) -> Callable:
lang="en",
error_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
loc=get_line_number_for_error(),
sys_msg=str(e),
)
return wrapper

View File

@ -7,6 +7,7 @@ operations and password-related functionality.
from typing import Any, Dict, Optional
from fastapi import HTTPException, status
from ApiLibrary.common.line_number import get_line_number_for_error
from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi
@ -26,9 +27,11 @@ class MongoBaseException(Exception):
def to_http_exception(self) -> HTTPException:
"""Convert to FastAPI HTTPException."""
return HTTPExceptionApi(
raise HTTPExceptionApi(
lang="en",
error_code=self.status_code,
loc=get_line_number_for_error(),
sys_msg=self.message,
)

View File

@ -39,24 +39,28 @@ def handle_mongo_errors(func):
error_code="HTTP_503_SERVICE_UNAVAILABLE",
lang="en",
loc=get_line_number_for_error(),
sys_msg="MongoDB connection failed",
)
except ServerSelectionTimeoutError:
raise HTTPExceptionApi(
error_code="HTTP_504_GATEWAY_TIMEOUT",
lang="en",
loc=get_line_number_for_error(),
sys_msg="MongoDB connection timed out",
)
except OperationFailure as e:
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang="en",
loc=get_line_number_for_error(),
sys_msg=str(e),
)
except PyMongoError as e:
raise HTTPExceptionApi(
error_code="HTTP_500_INTERNAL_SERVER_ERROR",
lang="en",
loc=get_line_number_for_error(),
sys_msg=str(e),
)
return wrapper

View File

@ -161,6 +161,7 @@ class BaseJsonResponse(Generic[T]):
lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST",
loc=get_line_number_for_error(),
sys_msg=f"Invalid data type: {type(data)}",
)
@ -201,6 +202,7 @@ class SinglePostgresResponse(BaseJsonResponse[T]):
lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST",
loc=get_line_number_for_error(),
sys_msg="No data found",
)
instance = super().__new__(cls)
@ -257,6 +259,7 @@ class AlchemyJsonResponse(BaseJsonResponse[T]):
lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST",
loc=get_line_number_for_error(),
sys_msg="No data found",
)
instance = super().__new__(cls)

View File

@ -139,6 +139,7 @@ class FilterAttributes:
error_code="HTTP_304_NOT_MODIFIED",
lang=cls.lang or "tr",
loc=get_line_number_for_error(),
sys_msg=str(e),
)
@classmethod
@ -173,6 +174,7 @@ class FilterAttributes:
error_code="HTTP_304_NOT_MODIFIED",
lang=cls.lang or "tr",
loc=get_line_number_for_error(),
sys_msg=str(e),
)
@classmethod
@ -193,6 +195,7 @@ class FilterAttributes:
error_code="HTTP_304_NOT_MODIFIED",
lang=cls.lang or "tr",
loc=get_line_number_for_error(),
sys_msg=str(e),
)
@classmethod
@ -225,6 +228,7 @@ class FilterAttributes:
error_code="HTTP_304_NOT_MODIFIED",
lang=cls.lang or "tr",
loc=get_line_number_for_error(),
sys_msg=str(e),
)
@classmethod