update events via wrapper routers

This commit is contained in:
2025-01-16 19:32:59 +03:00
parent 049a7c1e11
commit 426b69b33c
42 changed files with 2344 additions and 460 deletions

View File

@@ -0,0 +1,5 @@
"""Auth Service API initialization"""
from .route_configs import get_route_configs
__all__ = ["get_route_configs"]

View File

@@ -0,0 +1,977 @@
"""
Authentication related API endpoints.
"""
from typing import TYPE_CHECKING, Union, Optional, Dict, Any
# Regular imports (non-TYPE_CHECKING)
from ApiEvents.abstract_class import (
MethodToEvent,
RouteFactoryConfig,
EndpointFactoryConfig,
)
from ApiEvents.base_request_model import (
BaseRequestModel,
DictRequestModel,
SuccessResponse,
)
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]"
event_category = "AUTHENTICATION"
__event_keys__ = {
"e672846d-cc45-4d97-85d5-6f96747fac67": "authentication_login_with_domain_and_creds",
}
__event_validation__ = {
"e672846d-cc45-4d97-85d5-6f96747fac67": SuccessResponse,
}
@classmethod
async def authentication_login_with_domain_and_creds(
cls, request: "Request", data: LoginRequestModel
):
"""
Authenticate user with domain and credentials.
Args:
request: FastAPI request object
data: Request body containing login credentials
{
"domain": "evyos.com.tr",
"access_key": "karatay.berkay.sup@evyos.com.tr",
"password": "string",
"remember_me": false
}
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)
# Return response with token and headers
return {
"token": token,
"headers": dict(request.headers),
}
class AuthenticationSelectEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Select Employee Duty or Occupant Type"
event_category = "AUTHENTICATION"
__event_keys__ = {
"cee96b9b-8487-4e9f-aaed-2e8c79687bf9": "authentication_select_company_or_occupant_type",
}
# __event_validation__ = {
# "cee96b9b-8487-4e9f-aaed-2e8c79687bf9": "authentication_select_company_or_occupant_type",
# }
@classmethod
def _handle_employee_selection(
cls,
data: EmployeeSelectionRequestModel,
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")
@classmethod
def _handle_occupant_selection(
cls,
data: OccupantSelectionRequestModel,
token_dict: TokenDictType,
request: "Request",
):
"""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(),
)
except Exception as e:
return ResponseHandler.error(
str(e), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@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"
event_description = "Check Token is valid for user"
event_category = "AUTHENTICATION"
__event_keys__ = {
"73d77e45-a33f-4f12-909e-3b56f00d8a12": "authentication_check_token_is_valid",
}
# __event_validation__ = {
# "73d77e45-a33f-4f12-909e-3b56f00d8a12": "authentication_check_token_is_valid",
# }
@classmethod
def authentication_check_token_is_valid(
cls, request: "Request", data: DictRequestModel
):
# try:
# if RedisActions.get_object_via_access_key(request=request):
# return ResponseHandler.success("Access Token is valid")
# except HTTPException:
# return ResponseHandler.unauthorized("Access Token is NOT valid")
return
class AuthenticationRefreshEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Refresh user info using access token"
event_category = "AUTHENTICATION"
__event_keys__ = {
"48379bb2-ba81-4d8e-a9dd-58837cfcbf67": "authentication_refresh_user_info",
}
# __event_validation__ = {
# "48379bb2-ba81-4d8e-a9dd-58837cfcbf67": AuthenticationRefreshResponse,
# }
@classmethod
def authentication_refresh_user_info(
cls,
request: "Request",
token_dict: TokenDictType,
data: DictRequestModel,
):
# try:
# access_token = request.headers.get(Auth.ACCESS_TOKEN_TAG)
# if not access_token:
# return ResponseHandler.unauthorized()
# # Get user and token info
# found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data
# if not found_user:
# return ResponseHandler.not_found("User not found")
# user_token = UsersTokens.filter_one(
# UsersTokens.domain == found_user.domain_name,
# UsersTokens.user_id == found_user.id,
# UsersTokens.token_type == "RememberMe",
# ).data
# response_data = {
# "access_token": access_token,
# "refresh_token": getattr(user_token, "token", None),
# "user": found_user.get_dict(),
# }
# return ResponseHandler.success(
# "User info refreshed successfully",
# data=response_data,
# )
# except Exception as e:
# return ResponseHandler.error(str(e))
return
class AuthenticationChangePasswordEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Change password with access token"
event_category = "AUTHENTICATION"
__event_keys__ = {
"f09f7c1a-bee6-4e32-8444-962ec8f39091": "authentication_change_password",
}
# __event_validation__ = {
# "f09f7c1a-bee6-4e32-8444-962ec8f39091": "authentication_change_password",
# }
@classmethod
def authentication_change_password(
cls,
request: "Request",
data: ChangePasswordRequestModel,
token_dict: TokenDictType,
):
# try:
# if not isinstance(token_dict, EmployeeTokenObject):
# return ResponseHandler.unauthorized(
# "Only employees can change password"
# )
# found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data
# if not found_user:
# return ResponseHandler.not_found("User not found")
# if not found_user.check_password(data.old_password):
# # UserLogger.log_password_change(
# # request,
# # found_user.id,
# # "change",
# # success=False,
# # error="Invalid old password",
# # )
# return ResponseHandler.unauthorized("Old password is incorrect")
# found_user.set_password(data.new_password)
# # UserLogger.log_password_change(
# # request, found_user.id, "change", success=True
# # )
# return ResponseHandler.success("Password changed successfully")
# except Exception as e:
# # UserLogger.log_password_change(
# # request,
# # found_user.id if found_user else None,
# # "change",
# # success=False,
# # error=str(e),
# # )
# return ResponseHandler.error(str(e))
return
class AuthenticationCreatePasswordEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Create password with password reset token requested via email"
event_category = "AUTHENTICATION"
__event_keys__ = {
"c519f9af-92e1-47b2-abf7-5a3316d075f7": "authentication_create_password",
}
# __event_validation__ = {
# "c519f9af-92e1-47b2-abf7-5a3316d075f7": "authentication_create_password",
# }
@classmethod
def authentication_create_password(
cls, request: "Request", data: CreatePasswordRequestModel
):
# if not data.re_password == data.password:
# raise HTTPException(
# status_code=status.HTTP_406_NOT_ACCEPTABLE, detail="Password must match"
# )
# if found_user := Users.filter_one(
# Users.password_token == data.password_token
# ).data:
# found_user: Users = found_user
# found_user.create_password(found_user=found_user, password=data.password)
# found_user.password_token = ""
# found_user.save()
# # send_email_completed = send_email(
# # subject=f"Dear {found_user.user_tag}, your password has been changed.",
# # receivers=[str(found_user.email)],
# # html=password_is_changed_template(user_name=found_user.user_tag),
# # )
# # if not send_email_completed:
# # raise HTTPException(
# # status_code=400, detail="Email can not be sent. Try again later"
# # )
# return ResponseHandler.success(
# "Password is created successfully",
# data=found_user.get_dict(),
# )
# return ResponseHandler.not_found("Record not found")
return
class AuthenticationDisconnectUserEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Disconnect all sessions of user in access token"
event_category = "AUTHENTICATION"
__event_keys__ = {
"8b586848-2fb3-4161-abbe-642157eec7ce": "authentication_disconnect_user",
}
# __event_validation__ = {
# "8b586848-2fb3-4161-abbe-642157eec7ce": "authentication_disconnect_user",
# }
@classmethod
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
# if not found_user:
# return ResponseHandler.not_found("User not found")
# if already_tokens := RedisActions.get_object_via_user_uu_id(
# user_id=str(found_user.uu_id)
# ):
# for key, token_user in already_tokens.items():
# RedisActions.delete(key)
# selected_user = Users.filter_one(
# Users.uu_id == token_user.get("uu_id"),
# ).data
# selected_user.remove_refresher_token(
# domain=data.domain, disconnect=True
# )
# return ResponseHandler.success(
# "All sessions are disconnected",
# data=selected_user.get_dict(),
# )
# return ResponseHandler.not_found("Invalid data")
return
class AuthenticationLogoutEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Logout only single session of user which domain is provided"
event_category = "AUTHENTICATION"
__event_keys__ = {
"5cc22e4e-a0f7-4077-be41-1871feb3dfd1": "authentication_logout_user",
}
# __event_validation__ = {
# "5cc22e4e-a0f7-4077-be41-1871feb3dfd1": "authentication_logout_user",
# }
@classmethod
def authentication_logout_user(
cls,
request: "Request",
data: LogoutRequestModel,
token_dict: TokenDictType = None,
):
# token_user = None
# if already_tokens := RedisActions.get_object_via_access_key(request=request):
# for key in already_tokens:
# token_user = RedisActions.get_json(key)
# if token_user.get("domain") == data.domain:
# RedisActions.delete(key)
# selected_user = Users.filter_one(
# Users.uu_id == token_user.get("uu_id"),
# ).data
# selected_user.remove_refresher_token(domain=data.domain)
# return ResponseHandler.success(
# "Session is logged out",
# data=token_user,
# )
# return ResponseHandler.not_found("Logout is not successfully completed")
return
class AuthenticationRefreshTokenEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Refresh access token with refresher token"
event_category = "AUTHENTICATION"
__event_keys__ = {
"c90f3334-10c9-4181-b5ff-90d98a0287b2": "authentication_refresher_token",
}
# __event_validation__ = {
# "c90f3334-10c9-4181-b5ff-90d98a0287b2": AuthenticationRefreshResponse,
# }
@classmethod
def authentication_refresher_token(
cls, request: "Request", data: RememberRequestModel, token_dict: TokenDictType
):
# token_refresher = UsersTokens.filter_by_one(
# token=data.refresh_token,
# domain=data.domain,
# **UsersTokens.valid_record_dict,
# ).data
# if not token_refresher:
# return ResponseHandler.not_found("Invalid data")
# if found_user := Users.filter_one(
# Users.id == token_refresher.user_id,
# ).data:
# found_user: Users = found_user
# access_key = AuthActions.save_access_token_to_redis(
# request=request, found_user=found_user, domain=data.domain
# )
# found_user.last_agent = request.headers.get("User-Agent", None)
# found_user.last_platform = request.headers.get("Origin", None)
# found_user.last_remote_addr = getattr(
# request, "remote_addr", None
# ) or request.headers.get("X-Forwarded-For", None)
# found_user.last_seen = str(system_arrow.now())
# response_data = {
# "access_token": access_key,
# "refresh_token": data.refresh_token,
# }
# return ResponseHandler.success(
# "User is logged in successfully via refresher token",
# data=response_data,
# )
# return ResponseHandler.not_found("Invalid data")
return
class AuthenticationForgotPasswordEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Send an email to user for a valid password reset token"
event_category = "AUTHENTICATION"
__event_keys__ = {
"e3ca6e24-b9f8-4127-949c-3bfa364e3513": "authentication_forgot_password",
}
# __event_validation__ = {
# "e3ca6e24-b9f8-4127-949c-3bfa364e3513": "authentication_forgot_password",
# }
@classmethod
def authentication_forgot_password(
cls,
request: "Request",
data: ForgotRequestModel,
):
# found_user: Users = Users.check_user_exits(
# access_key=data.access_key, domain=data.domain
# )
# forgot_key = AuthActions.save_access_token_to_redis(
# request=request, found_user=found_user, domain=data.domain
# )
# forgot_link = ApiStatic.forgot_link(forgot_key=forgot_key)
# send_email_completed = send_email(
# subject=f"Dear {found_user.user_tag}, your forgot password link has been sent.",
# receivers=[str(found_user.email)],
# html=change_your_password_template(
# user_name=found_user.user_tag, forgot_link=forgot_link
# ),
# )
# if not send_email_completed:
# raise HTTPException(
# status_code=400, detail="Email can not be sent. Try again later"
# )
# found_user.password_token = forgot_key
# found_user.password_token_is_valid = str(system_arrow.shift(days=1))
# found_user.save()
# return ResponseHandler.success(
# "Password is change link is sent to your email or phone",
# data={},
# )
return
class AuthenticationResetPasswordEventMethods(MethodToEvent):
event_type = "UPDATE"
__event_keys__ = {
"af9e121e-24bb-44ac-a616-471d5754360e": "authentication_reset_password",
}
@classmethod
def authentication_reset_password(
cls, request: "Request", data: ForgotRequestModel
):
# from sqlalchemy import or_
# found_user = Users.query.filter(
# or_(
# Users.email == str(data.access_key).lower(),
# Users.phone_number == str(data.access_key).replace(" ", ""),
# ),
# ).first()
# if not found_user:
# raise HTTPException(
# status_code=status.HTTP_400_BAD_REQUEST,
# detail="Given access key or domain is not matching with the any user record.",
# )
# reset_password_token = found_user.reset_password_token(found_user=found_user)
# send_email_completed = send_email(
# subject=f"Dear {found_user.user_tag}, a password reset request has been received.",
# receivers=[str(found_user.email)],
# html=change_your_password_template(
# user_name=found_user.user_tag,
# forgot_link=ApiStatic.forgot_link(forgot_key=reset_password_token),
# ),
# )
# if not send_email_completed:
# raise found_user.raise_http_exception(
# status_code=400, message="Email can not be sent. Try again later"
# )
# return ResponseHandler.success(
# "Password change link is sent to your email or phone",
# data=found_user.get_dict(),
# )
return
class AuthenticationDownloadAvatarEventMethods(MethodToEvent):
event_type = "LOGIN"
event_description = "Download avatar icon and profile info of user"
event_category = "AUTHENTICATION"
__event_keys__ = {
"c140cd5f-307f-4046-a93e-3ade032a57a7": "authentication_download_avatar",
}
# __event_validation__ = {
# "c140cd5f-307f-4046-a93e-3ade032a57a7": AuthenticationUserInfoResponse,
# }
@classmethod
def authentication_download_avatar(
cls,
token_dict: TokenDictType,
request: "Request",
data: DictRequestModel,
):
# if found_user := Users.filter_one(Users.id == token_dict.user_id).data:
# expired_starts = str(
# system_arrow.now() - system_arrow.get(str(found_user.expiry_ends))
# )
# expired_int = (
# system_arrow.now() - system_arrow.get(str(found_user.expiry_ends))
# ).days
# user_info = {
# "lang": token_dict.lang,
# "full_name": found_user.person.full_name,
# "avatar": found_user.avatar,
# "remember_me": found_user.remember_me,
# "expiry_ends": str(found_user.expiry_ends),
# "expired_str": expired_starts,
# "expired_int": int(expired_int),
# }
# return ResponseHandler.success(
# "Avatar and profile is shared via user credentials",
# data=user_info,
# )
# 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,20 @@
"""
Route configuration registry.
This module collects and registers all route configurations from different modules
to be used by the dynamic route creation system.
"""
from typing import Dict, List, Any
from .auth.auth import AUTH_CONFIG
# Registry of all route configurations
ROUTE_CONFIGS = [
AUTH_CONFIG,
]
def get_route_configs() -> List[Dict[str, Any]]:
"""Get all registered route configurations."""
return [AUTH_CONFIG]

View File

@@ -0,0 +1,5 @@
"""Event Service API initialization"""
from .route_configs import get_route_configs
__all__ = ["get_route_configs"]

View File

@@ -1,8 +1,19 @@
import typing
from collections.abc import Callable
from fastapi import Request
from typing import Dict, Any
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 ApiValidations.Custom.token_objects import (
OccupantTokenObject,
@@ -21,15 +32,43 @@ from ApiValidations.Request import (
UpdateAccountRecord,
ListOptions,
)
from Services.PostgresDb.Models.alchemy_response import AlchemyJsonResponse
from ApiValidations.Response import AccountRecordResponse
from events.abstract_class import (
MethodToEvent,
RouteFactoryConfig,
EndpointFactoryConfig,
from Services.PostgresDb.Models.alchemy_response import (
AlchemyJsonResponse,
DictJsonResponse,
)
from ApiValidations.Response import AccountRecordResponse
# from events.utils import with_token_event
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
class AccountRecordsListEventMethods(MethodToEvent):
@@ -50,7 +89,7 @@ class AccountRecordsListEventMethods(MethodToEvent):
@classmethod
def account_records_list(
cls,
list_options: ListOptions,
list_options: ListOptionsRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
db_session = AccountRecords.new_session()
@@ -79,7 +118,7 @@ class AccountRecordsListEventMethods(MethodToEvent):
@classmethod
def account_records_list_flt_res(
cls,
list_options: ListOptions,
list_options: ListOptionsRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
db_session = AccountRecords.new_session()
@@ -229,7 +268,7 @@ class AccountRecordsCreateEventMethods(MethodToEvent):
@classmethod
def account_records_create(
cls,
data: InsertAccountRecord,
data: InsertAccountRecordRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
data_dict = data.excluded_dump()
@@ -318,7 +357,7 @@ class AccountRecordsUpdateEventMethods(MethodToEvent):
def build_area_update(
cls,
build_uu_id: str,
data: UpdateAccountRecord,
data: UpdateAccountRecordRequestModel,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
if isinstance(token_dict, OccupantTokenObject):
@@ -336,52 +375,19 @@ class AccountRecordsUpdateEventMethods(MethodToEvent):
)
class AccountRecordsPatchEventMethods(MethodToEvent):
event_type = "PATCH"
event_description = ""
event_category = ""
__event_keys__ = {
"34c38937-42a2-45f1-b2ef-a23978650aee": "account_records_patch",
}
__event_validation__ = {
"34c38937-42a2-45f1-b2ef-a23978650aee": None,
}
@classmethod
def build_area_patch(
cls,
build_uu_id: str,
data,
token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject],
):
account_record = AccountRecords.patch_one(build_uu_id, data).data
return AlchemyJsonResponse(
completed=True,
message="Account record patched successfully",
result=account_record,
)
def address_list(request: Request, data: dict) -> Dict[str, Any]:
@endpoint_wrapper("/account/records/address/list")
async def address_list(request: "Request", data: ListOptionsRequestModel):
"""Handle address list endpoint."""
# Access context through the handler
handler = address_list.handler
handler_context = address_list.handler.context
function_name = AccountRecordsListEventMethods.__event_keys__.get(handler.function_code)
original_function = getattr(AccountRecordsListEventMethods, function_name)
# original_function(data, request)
return {
"data": data,
"function_code": handler.function_code, # This will be the URL
"token_dict": handler_context.get('token_dict'),
"url_of_endpoint": handler_context.get('url_of_endpoint'),
"request": str(request.headers),
"request_url": str(request.url),
"request_base_url": str(request.base_url),
}
def address_create(request: Request, data: dict):
@endpoint_wrapper("/account/records/address/create")
async def address_create(request: "Request", data: DictRequestModel):
"""Handle address creation endpoint."""
return {
"data": data,
@@ -390,80 +396,100 @@ def address_create(request: Request, data: dict):
"request_base_url": str(request.base_url),
}
def address_search(request: Request, data: dict):
@endpoint_wrapper("/account/records/address/search")
async def address_search(request: "Request", data: DictRequestModel):
"""Handle address search endpoint."""
# Get function_code from the wrapper's closure
function_code = address_search.function_code
return {
"data": data,
"function_code": function_code
}
return {"data": data}
def address_update(request: Request, address_uu_id: str, data: dict):
"""Handle address update endpoint."""
# Get function_code from the wrapper's closure
function_code = address_update.function_code
return {
"address_uu_id": address_uu_id,
"data": data,
"function_code": function_code
}
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='/account/records',
tags=['Account Records'],
name="account_records",
prefix=prefix,
tags=["Account Records"],
include_in_schema=True,
endpoints=[
EndpointFactoryConfig(
url_prefix = "/account/records",
url_prefix=prefix,
url_endpoint="/address/list",
url_of_endpoint = "/account/records/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
endpoint_function=address_list,
),
EndpointFactoryConfig(
url_prefix = "/account/records",
url_prefix=prefix,
url_endpoint="/address/create",
url_of_endpoint = "/account/records/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
endpoint_function=address_create,
),
EndpointFactoryConfig(
url_prefix = "/account/records",
url_prefix=prefix,
url_endpoint="/address/search",
url_of_endpoint = "/account/records/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
endpoint_function=address_search,
),
EndpointFactoryConfig(
url_prefix = "/account/records",
url_endpoint="/address/update/{address_uu_id}",
url_of_endpoint="/account/records/address/update/{address_uu_id}",
endpoint="/address/update/{address_uu_id}",
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
)
]
endpoint_function=address_update,
),
],
).as_dict()

View File

@@ -1,12 +1,12 @@
"""
Route configuration registry.
Route configuration registry for Event Service.
This module collects and registers all route configurations from different modules
to be used by the dynamic route creation system.
"""
from typing import Dict, List, Any
from events.account.account_records import ACCOUNT_RECORDS_CONFIG
from .account.account_records import ACCOUNT_RECORDS_CONFIG
# Registry of all route configurations
@@ -14,6 +14,7 @@ ROUTE_CONFIGS = [
ACCOUNT_RECORDS_CONFIG,
]
def get_route_configs() -> List[Dict[str, Any]]:
"""Get all registered route configurations."""
return ROUTE_CONFIGS
return [ACCOUNT_RECORDS_CONFIG]

View File

@@ -0,0 +1,5 @@
"""Event Service API initialization"""
from .route_configs import get_route_configs
__all__ = ["get_route_configs"]

View File

@@ -0,0 +1,17 @@
"""
Route configuration registry for Event Service.
This module collects and registers all route configurations from different modules
to be used by the dynamic route creation system.
"""
from typing import Dict, List, Any
# Registry of all route configurations
ROUTE_CONFIGS = []
def get_route_configs() -> List[Dict[str, Any]]:
"""Get all registered route configurations."""
return []

View File

@@ -0,0 +1,21 @@
"""ApiEvents package initialization.
This module serves as the main entry point for the ApiEvents package,
making common utilities and base classes available for all API services.
"""
from .abstract_class import (
MethodToEvent,
RouteFactoryConfig,
EndpointFactoryConfig,
)
from .base_request_model import BaseRequestModel, DictRequestModel
# Re-export commonly used classes
__all__ = [
"MethodToEvent",
"RouteFactoryConfig",
"EndpointFactoryConfig",
"BaseRequestModel",
"DictRequestModel",
]

View File

@@ -5,12 +5,66 @@ This module provides core abstractions for route configuration and factory,
with support for authentication and event handling.
"""
from typing import Optional, Dict, Any, List, Type, Union, ClassVar, Tuple, TypeVar, Callable
from typing import (
Tuple,
TypeVar,
Optional,
Callable,
Dict,
Any,
List,
Type,
ClassVar,
Union,
Awaitable,
)
from dataclasses import dataclass, field
from pydantic import BaseModel
from fastapi import Request, Depends, APIRouter
from functools import wraps
import inspect
ResponseModel = TypeVar('ResponseModel', bound=BaseModel)
ResponseModel = TypeVar("ResponseModel", bound=BaseModel)
def endpoint_wrapper(url_of_endpoint: Optional[str] = None):
"""Create a wrapper for endpoints that stores url_of_endpoint in closure.
Args:
url_of_endpoint: Optional URL path for the endpoint
"""
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
@wraps(func)
async def wrapper(
*args: Any, **kwargs: Any
) -> Union[Dict[str, Any], BaseModel]:
# Handle both async and sync functions
if inspect.iscoroutinefunction(func):
result = await func(*args, **kwargs)
else:
result = func(*args, **kwargs)
# If result is a coroutine, await it
if inspect.iscoroutine(result):
result = await result
# Add function_code to the result
if isinstance(result, dict):
result["function_code"] = url_of_endpoint
return result
elif isinstance(result, BaseModel):
# Convert Pydantic model to dict and add function_code
result_dict = result.model_dump()
result_dict["function_code"] = url_of_endpoint
return result_dict
return result
wrapper.url_of_endpoint = url_of_endpoint
return wrapper
return decorator
@dataclass
@@ -25,40 +79,53 @@ class EndpointFactoryConfig:
description: Detailed description for API documentation
endpoint_function: Function to handle the endpoint
is_auth_required: Whether authentication is required
response_model: Optional response model for OpenAPI schema
request_model: Optional request model for OpenAPI schema
is_event_required: Whether event handling is required
extra_options: Additional endpoint options
"""
url_prefix :str
url_prefix: str
url_endpoint: str
url_of_endpoint: str
endpoint: str
method: str
summary: str
description: str
endpoint_function: Callable
endpoint_function: Callable[..., Any] # Now accepts any parameters and return type
response_model: Optional[type] = None
request_model: Optional[type] = None
is_auth_required: bool = True
is_event_required: bool = False
extra_options: Dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
"""Post-initialization processing.
Apply appropriate wrappers based on auth and event requirements:
- If both auth and event required -> wrap with with_token_event
"""Post initialization hook.
Wraps endpoint function with appropriate middleware based on configuration:
- If auth and event required -> wrap with TokenEventMiddleware
- If only event required -> wrap with EventMiddleware
- If only auth required -> wrap with MiddlewareModule.auth_required
"""
# Store url_of_endpoint for the handler
self.endpoint_function.url_of_endpoint = self.url_of_endpoint
# Wrap the endpoint function to store url_of_endpoint
self.endpoint_function = endpoint_wrapper(self.url_of_endpoint)(
self.endpoint_function
)
if self.is_auth_required and self.is_event_required:
from events.utils import with_token_event
self.endpoint_function = with_token_event(self.endpoint_function)
from middleware import TokenEventMiddleware
self.endpoint_function = TokenEventMiddleware.event_required(
self.endpoint_function
)
elif self.is_auth_required:
from DockerApiServices.AllApiNeeds.middleware.auth_middleware import MiddlewareModule
self.endpoint_function = MiddlewareModule.auth_required(self.endpoint_function)
from middleware import MiddlewareModule
self.endpoint_function = MiddlewareModule.auth_required(
self.endpoint_function
)
@dataclass
class RouteFactoryConfig:
"""Configuration class for API route factories.
@@ -70,17 +137,29 @@ class RouteFactoryConfig:
endpoints: List of endpoint configurations
extra_options: Additional route options
"""
name: str
tags: Union[str, List[str]]
prefix: str
include_in_schema: bool = True
endpoints: List[EndpointFactoryConfig] = field(default_factory=list)
extra_options: Dict[str, Any] = field(default_factory=dict)
def __init__(
self,
name: str,
tags: List[str],
prefix: str,
include_in_schema: bool = True,
endpoints: List[EndpointFactoryConfig] = None,
extra_options: Dict[str, Any] = None,
):
self.name = name
self.tags = tags
self.prefix = prefix
self.include_in_schema = include_in_schema
self.endpoints = endpoints or []
self.extra_options = extra_options or {}
def __post_init__(self):
"""Validate and normalize configuration after initialization."""
if isinstance(self.tags, str):
self.tags = [self.tags]
if self.endpoints is None:
self.endpoints = []
if self.extra_options is None:
self.extra_options = {}
def as_dict(self) -> Dict[str, Any]:
"""Convert configuration to dictionary format."""
@@ -89,20 +168,8 @@ class RouteFactoryConfig:
"tags": self.tags,
"prefix": self.prefix,
"include_in_schema": self.include_in_schema,
"endpoints": [
{
"endpoint": ep.endpoint,
"method": ep.method,
"summary": ep.summary,
"description": ep.description,
"endpoint_function": ep.endpoint_function,
"is_auth_required": ep.is_auth_required,
"is_event_required": ep.is_event_required,
"extra_options": ep.extra_options
}
for ep in self.endpoints
],
"extra_options": self.extra_options
"endpoints": [endpoint.__dict__ for endpoint in self.endpoints],
"extra_options": self.extra_options,
}
@@ -112,6 +179,7 @@ class ActionsSchema:
This class handles endpoint registration and validation in the database.
Subclasses should implement specific validation logic.
"""
def __init__(self, endpoint: str):
"""Initialize with an API endpoint path.
@@ -129,7 +197,9 @@ class ActionsSchema:
Raises:
HTTPException: If endpoint is not found in database
"""
raise NotImplementedError("Subclasses must implement retrieve_action_from_endpoint")
raise NotImplementedError(
"Subclasses must implement retrieve_action_from_endpoint"
)
class ActionsSchemaFactory:
@@ -138,6 +208,7 @@ class ActionsSchemaFactory:
This class ensures proper initialization and validation of API endpoints
through their action schemas.
"""
def __init__(self, action: ActionsSchema):
"""Initialize with an action schema.
@@ -161,12 +232,15 @@ class MethodToEvent:
TokenType: Type of authentication token
ResponseModel: Type of response model
"""
action_key: ClassVar[Optional[str]] = None
event_type: ClassVar[Optional[str]] = None
event_description: ClassVar[str] = ""
event_category: ClassVar[str] = ""
__event_keys__: ClassVar[Dict[str, str]] = {}
__event_validation__: ClassVar[List[Tuple[Type[ResponseModel], List[Dict[str, Any]]]]] = []
__event_validation__: ClassVar[Dict[str, Tuple[Type[ResponseModel], List[Any]]]] = (
{}
)
@classmethod
def retrieve_language_parameters(
@@ -184,9 +258,9 @@ class MethodToEvent:
validation_dict = dict(cls.__event_validation__)
if function_code not in validation_dict:
return {}
event_response_model, event_language_models = validation_dict[function_code]
# Collect language-specific field mappings
language_models = {}
for model in event_language_models:

View File

@@ -0,0 +1,123 @@
"""
Base request models for API endpoints.
This module provides base request models that can be used across different endpoints
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
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.
"""
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": {
"key1": "value1",
"key2": "value2",
"nested": {"inner_key": "inner_value"},
}
}
)
@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(
...,
example={
"id": "123",
"username": "john.doe",
"email": "john@example.com",
"role": "user",
},
)
model_config = ConfigDict(
json_schema_extra={
"example": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user_info": {
"id": "123",
"username": "john.doe",
"email": "john@example.com",
"role": "user",
},
}
}
)

View File

@@ -1,104 +0,0 @@
"""
Utility functions for API event handling.
"""
from typing import TypeVar, Callable, Dict, Any
from functools import wraps
from fastapi import Request
R = TypeVar('R')
class BaseEndpointHandler:
"""Base class for handling endpoint execution with context."""
def __init__(self, func: Callable, url_of_endpoint: str):
self.func = func
self.url_of_endpoint = url_of_endpoint
self.function_code = url_of_endpoint # Set initial function_code
self._context = {
'url_of_endpoint': url_of_endpoint,
'function_code': url_of_endpoint, # Initialize with URL
}
@property
def context(self) -> dict:
"""Get the endpoint context."""
return self._context
def update_context(self, **kwargs):
"""Update the endpoint context with new values."""
self._context.update(kwargs)
# Update function_code property if it's in the context
if 'function_code' in kwargs:
self.function_code = kwargs['function_code']
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
class TokenEventHandler(BaseEndpointHandler):
"""Handler for endpoints that require token and event tracking."""
def __init__(self, func: Callable, url_of_endpoint: str):
super().__init__(func, url_of_endpoint)
self.update_context(
token_dict={
'user_id': '1234567890',
'username': 'test_user',
'email': 'asda@email.com',
}
)
class AuthHandler(BaseEndpointHandler):
"""Handler for endpoints that require only authentication."""
def __init__(self, func: Callable, url_of_endpoint: str):
super().__init__(func, url_of_endpoint)
self.update_context(
auth_level="user",
permissions=["read", "write"]
)
def with_token_event(func: Callable[..., Dict[str, Any]]) -> Callable[..., Dict[str, Any]]:
"""Decorator for endpoints with token and event requirements."""
@wraps(func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
# Create handler with context
handler = TokenEventHandler(
func=func,
url_of_endpoint=func.url_of_endpoint
)
# Update event-specific context
handler.update_context(
function_code=f"7192c2aa-5352-4e36-98b3-dafb7d036a3d" # Keep function_code as URL
)
# Make context available to the function
func.handler = handler
# Call the original function
return func(*args, **kwargs)
return wrapper
def auth_required(func: Callable[..., Dict[str, Any]]) -> Callable[..., Dict[str, Any]]:
"""Decorator for endpoints with only auth requirements."""
@wraps(func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
# Create handler with context
handler = AuthHandler(
func=func,
url_of_endpoint=func.url_of_endpoint
)
# Make context available to the function
func.handler = handler
# Call the original function
return func(*args, **kwargs)
return wrapper