events auth repair

This commit is contained in:
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(
...,