diff --git a/AllConfigs/Token/config.py b/AllConfigs/Token/config.py index 73e6364..e95c300 100644 --- a/AllConfigs/Token/config.py +++ b/AllConfigs/Token/config.py @@ -19,7 +19,7 @@ class ApiStatic: class Auth: ACCESS_EMAIL_EXT = "evyos.com.tr" ACCESS_TOKEN_TAG = "evyos-session-key" - REFRESHER_TOKEN_TAG = "eys_token_refresher" + REFRESHER_TOKEN_TAG = "eys-session-refresher" SECRET_KEY_72 = ( "t3sUAmjTGeTgDc6dAUrB41u2SNg0ZHzj4HTjem95y3fRH1nZXOHIBj163kib6iLybT0gLaxq" ) diff --git a/ApiEvents/AuthServiceApi/auth/auth.py b/ApiEvents/AuthServiceApi/auth/auth.py index c60a16f..c80f56c 100644 --- a/ApiEvents/AuthServiceApi/auth/auth.py +++ b/ApiEvents/AuthServiceApi/auth/auth.py @@ -8,10 +8,22 @@ from typing import TYPE_CHECKING, Union, Dict, Any 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 ApiLibrary.date_time_actions.date_functions import DateTimeLocal 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 ApiServices.Token.token_handler import TokenService +from ApiValidations.Custom.token_objects import CompanyToken +from ApiValidations.Request.authentication import ( + Login, + EmployeeSelectionValidation, + OccupantSelectionValidation, OccupantSelection, EmployeeSelection, +) from ErrorHandlers import HTTPExceptionApi +from Schemas.company.company import Companies +from Schemas.company.department import Departments, Duties, Duty +from Schemas.company.employee import Staff, Employees +from Schemas.event.event import Event2Employee +from Schemas.identity.identity import Users +from Services.Redis.Actions.actions import RedisActions from .models import ( LoginData, LoginRequestModel, @@ -27,7 +39,10 @@ from .models import ( if TYPE_CHECKING: from fastapi import Request - from ApiServices.Token.token_handler import OccupantTokenObject, EmployeeTokenObject + from ApiServices.Token.token_handler import ( + OccupantTokenObject, + EmployeeTokenObject + ) # Type aliases for common types TokenDictType = Union["EmployeeTokenObject", "OccupantTokenObject"] @@ -69,7 +84,7 @@ class AuthenticationLoginEventMethods(MethodToEvent): # Return response with token and headers return { - "token": token, + **token, "headers": dict(request.headers), } @@ -89,18 +104,113 @@ class AuthenticationSelectEventMethods(MethodToEvent): @classmethod def _handle_employee_selection( cls, - data: SelectionDataEmployee, + data: EmployeeSelection, token_dict: TokenDictType, request: "Request", ): - raise HTTPExceptionApi( - error_code="", lang="en", loc=get_line_number_for_error() + Users.set_user_define_properties(token=token_dict) + db_session = Users.new_session() + + if data.company_uu_id not in token_dict.companies_uu_id_list: + raise HTTPExceptionApi( + error_code="HTTP_400_BAD_REQUEST", + lang=token_dict.lang, + loc=get_line_number_for_error(), + sys_msg="Company not found in token" + ) + selected_company = Companies.filter_one( + Companies.uu_id == data.company_uu_id, + db=db_session, + ).first + if not selected_company: + raise HTTPExceptionApi( + error_code="HTTP_400_BAD_REQUEST", + lang=token_dict.lang, + loc=get_line_number_for_error(), + sys_msg="Company not found in token" + ) + + # Get department IDs for the company + department_ids = [ + dept.id + for dept in Departments.filter_all( + Departments.company_id == selected_company.id, + db=db_session, + ).data + ] + + # Get duties IDs for the company + duties_ids = [ + duty.id + for duty in Duties.filter_all(Duties.company_id == selected_company.id, db=db_session).data + ] + + # Get staff IDs + staff_ids = [ + staff.id for staff in Staff.filter_all(Staff.duties_id.in_(duties_ids), db=db_session).data + ] + + # Get employee + employee = Employees.filter_one( + Employees.people_id == token_dict.person_id, + Employees.staff_id.in_(staff_ids), + db=db_session, + ).first + + if not employee: + raise HTTPExceptionApi( + error_code="HTTP_400_BAD_REQUEST", + lang=token_dict.lang, + loc=get_line_number_for_error(), + sys_msg="Employee not found in token" + ) + + # 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, db=db_session).data + duties = Duties.filter_one(Duties.id == staff.duties_id, db=db_session).data + department = Departments.filter_one(Departments.id == duties.department_id, db=db_session).data + + # Get bulk duty + bulk_id = Duty.filter_by_one(system=True, duty_code="BULK", db=db_session).data + bulk_duty_id = Duties.filter_by_one( + company_id=selected_company.id, + duties_id=bulk_id.id, + **Duties.valid_record_dict, + db=db_session, + ).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, + ) + try: # Update Redis + update_token = TokenService.update_token_at_redis(request=request, add_payload=company_token) + return update_token + except Exception as e: + raise HTTPExceptionApi( + error_code="", lang="en", loc=get_line_number_for_error(), sys_msg=f"{e}" + ) + @classmethod def _handle_occupant_selection( cls, - data: SelectionDataOccupant, + data: OccupantSelection, token_dict: TokenDictType, request: "Request", ): @@ -116,25 +226,22 @@ class AuthenticationSelectEventMethods(MethodToEvent): async def authentication_select_company_or_occupant_type( cls, request: "Request", - data: Union[EmployeeSelectionValidation, OccupantSelectionValidation], + data: Union[EmployeeSelection, OccupantSelection], token_dict: TokenDictType, ): """Handle selection of company or occupant type""" - try: - print( - dict( - data=data, - token_dict=token_dict.model_dump(), - request=dict(request.headers) - ) - ) - except Exception as e: - raise HTTPExceptionApi( - error_code="HTTP_500_INTERNAL_SERVER_ERROR", - lang="en", - loc=get_line_number_for_error(), - sys_msg=str(e), - ) + if token_dict.is_employee: + return cls._handle_employee_selection(data, token_dict, request) + elif token_dict.is_occupant: + return cls._handle_occupant_selection(data, token_dict, request) + + # except Exception as e: + # raise HTTPExceptionApi( + # error_code="HTTP_500_INTERNAL_SERVER_ERROR", + # lang="en", + # loc=get_line_number_for_error(), + # sys_msg=str(e), + # ) class AuthenticationCheckTokenEventMethods(MethodToEvent): diff --git a/ApiEvents/AuthServiceApi/auth/endpoints.py b/ApiEvents/AuthServiceApi/auth/endpoints.py index ef89bcf..9547422 100644 --- a/ApiEvents/AuthServiceApi/auth/endpoints.py +++ b/ApiEvents/AuthServiceApi/auth/endpoints.py @@ -5,7 +5,16 @@ 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 ApiValidations.Request import ( + Logout, + Login, + Remember, + Forgot, + CreatePassword, + ChangePassword, + OccupantSelection, + EmployeeSelection, +) from .auth import ( AuthenticationChangePasswordEventMethods, @@ -32,7 +41,7 @@ from .models import ( SelectionDataOccupant, RememberRequestModel, ) -from ApiEvents.base_request_model import DictRequestModel +from ApiEvents.base_request_model import DictRequestModel, EndpointBaseRequestModel from ApiEvents.abstract_class import RouteFactoryConfig, EndpointFactoryConfig, endpoint_wrapper if TYPE_CHECKING: @@ -41,30 +50,27 @@ from ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTok # 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 + data: EndpointBaseRequestModel, ) -> 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 + Select company or occupant type. """ - return { - "headers": dict(request.headers), - "data": data, - "token": token_dict - } + auth_dict = authentication_select_company_or_occupant_type.auth + if data.data.get("company_uu_id"): + data = EmployeeSelection(**data.data) + elif data.data.get("build_living_space_uu_id"): + data = OccupantSelection(**data.data) + if r := await AuthenticationSelectEventMethods.authentication_select_company_or_occupant_type( + request=request, data=data, token_dict=auth_dict + ): + if isinstance(data, EmployeeSelection): + return {"selected_company": data.company_uu_id} + elif isinstance(data, OccupantSelection): + return {"selected_occupant": data.build_living_space_uu_id} @endpoint_wrapper("/authentication/login") @@ -75,7 +81,7 @@ async def authentication_login_with_domain_and_creds( """ Authenticate user with domain and credentials. """ - return AuthenticationLoginEventMethods.authentication_login_with_domain_and_creds( + return await AuthenticationLoginEventMethods.authentication_login_with_domain_and_creds( request=request, data=data ) @@ -83,13 +89,14 @@ async def authentication_login_with_domain_and_creds( @endpoint_wrapper("/authentication/check") async def authentication_check_token_is_valid( request: "Request", - data: DictRequestModel, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Check if a token is valid. """ return { - "status": "OK", + "headers": dict(request.headers), + "data": data.model_dump(), } @@ -97,8 +104,7 @@ async def authentication_check_token_is_valid( @endpoint_wrapper("/authentication/refresh") async def authentication_refresh_user_info( request: "Request", - data: DictRequestModel, - token_dict: TokenDictType = None, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Refresh user information. @@ -111,8 +117,7 @@ async def authentication_refresh_user_info( @endpoint_wrapper("/authentication/change-password") async def authentication_change_password( request: "Request", - data: ChangePasswordRequestModel, - token_dict: TokenDictType = None, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Change user password. @@ -125,7 +130,7 @@ async def authentication_change_password( @endpoint_wrapper("/authentication/create-password") async def authentication_create_password( request: "Request", - data: CreatePasswordRequestModel, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Create new password. @@ -137,7 +142,7 @@ async def authentication_create_password( @endpoint_wrapper("/authentication/forgot-password") async def authentication_forgot_password( request: "Request", - data: ForgotRequestModel, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Handle forgot password request. @@ -149,7 +154,7 @@ async def authentication_forgot_password( @endpoint_wrapper("/authentication/reset-password") async def authentication_reset_password( request: "Request", - data: ForgotRequestModel, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Reset password. @@ -161,8 +166,7 @@ async def authentication_reset_password( @endpoint_wrapper("/authentication/disconnect") async def authentication_disconnect_user( request: "Request", - data: LogoutRequestModel, - token_dict: TokenDictType = None, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Disconnect user. @@ -175,8 +179,7 @@ async def authentication_disconnect_user( @endpoint_wrapper("/authentication/logout") async def authentication_logout_user( request: "Request", - data: LogoutRequestModel, - token_dict: TokenDictType = None, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Logout user. @@ -189,8 +192,7 @@ async def authentication_logout_user( @endpoint_wrapper("/authentication/remember") async def authentication_refresher_token( request: "Request", - data: RememberRequestModel, - token_dict: TokenDictType = None, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Refresh remember token. @@ -203,8 +205,7 @@ async def authentication_refresher_token( @endpoint_wrapper("/authentication/avatar") async def authentication_download_avatar( request: "Request", - data: DictRequestModel, - token_dict: TokenDictType = None, + data: EndpointBaseRequestModel, ) -> Dict[str, Any]: """ Download user avatar. diff --git a/ApiEvents/AuthServiceApi/auth/models.py b/ApiEvents/AuthServiceApi/auth/models.py index c130d1a..35185ee 100644 --- a/ApiEvents/AuthServiceApi/auth/models.py +++ b/ApiEvents/AuthServiceApi/auth/models.py @@ -127,12 +127,12 @@ class CreatePasswordRequestModel(BaseRequestModel[CreatePasswordData]): ) -class SelectionDataOccupant(TypedDict): +class SelectionDataOccupant(BaseModel): """Type for selection data.""" build_living_space_uu_id: Optional[str] -class SelectionDataEmployee(TypedDict): +class SelectionDataEmployee(BaseModel): """Type for selection data.""" company_uu_id: Optional[str] diff --git a/ApiEvents/EventServiceApi/account/endpoints.py b/ApiEvents/EventServiceApi/account/endpoints.py index 8df4a67..6708d82 100644 --- a/ApiEvents/EventServiceApi/account/endpoints.py +++ b/ApiEvents/EventServiceApi/account/endpoints.py @@ -4,18 +4,16 @@ Account records endpoint configurations. """ from ApiEvents.abstract_class import RouteFactoryConfig, EndpointFactoryConfig, endpoint_wrapper -from ApiEvents.base_request_model import DictRequestModel - +from ApiEvents.base_request_model import EndpointBaseRequestModel 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): +async def address_list(request: "Request", data: EndpointBaseRequestModel): """Handle address list endpoint.""" + auth_dict = address_list.auth return { "data": data, "request": str(request.headers), @@ -25,7 +23,7 @@ async def address_list(request: "Request", data: ListOptionsRequestModel): @endpoint_wrapper("/account/records/address/create") -async def address_create(request: "Request", data: DictRequestModel): +async def address_create(request: "Request", data: EndpointBaseRequestModel): """Handle address creation endpoint.""" return { "data": data, @@ -36,16 +34,18 @@ async def address_create(request: "Request", data: DictRequestModel): @endpoint_wrapper("/account/records/address/search") -async def address_search(request: "Request", data: DictRequestModel): +async def address_search(request: "Request", data: EndpointBaseRequestModel): """Handle address search endpoint.""" - return {"data": data} + auth_dict = address_search.auth + code_dict = getattr(address_search, 'func_code', {"function_code": None}) + return {"auth_dict": auth_dict, "code_dict": code_dict, "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"), + request_data: EndpointBaseRequestModel = Body(..., description="Request body"), ): """ Handle address update endpoint. @@ -58,6 +58,7 @@ async def address_update( Returns: DictJsonResponse: Response containing updated address info """ + auth_dict = address_update.auth return DictJsonResponse( data={ "address_uu_id": address_uu_id, diff --git a/ApiEvents/abstract_class.py b/ApiEvents/abstract_class.py index 72c43af..799ed6a 100644 --- a/ApiEvents/abstract_class.py +++ b/ApiEvents/abstract_class.py @@ -49,15 +49,15 @@ def endpoint_wrapper(url_of_endpoint: Optional[str] = None): # If result is a coroutine, await it if inspect.iscoroutine(result): result = await result - - # Add function_code to the result + print('result', result) + # Add endpoint to the result if isinstance(result, dict): - result["function_code"] = url_of_endpoint + result["endpoint"] = url_of_endpoint return result elif isinstance(result, BaseModel): - # Convert Pydantic model to dict and add function_code + # Convert Pydantic model to dict and add endpoint result_dict = result.model_dump() - result_dict["function_code"] = url_of_endpoint + result_dict["endpoint"] = url_of_endpoint return result_dict return result @@ -107,24 +107,23 @@ class EndpointFactoryConfig: - If only event required -> wrap with EventMiddleware - If only auth required -> wrap with MiddlewareModule.auth_required """ - # 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: + # First apply auth/event middleware + if self.is_event_required: from middleware import TokenEventMiddleware - self.endpoint_function = TokenEventMiddleware.event_required( self.endpoint_function ) elif self.is_auth_required: from middleware import MiddlewareModule - self.endpoint_function = MiddlewareModule.auth_required( self.endpoint_function ) + # Then wrap with endpoint_wrapper to store url_of_endpoint + self.endpoint_function = endpoint_wrapper(self.url_of_endpoint)( + self.endpoint_function + ) + class RouteFactoryConfig: """Configuration class for API route factories. diff --git a/ApiEvents/base_request_model.py b/ApiEvents/base_request_model.py index dbd6817..b9f1c04 100644 --- a/ApiEvents/base_request_model.py +++ b/ApiEvents/base_request_model.py @@ -12,10 +12,21 @@ from pydantic import BaseModel, Field, ConfigDict, RootModel T = TypeVar("T") +class EndpointBaseRequestModel(BaseModel): + + data: dict = Field(..., description="Data to be sent with the request") + class Config: + json_schema_extra = { + "data": { + "key": "value", + } + } + + class BaseRequestModel(RootModel[T], Generic[T]): """Base model for all API requests.""" model_config = ConfigDict( - json_schema_extra={"example": {}} # Will be populated by subclasses + json_schema_extra={"example": {"base": "example"}} # Will be populated by subclasses ) diff --git a/ApiServices/Token/token_handler.py b/ApiServices/Token/token_handler.py index 2e8ec75..2088404 100644 --- a/ApiServices/Token/token_handler.py +++ b/ApiServices/Token/token_handler.py @@ -8,12 +8,14 @@ from ApiLibrary.common.line_number import get_line_number_for_error from ApiLibrary.date_time_actions.date_functions import DateTimeLocal from ApiLibrary.token.password_module import PasswordModule from ErrorHandlers import HTTPExceptionApi -from Schemas.identity.identity import UsersTokens +from Schemas.identity.identity import UsersTokens, People from Services.Redis import RedisActions, AccessToken from ApiValidations.Custom.token_objects import ( EmployeeTokenObject, OccupantTokenObject, UserType, + CompanyToken, + OccupantToken, ) from Schemas import ( Users, @@ -37,6 +39,7 @@ if TYPE_CHECKING: T = TypeVar("T", EmployeeTokenObject, OccupantTokenObject) + class TokenService: """Service class for handling authentication tokens and user sessions.""" @@ -128,22 +131,57 @@ class TokenService: timezone=user.local_timezone or "GMT+0", lang=user.lang or "tr", ).model_dump() - cls.set_object_to_redis(user, model_value) - return { - "user_type": UserType.occupant.name, - "available_occupants": occupants_selection_dict, - } + if access_token := cls.set_object_to_redis(user, model_value): + return { + "access_token": access_token, + "user_type": UserType.occupant.name, + "available_occupants": occupants_selection_dict, + } + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Creating Token failed...", + ) @classmethod def set_object_to_redis(cls, user, model: Dict): - accessObject = AccessToken( + access_object = AccessToken( userUUID=user.uu_id, accessToken=cls._create_access_token(), ) - return RedisActions.set_json( - list_keys=accessObject.to_list(), - value=json.dumps(model), - expires=Auth.TOKEN_EXPIRE_MINUTES_30.seconds, + redis_action = RedisActions.set_json( + list_keys=access_object.to_list(), + value=model, + expires={"seconds": int(Auth.TOKEN_EXPIRE_MINUTES_30.seconds)}, + ) + if redis_action.status: + return access_object.accessToken + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Saving Token failed...", + ) + + @classmethod + def update_object_to_redis(cls, access_token: str, user_uu_id: str, model: Dict): + access_object = AccessToken( + userUUID=user_uu_id, + accessToken=access_token, + ) + redis_action = RedisActions.set_json( + list_keys=access_object.to_list(), + value=model, + expires={"seconds": int(Auth.TOKEN_EXPIRE_MINUTES_30.seconds)}, + ) + if redis_action.status: + return access_object.accessToken + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Saving Token failed...", ) @classmethod @@ -194,15 +232,17 @@ class TokenService: "company_address": company_address, } ) - + person = People.filter_one( + People.id == user.person_id, db=db_session + ).data model_value = EmployeeTokenObject( domain=domain, user_type=UserType.employee.value, user_uu_id=str(user.uu_id), credentials=user.credentials(), user_id=user.id, - person_id=user.person_id, - person_uu_id=str(user.person.uu_id), + person_id=person.id, + person_uu_id=str(person.uu_id), request=dict(request.headers), companies_uu_id_list=companies_uu_id_list, companies_id_list=companies_id_list, @@ -211,8 +251,9 @@ class TokenService: timezone=user.local_timezone or "GMT+0", lang=user.lang or "tr", ).model_dump() - if cls.set_object_to_redis(user, model_value): + if access_token := cls.set_object_to_redis(user, model_value): return { + "access_token": access_token, "user_type": UserType.employee.name, "companies_list": companies_list, } @@ -228,7 +269,8 @@ class TokenService: """Remove all tokens for a user with specific domain.""" redis_rows = cls._get_user_tokens(user) for redis_row in redis_rows.all: - if redis_row.get("domain") == domain: + print('redis_row', redis_row.data) + if redis_row.data.get("domain") == domain: RedisActions.delete_key(redis_row.key) @classmethod @@ -291,6 +333,36 @@ class TokenService: "user": user.get_dict(), } + @classmethod + def update_token_at_redis( + cls, request: "Request", add_payload: Union[CompanyToken, OccupantToken] + ) -> Dict[str, Any]: + """Update token at Redis.""" + access_token = cls.get_access_token_from_request(request=request) + token_object = cls.get_object_via_access_key(access_token=access_token) + if isinstance(token_object, EmployeeTokenObject) and isinstance(add_payload, CompanyToken): + token_object.selected_company = add_payload + cls.update_object_to_redis( + access_token=access_token, + user_uu_id=token_object.user_uu_id, + model=token_object.model_dump() + ) + return token_object.selected_company.model_dump() + elif isinstance(token_object, OccupantTokenObject) and isinstance(add_payload, OccupantToken): + token_object.selected_occupant = add_payload + cls.update_object_to_redis( + access_token=access_token, + user_uu_id=token_object.user_uu_id, + model=token_object.model_dump() + ) + return token_object.selected_occupant.model_dump() + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Token not found", + ) + @classmethod def raise_error_if_request_has_no_token(cls, request: "Request") -> None: """Validate request has required token headers.""" @@ -330,7 +402,6 @@ class TokenService: redis_object["selected_company"] = None if not redis_object.get("selected_occupant"): redis_object["selected_occupant"] = None - if redis_object.get("user_type") == UserType.employee.value: return EmployeeTokenObject(**redis_object) elif redis_object.get("user_type") == UserType.occupant.value: @@ -348,17 +419,17 @@ class TokenService: """Get token object using access key.""" access_token_obj = AccessToken(accessToken=access_token) redis_response = RedisActions.get_json(list_keys=access_token_obj.to_list()) - - if redis_object := redis_response.first.data: - access_token_obj.userUUID = redis_object.get("user_uu_id") - return cls._process_redis_object(redis_object) - - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Invalid access token", - ) + if not redis_response.status: + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Access token token is not found or unable to retrieve", + ) + if redis_object := redis_response.first: + redis_object_dict = redis_object.data + access_token_obj.userUUID = redis_object_dict.get("user_uu_id") + return cls._process_redis_object(redis_object_dict) @classmethod def get_object_via_user_uu_id(cls, user_id: str) -> T: diff --git a/ApiValidations/Custom/token_objects.py b/ApiValidations/Custom/token_objects.py index 92fb845..7a7f40b 100644 --- a/ApiValidations/Custom/token_objects.py +++ b/ApiValidations/Custom/token_objects.py @@ -91,7 +91,15 @@ class OccupantTokenObject(ApplicationToken): available_occupants: dict = None selected_occupant: Optional[OccupantToken] = None # Selected Occupant Type - available_event: Optional[Any] = None + + + @property + def is_employee(self) -> bool: + return False + + @property + def is_occupant(self) -> bool: + return True class EmployeeTokenObject(ApplicationToken): @@ -104,4 +112,12 @@ class EmployeeTokenObject(ApplicationToken): duty_uu_id_list: List[str] # List of duty objects selected_company: Optional[CompanyToken] = None # Selected Company Object - available_event: Optional[Any] = None + + + @property + def is_employee(self) -> bool: + return True + + @property + def is_occupant(self) -> bool: + return False diff --git a/ApiValidations/Request/authentication.py b/ApiValidations/Request/authentication.py index 2e33382..4b3223e 100644 --- a/ApiValidations/Request/authentication.py +++ b/ApiValidations/Request/authentication.py @@ -1,3 +1,5 @@ + + from ApiValidations.Request import BaseModelRegular from typing import Optional @@ -55,15 +57,11 @@ class OccupantSelectionValidation: class OccupantSelection(BaseModel, OccupantSelectionValidation): - occupant_uu_id: str = Field(..., example="123e4567-e89b-12d3-a456-426614174000") - build_part_uu_id: str = Field(..., example="987fcdeb-51a2-43e7-9876-543210987654") + build_living_space_uu_id: str = Field(..., example="987fcdeb-51a2-43e7-9876-543210987654") model_config = ConfigDict( json_schema_extra={ - "example": { - "occupant_uu_id": "123e4567-e89b-12d3-a456-426614174000", - "build_part_uu_id": "987fcdeb-51a2-43e7-9876-543210987654", - } + "example": {"build_living_space_uu_id": "987fcdeb-51a2-43e7-9876-543210987654"} } ) @@ -107,10 +105,10 @@ class Login(BaseModelRegular, LoginValidation): model_config = ConfigDict( json_schema_extra={ "example": { - "domain": "example.com", - "access_key": "user@example.com", - "password": "password123", - "remember_me": True, + "domain": "evyos.com.tr", + "access_key": "karatay.berkay.sup@evyos.com.tr", + "password": "string", + "remember_me": False } } ) diff --git a/DockerApiServices/AllApiNeeds/app.py b/DockerApiServices/AllApiNeeds/app.py index cc7033b..1d12a8b 100644 --- a/DockerApiServices/AllApiNeeds/app.py +++ b/DockerApiServices/AllApiNeeds/app.py @@ -10,77 +10,9 @@ This module initializes and configures the FastAPI application with: """ import uvicorn -from fastapi import FastAPI -from create_routes import get_all_routers from prometheus_fastapi_instrumentator import Instrumentator from app_handler import setup_middleware, get_uvicorn_config -from create_file import setup_security_schema, configure_route_security -from open_api_creator import OpenAPISchemaCreator, create_openapi_schema - - -def create_app() -> FastAPI: - """Create and configure the FastAPI application.""" - app = FastAPI( - responses={ - 422: { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "detail": { - "type": "array", - "items": { - "type": "object", - "properties": { - "loc": { - "type": "array", - "items": {"type": "string"}, - }, - "msg": {"type": "string"}, - "type": {"type": "string"}, - }, - }, - } - }, - } - } - }, - } - } - ) - - # Get all routers and protected routes from the new configuration - routers, protected_routes = get_all_routers() - - # Include all routers - for router in routers: - app.include_router(router) - - # Configure OpenAPI schema with security - def custom_openapi(): - if app.openapi_schema: - return app.openapi_schema - - # Create OpenAPI schema using our custom creator - openapi_schema = create_openapi_schema(app) - - # Add security scheme - openapi_schema.update(setup_security_schema()) - - # Configure security for protected routes - for path, methods in protected_routes.items(): - for method in methods: - configure_route_security( - path, method, openapi_schema, list(protected_routes.keys()) - ) - - app.openapi_schema = openapi_schema - return app.openapi_schema - - app.openapi = custom_openapi - return app +from create_file import create_app app = create_app() # Initialize FastAPI application diff --git a/DockerApiServices/AllApiNeeds/create_file.py b/DockerApiServices/AllApiNeeds/create_file.py index c110a8b..38f782a 100644 --- a/DockerApiServices/AllApiNeeds/create_file.py +++ b/DockerApiServices/AllApiNeeds/create_file.py @@ -14,6 +14,7 @@ from fastapi import FastAPI, APIRouter from fastapi.responses import JSONResponse, RedirectResponse from fastapi.openapi.utils import get_openapi +from AllConfigs.Token.config import Auth from AllConfigs.main import MainConfig as Config from create_routes import get_all_routers @@ -29,11 +30,11 @@ def setup_security_schema() -> Dict[str, Any]: return { "components": { "securitySchemes": { - "Bearer": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT", - "description": "Enter the token", + "Bearer Auth": { + "type": "apiKey", + "in": "header", + "name": Auth.ACCESS_TOKEN_TAG, + "description": "Enter: **'Bearer <JWT>'**, where JWT is the access token", } } } @@ -65,12 +66,15 @@ def create_app() -> FastAPI: Returns: FastAPI: Configured FastAPI application instance """ - # Initialize FastAPI app + + from open_api_creator import create_openapi_schema + # Get all routers and protected routes using the dynamic route creation + app = FastAPI( title=Config.TITLE, description=Config.DESCRIPTION, default_response_class=JSONResponse, - ) + ) # Initialize FastAPI app @app.get("/", include_in_schema=False, summary=str(Config.DESCRIPTION)) async def home() -> RedirectResponse: @@ -84,31 +88,5 @@ def create_app() -> FastAPI: for router in routers: app.include_router(router) - # Configure OpenAPI schema with security - def custom_openapi(): - if app.openapi_schema: - return app.openapi_schema - - openapi_schema = get_openapi( - title="WAG Management API", - version="4.0.0", - description="WAG Management API Service", - routes=app.routes, - ) - - # Add security scheme - security_schema = setup_security_schema() - openapi_schema.update(security_schema) - - # Configure security for protected routes - for path, methods in protected_routes.items(): - for method in methods: - configure_route_security( - path, method, openapi_schema, list(protected_routes.keys()) - ) - - app.openapi_schema = openapi_schema - return app.openapi_schema - - app.openapi = custom_openapi + app.openapi = lambda app=app: create_openapi_schema(app) return app diff --git a/DockerApiServices/AllApiNeeds/create_routes.py b/DockerApiServices/AllApiNeeds/create_routes.py index 6f242ad..b969985 100644 --- a/DockerApiServices/AllApiNeeds/create_routes.py +++ b/DockerApiServices/AllApiNeeds/create_routes.py @@ -24,8 +24,6 @@ class EndpointFactoryConfig: summary: str description: str endpoint_function: Callable[P, R] # 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] = None @@ -56,7 +54,7 @@ class EnhancedEndpointFactory: endpoint_function = config.endpoint_function if config.is_auth_required: - endpoint_function = MiddlewareModule.auth_required(endpoint_function) + # endpoint_function = MiddlewareModule.auth_required(endpoint_function) # Track protected routes full_path = f"{self.router.prefix}{endpoint_path}" if full_path not in self.protected_routes: @@ -66,7 +64,6 @@ class EnhancedEndpointFactory: # Register the endpoint with FastAPI router getattr(self.router, config.method.lower())( endpoint_path, - response_model=config.response_model, summary=config.summary, description=config.description, **config.extra_options, diff --git a/DockerApiServices/AllApiNeeds/middleware/auth_middleware.py b/DockerApiServices/AllApiNeeds/middleware/auth_middleware.py index 45c3ba3..c909e22 100644 --- a/DockerApiServices/AllApiNeeds/middleware/auth_middleware.py +++ b/DockerApiServices/AllApiNeeds/middleware/auth_middleware.py @@ -15,6 +15,7 @@ from ApiLibrary.common.line_number import get_line_number_for_error from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi from .base_context import BaseContext from ApiServices.Token.token_handler import OccupantTokenObject, EmployeeTokenObject +import inspect class AuthContext(BaseContext): @@ -42,7 +43,12 @@ class AuthContext(BaseContext): @property def user_id(self) -> str: """Get the user's UUID from token context.""" - return self.token_context.user_uu_id if self.token_context else "" + return self.token_context.user_uu_id if self.token_context else None + + def as_dict(self): + if not isinstance(self.token_context, dict): + return self.token_context.model_dump() + return self.token_context def __repr__(self) -> str: user_type = "Employee" if self.is_employee else "Occupant" @@ -57,7 +63,7 @@ class MiddlewareModule: @staticmethod def get_user_from_request( request: Request, - ) -> AuthContext: + ) -> dict: """ Get authenticated token context from request. @@ -74,7 +80,6 @@ class MiddlewareModule: # Get token and validate - will raise HTTPExceptionApi if invalid redis_token = TokenService.get_access_token_from_request(request=request) - # Get token context - will validate token and raise appropriate errors token_context = TokenService.get_object_via_access_key(access_token=redis_token) if not token_context: @@ -85,7 +90,7 @@ class MiddlewareModule: sys_msg="TokenService: Token Context couldnt retrieved from redis", ) - return AuthContext(token_context=token_context) + return token_context @classmethod def auth_required(cls, func: Callable) -> Callable: @@ -116,14 +121,13 @@ class MiddlewareModule: """ @wraps(func) - def wrapper(request: Request, *args, **kwargs): + async def wrapper(request: Request, *args, **kwargs): # Get and validate token context from request - auth_context = cls.get_user_from_request(request) - - # Attach auth context to function - func.auth = auth_context - + # Create auth context and Attach auth context to both wrapper and original function + func.auth = cls.get_user_from_request(request) # This ensures the context is available in both places # Call the original endpoint function + if inspect.iscoroutinefunction(func): + return await func(request, *args, **kwargs) return func(request, *args, **kwargs) return wrapper @@ -147,7 +151,6 @@ class RequestTimingMiddleware(BaseHTTPMiddleware): Response: Processed response with timing headers """ start_time = perf_counter() - # Process the request response = await call_next(request) diff --git a/DockerApiServices/AllApiNeeds/middleware/token_event_middleware.py b/DockerApiServices/AllApiNeeds/middleware/token_event_middleware.py index 632348f..1e76fe8 100644 --- a/DockerApiServices/AllApiNeeds/middleware/token_event_middleware.py +++ b/DockerApiServices/AllApiNeeds/middleware/token_event_middleware.py @@ -6,6 +6,7 @@ from functools import wraps from typing import Callable, Dict, Any from .auth_middleware import MiddlewareModule from .base_context import BaseContext +import inspect class TokenEventHandler(BaseContext): @@ -51,26 +52,14 @@ class TokenEventMiddleware: authenticated_func = MiddlewareModule.auth_required(func) @wraps(authenticated_func) - def wrapper(*args, **kwargs) -> Dict[str, Any]: + async def wrapper(*args, **kwargs) -> Dict[str, Any]: # Create handler with context - handler = TokenEventHandler( - func=authenticated_func, - url_of_endpoint=authenticated_func.url_of_endpoint, - ) - - # Update event-specific context - handler.update_context( - function_code="7192c2aa-5352-4e36-98b3-dafb7d036a3d" # Keep function_code as URL - ) - - # Copy auth context from authenticated function - if hasattr(authenticated_func, "auth"): - handler.token_context = authenticated_func.auth.token_context - - # Make handler available to the function - authenticated_func.handler = handler + function_code = "7192c2aa-5352-4e36-98b3-dafb7d036a3d" # Keep function_code as URL + # Make handler available to all functions in the chain + func.func_code = {"function_code": function_code} # Call the authenticated function + if inspect.iscoroutinefunction(authenticated_func): + return await authenticated_func(*args, **kwargs) return authenticated_func(*args, **kwargs) - return wrapper diff --git a/DockerApiServices/AllApiNeeds/open_api_creator.py b/DockerApiServices/AllApiNeeds/open_api_creator.py index afb1fbb..5aaa5d3 100644 --- a/DockerApiServices/AllApiNeeds/open_api_creator.py +++ b/DockerApiServices/AllApiNeeds/open_api_creator.py @@ -13,6 +13,8 @@ from typing import Any, Dict, List, Optional, Set from fastapi import FastAPI, APIRouter from fastapi.routing import APIRoute from fastapi.openapi.utils import get_openapi + +from AllConfigs.Token.config import Auth from AllConfigs.main import MainConfig as Config from create_routes import get_all_routers @@ -62,17 +64,11 @@ class OpenAPISchemaCreator: """ return { "Bearer Auth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT", - "description": "Enter the token with the `Bearer: ` prefix", - }, - "API Key": { "type": "apiKey", "in": "header", - "name": "X-API-Key", - "description": "Optional API key for service authentication", - }, + "name": Auth.ACCESS_TOKEN_TAG, + "description": "Enter: **'Bearer <JWT>'**, where JWT is the access token", + } } def _create_common_responses(self) -> Dict[str, Any]: @@ -198,7 +194,6 @@ class OpenAPISchemaCreator: if path in self.protected_routes and method in self.protected_routes[path]: schema["paths"][path][method]["security"] = [ {"Bearer Auth": []}, - {"API Key": []}, ] schema["paths"][path][method]["responses"].update( self._create_common_responses() @@ -219,7 +214,7 @@ class OpenAPISchemaCreator: openapi_schema = get_openapi( title=Config.TITLE, description=Config.DESCRIPTION, - version="1.0.0", + version="1.1.1", routes=self.app.routes, tags=self.tags_metadata, ) @@ -229,17 +224,15 @@ class OpenAPISchemaCreator: openapi_schema["components"] = {} openapi_schema["components"]["securitySchemes"] = self._create_security_schemes() - # Configure route security and responses for route in self.app.routes: if isinstance(route, APIRoute) and route.include_in_schema: path = str(route.path) methods = [method.lower() for method in route.methods] - for method in methods: self.configure_route_security(path, method, openapi_schema) - # Add custom documentation extensions + # # Add custom documentation extensions openapi_schema["x-documentation"] = { "postman_collection": "/docs/postman", "swagger_ui": "/docs", diff --git a/Schemas/event/event.py b/Schemas/event/event.py index 2a85a6e..f031fd7 100644 --- a/Schemas/event/event.py +++ b/Schemas/event/event.py @@ -251,18 +251,22 @@ class Event2Employee(CrudCollection): @classmethod def get_event_id_by_employee_id(cls, employee_id) -> list: + db = cls.new_session() occupant_events = cls.filter_all( cls.employee_id == employee_id, + db=db, ).data active_events = Service2Events.filter_all( Service2Events.service_id.in_( [event.event_service_id for event in occupant_events] ), + db=db, system=True, ).data active_events_id = [event.event_id for event in active_events] if extra_events := Event2EmployeeExtra.filter_all( - Event2EmployeeExtra.employee_id == employee_id + Event2EmployeeExtra.employee_id == employee_id, + db=db, ).data: active_events_id.extend([event.event_id for event in extra_events]) return active_events_id diff --git a/Services/Redis/Actions/actions.py b/Services/Redis/Actions/actions.py index c799840..67a670e 100644 --- a/Services/Redis/Actions/actions.py +++ b/Services/Redis/Actions/actions.py @@ -77,12 +77,7 @@ class RedisActions: time=expiry_time, value=redis_row.value, ) - redis_row.expires_at = ( - arrow.now() - .shift(seconds=expiry_time) - .format(MainConfig.DATETIME_FORMAT) - ) - + redis_row.expires_at = str(arrow.now().shift(seconds=expiry_time).format(MainConfig.DATETIME_FORMAT)) else: redis_cli.set(name=redis_row.redis_key, value=redis_row.value) @@ -115,7 +110,6 @@ class RedisActions: list_of_rows = [] regex = RedisRow.regex(list_keys=list_keys) json_get = redis_cli.scan_iter(match=regex) - for row in list(json_get): redis_row = RedisRow() redis_row.set_key(key=row) @@ -123,11 +117,16 @@ class RedisActions: redis_value = redis_cli.get(redis_row.redis_key) redis_row.feed(redis_value) list_of_rows.append(redis_row) - + if list_of_rows: + return RedisResponse( + status=True, + message="Value is get successfully.", + data=list_of_rows, + ) return RedisResponse( - status=True, - message="Value is get successfully.", - data=list_of_rows, + status=False, + message="Value is not get successfully.", + data=list_of_rows ) except Exception as e: return RedisResponse( @@ -135,3 +134,4 @@ class RedisActions: message="Value is not get successfully.", error=str(e), ) + diff --git a/Services/Redis/Models/base.py b/Services/Redis/Models/base.py index 7d706fa..179b5bc 100644 --- a/Services/Redis/Models/base.py +++ b/Services/Redis/Models/base.py @@ -94,7 +94,7 @@ class RedisRow: # Filter and convert valid keys valid_keys = [] for key in list_keys: - if key is None: + if key is None or str(key) == "None": continue if isinstance(key, bytes): key = key.decode() @@ -108,7 +108,8 @@ class RedisRow: # Add wildcard if first key was None if list_keys[0] is None: pattern = f"*{cls.delimiter}{pattern}" - + if '*' not in pattern: + pattern = f"{pattern}:*" return pattern @classmethod diff --git a/Services/Redis/Models/response.py b/Services/Redis/Models/response.py index a4ab3a8..cc1d85c 100644 --- a/Services/Redis/Models/response.py +++ b/Services/Redis/Models/response.py @@ -47,4 +47,7 @@ class RedisResponse: @property def first(self) -> Union[RedisRow, None]: - return self.data[0] if self.data else None + if self.data: + return self.data[0] + self.status = False + return diff --git a/Services/Redis/Models/row.py b/Services/Redis/Models/row.py index 4ac6099..86ec4c5 100644 --- a/Services/Redis/Models/row.py +++ b/Services/Redis/Models/row.py @@ -1,23 +1,23 @@ -from typing import Optional +from typing import Optional, Literal from uuid import UUID -from pydantic import BaseModel, validator +from pydantic import BaseModel, field_validator class AccessToken(BaseModel): accessToken: Optional[str] = None - userUUID: Optional[str] = None + userUUID: Optional[str | UUID] = None - @validator("userUUID", pre=True) + @field_validator("userUUID", mode="after") def validate_uuid(cls, v): """Convert UUID to string during validation.""" - if isinstance(v, UUID): - return str(v) - return v + if v is None: + return None + return str(v) def to_list(self): """Convert to list for Redis storage.""" - return [self.accessToken, self.userUUID] + return [self.accessToken, str(self.userUUID) if self.userUUID else None] @property def count(self):