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. 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) # Regular imports (non-TYPE_CHECKING)
from ApiEvents.abstract_class import ( from ApiEvents.abstract_class import MethodToEvent
MethodToEvent, from ApiEvents.base_request_model import DictRequestModel, SuccessResponse
RouteFactoryConfig, from ApiLibrary.common.line_number import get_line_number_for_error
EndpointFactoryConfig, from ApiServices.Login.user_login_handler import UserLoginModule
) from ApiServices.Token.token_handler import AccessToken, TokenService
from ApiEvents.base_request_model import ( from ApiValidations.Request.authentication import EmployeeSelectionValidation, Login, OccupantSelectionValidation
BaseRequestModel, from ErrorHandlers import HTTPExceptionApi
DictRequestModel, from .models import (
SuccessResponse, LoginData,
LoginRequestModel,
LogoutRequestModel,
RememberRequestModel,
ForgotRequestModel,
ChangePasswordRequestModel,
CreatePasswordRequestModel,
SelectionDataEmployee,
SelectionDataOccupant,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from fastapi import Request from fastapi import Request
from ApiServices.Token.token_handler import OccupantTokenObject, EmployeeTokenObject 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 # Type aliases for common types
TokenDictType = Union["EmployeeTokenObject", "OccupantTokenObject"] 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): class AuthenticationLoginEventMethods(MethodToEvent):
event_type = "LOGIN" event_type = "LOGIN"
event_description = "Login via domain and access key : [email] | [phone]" event_description = "Login via domain and access key : [email] | [phone]"
@ -112,7 +46,7 @@ class AuthenticationLoginEventMethods(MethodToEvent):
@classmethod @classmethod
async def authentication_login_with_domain_and_creds( async def authentication_login_with_domain_and_creds(
cls, request: "Request", data: LoginRequestModel cls, request: "Request", data: Login
): ):
""" """
Authenticate user with domain and credentials. Authenticate user with domain and credentials.
@ -129,11 +63,9 @@ class AuthenticationLoginEventMethods(MethodToEvent):
Returns: Returns:
SuccessResponse containing authentication token and user info SuccessResponse containing authentication token and user info
""" """
# Create login module instance
login_module = UserLoginModule(request=request)
# Get token from login module # 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 response with token and headers
return { return {
@ -157,134 +89,52 @@ class AuthenticationSelectEventMethods(MethodToEvent):
@classmethod @classmethod
def _handle_employee_selection( def _handle_employee_selection(
cls, cls,
data: EmployeeSelectionRequestModel, data: SelectionDataEmployee,
token_dict: TokenDictType, token_dict: TokenDictType,
request: "Request", request: "Request",
): ):
return raise HTTPExceptionApi(
# """Handle employee company selection""" error_code="", lang="en", loc=get_line_number_for_error()
# 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")
@classmethod @classmethod
def _handle_occupant_selection( def _handle_occupant_selection(
cls, cls,
data: OccupantSelectionRequestModel, data: SelectionDataOccupant,
token_dict: TokenDictType, token_dict: TokenDictType,
request: "Request", request: "Request",
): ):
"""Handle selection of company or occupant type""" """Handle occupant type selection"""
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( raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST", error_code="HTTP_400_BAD_REQUEST",
lang=token_dict.lang, lang=token_dict.lang,
loc=get_line_number_for_error(), loc=get_line_number_for_error(),
) sys_msg="Occupant selection not implemented",
except Exception as e:
return ResponseHandler.error(
str(e), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
) )
@classmethod @classmethod
def authentication_select_company_or_occupant_type( async def authentication_select_company_or_occupant_type(
cls, cls,
request: "Request", request: "Request",
data: Union[EmployeeSelectionRequestModel, OccupantSelectionRequestModel], data: Union[EmployeeSelectionValidation, OccupantSelectionValidation],
token_dict: TokenDictType, token_dict: TokenDictType,
): ):
"""Handle selection of company or occupant type""" """Handle selection of company or occupant type"""
# try: try:
# if isinstance(token_dict, EmployeeTokenObject): print(
# return cls._handle_employee_selection(data, token_dict, request) dict(
# elif isinstance(token_dict, OccupantTokenObject): data=data,
# return cls._handle_occupant_selection(data, token_dict, request) token_dict=token_dict.model_dump(),
# return ResponseHandler.error( request=dict(request.headers)
# "Invalid token type", status_code=status.HTTP_400_BAD_REQUEST )
# ) )
# except Exception as e: except Exception as e:
# return ResponseHandler.error( raise HTTPExceptionApi(
# str(e), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR error_code="HTTP_500_INTERNAL_SERVER_ERROR",
# ) lang="en",
return loc=get_line_number_for_error(),
sys_msg=str(e),
)
class AuthenticationCheckTokenEventMethods(MethodToEvent): class AuthenticationCheckTokenEventMethods(MethodToEvent):
@ -300,7 +150,7 @@ class AuthenticationCheckTokenEventMethods(MethodToEvent):
# } # }
@classmethod @classmethod
def authentication_check_token_is_valid( async def authentication_check_token_is_valid(
cls, request: "Request", data: DictRequestModel cls, request: "Request", data: DictRequestModel
): ):
# try: # try:
@ -324,7 +174,7 @@ class AuthenticationRefreshEventMethods(MethodToEvent):
# } # }
@classmethod @classmethod
def authentication_refresh_user_info( async def authentication_refresh_user_info(
cls, cls,
request: "Request", request: "Request",
token_dict: TokenDictType, token_dict: TokenDictType,
@ -371,7 +221,7 @@ class AuthenticationChangePasswordEventMethods(MethodToEvent):
# } # }
@classmethod @classmethod
def authentication_change_password( async def authentication_change_password(
cls, cls,
request: "Request", request: "Request",
data: ChangePasswordRequestModel, data: ChangePasswordRequestModel,
@ -429,7 +279,7 @@ class AuthenticationCreatePasswordEventMethods(MethodToEvent):
# } # }
@classmethod @classmethod
def authentication_create_password( async def authentication_create_password(
cls, request: "Request", data: CreatePasswordRequestModel cls, request: "Request", data: CreatePasswordRequestModel
): ):
@ -475,7 +325,7 @@ class AuthenticationDisconnectUserEventMethods(MethodToEvent):
# } # }
@classmethod @classmethod
def authentication_disconnect_user( async def authentication_disconnect_user(
cls, request: "Request", data: LogoutRequestModel, token_dict: TokenDictType cls, request: "Request", data: LogoutRequestModel, token_dict: TokenDictType
): ):
# found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data # found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data
@ -514,11 +364,11 @@ class AuthenticationLogoutEventMethods(MethodToEvent):
# } # }
@classmethod @classmethod
def authentication_logout_user( async def authentication_logout_user(
cls, cls,
request: "Request", request: "Request",
data: LogoutRequestModel, data: LogoutRequestModel,
token_dict: TokenDictType = None, token_dict: TokenDictType,
): ):
# token_user = None # token_user = None
# if already_tokens := RedisActions.get_object_via_access_key(request=request): # if already_tokens := RedisActions.get_object_via_access_key(request=request):
@ -553,7 +403,7 @@ class AuthenticationRefreshTokenEventMethods(MethodToEvent):
# } # }
@classmethod @classmethod
def authentication_refresher_token( async def authentication_refresher_token(
cls, request: "Request", data: RememberRequestModel, token_dict: TokenDictType cls, request: "Request", data: RememberRequestModel, token_dict: TokenDictType
): ):
# token_refresher = UsersTokens.filter_by_one( # token_refresher = UsersTokens.filter_by_one(
@ -602,7 +452,7 @@ class AuthenticationForgotPasswordEventMethods(MethodToEvent):
# } # }
@classmethod @classmethod
def authentication_forgot_password( async def authentication_forgot_password(
cls, cls,
request: "Request", request: "Request",
data: ForgotRequestModel, data: ForgotRequestModel,
@ -644,7 +494,7 @@ class AuthenticationResetPasswordEventMethods(MethodToEvent):
} }
@classmethod @classmethod
def authentication_reset_password( async def authentication_reset_password(
cls, request: "Request", data: ForgotRequestModel cls, request: "Request", data: ForgotRequestModel
): ):
# from sqlalchemy import or_ # from sqlalchemy import or_
@ -695,11 +545,11 @@ class AuthenticationDownloadAvatarEventMethods(MethodToEvent):
# } # }
@classmethod @classmethod
def authentication_download_avatar( async def authentication_download_avatar(
cls, cls,
token_dict: TokenDictType,
request: "Request", request: "Request",
data: DictRequestModel, data: DictRequestModel,
token_dict: TokenDictType,
): ):
# if found_user := Users.filter_one(Users.id == token_dict.user_id).data: # if found_user := Users.filter_one(Users.id == token_dict.user_id).data:
# expired_starts = str( # expired_starts = str(
@ -725,253 +575,3 @@ class AuthenticationDownloadAvatarEventMethods(MethodToEvent):
# return ResponseHandler.not_found("Invalid data") # return ResponseHandler.not_found("Invalid data")
return 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 typing import Dict, List, Any
from .auth.auth import AUTH_CONFIG from .auth.endpoints import AUTH_CONFIG
# Registry of all route configurations # 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 ( from typing import Dict, Any, Union
MethodToEvent, from pydantic import Field
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 ApiEvents.abstract_class import MethodToEvent, endpoint_wrapper
from ApiEvents.base_request_model import DictRequestModel
from ApiValidations.Custom.token_objects import ( from ApiValidations.Custom.token_objects import (
OccupantTokenObject, OccupantTokenObject,
EmployeeTokenObject, EmployeeTokenObject,
) )
from ApiLibrary import system_arrow
from ApiValidations.Request.account_records import (
InsertAccountRecord,
UpdateAccountRecord,
)
from ApiValidations.Request.base_validations import ListOptions
from Schemas import ( from Schemas import (
BuildLivingSpace, BuildLivingSpace,
AccountRecords, AccountRecords,
@ -26,49 +24,16 @@ from Schemas import (
BuildDecisionBookPayments, BuildDecisionBookPayments,
ApiEnumDropdown, ApiEnumDropdown,
) )
from ApiLibrary import system_arrow
from ApiValidations.Request import (
InsertAccountRecord,
UpdateAccountRecord,
ListOptions,
)
from Services.PostgresDb.Models.alchemy_response import ( from Services.PostgresDb.Models.alchemy_response import (
AlchemyJsonResponse, AlchemyJsonResponse,
DictJsonResponse, DictJsonResponse,
) )
from ApiValidations.Response import AccountRecordResponse from ApiValidations.Response import AccountRecordResponse
from .models import (
InsertAccountRecordRequestModel,
class AddressUpdateRequest(BaseModel): UpdateAccountRecordRequestModel,
"""Request model for address update.""" ListOptionsRequestModel,
)
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
class AccountRecordsListEventMethods(MethodToEvent): class AccountRecordsListEventMethods(MethodToEvent):
@ -90,7 +55,7 @@ class AccountRecordsListEventMethods(MethodToEvent):
def account_records_list( def account_records_list(
cls, cls,
list_options: ListOptionsRequestModel, list_options: ListOptionsRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject], token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
): ):
db_session = AccountRecords.new_session() db_session = AccountRecords.new_session()
if isinstance(token_dict, OccupantTokenObject): if isinstance(token_dict, OccupantTokenObject):
@ -119,7 +84,7 @@ class AccountRecordsListEventMethods(MethodToEvent):
def account_records_list_flt_res( def account_records_list_flt_res(
cls, cls,
list_options: ListOptionsRequestModel, list_options: ListOptionsRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject], token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
): ):
db_session = AccountRecords.new_session() db_session = AccountRecords.new_session()
if not isinstance(token_dict, OccupantTokenObject): if not isinstance(token_dict, OccupantTokenObject):
@ -269,7 +234,7 @@ class AccountRecordsCreateEventMethods(MethodToEvent):
def account_records_create( def account_records_create(
cls, cls,
data: InsertAccountRecordRequestModel, data: InsertAccountRecordRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject], token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
): ):
data_dict = data.excluded_dump() data_dict = data.excluded_dump()
if isinstance(token_dict, OccupantTokenObject): if isinstance(token_dict, OccupantTokenObject):
@ -358,7 +323,7 @@ class AccountRecordsUpdateEventMethods(MethodToEvent):
cls, cls,
build_uu_id: str, build_uu_id: str,
data: UpdateAccountRecordRequestModel, data: UpdateAccountRecordRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject], token_dict: Union[EmployeeTokenObject, OccupantTokenObject],
): ):
if isinstance(token_dict, OccupantTokenObject): if isinstance(token_dict, OccupantTokenObject):
pass pass
@ -373,123 +338,3 @@ class AccountRecordsUpdateEventMethods(MethodToEvent):
cls_object=AccountRecords, cls_object=AccountRecords,
response_model=UpdateAccountRecord, 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 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 # 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. to ensure consistent request handling and validation.
""" """
from typing import Dict, Any, Generic, TypeVar, Optional, get_args, get_origin from typing import Dict, Any, Generic, TypeVar, Optional, Union, get_args
from pydantic import RootModel, BaseModel, Field, ConfigDict from pydantic import BaseModel, Field, ConfigDict, RootModel
T = TypeVar("T") T = TypeVar("T")
class BaseRequestModel(RootModel[T]): class BaseRequestModel(RootModel[T], Generic[T]):
"""Base model for all API requests. """Base model for all API requests."""
This model can be extended to create specific request models for different endpoints.
"""
model_config = ConfigDict( model_config = ConfigDict(
json_schema_extra={"example": {}} # Will be populated by subclasses 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]]): class DictRequestModel(RootModel[Dict[str, Any]]):
"""Request model for endpoints that accept dictionary data.""" """Request model for endpoints that accept dictionary data."""
model_config = ConfigDict( model_config = ConfigDict(
json_schema_extra={ json_schema_extra={
"example": { "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): class SuccessResponse(BaseModel):
"""Standard success response model.""" """Standard success response model."""
token: str = Field(..., example="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...") token: str = Field(..., example="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
user_info: Dict[str, Any] = Field( user_info: Dict[str, Any] = Field(
..., ...,

View File

@ -1,5 +1,14 @@
def get_line_number_for_error(): """Utility functions for getting line numbers and file locations."""
from inspect import currentframe, getframeinfo
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}" return f"{frameinfo.filename} | {frameinfo.lineno}"

View File

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

View File

@ -67,7 +67,10 @@ class TokenService:
).data ).data
if not living_spaces: if not living_spaces:
raise HTTPExceptionApi( 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] = {} occupants_selection_dict: Dict[str, Any] = {}
@ -79,7 +82,10 @@ class TokenService:
).data ).data
if not build_parts_selection: if not build_parts_selection:
raise HTTPExceptionApi( 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) build_part = build_parts_selection.get(1)
@ -211,7 +217,10 @@ class TokenService:
"companies_list": companies_list, "companies_list": companies_list,
} }
raise HTTPExceptionApi( 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 @classmethod
@ -287,11 +296,17 @@ class TokenService:
"""Validate request has required token headers.""" """Validate request has required token headers."""
if not hasattr(request, "headers"): if not hasattr(request, "headers"):
raise HTTPExceptionApi( 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): if not request.headers.get(Auth.ACCESS_TOKEN_TAG):
raise HTTPExceptionApi( 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 @classmethod
@ -322,7 +337,10 @@ class TokenService:
return OccupantTokenObject(**redis_object) return OccupantTokenObject(**redis_object)
raise HTTPExceptionApi( 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 @classmethod
@ -336,7 +354,10 @@ class TokenService:
return cls._process_redis_object(redis_object) return cls._process_redis_object(redis_object)
raise HTTPExceptionApi( 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 @classmethod
@ -350,5 +371,8 @@ class TokenService:
return cls._process_redis_object(redis_object) return cls._process_redis_object(redis_object)
raise HTTPExceptionApi( 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 prometheus_fastapi_instrumentator import Instrumentator
from app_handler import setup_middleware, get_uvicorn_config from app_handler import setup_middleware, get_uvicorn_config
from create_file import setup_security_schema, configure_route_security 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: def create_app() -> FastAPI:
@ -63,12 +63,8 @@ def create_app() -> FastAPI:
if app.openapi_schema: if app.openapi_schema:
return app.openapi_schema return app.openapi_schema
openapi_schema = get_openapi( # Create OpenAPI schema using our custom creator
title="WAG Management API", openapi_schema = create_openapi_schema(app)
version="4.0.0",
description="WAG Management API Service",
routes=app.routes,
)
# Add security scheme # Add security scheme
openapi_schema.update(setup_security_schema()) 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) token_context = TokenService.get_object_via_access_key(access_token=redis_token)
if not token_context: if not token_context:
raise HTTPExceptionApi( 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) 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 typing import Any, Dict, List, Optional, Set
from fastapi import FastAPI, APIRouter from fastapi import FastAPI, APIRouter
from fastapi.routing import APIRoute
from fastapi.openapi.utils import get_openapi from fastapi.openapi.utils import get_openapi
from AllConfigs.main import MainConfig as Config from AllConfigs.main import MainConfig as Config
from create_routes import get_all_routers
class OpenAPISchemaCreator: class OpenAPISchemaCreator:
@ -28,7 +30,7 @@ class OpenAPISchemaCreator:
app: FastAPI application instance app: FastAPI application instance
""" """
self.app = app self.app = app
self.protected_paths: Set[str] = set() _, self.protected_routes = get_all_routers()
self.tags_metadata = self._create_tags_metadata() self.tags_metadata = self._create_tags_metadata()
@staticmethod @staticmethod
@ -60,16 +62,16 @@ class OpenAPISchemaCreator:
""" """
return { return {
"Bearer Auth": { "Bearer Auth": {
"type": "apiKey", "type": "http",
"in": "header", "scheme": "bearer",
"name": "evyos-session-key", "bearerFormat": "JWT",
"description": "Enter: **'Bearer <JWT>'**, where JWT is the access token", "description": "Enter the token with the `Bearer: ` prefix",
}, },
"API Key": { "API Key": {
"type": "apiKey", "type": "apiKey",
"in": "header", "in": "header",
"name": "X-API-Key", "name": "X-API-Key",
"description": "API key for service authentication", "description": "Optional API key for service authentication",
}, },
} }
@ -82,20 +84,10 @@ class OpenAPISchemaCreator:
""" """
return { return {
"401": { "401": {
"description": "Unauthorized - Authentication failed or not provided", "description": "Unauthorized - Invalid or missing credentials",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {"$ref": "#/components/schemas/HTTPValidationError"}
"type": "object",
"properties": {
"detail": {"type": "string"},
"error_code": {"type": "string"},
},
},
"example": {
"detail": "Invalid authentication credentials",
"error_code": "INVALID_CREDENTIALS",
},
} }
}, },
}, },
@ -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( def configure_route_security(
self, path: str, method: str, schema: Dict[str, Any] self, path: str, method: str, schema: Dict[str, Any]
) -> None: ) -> None:
@ -146,7 +194,8 @@ class OpenAPISchemaCreator:
method: HTTP method method: HTTP method
schema: OpenAPI schema to modify 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"] = [ schema["paths"][path][method]["security"] = [
{"Bearer Auth": []}, {"Bearer Auth": []},
{"API Key": []}, {"API Key": []},
@ -155,6 +204,11 @@ class OpenAPISchemaCreator:
self._create_common_responses() 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]: def create_schema(self) -> Dict[str, Any]:
""" """
Create the complete OpenAPI schema. Create the complete OpenAPI schema.
@ -174,9 +228,7 @@ class OpenAPISchemaCreator:
if "components" not in openapi_schema: if "components" not in openapi_schema:
openapi_schema["components"] = {} openapi_schema["components"] = {}
openapi_schema["components"][ openapi_schema["components"]["securitySchemes"] = self._create_security_schemes()
"securitySchemes"
] = self._create_security_schemes()
# Configure route security and responses # Configure route security and responses
for route in self.app.routes: for route in self.app.routes:

View File

@ -1,6 +1,7 @@
class HTTPExceptionApi(Exception): 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.error_code = error_code
self.lang = lang self.lang = lang
self.loc = loc self.loc = loc
self.sys_msg = sys_msg

View File

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

View File

@ -7,6 +7,7 @@ operations and password-related functionality.
from typing import Any, Dict, Optional from typing import Any, Dict, Optional
from fastapi import HTTPException, status from fastapi import HTTPException, status
from ApiLibrary.common.line_number import get_line_number_for_error
from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi
@ -26,9 +27,11 @@ class MongoBaseException(Exception):
def to_http_exception(self) -> HTTPException: def to_http_exception(self) -> HTTPException:
"""Convert to FastAPI HTTPException.""" """Convert to FastAPI HTTPException."""
return HTTPExceptionApi( raise HTTPExceptionApi(
lang="en", lang="en",
error_code=self.status_code, 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", error_code="HTTP_503_SERVICE_UNAVAILABLE",
lang="en", lang="en",
loc=get_line_number_for_error(), loc=get_line_number_for_error(),
sys_msg="MongoDB connection failed",
) )
except ServerSelectionTimeoutError: except ServerSelectionTimeoutError:
raise HTTPExceptionApi( raise HTTPExceptionApi(
error_code="HTTP_504_GATEWAY_TIMEOUT", error_code="HTTP_504_GATEWAY_TIMEOUT",
lang="en", lang="en",
loc=get_line_number_for_error(), loc=get_line_number_for_error(),
sys_msg="MongoDB connection timed out",
) )
except OperationFailure as e: except OperationFailure as e:
raise HTTPExceptionApi( raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST", error_code="HTTP_400_BAD_REQUEST",
lang="en", lang="en",
loc=get_line_number_for_error(), loc=get_line_number_for_error(),
sys_msg=str(e),
) )
except PyMongoError as e: except PyMongoError as e:
raise HTTPExceptionApi( raise HTTPExceptionApi(
error_code="HTTP_500_INTERNAL_SERVER_ERROR", error_code="HTTP_500_INTERNAL_SERVER_ERROR",
lang="en", lang="en",
loc=get_line_number_for_error(), loc=get_line_number_for_error(),
sys_msg=str(e),
) )
return wrapper return wrapper

View File

@ -161,6 +161,7 @@ class BaseJsonResponse(Generic[T]):
lang=cls_object.lang, lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST", error_code="HTTP_400_BAD_REQUEST",
loc=get_line_number_for_error(), 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, lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST", error_code="HTTP_400_BAD_REQUEST",
loc=get_line_number_for_error(), loc=get_line_number_for_error(),
sys_msg="No data found",
) )
instance = super().__new__(cls) instance = super().__new__(cls)
@ -257,6 +259,7 @@ class AlchemyJsonResponse(BaseJsonResponse[T]):
lang=cls_object.lang, lang=cls_object.lang,
error_code="HTTP_400_BAD_REQUEST", error_code="HTTP_400_BAD_REQUEST",
loc=get_line_number_for_error(), loc=get_line_number_for_error(),
sys_msg="No data found",
) )
instance = super().__new__(cls) instance = super().__new__(cls)

View File

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