diff --git a/ApiLayers/AllConfigs/Redis/configs.py b/ApiLayers/AllConfigs/Redis/configs.py index 1303782..517ae84 100644 --- a/ApiLayers/AllConfigs/Redis/configs.py +++ b/ApiLayers/AllConfigs/Redis/configs.py @@ -17,6 +17,12 @@ class WagRedis: ) +class RedisAuthKeys: + AUTH: str = "AUTH" + OCCUPANT: str = "OCCUPANT" + EMPLOYEE: str = "EMPLOYEE" + + class RedisCategoryKeys: REBUILD: str = "REBUILD" ENDPOINT2CLASS: str = "ENDPOINT2CLASS" @@ -28,6 +34,3 @@ class RedisCategoryKeys: MENU_FIRST_LAYER: str = "MENU_FIRST_LAYER" PAGE_MAPPER: str = "PAGE_MAPPER" MENU_MAPPER: str = "MENU_MAPPER" - AUTH: str = "AUTH" - OCCUPANT: str = "OCCUPANT" - EMPLOYEE: str = "EMPLOYEE" \ No newline at end of file diff --git a/ApiLayers/ApiServices/Cluster/create_router.py b/ApiLayers/ApiServices/Cluster/create_router.py index f59dd5f..55b83a0 100644 --- a/ApiLayers/ApiServices/Cluster/create_router.py +++ b/ApiLayers/ApiServices/Cluster/create_router.py @@ -16,23 +16,17 @@ class CreateEndpointFromCluster: def __init__(self, **kwargs): self.router: CategoryCluster = kwargs.get("router") self.method_endpoint: MethodToEvent = kwargs.get("method_endpoint") - self.unique_id = str(uuid.uuid4())[:8] # Use first 8 chars of UUID for brevity self.attach_router() def attach_router(self): method = getattr(self.router, self.method_endpoint.METHOD.lower()) # Create a unique operation ID based on the endpoint path, method, and a unique identifier - base_path = self.method_endpoint.URL.strip('/').replace('/', '_').replace('-', '_') - operation_id = f"{base_path}_{self.method_endpoint.METHOD.lower()}_{self.unique_id}" - kwargs = { "path": self.method_endpoint.URL, "summary": self.method_endpoint.SUMMARY, "description": self.method_endpoint.DESCRIPTION, - "operation_id": operation_id } - if hasattr(self.method_endpoint, 'RESPONSE_MODEL') and self.method_endpoint.RESPONSE_MODEL is not None: kwargs["response_model"] = self.method_endpoint.RESPONSE_MODEL diff --git a/ApiLayers/ApiServices/Token/token_handler.py b/ApiLayers/ApiServices/Token/token_handler.py index 7f8538f..5b559e8 100644 --- a/ApiLayers/ApiServices/Token/token_handler.py +++ b/ApiLayers/ApiServices/Token/token_handler.py @@ -1,6 +1,6 @@ """Token service for handling authentication tokens and user sessions.""" -from typing import List, Union, TypeVar, Dict, Any, Optional, TYPE_CHECKING +from typing import List, Union, TypeVar, Dict, Any, TYPE_CHECKING from ApiLayers.AllConfigs.Token.config import Auth from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error diff --git a/ApiLayers/ApiValidations/Custom/wrapper_contexts.py b/ApiLayers/ApiValidations/Custom/wrapper_contexts.py new file mode 100644 index 0000000..b5164a8 --- /dev/null +++ b/ApiLayers/ApiValidations/Custom/wrapper_contexts.py @@ -0,0 +1,18 @@ +from typing import Optional, Any +from pydantic import BaseModel + + +class DefaultContext(BaseModel): + ... + + +class EventContext(DefaultContext): + + auth: Any + code: str + url: str + + +class AuthContext(DefaultContext): + auth: Any + url: str diff --git a/ApiLayers/Middleware/auth_middleware.py b/ApiLayers/Middleware/auth_middleware.py index 51b62a0..09ca3ba 100644 --- a/ApiLayers/Middleware/auth_middleware.py +++ b/ApiLayers/Middleware/auth_middleware.py @@ -15,6 +15,7 @@ from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error +from ApiLayers.ApiValidations.Custom.wrapper_contexts import AuthContext from ApiLayers.ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi from ApiLayers.AllConfigs.Token.config import Auth from ApiLayers.ApiServices.Token.token_handler import TokenService @@ -87,20 +88,25 @@ class MiddlewareModule: @wraps(func) async def wrapper(request: Request, *args, **kwargs): # Get and validate token context from request - auth_context = { - "is_employee": False, - "is_occupant": False, - "context": {} - } - + endpoint_url = str(request.url.path) + auth_context = AuthContext( + auth={"token": {"access_token": "", "refresher_token": "", "context": {}}}, + url=endpoint_url, + ) + # Set auth context on the wrapper function itself - setattr(wrapper, 'auth', auth_context) - + setattr(func, 'auth_context', auth_context) + setattr(wrapper, 'auth_context', auth_context) + # Call the original endpoint function if inspect.iscoroutinefunction(func): result = await func(request, *args, **kwargs) else: result = func(request, *args, **kwargs) + + # Set auth context on the wrapper function itself + setattr(func, 'auth_context', auth_context) + setattr(wrapper, 'auth_context', auth_context) return result return wrapper diff --git a/ApiLayers/Middleware/token_event_middleware.py b/ApiLayers/Middleware/token_event_middleware.py index 1129762..4a6b84d 100644 --- a/ApiLayers/Middleware/token_event_middleware.py +++ b/ApiLayers/Middleware/token_event_middleware.py @@ -5,110 +5,22 @@ Token event middleware for handling authentication and event tracking. import inspect from functools import wraps -from typing import Callable, Dict, Any, Optional, Union +from typing import Callable, Dict, Any, Optional, Tuple, Union from fastapi import Request from pydantic import BaseModel from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error from ApiLayers.ApiServices.Token.token_handler import TokenService +from ApiLayers.ApiValidations.Custom.wrapper_contexts import EventContext from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi from ApiLayers.Schemas import Events, EndpointRestriction +from ApiLayers.AllConfigs.Redis.configs import RedisCategoryKeys + +from Services.Redis.Actions.actions import RedisActions from .auth_middleware import MiddlewareModule -class EventFunctions: - - def __init__(self, endpoint: str, request: Request): - self.endpoint = endpoint - self.request = request - - - - def retrieve_function_dict(self) -> Optional[Dict[str, Any]]: - """ - Retrieve function dictionary for a given endpoint. - - Args: - endpoint: The endpoint to retrieve the function dictionary for - - Returns: - Dictionary containing the function dictionary - None if endpoint is not found - """ - access_token = TokenService.get_access_token_from_request(self.request) - token_context = TokenService.get_object_via_access_key( - access_token=access_token - ) - if token_context.is_employee: - reachable_event_codes: list[str] = ( - token_context.selected_company.reachable_event_codes - ) - elif token_context.is_occupant: - reachable_event_codes: list[str] = ( - token_context.selected_occupant.reachable_event_codes - ) - else: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Token not found", - ) - - if not access_token: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Token not found", - ) - - db = EndpointRestriction.new_session() - restriction = EndpointRestriction.filter_one( - EndpointRestriction.endpoint_name == self.endpoint, - db=db, - ).data - if not restriction: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Function code not found", - ) - - event_related = Events.filter_all( - Events.endpoint_id == restriction.id, - db=db, - ).data - if not event_related: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="No event is registered for this user.", - ) - an_event = event_related[0] - event_related_codes: list[str] = [ - event.function_code for event in event_related - ] - intersected_code: set = set(reachable_event_codes).intersection( - set(event_related_codes) - ) - if not len(list(intersected_code)) == 1: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="No event is registered for this user.", - ) - return { - "endpoint_url": self.endpoint, - "reachable_event_code": list(intersected_code)[0], - "class": an_event.function_class, - } - - class TokenEventMiddleware: """ Module containing token and event handling functionality. @@ -119,37 +31,18 @@ class TokenEventMiddleware: """ @staticmethod - def match_endpoint_with_accessible_event(request_from_scope, endpoint_from_scope) -> Optional[Dict[str, Any]]: + def retrieve_access_content(request_from_scope: Request) -> Tuple[str, list[str]]: """ - Match an endpoint with accessible events. + Retrieves the access token and validates it. Args: - request_from_scope: The endpoint to match + request_from_scope: The FastAPI request object Returns: - Dict containing the endpoint registration data - None if endpoint is not found in database + Tuple[str, list[str]]: The access token and a list of reachable event codes """ + # Get token context from request access_token = TokenService.get_access_token_from_request(request_from_scope) - token_context = TokenService.get_object_via_access_key( - access_token=access_token - ) - if token_context.is_employee: - reachable_event_codes: list[str] = ( - token_context.selected_company.reachable_event_codes - ) - elif token_context.is_occupant: - reachable_event_codes: list[str] = ( - token_context.selected_occupant.reachable_event_codes - ) - else: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Token not found", - ) - if not access_token: raise HTTPExceptionApi( error_code="", @@ -157,50 +50,60 @@ class TokenEventMiddleware: loc=get_line_number_for_error(), sys_msg="Token not found", ) + + # Get token context from Redis by access token and collect reachable event codes + token_context = TokenService.get_object_via_access_key(access_token=access_token) + if token_context.is_employee: + reachable_event_codes: list[str] = token_context.selected_company.reachable_event_codes + elif token_context.is_occupant: + reachable_event_codes: list[str] = token_context.selected_occupant.reachable_event_codes + else: + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Token not found", + ) + return token_context, reachable_event_codes - db = EndpointRestriction.new_session() - restriction = EndpointRestriction.filter_one( - EndpointRestriction.endpoint_name == endpoint_from_scope, - db=db, - ).data - if not restriction: + @staticmethod + def retrieve_intersected_event_code(request: Request, reachable_event_codes: list[str]) -> str: + """ + Match an endpoint with accessible events. + + Args: + request: The endpoint to match + + Returns: + Dict containing the endpoint registration data + None if endpoint is not found in database + """ + endpoint_url = str(request.url.path) + # Get the endpoint URL for matching with events + function_codes_of_endpoint = RedisActions.get_json( + list_keys=[ + RedisCategoryKeys.METHOD_FUNCTION_CODES, "*", endpoint_url + ] + ) + function_code_list_of_event = function_codes_of_endpoint.first + if not function_codes_of_endpoint.status: raise HTTPExceptionApi( error_code="", lang="en", loc=get_line_number_for_error(), sys_msg="Function code not found", ) - - event_related = Events.filter_all( - Events.endpoint_id == restriction.id, - db=db, - ).data - if not event_related: + + # Intersect function codes with user accers objects available event codes + intersected_code = list(set(function_code_list_of_event) & set(reachable_event_codes)) + if not len(intersected_code) == 1: raise HTTPExceptionApi( error_code="", lang="en", loc=get_line_number_for_error(), sys_msg="No event is registered for this user.", ) - an_event = event_related[0] - event_related_codes: list[str] = [ - event.function_code for event in event_related - ] - intersected_code: set = set(reachable_event_codes).intersection( - set(event_related_codes) - ) - if not len(list(intersected_code)) == 1: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="No event is registered for this user.", - ) - return { - "endpoint_url": endpoint_from_scope, - "reachable_event_code": list(intersected_code)[0], - "class": an_event.function_class, - } + return endpoint_url, intersected_code[0] @classmethod def event_required(cls, func: Callable) -> Callable: @@ -216,88 +119,22 @@ class TokenEventMiddleware: Returns: Callable: The wrapped function with both auth and event handling """ - # First apply authentication - authenticated_func = MiddlewareModule.auth_required(func) - @wraps(authenticated_func) + @wraps(func) async def wrapper(request: Request, *args, **kwargs) -> Dict[str, Any]: - # Get the endpoint URL for matching with events - endpoint_url = str(request.url.path) - - # Set func_code first - func_code = "8aytr-" - setattr(wrapper, 'func_code', func_code) + # Get and validate token context from request + # token_context, reachable_event_codes = cls.retrieve_access_content(request) + token_context, reachable_event_codes = {"token": "context", "context": {}}, ["g1j8i6j7-9k4h-0h6l-4i3j-2j0k1k0j0i0k"] + endpoint_url, reachable_event_code = cls.retrieve_intersected_event_code(request, reachable_event_codes) + event_context = EventContext(auth=token_context, code=reachable_event_code, url=endpoint_url) + # Get auth context from the authenticated function's wrapper - auth_context = getattr(authenticated_func, 'auth', None) - print('auth_context', auth_context) - if auth_context is not None: - setattr(wrapper, 'auth', auth_context) + if token_context is not None: + setattr(wrapper, 'event_context', event_context) + setattr(func, 'event_context', event_context) # Execute the authenticated function and get its result - if inspect.iscoroutinefunction(authenticated_func): - result = await authenticated_func(request, *args, **kwargs) - else: - result = authenticated_func(request, *args, **kwargs) - - return result - - # Copy any existing attributes from the authenticated function - for attr in dir(authenticated_func): - if not attr.startswith('__'): - setattr(wrapper, attr, getattr(authenticated_func, attr)) - - return wrapper - - @staticmethod - def validation_required( - func: Callable[..., Dict[str, Any]] - ) -> Callable[..., Dict[str, Any]]: - """ - Decorator for endpoints with token and event requirements. - This decorator: - 1. First validates authentication using MiddlewareModule.auth_required - 2. Then adds event tracking context - - Args: - func: The function to be decorated - - Returns: - Callable: The wrapped function with both auth and event handling - """ - # First apply authentication - # authenticated_func = MiddlewareModule.auth_required(func) - authenticated_func = func - @wraps(authenticated_func) - async def wrapper( - request: Request, *args: Any, **kwargs: Any - ) -> Union[Dict[str, Any], BaseModel]: - # Handle both async and sync functions - endpoint_asked = getattr(kwargs.get("data", None), "data", None).get( - "endpoint", None - ) - if not endpoint_asked: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Endpoint not found", - ) - func.func_code = cls.match_endpoint_with_accessible_event( - endpoint_url, request - ) - if inspect.iscoroutinefunction(authenticated_func): - result = await authenticated_func(request, *args, **kwargs) - else: - result = authenticated_func(request, *args, **kwargs) - function_auth = getattr(authenticated_func, "auth", None) - wrapper.auth = function_auth - func.auth = function_auth - authenticated_func.auth = function_auth - if inspect.iscoroutinefunction(authenticated_func): - result = await authenticated_func(request, *args, **kwargs) - else: - result = authenticated_func(request, *args, **kwargs) if inspect.iscoroutinefunction(func): result = await func(request, *args, **kwargs) else: @@ -305,3 +142,41 @@ class TokenEventMiddleware: return result return wrapper + + # event_required is already sets function_code state to wrapper + # @classmethod + # def validation_required(cls, func: Callable[..., Dict[str, Any]]) -> Callable[..., Dict[str, Any]]: + # """ + # Decorator for endpoints with token and event requirements. + # This decorator: + # 1. First validates authentication using MiddlewareModule.auth_required + # 2. Then adds event tracking context + + # Args: + # func: The function to be decorated + + # Returns: + # Callable: The wrapped function with both auth and event handling + # """ + + # @wraps(func) + # async def wrapper(request: Request, *args: Any, **kwargs: Any) -> Union[Dict[str, Any], BaseModel]: + + # # Get and validate token context from request + # token_context, reachable_event_codes = cls.retrieve_access_content(request) + # endpoint_url, reachable_event_code = cls.retrieve_intersected_event_code(request, reachable_event_codes) + + # # Get auth context from the authenticated function's wrapper + # if token_context is not None: + # setattr(wrapper, 'auth', token_context) + # setattr(wrapper, 'url', endpoint_url) + # setattr(wrapper, 'func_code', reachable_event_code) + + # # Execute the authenticated function and get its result + # if inspect.iscoroutinefunction(func): + # result = await func(request, *args, **kwargs) + # else: + # result = func(request, *args, **kwargs) + # return result + + # return wrapper \ No newline at end of file diff --git a/DockerApiServices/AuthServiceApi/create_routes.py b/DockerApiServices/AuthServiceApi/create_routes.py index 62c276e..a47da24 100644 --- a/DockerApiServices/AuthServiceApi/create_routes.py +++ b/DockerApiServices/AuthServiceApi/create_routes.py @@ -4,7 +4,10 @@ Handles dynamic route creation based on configurations. """ from fastapi import Request -from Events.Engine.set_defaults.setClusters import PrepareRouting +from Events.Engine.set_defaults.run import get_cluster_controller_group +from Events.Engine.set_defaults.setClusters import PrepareRouting, SetItems2Redis, PrepareEvents + +routers = None async def health_check(request: Request): @@ -24,8 +27,11 @@ def get_all_routers() -> PrepareRouting: Returns: tuple: (routers, protected_routes) """ - from Events.Engine.set_defaults.run import get_cluster_controller_group - - cluster_list = get_cluster_controller_group() - prepare_routing = PrepareRouting(cluster_controller_group=cluster_list) - return prepare_routing + global routers + if not routers: + cluster_list = get_cluster_controller_group() + prepare_routing = PrepareRouting(cluster_controller_group=cluster_list) + prepare_events = PrepareEvents(cluster_controller_group=cluster_list) + set_items_2_redis = SetItems2Redis(prepare_events=prepare_events) + return prepare_routing + return routers diff --git a/DockerApiServices/EventServiceApi/create_routes.py b/DockerApiServices/EventServiceApi/create_routes.py index 8006964..a47da24 100644 --- a/DockerApiServices/EventServiceApi/create_routes.py +++ b/DockerApiServices/EventServiceApi/create_routes.py @@ -4,7 +4,10 @@ Handles dynamic route creation based on configurations. """ from fastapi import Request -from Events.Engine.set_defaults.setClusters import PrepareRouting +from Events.Engine.set_defaults.run import get_cluster_controller_group +from Events.Engine.set_defaults.setClusters import PrepareRouting, SetItems2Redis, PrepareEvents + +routers = None async def health_check(request: Request): @@ -24,10 +27,11 @@ def get_all_routers() -> PrepareRouting: Returns: tuple: (routers, protected_routes) """ - from Events.Engine.set_defaults.run import get_cluster_controller_group - - cluster_list = get_cluster_controller_group() - prepare_routing = PrepareRouting(cluster_controller_group=cluster_list) - return prepare_routing - - + global routers + if not routers: + cluster_list = get_cluster_controller_group() + prepare_routing = PrepareRouting(cluster_controller_group=cluster_list) + prepare_events = PrepareEvents(cluster_controller_group=cluster_list) + set_items_2_redis = SetItems2Redis(prepare_events=prepare_events) + return prepare_routing + return routers diff --git a/Events/AllEvents/authentication/auth/auth.py b/Events/AllEvents/authentication/auth/auth.py index 0e8f680..5ec1a05 100644 --- a/Events/AllEvents/authentication/auth/auth.py +++ b/Events/AllEvents/authentication/auth/auth.py @@ -4,6 +4,7 @@ Authentication related API endpoints. from typing import Union, Any, Dict +from ApiLayers.ApiValidations.Custom.wrapper_contexts import AuthContext, EventContext from ApiLayers.Middleware import MiddlewareModule, TokenEventMiddleware from ApiLayers.ApiValidations.Request import EmployeeSelection, OccupantSelection @@ -39,7 +40,7 @@ AuthenticationLoginEventMethods = MethodToEvent( }, headers=[], errors=[], - url="/authentication/login", + url="/login", method="POST", summary="Login via domain and access key : [email] | [phone]", description="Login to the system via domain, access key : [email] | [phone]", @@ -49,9 +50,7 @@ def authentication_login_with_domain_and_creds( request: Request, data: EndpointBaseRequestModel, ) -> Dict[str, Any]: - function = AuthenticationLoginEventMethods.retrieve_event( - event_function_code=f"{authentication_login_super_user_event.key}" - ) + function = AuthenticationLoginEventMethods.retrieve_event(event_function_code=f"{authentication_login_super_user_event.key}") return function.endpoint_callable(request=request, data=data) @@ -65,7 +64,7 @@ AuthenticationSelectEventMethods = MethodToEvent( decorators_list=[MiddlewareModule.auth_required], headers=[], errors=[], - url="/authentication/select", + url="/select", method="POST", summary="Select company or occupant type", description="Select company or occupant type", @@ -95,7 +94,7 @@ AuthenticationCheckTokenEventMethods = MethodToEvent( headers=[], errors=[], decorators_list=[MiddlewareModule.auth_required], - url="/authentication/check-token", + url="/check-token", method="POST", summary="Check if token is valid", description="Check if access token is valid for user", @@ -118,7 +117,7 @@ AuthenticationRefreshEventMethods = MethodToEvent( headers=[], errors=[], decorators_list=[MiddlewareModule.auth_required], - url="/authentication/refresh", + url="/refresh", method="POST", summary="Refresh user info", description="Refresh user info using access token", @@ -143,7 +142,7 @@ AuthenticationChangePasswordEventMethods = MethodToEvent( headers=[], errors=[], decorators_list=[MiddlewareModule.auth_required], - url="/authentication/change-password", + url="/change-password", method="POST", summary="Change password", description="Change password with access token", @@ -166,7 +165,7 @@ AuthenticationCreatePasswordEventMethods = MethodToEvent( }, headers=[], errors=[], - url="/authentication/create-password", + url="/create-password", method="POST", summary="Create password", description="Create password with password reset token requested via email", @@ -189,7 +188,7 @@ AuthenticationDisconnectUserEventMethods = MethodToEvent( decorators_list=[MiddlewareModule.auth_required], headers=[], errors=[], - url="/authentication/disconnect", + url="/disconnect", method="POST", summary="Disconnect all sessions", description="Disconnect all sessions of user in access token", @@ -210,28 +209,25 @@ AuthenticationLogoutEventMethods = MethodToEvent( }, headers=[], errors=[], - url="/authentication/logout", + decorators_list=[TokenEventMiddleware.event_required], + url="/logout", method="POST", summary="Logout user", description="Logout only single session of user which domain is provided", ) -@TokenEventMiddleware.event_required + def authentication_logout_user(request: Request, data: EndpointBaseRequestModel): - function = AuthenticationLogoutEventMethods.retrieve_event( - event_function_code=f"{authentication_logout_user_event.key}" - ) - print('authentication_logout_user', dict( - auth=getattr(authentication_logout_user, "auth", None), - func_code=getattr(authentication_logout_user, "func_code", None), - )) - function.endpoint_callable.auth = getattr(authentication_logout_user, "auth", None) - function.endpoint_callable.func_code = getattr(authentication_logout_user, "func_code", None) + event_context: EventContext = getattr(authentication_logout_user, "event_context", None) + print('event_context', event_context) + function = AuthenticationLogoutEventMethods.retrieve_event(event_function_code=f"{event_context.code}") + function.endpoint_callable.event_context = event_context return function.endpoint_callable(request=request, data=data) AuthenticationLogoutEventMethods.endpoint_callable = authentication_logout_user + AuthenticationRefreshTokenEventMethods = MethodToEvent( name="AuthenticationRefreshTokenEventMethods", events={ @@ -239,19 +235,22 @@ AuthenticationRefreshTokenEventMethods = MethodToEvent( }, headers=[], errors=[], - decorators_list=[MiddlewareModule.auth_required, ], - url="/authentication/refresh-token", + decorators_list=[MiddlewareModule.auth_required], + url="/refresh-token", method="POST", summary="Refresh token", description="Refresh access token with refresher token", ) + def authentication_refresher_token(request: Request, data: EndpointBaseRequestModel): - token_dict = authentication_refresher_token.auth + auth_context: AuthContext = getattr(authentication_refresher_token, "auth_context", None) function = AuthenticationRefreshTokenEventMethods.retrieve_event( event_function_code=f"{authentication_refresher_token_event.key}" ) - return function.endpoint_callable(data=data, request=request, token_dict=token_dict) + function.endpoint_callable.auth_context = auth_context + return function.endpoint_callable(data=data, request=request) + AuthenticationRefreshTokenEventMethods.endpoint_callable = authentication_refresher_token @@ -263,7 +262,7 @@ AuthenticationForgotPasswordEventMethods = MethodToEvent( }, headers=[], errors=[], - url="/authentication/forgot-password", + url="/forgot-password", method="POST", summary="Request password reset", description="Send an email to user for a valid password reset token", @@ -289,7 +288,7 @@ AuthenticationResetPasswordEventMethods = MethodToEvent( headers=[], errors=[], decorators_list=[MiddlewareModule.auth_required], - url="/authentication/reset-password", + url="/reset-password", method="POST", summary="Reset password", description="Reset user password", @@ -314,7 +313,7 @@ AuthenticationDownloadAvatarEventMethods = MethodToEvent( headers=[], errors=[], decorators_list=[], - url="/authentication/download-avatar", + url="/download-avatar", method="POST", summary="Download avatar", description="Download avatar icon and profile info of user", diff --git a/Events/AllEvents/authentication/auth/cluster.py b/Events/AllEvents/authentication/auth/cluster.py index 7846a2a..62082bc 100644 --- a/Events/AllEvents/authentication/auth/cluster.py +++ b/Events/AllEvents/authentication/auth/cluster.py @@ -23,20 +23,20 @@ AuthCluster = CategoryCluster( prefix="/authentication", description="Authentication cluster", pageinfo=authentication_page_info, - endpoints=[ - AuthenticationLoginEventMethods, - AuthenticationLogoutEventMethods, - AuthenticationRefreshTokenEventMethods, - AuthenticationForgotPasswordEventMethods, - AuthenticationChangePasswordEventMethods, - AuthenticationCheckTokenEventMethods, - AuthenticationCreatePasswordEventMethods, - AuthenticationDisconnectUserEventMethods, - AuthenticationDownloadAvatarEventMethods, - AuthenticationResetPasswordEventMethods, - AuthenticationRefreshEventMethods, - AuthenticationSelectEventMethods, - ], + endpoints={ + "AuthenticationLoginEventMethods": AuthenticationLoginEventMethods, + "AuthenticationLogoutEventMethods": AuthenticationLogoutEventMethods, + "AuthenticationRefreshTokenEventMethods": AuthenticationRefreshTokenEventMethods, + "AuthenticationForgotPasswordEventMethods": AuthenticationForgotPasswordEventMethods, + "AuthenticationChangePasswordEventMethods": AuthenticationChangePasswordEventMethods, + "AuthenticationCheckTokenEventMethods": AuthenticationCheckTokenEventMethods, + "AuthenticationCreatePasswordEventMethods": AuthenticationCreatePasswordEventMethods, + "AuthenticationDisconnectUserEventMethods": AuthenticationDisconnectUserEventMethods, + "AuthenticationDownloadAvatarEventMethods": AuthenticationDownloadAvatarEventMethods, + "AuthenticationResetPasswordEventMethods": AuthenticationResetPasswordEventMethods, + "AuthenticationRefreshEventMethods": AuthenticationRefreshEventMethods, + "AuthenticationSelectEventMethods": AuthenticationSelectEventMethods, + }, include_in_schema=True, sub_category=[], ) diff --git a/Events/AllEvents/authentication/auth/function_handlers.py b/Events/AllEvents/authentication/auth/function_handlers.py index 4aa71f9..64e9f3f 100644 --- a/Events/AllEvents/authentication/auth/function_handlers.py +++ b/Events/AllEvents/authentication/auth/function_handlers.py @@ -5,6 +5,7 @@ from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error from ApiLayers.ApiServices.Login.user_login_handler import UserLoginModule from ApiLayers.ApiServices.Token.token_handler import TokenService from ApiLayers.ApiValidations.Custom.token_objects import CompanyToken, OccupantToken +from ApiLayers.ApiValidations.Custom.wrapper_contexts import AuthContext, EventContext from ApiLayers.ErrorHandlers import HTTPExceptionApi from ApiLayers.Schemas import ( BuildLivingSpace, @@ -372,14 +373,12 @@ def authentication_logout_user(request: Request, data: Any): # 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") - token_dict = authentication_logout_user.auth - print('token_dict', token_dict) - func_code = authentication_logout_user.func_code - print('func_code', func_code) - return + + event_context: EventContext = getattr(authentication_logout_user, "event_context", None) + return event_context.model_dump() -def authentication_refresher_token(request: Request, token_dict: TokenDictType, data: Any): +def authentication_refresher_token(request: Request, data: Any): """Refresh access token with refresher token""" # token_refresher = UsersTokens.filter_by_one( # token=data.refresh_token, @@ -402,7 +401,8 @@ def authentication_refresher_token(request: Request, token_dict: TokenDictType, # } # return ResponseHandler.success("User is logged in successfully via refresher token", data=response_data) # return ResponseHandler.not_found("Invalid data") - return + auth_context: AuthContext = getattr(authentication_refresher_token, "auth_context", None) + return auth_context.model_dump() def authentication_forgot_password(request: Request, data: Any): diff --git a/Events/AllEvents/events_file.py b/Events/AllEvents/events_file.py index 106bc8a..e0de49e 100644 --- a/Events/AllEvents/events_file.py +++ b/Events/AllEvents/events_file.py @@ -4,3 +4,10 @@ import Events.AllEvents.validations as validations_events events_list = (auths_events, events_events, validations_events) + + +def retrieve_cluster_by_name(cluster_name: str): + for module in events_list: + if hasattr(module, cluster_name, None): + return getattr(module, cluster_name, None) + diff --git a/Events/Engine/abstract_class.py b/Events/Engine/abstract_class.py index 7ceaf11..b64c39b 100644 --- a/Events/Engine/abstract_class.py +++ b/Events/Engine/abstract_class.py @@ -135,11 +135,11 @@ class MethodToEvent: return found_event raise ValueError(f"Event with function code {event_function_code} not found") - def retrieve_redis_value(self, cluster_name: str) -> Dict: + def retrieve_redis_value(self, cluster: "CategoryCluster") -> Dict: """ Key("METHOD_FUNCTION_CODES:{ClusterToMethod}:MethodEvent:Endpoint") : Value([FUNCTION_CODE, ...]) """ - redis_key = f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}:{cluster_name}:{self.name}:{self.URL}" + redis_key = f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}:{cluster.name}:{self.name}:{f"{cluster.PREFIX}{self.URL}"}" return {redis_key: self.retrieve_all_event_keys()} @staticmethod @@ -156,8 +156,8 @@ class CategoryCluster: PREFIX: str PAGEINFO: PageInfo DESCRIPTION: str - ENDPOINTS: list[MethodToEvent] # [MethodToEvent, ...] - SUBCATEGORY: Optional[List["CategoryCluster"]] # [CategoryCluster, ...] + ENDPOINTS: dict[str, MethodToEvent] # {"MethodToEvent": MethodToEvent, ...} + SUBCATEGORY: Optional[List["CategoryCluster"]] # [CategoryCluster, ...] INCLUDE_IN_SCHEMA: Optional[bool] = True def __init__( @@ -167,7 +167,7 @@ class CategoryCluster: prefix: str, description: str, pageinfo: PageInfo, - endpoints: list[MethodToEvent], + endpoints: dict[str, MethodToEvent], sub_category: list, include_in_schema: Optional[bool] = True, ): @@ -176,7 +176,7 @@ class CategoryCluster: self.PREFIX = prefix self.PAGEINFO = pageinfo self.DESCRIPTION = description - self.ENDPOINTS = endpoints or [] + self.ENDPOINTS = endpoints or {} self.SUBCATEGORY = sub_category or [] self.INCLUDE_IN_SCHEMA = include_in_schema @@ -189,7 +189,7 @@ class CategoryCluster: RedisCategoryKeys.CLUSTER_2_METHOD_EVENT Returns the class name and function codes for the class. """ - dict_cluster_2_method, list_endpoints = {}, [i.name for i in self.ENDPOINTS] + dict_cluster_2_method, list_endpoints = {}, [i.name for i in self.ENDPOINTS.values()] for endpoint_name in list_endpoints: dict_cluster_2_method[endpoint_name] = self.name dict_cluster_2_method[self.name] = list_endpoints @@ -200,7 +200,7 @@ class CategoryCluster: Retrieves all function codes by iterating over the events list. """ all_function_codes = [] - for event_method in self.ENDPOINTS: + for event_method in self.ENDPOINTS.values(): all_function_codes.extend([str(event_key) for event_key in event_method.EVENTS.keys()]) return all_function_codes diff --git a/Events/Engine/set_defaults/run.py b/Events/Engine/set_defaults/run.py index 24f95d9..47092c7 100644 --- a/Events/Engine/set_defaults/run.py +++ b/Events/Engine/set_defaults/run.py @@ -1,4 +1,5 @@ from Events.AllEvents.events_file import events_list + from .category_cluster_models import cluster_controller @@ -7,10 +8,12 @@ def get_cluster_controller_group(): cluster_controller.import_all_category_clusters(cluster) return cluster_controller + + + """ prepare_routing = PrepareRouting(cluster_controller_group=cluster_controller) prepare_events = PrepareEvents(cluster_controller_group=cluster_controller) -set_items_2_redis = SetItems2Redis(prepare_events=prepare_events) print(set_items_2_redis) print(prepare_routing) """ diff --git a/Events/Engine/set_defaults/setClusters.py b/Events/Engine/set_defaults/setClusters.py index 30449b9..4c62311 100644 --- a/Events/Engine/set_defaults/setClusters.py +++ b/Events/Engine/set_defaults/setClusters.py @@ -38,7 +38,7 @@ class PrepareRouting(DecoratorModule): return self.__safe_endpoint_list def create_endpoints(self, cluster: CategoryCluster, created_router): - for method_endpoint in list(cluster.ENDPOINTS): + for method_endpoint in list(cluster.ENDPOINTS.values()): # Filter out the original function and apply decorators applied_decorators_qualname = self.apply_decorators(method_endpoint) # Register the endpoint with FastAPI router @@ -89,17 +89,17 @@ class PrepareEvents(DecoratorModule): f"{self.valid_redis_items.CLUSTER_FUNCTION_CODES_KEY}:{cluster.name}" : tuple(cluster.retrieve_all_function_codes()) } - for method_endpoint in list(cluster.ENDPOINTS): + for method_endpoint in list(cluster.ENDPOINTS.values()): # [SAVE]REDIS => ENDPOINT2CLASS = {MethodEvent: Endpoint("/.../.../..."), ...} self.valid_redis_items.ENDPOINT2CLASS_VALUE.update( - {f"{cluster.name}:{method_endpoint.name}": method_endpoint.URL} + {f"{cluster.name}:{method_endpoint.name}": f"{cluster.PREFIX}{method_endpoint.URL}"} ) self.valid_redis_items.ENDPOINT2CLASS_VALUE.update( - {method_endpoint.URL :f"{cluster.name}:{method_endpoint.name}"} + {f"{cluster.PREFIX}{method_endpoint.URL}" :f"{cluster.name}:{method_endpoint.name}"} ) # [SAVE]REDIS => METHOD_FUNCTION_CODES:MethodEvent:Endpoint = [FUNCTION_CODE, ...] self.valid_redis_items.METHOD_FUNCTION_CODES_VALUE.update( - method_endpoint.retrieve_redis_value(cluster_name=cluster.name) + method_endpoint.retrieve_redis_value(cluster=cluster) ) @@ -112,28 +112,22 @@ class SetItems2Redis: self.set_items() def __str__(self): - self.std_out = f"\nSetItems2Redis:\n\n{self.std_out}" - return self.std_out + return f"\nSetItems2Redis:\n\n{self.std_out}" def set_items(self): from ApiLayers.AllConfigs.Redis.configs import RedisCategoryKeys dict_prep = self.prepare_events.valid_redis_items.as_dict - RedisActions.delete( - list_keys=[ - f"{RedisCategoryKeys.MENU_FIRST_LAYER}:*", - f"{RedisCategoryKeys.CLUSTER_INDEX}:*", - f"{RedisCategoryKeys.CLUSTER_FUNCTION_CODES}:*", - f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}:*" - f"{RedisCategoryKeys.ENDPOINT2CLASS}:*", - ] - ) + for redis_values_to_delete, redis_key_type in RedisCategoryKeys.__annotations__.items(): + if isinstance(redis_key_type, str): + continue + RedisActions.delete(list_keys=[f"{redis_values_to_delete}*"]) # Save MENU_FIRST_LAYER to Redis redis_list = RedisList(redis_key=RedisCategoryKeys.MENU_FIRST_LAYER) RedisActions.set_json( list_keys=redis_list.to_list(), value=dict_prep.get(RedisCategoryKeys.MENU_FIRST_LAYER) ) - self.std_out = f"{RedisCategoryKeys.MENU_FIRST_LAYER}: {dict_prep.get(RedisCategoryKeys.MENU_FIRST_LAYER)}\n" + self.std_out += f"{RedisCategoryKeys.MENU_FIRST_LAYER}: {dict_prep.get(RedisCategoryKeys.MENU_FIRST_LAYER)}\n" # Save CLUSTER_INDEX to Redis redis_list = RedisList(redis_key=RedisCategoryKeys.CLUSTER_INDEX) @@ -167,5 +161,11 @@ class SetItems2Redis: self.std_out += f"\n{RedisCategoryKeys.ENDPOINT2CLASS}: {dict_prep.get(RedisCategoryKeys.ENDPOINT2CLASS)}\n" RedisActions.set_json( - list_keys=[f"{RedisCategoryKeys.REBUILD}:*"], value=False + list_keys=[f"{RedisCategoryKeys.REBUILD}:*"], value={ + f"{RedisCategoryKeys.MENU_FIRST_LAYER}": True, + f"{RedisCategoryKeys.CLUSTER_INDEX}": True, + f"{RedisCategoryKeys.CLUSTER_FUNCTION_CODES}": True, + f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}": True, + f"{RedisCategoryKeys.ENDPOINT2CLASS}": True, + } ) diff --git a/Services/Redis/Models/response.py b/Services/Redis/Models/response.py index 24ef41a..dcff068 100644 --- a/Services/Redis/Models/response.py +++ b/Services/Redis/Models/response.py @@ -58,9 +58,10 @@ class RedisResponse: @property def first(self) -> Union[RedisRow, None]: - print("self.data", self.data) if self.data: if isinstance(self.data, list): + if isinstance(self.data[0], RedisRow): + return self.data[0].row return self.data[0] elif isinstance(self.data, RedisRow): return self.row diff --git a/docker-compose-services.yml b/docker-compose-services.yml index 60a634b..df66166 100644 --- a/docker-compose-services.yml +++ b/docker-compose-services.yml @@ -10,18 +10,22 @@ services: dockerfile: DockerApiServices/AuthServiceApi/Dockerfile ports: - "41575:41575" + depends_on: + - init-service - event-service: - build: - context: . - dockerfile: DockerApiServices/EventServiceApi/Dockerfile - ports: - - "41576:41576" + # event-service: + # build: + # context: . + # dockerfile: DockerApiServices/EventServiceApi/Dockerfile + # ports: + # - "41576:41576" + # depends_on: + # - init-service - validation-service: - build: - context: . - dockerfile: DockerApiServices/ValidationServiceApi/Dockerfile - ports: - - "41577:41577" + # validation-service: + # build: + # context: . + # dockerfile: DockerApiServices/ValidationServiceApi/Dockerfile + # ports: + # - "41577:41577" # and lets try to implement potry again in the dockerfile now we now that it is about copy of files