diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 30af1c0..2116d7e 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,29 +6,37 @@ - + + + - + + + + + + + - - - - - - - + + + + + + + + - - - - + - + + + + @@ -71,7 +82,7 @@ - + + + + + + + + + diff --git a/ApiLayers/AllConfigs/Redis/configs.py b/ApiLayers/AllConfigs/Redis/configs.py index 9c80d61..e51c1d6 100644 --- a/ApiLayers/AllConfigs/Redis/configs.py +++ b/ApiLayers/AllConfigs/Redis/configs.py @@ -18,8 +18,6 @@ class WagRedis: ) - - class RedisValidationKeys: ENDPOINTS: str = "ENDPOINTS" VALIDATIONS: str = "VALIDATIONS" @@ -51,17 +49,29 @@ class RedisCategoryKeys: PAGE_MAPPER: str = "PAGE_MAPPER" MENU_MAPPER: str = "MENU_MAPPER" + class RedisValidationKeysAction: # LANGUAGE_MODELS:DYNAMIC:VALIDATIONS: - dynamic_validation_key: str = f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.DYNAMIC}:{RedisValidationKeys.VALIDATIONS}" + dynamic_validation_key: str = ( + f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.DYNAMIC}:{RedisValidationKeys.VALIDATIONS}" + ) # LANGUAGE_MODELS:DYNAMIC:HEADERS:REQUEST - dynamic_header_request_key: str = f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.DYNAMIC}:{RedisValidationKeys.HEADERS}:{RedisValidationKeys.REQUESTS}" + dynamic_header_request_key: str = ( + f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.DYNAMIC}:{RedisValidationKeys.HEADERS}:{RedisValidationKeys.REQUESTS}" + ) # LANGUAGE_MODELS:DYNAMIC:HEADERS:RESPONSE - dynamic_header_response_key: str = f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.DYNAMIC}:{RedisValidationKeys.HEADERS}:{RedisValidationKeys.RESPONSES}" + dynamic_header_response_key: str = ( + f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.DYNAMIC}:{RedisValidationKeys.HEADERS}:{RedisValidationKeys.RESPONSES}" + ) # LANGUAGE_MODELS:STATIC:ERRORCODES: - static_error_code_key: str = f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.STATIC}:{RedisValidationKeys.ERRORCODES}" + static_error_code_key: str = ( + f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.STATIC}:{RedisValidationKeys.ERRORCODES}" + ) # LANGUAGE_MODELS:STATIC:RESPONSES: - static_response_key: str = f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.STATIC}:{RedisValidationKeys.RESPONSES}" + static_response_key: str = ( + f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.STATIC}:{RedisValidationKeys.RESPONSES}" + ) # LANGUAGE_MODELS:STATIC:REQUESTS: - static_request_key: str = f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.STATIC}:{RedisValidationKeys.REQUESTS}" - + static_request_key: str = ( + f"{RedisValidationKeys.LANGUAGE_MODELS}:{RedisValidationKeys.STATIC}:{RedisValidationKeys.REQUESTS}" + ) diff --git a/ApiLayers/ApiServices/Token/token_handler.py b/ApiLayers/ApiServices/Token/token_handler.py index 362ab0b..edb4fa1 100644 --- a/ApiLayers/ApiServices/Token/token_handler.py +++ b/ApiLayers/ApiServices/Token/token_handler.py @@ -9,7 +9,7 @@ from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error from ApiLayers.ApiLibrary.date_time_actions.date_functions import DateTimeLocal from ApiLayers.ApiLibrary.token.password_module import PasswordModule from ApiLayers.ErrorHandlers import HTTPExceptionApi -from ApiLayers.Schemas.identity.identity import UsersTokens, People + from ApiLayers.ApiValidations.Custom.token_objects import ( EmployeeTokenObject, OccupantTokenObject, @@ -37,6 +37,7 @@ from Services.Redis import RedisActions, AccessToken if TYPE_CHECKING: from fastapi import Request + T = TypeVar("T", EmployeeTokenObject, OccupantTokenObject) @@ -55,6 +56,85 @@ class TokenService: """Get all tokens for a user from Redis.""" return RedisActions.get_json(list_keys=[f"*:{str(user.uu_id)}"]) + @classmethod + def do_employee_login( + cls, request: "Request", user: Users, domain: str + ) -> Dict[str, Any]: + """Handle employee login process and return login information.""" + from ApiLayers.Schemas.identity.identity import UsersTokens, People + db_session = Employees.new_session() + list_employee = Employees.filter_all( + Employees.people_id == user.person_id, db=db_session + ).data + + companies_uu_id_list: List[str] = [] + companies_id_list: List[int] = [] + companies_list: List[Dict[str, Any]] = [] + duty_uu_id_list: List[str] = [] + duty_id_list: List[int] = [] + + for employee in list_employee: + staff = Staff.filter_one(Staff.id == employee.staff_id, db=db_session).data + if duties := Duties.filter_one( + Duties.id == staff.duties_id, db=db_session + ).data: + if duty_found := Duty.filter_by_one( + id=duties.duties_id, db=db_session + ).data: + duty_uu_id_list.append(str(duty_found.uu_id)) + duty_id_list.append(duty_found.id) + + department = Departments.filter_one( + Departments.id == duties.department_id, db=db_session + ).data + + if company := Companies.filter_one( + Companies.id == department.company_id, db=db_session + ).data: + companies_uu_id_list.append(str(company.uu_id)) + companies_id_list.append(company.id) + company_address = Addresses.filter_by_one( + id=company.official_address_id, db=db_session + ).data + companies_list.append( + { + "uu_id": str(company.uu_id), + "public_name": company.public_name, + "company_type": company.company_type, + "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=person.id, + person_uu_id=str(person.uu_id), + full_name=person.full_name, + request=dict(request.headers), + companies_uu_id_list=companies_uu_id_list, + companies_id_list=companies_id_list, + duty_uu_id_list=duty_uu_id_list, + duty_id_list=duty_id_list, + timezone=user.local_timezone or "GMT+0", + lang="tr", + ).model_dump() + if access_token := cls.set_object_to_redis(user, model_value): + return { + "access_token": access_token, + "user_type": UserType.employee.name, + "selection_list": companies_list, + } + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Creating Token failed...", + ) + @classmethod def do_occupant_login( cls, request: "Request", user: Users, domain: str @@ -113,14 +193,17 @@ class TokenService: } else: occupants_selection_dict[build_key]["occupants"].append(occupant_data) + + person = user.person model_value = OccupantTokenObject( domain=domain, user_type=UserType.occupant.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), + full_name=person.full_name, request=dict(request.headers), available_occupants=occupants_selection_dict, timezone=user.local_timezone or "GMT+0", @@ -180,84 +263,6 @@ class TokenService: sys_msg="Saving Token failed...", ) - @classmethod - def do_employee_login( - cls, request: "Request", user: Users, domain: str - ) -> Dict[str, Any]: - """Handle employee login process and return login information.""" - db_session = Employees.new_session() - list_employee = Employees.filter_all( - Employees.people_id == user.person_id, db=db_session - ).data - - companies_uu_id_list: List[str] = [] - companies_id_list: List[int] = [] - companies_list: List[Dict[str, Any]] = [] - duty_uu_id_list: List[str] = [] - duty_id_list: List[int] = [] - - for employee in list_employee: - staff = Staff.filter_one(Staff.id == employee.staff_id, db=db_session).data - if duties := Duties.filter_one( - Duties.id == staff.duties_id, db=db_session - ).data: - if duty_found := Duty.filter_by_one( - id=duties.duties_id, db=db_session - ).data: - duty_uu_id_list.append(str(duty_found.uu_id)) - duty_id_list.append(duty_found.id) - - department = Departments.filter_one( - Departments.id == duties.department_id, db=db_session - ).data - - if company := Companies.filter_one( - Companies.id == department.company_id, db=db_session - ).data: - companies_uu_id_list.append(str(company.uu_id)) - companies_id_list.append(company.id) - company_address = Addresses.filter_by_one( - id=company.official_address_id, db=db_session - ).data - companies_list.append( - { - "uu_id": str(company.uu_id), - "public_name": company.public_name, - "company_type": company.company_type, - "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=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, - duty_uu_id_list=duty_uu_id_list, - duty_id_list=duty_id_list, - timezone=user.local_timezone or "GMT+0", - lang="tr", - ).model_dump() - if access_token := cls.set_object_to_redis(user, model_value): - return { - "access_token": access_token, - "user_type": UserType.employee.name, - "selection_list": companies_list, - } - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Creating Token failed...", - ) - @classmethod def remove_token_with_domain(cls, user: Users, domain: str) -> None: """Remove all tokens for a user with specific domain.""" @@ -282,6 +287,8 @@ class TokenService: ) -> Dict[str, Any]: """Set access token to redis and handle user session.""" from ApiLayers.AllConfigs.Token.config import Auth + from ApiLayers.Schemas.identity.identity import UsersTokens, People + cls.remove_token_with_domain(user=user, domain=domain) # Users.client_arrow = DateTimeLocal(is_client=True, timezone=user.local_timezone) login_dict, db_session = {}, UsersTokens.new_session() @@ -309,9 +316,13 @@ class TokenService: users_token.token = users_token_created users_token.save(db=db_session) else: - if arrow.now() > arrow.get(str(users_token.expires_at)): # Check if token is expired + if arrow.now() > arrow.get( + str(users_token.expires_at) + ): # Check if token is expired users_token.token = users_token_created - users_token.expires_at = str(arrow.now().datetime + Auth.TOKEN_EXPIRE_DAY_1) + users_token.expires_at = str( + arrow.now().datetime + Auth.TOKEN_EXPIRE_DAY_1 + ) users_token.save(db=db_session) else: login_dict["refresh_token"] = users_token.token diff --git a/ApiLayers/ApiValidations/Custom/token_objects.py b/ApiLayers/ApiValidations/Custom/token_objects.py index 8af2487..3bf3d40 100644 --- a/ApiLayers/ApiValidations/Custom/token_objects.py +++ b/ApiLayers/ApiValidations/Custom/token_objects.py @@ -33,6 +33,7 @@ class ApplicationToken(BaseModel): person_id: int person_uu_id: str + full_name: Optional[str] = None request: Optional[dict] = None # Request Info of Client expires_at: Optional[float] = None # Expiry timestamp diff --git a/ApiLayers/ApiValidations/Request/authentication.py b/ApiLayers/ApiValidations/Request/authentication.py index 3f2f1b7..7338095 100644 --- a/ApiLayers/ApiValidations/Request/authentication.py +++ b/ApiLayers/ApiValidations/Request/authentication.py @@ -51,7 +51,9 @@ class CreatePassword(BaseModelRegular, CreatePasswordValidation): class OccupantSelection(BaseModel): - build_living_space_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={ diff --git a/ApiLayers/ApiValidations/Request/base_validations.py b/ApiLayers/ApiValidations/Request/base_validations.py index 832193f..b1eb0b8 100644 --- a/ApiLayers/ApiValidations/Request/base_validations.py +++ b/ApiLayers/ApiValidations/Request/base_validations.py @@ -5,9 +5,8 @@ from ApiLayers.ApiValidations.handler import BaseModelRegular class ListOptions(BaseModelRegular): page: Optional[int] = 1 size: Optional[int] = 10 - order_field: Optional[str] = "id" - order_type: Optional[str] = "asc" - include_joins: Optional[list] = None + order_field: Optional[list[str]] = None + order_type: Optional[list[str]] = None query: Optional[dict] = None diff --git a/ApiLayers/ApiValidations/Response/default_response.py b/ApiLayers/ApiValidations/Response/default_response.py index 29a49dd..2f546b8 100644 --- a/ApiLayers/ApiValidations/Response/default_response.py +++ b/ApiLayers/ApiValidations/Response/default_response.py @@ -2,7 +2,9 @@ from typing import Optional from fastapi import status from fastapi.responses import JSONResponse -from ApiLayers.LanguageModels.set_defaults.static_validation_retriever import StaticValidationRetriever +from ApiLayers.LanguageModels.set_defaults.static_validation_retriever import ( + StaticValidationRetriever, +) class BaseEndpointResponse(StaticValidationRetriever): diff --git a/ApiLayers/LanguageModels/Request/Auth/login.py b/ApiLayers/LanguageModels/Request/Auth/login.py index f1a95f4..639e7c3 100644 --- a/ApiLayers/LanguageModels/Request/Auth/login.py +++ b/ApiLayers/LanguageModels/Request/Auth/login.py @@ -26,4 +26,4 @@ SelectRequestLanguageModel: Dict[str, Dict[str, str]] = { "company_uu_id": "Company UU ID", "build_living_space_uu_id": "Build Living Space UU ID", }, -} \ No newline at end of file +} diff --git a/ApiLayers/LanguageModels/Response/authentication/auth.py b/ApiLayers/LanguageModels/Response/authentication/auth.py index 8edef2a..be19940 100644 --- a/ApiLayers/LanguageModels/Response/authentication/auth.py +++ b/ApiLayers/LanguageModels/Response/authentication/auth.py @@ -1,5 +1,3 @@ - - authResponses = { "LOGIN_SELECT": { "tr": { @@ -96,5 +94,5 @@ authResponses = { "en": { "message": "Token successfully refreshed.", }, - } + }, } diff --git a/ApiLayers/LanguageModels/set_defaults/language_setters.py b/ApiLayers/LanguageModels/set_defaults/language_setters.py index 686e1e4..b6c226c 100644 --- a/ApiLayers/LanguageModels/set_defaults/language_setters.py +++ b/ApiLayers/LanguageModels/set_defaults/language_setters.py @@ -1,4 +1,7 @@ -from ApiLayers.AllConfigs.Redis.configs import RedisValidationKeysAction, RedisValidationKeys +from ApiLayers.AllConfigs.Redis.configs import ( + RedisValidationKeysAction, + RedisValidationKeys, +) from ApiLayers.AllConfigs.main import LanguageConfig from Events.Engine.set_defaults.category_cluster_models import CategoryClusterController from Services.Redis.Actions.actions import RedisActions @@ -28,7 +31,9 @@ class SetDefaultLanguageModelsRedis: for lang in list(LanguageConfig.SUPPORTED_LANGUAGES): for code, dict_to_set in response.items(): # [SAVE]REDIS => LANGUAGE_MODELS:STATIC:RESPONSES:{ResponseCode}:tr = {...} - set_key = f"{RedisValidationKeysAction.static_response_key}:{code}:{lang}" + set_key = ( + f"{RedisValidationKeysAction.static_response_key}:{code}:{lang}" + ) RedisActions.set_json(list_keys=[set_key], value=dict_to_set[lang]) self.std_out += f"Language Response Models are set to Redis\n" @@ -81,10 +86,18 @@ class SetClusterLanguageModelsRedis: self.std_out += f"Setting models from cluster : {cluster_control.name}\n" for endpoint in cluster_control.category_cluster.ENDPOINTS.values(): for key_event, event in endpoint.EVENTS.items(): - merged_language_dict = self.merge_language_dicts(event.LANGUAGE_MODELS) - request_validation = getattr(event.REQUEST_VALIDATOR, "model_fields", None) - response_validation = getattr(event.RESPONSE_VALIDATOR, "model_fields", None) - objects_missing = bool(request_validation) and bool(merged_language_dict) + merged_language_dict = self.merge_language_dicts( + event.LANGUAGE_MODELS + ) + request_validation = getattr( + event.REQUEST_VALIDATOR, "model_fields", None + ) + response_validation = getattr( + event.RESPONSE_VALIDATOR, "model_fields", None + ) + objects_missing = bool(request_validation) and bool( + merged_language_dict + ) if not objects_missing: continue if merged_language_dict: @@ -105,17 +118,23 @@ class SetClusterLanguageModelsRedis: [SAVE]REDIS => LANGUAGE_MODELS:DYNAMIC:HEADERS:REQUEST:{FunctionCode}:tr = {...} Get Request BaseModel pydantic model_fields of each event and set headers which are included in model_fields """ - for lang in list(LanguageConfig.SUPPORTED_LANGUAGES): # Iterate(languages ["tr", "en"]) + for lang in list( + LanguageConfig.SUPPORTED_LANGUAGES + ): # Iterate(languages ["tr", "en"]) for key_field in self.events_rq_dict.keys(): # Iterate(function_code) request_model = self.events_rq_dict[key_field] if not request_model: - self.std_out += f"Request validation model not found for {key_field}\n" + self.std_out += ( + f"Request validation model not found for {key_field}\n" + ) continue if ( key_field not in self.events_rq_dict or key_field not in self.events_lm_dict ): - self.std_out += f"Request language model are missing {key_field}\n" + self.std_out += ( + f"Request language model are missing {key_field}\n" + ) continue value_to_set = {} @@ -130,17 +149,23 @@ class SetClusterLanguageModelsRedis: [SAVE]REDIS => LANGUAGE_MODELS:DYNAMIC:HEADERS:RESPONSE:{FunctionCode}:en = {...} Get Response BaseModel pydantic model_fields of each event and set headers which are included in model_fields """ - for lang in list(LanguageConfig.SUPPORTED_LANGUAGES): # Iterate(languages ["tr", "en"]) + for lang in list( + LanguageConfig.SUPPORTED_LANGUAGES + ): # Iterate(languages ["tr", "en"]) for key_field in self.events_rs_dict.keys(): # Iterate(function_code) response_model = self.events_rs_dict[key_field] if not response_model: - self.std_out += f"Response validation model not found for {key_field}\n" + self.std_out += ( + f"Response validation model not found for {key_field}\n" + ) continue if ( key_field not in self.events_rs_dict or key_field not in self.events_lm_dict ): - self.std_out += f"Response language model are missing {key_field}\n" + self.std_out += ( + f"Response language model are missing {key_field}\n" + ) continue value_to_set = {} diff --git a/ApiLayers/LanguageModels/set_defaults/static_validation_retriever.py b/ApiLayers/LanguageModels/set_defaults/static_validation_retriever.py index 2e5e996..764b4eb 100644 --- a/ApiLayers/LanguageModels/set_defaults/static_validation_retriever.py +++ b/ApiLayers/LanguageModels/set_defaults/static_validation_retriever.py @@ -15,9 +15,12 @@ class StaticValidationRetriever: @property def response(self) -> Optional[dict]: language_model = RedisActions.get_json( - list_keys=[RedisValidationKeysAction.static_response_key, self.code, self.lang] + list_keys=[ + RedisValidationKeysAction.static_response_key, + self.code, + self.lang, + ] ) if language_model.status: return language_model.first return {"message": f"{self.code} -> Language model not found"} - diff --git a/ApiLayers/Schemas/identity/identity.py b/ApiLayers/Schemas/identity/identity.py index f1df41d..587957a 100644 --- a/ApiLayers/Schemas/identity/identity.py +++ b/ApiLayers/Schemas/identity/identity.py @@ -16,7 +16,7 @@ from sqlalchemy import ( from sqlalchemy.orm import mapped_column, relationship, Mapped from Services.PostgresDb import CrudCollection -from config import ApiStatic +# from config import ApiStatic from ApiLayers.ApiLibrary.date_time_actions.date_functions import system_arrow from ApiLayers.ApiLibrary.extensions.select import ( @@ -24,7 +24,7 @@ from ApiLayers.ApiLibrary.extensions.select import ( SelectActionWithEmployee, ) from ApiLayers.AllConfigs.Token.config import Auth -from ApiLayers.ApiServices.Login.user_login_handler import UserLoginModule + from ApiLayers.ApiValidations.Request import InsertUsers, InsertPerson from ApiLayers.LanguageModels.Database.identity.identity import ( UsersTokensLanguageModel, @@ -66,7 +66,7 @@ class UsersTokens(CrudCollection): # users = relationship("Users", back_populates="tokens", foreign_keys=[user_id]) -class Users(CrudCollection, UserLoginModule, SelectAction): +class Users(CrudCollection, SelectAction): """ Application User frame to connect to API with assigned token-based HTTP connection """ diff --git a/DockerApiServices/AuthServiceApi/config.py b/DockerApiServices/AuthServiceApi/config.py index 5b46739..b8dd8f9 100644 --- a/DockerApiServices/AuthServiceApi/config.py +++ b/DockerApiServices/AuthServiceApi/config.py @@ -69,4 +69,3 @@ class LanguageConfig: SUPPORTED_LANGUAGES = ["en", "tr"] DEFAULT_LANGUAGE = "tr" - diff --git a/DockerApiServices/InitServiceApi/config.py b/DockerApiServices/InitServiceApi/config.py index 7068848..8b7eb16 100644 --- a/DockerApiServices/InitServiceApi/config.py +++ b/DockerApiServices/InitServiceApi/config.py @@ -70,6 +70,7 @@ class LanguageConfig: SUPPORTED_LANGUAGES = ["en", "tr"] DEFAULT_LANGUAGE = "tr" + class ValidationsConfig: SUPPORTED_VALIDATIONS = ["header", "validation", "all"] diff --git a/Events/AllEvents/authentication/auth/auth.py b/Events/AllEvents/authentication/auth/auth.py index dc75e66..4ae7313 100644 --- a/Events/AllEvents/authentication/auth/auth.py +++ b/Events/AllEvents/authentication/auth/auth.py @@ -169,7 +169,9 @@ AuthenticationChangePasswordEventMethods = MethodToEvent( ) -def authentication_change_password_event_callable(request: Request, data: EndpointBaseRequestModel): +def authentication_change_password_event_callable( + request: Request, data: EndpointBaseRequestModel +): context_retriever = ContextRetrievers( func=authentication_change_password_event_callable ) diff --git a/Events/AllEvents/authentication/auth/function_handlers.py b/Events/AllEvents/authentication/auth/function_handlers.py index 386ae6b..914edf6 100644 --- a/Events/AllEvents/authentication/auth/function_handlers.py +++ b/Events/AllEvents/authentication/auth/function_handlers.py @@ -288,25 +288,30 @@ class AuthenticationFunctions(BaseRouteModel): """Refresh user info using access token""" if cls.context_retriever.token: db = Users.new_session() - if found_user := Users.filter_one(Users.id == cls.context_retriever.token.user_id, db=db).data: + if found_user := Users.filter_one( + Users.id == cls.context_retriever.token.user_id, db=db + ).data: return EndpointSuccessResponse( code="USER_INFO_REFRESHED", lang=cls.context_retriever.token.lang - ).as_dict({ - "access_token": cls.context_retriever.get_token, "user": found_user.get_dict(), - }) + ).as_dict( + { + "access_token": cls.context_retriever.get_token, + "user": found_user.get_dict(), + } + ) if not found_user: return EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=cls.context_retriever.token.lang - ).as_dict( - data={} - ) + ).as_dict(data={}) @classmethod # Requires no auth context def authentication_change_password(cls, data: Any): """Change password with access token""" if cls.context_retriever.token: db = Users.new_session() - if found_user := Users.filter_one(Users.id == cls.context_retriever.token.user_id, db=db).data: + if found_user := Users.filter_one( + Users.id == cls.context_retriever.token.user_id, db=db + ).data: found_user.set_password(data.new_password) return EndpointSuccessResponse( code="PASSWORD_CHANGED", lang=cls.context_retriever.token.lang @@ -314,9 +319,7 @@ class AuthenticationFunctions(BaseRouteModel): if not found_user: return EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=cls.context_retriever.token.lang - ).as_dict( - data={} - ) + ).as_dict(data={}) @classmethod # Requires not auth context def authentication_create_password(cls, data: Any): @@ -325,11 +328,9 @@ class AuthenticationFunctions(BaseRouteModel): if not data.re_password == data.password: return EndpointNotAcceptableResponse( code="PASSWORD_NOT_MATCH", lang=cls.context_retriever.token.lang - ).as_dict( - data={"password": data.password, "re_password": data.re_password} - ) + ).as_dict(data={"password": data.password, "re_password": data.re_password}) if found_user := Users.filter_one( - Users.password_token == data.password_token, db=db + Users.password_token == data.password_token, db=db ).data: found_user.create_password(found_user=found_user, password=data.password) found_user.password_token = "" @@ -346,11 +347,9 @@ class AuthenticationFunctions(BaseRouteModel): Users.id == cls.context_retriever.token.user_id, db=db ).data if not found_user: - return EndpointNotAcceptableResponse( + return EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=cls.context_retriever.token.lang - ).as_dict( - data={} - ) + ).as_dict(data={}) registered_tokens = UsersTokens.filter_all( UsersTokens.user_id == cls.context_retriever.token.user_id, db=db ) @@ -375,9 +374,7 @@ class AuthenticationFunctions(BaseRouteModel): if not found_user: return EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=cls.context_retriever.token.lang - ).as_dict( - data={} - ) + ).as_dict(data={}) registered_tokens = UsersTokens.filter_all_system( UsersTokens.user_id == cls.context_retriever.token.user_id, UsersTokens.domain == cls.context_retriever.token.domain, @@ -404,6 +401,7 @@ class AuthenticationFunctions(BaseRouteModel): """ import arrow from ApiLayers.ApiServices.Token.token_handler import TokenService + db = UsersTokens.new_session() token_refresher: UsersTokens = UsersTokens.filter_by_one( token=data.refresh_token, @@ -414,11 +412,11 @@ class AuthenticationFunctions(BaseRouteModel): if not token_refresher: return EndpointNotAcceptableResponse( code="REFRESHER_NOT_FOUND", lang=language - ).as_dict( - data={"refresh_token": data.refresh_token} - ) + ).as_dict(data={"refresh_token": data.refresh_token}) - if found_user := Users.filter_one(Users.id == token_refresher.user_id, db=db).data: + if found_user := Users.filter_one( + Users.id == token_refresher.user_id, db=db + ).data: token_created = TokenService.set_access_token_to_redis( request=request, user=found_user, @@ -427,51 +425,63 @@ class AuthenticationFunctions(BaseRouteModel): ) 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_remote_addr = getattr( + request, "remote_addr", None + ) or request.headers.get("X-Forwarded-For", None) found_user.last_seen = str(arrow.now()) response_data = { "access_token": token_created.get("access_token"), "refresh_token": data.refresh_token, } - return EndpointSuccessResponse(code="TOKEN_REFRESH", lang=language).as_dict(data=response_data) + return EndpointSuccessResponse(code="TOKEN_REFRESH", lang=language).as_dict( + data=response_data + ) raise EndpointNotAcceptableResponse( code="USER_NOT_FOUND", lang=language - ).as_dict( - data={} - ) + ).as_dict(data={}) @classmethod # Requires not auth context def authentication_forgot_password(cls, data: Any): """Send an email to user for a valid password reset token""" import arrow from ApiLayers.ApiServices.Token.token_handler import TokenService - from ApiLayers.AllConfigs.Templates.password_templates import change_your_password_template + from ApiLayers.AllConfigs.Templates.password_templates import ( + change_your_password_template, + ) from Services.Email.send_email import email_sender from config import ApiStatic db = Users.new_session() request = cls.context_retriever.request - found_user: Users = Users.check_user_exits(access_key=data.access_key, domain=data.domain) + found_user: Users = Users.check_user_exits( + access_key=data.access_key, domain=data.domain + ) forgot_key = TokenService._create_access_token(access=False) forgot_link = ApiStatic.forgot_link(forgot_key=forgot_key) send_email_completed = email_sender.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), + html=change_your_password_template( + user_name=found_user.user_tag, forgot_link=forgot_link + ), ) if not send_email_completed: return EndpointBadRequestResponse( code="EMAIL_NOT_SENT", lang=cls.context_retriever.token.lang - ).as_dict( - data={"email": found_user.email} - ) + ).as_dict(data={"email": found_user.email}) found_user.password_token = forgot_key found_user.password_token_is_valid = str(arrow.now().shift(days=1)) found_user.save(db=db) return EndpointSuccessResponse( code="FORGOT_PASSWORD", lang=cls.context_retriever.token.lang - ).as_dict(data={"user": found_user.get_dict(), "forgot_link": forgot_link, "token": forgot_key}) + ).as_dict( + data={ + "user": found_user.get_dict(), + "forgot_link": forgot_link, + "token": forgot_key, + } + ) @classmethod # Requires not auth context def authentication_reset_password(cls, data: Any): @@ -482,12 +492,15 @@ class AuthenticationFunctions(BaseRouteModel): def authentication_download_avatar(cls): """Download avatar icon and profile info of user""" import arrow + db = Users.new_session() if found_user := Users.filter_one( - Users.id == cls.context_retriever.token.user_id, db=db + Users.id == cls.context_retriever.token.user_id, db=db ).data: expired_starts = str(arrow.now() - arrow.get(str(found_user.expiry_ends))) - expired_int = arrow.now().datetime - arrow.get(str(found_user.expiry_ends)).datetime + expired_int = ( + arrow.now().datetime - arrow.get(str(found_user.expiry_ends)).datetime + ) user_info = { "lang": cls.context_retriever.token.lang, "full_name": found_user.person.full_name, diff --git a/Events/AllEvents/authentication/auth/info.py b/Events/AllEvents/authentication/auth/info.py index 577eed9..dc58175 100644 --- a/Events/AllEvents/authentication/auth/info.py +++ b/Events/AllEvents/authentication/auth/info.py @@ -7,5 +7,5 @@ authentication_page_info = PageInfo( description={"en": "Authentication"}, icon="", parent="", - url="" + url="", ) diff --git a/Events/AllEvents/authentication/auth/models.py b/Events/AllEvents/authentication/auth/models.py index 4e041ef..f4572e6 100644 --- a/Events/AllEvents/authentication/auth/models.py +++ b/Events/AllEvents/authentication/auth/models.py @@ -13,7 +13,8 @@ from ApiLayers.ApiValidations.Request import ( class AuthenticationRequestModels: LoginSuperUserRequestModel = Login SelectCompanyOrOccupantTypeSuperUserRequestModel = { - "EmployeeSelection": EmployeeSelection, "OccupantSelection": OccupantSelection, + "EmployeeSelection": EmployeeSelection, + "OccupantSelection": OccupantSelection, } RefresherRequestModel = Remember LogoutRequestModel = Logout diff --git a/Events/AllEvents/events/account/account_records.py b/Events/AllEvents/events/account/account_records.py index 4c73128..5024278 100644 --- a/Events/AllEvents/events/account/account_records.py +++ b/Events/AllEvents/events/account/account_records.py @@ -1,351 +1,39 @@ """ -Account records service implementation. +template related API endpoints. """ -from typing import Union -from pydantic import Field +from typing import Any, Dict +from fastapi import Request -from ApiEvents.abstract_class import MethodToEvent, endpoint_wrapper -from ApiEvents.base_request_model import DictRequestModel -from ApiValidations.Custom.token_objects import ( - OccupantTokenObject, - EmployeeTokenObject, -) -from ApiLibrary import system_arrow -from ApiValidations.Request.account_records import ( - InsertAccountRecord, - UpdateAccountRecord, -) -from ApiValidations.Request.base_validations import ListOptions -from Schemas import ( - BuildLivingSpace, - AccountRecords, - BuildIbans, - BuildDecisionBookPayments, - ApiEnumDropdown, -) -from Services.PostgresDb.Models.alchemy_response import ( - AlchemyJsonResponse, -) -from ApiValidations.Response import AccountRecordResponse -from .models import ( - InsertAccountRecordRequestModel, - UpdateAccountRecordRequestModel, - ListOptionsRequestModel, +from Events.Engine.abstract_class import MethodToEvent +from Events.base_request_model import EndpointBaseRequestModel, ContextRetrievers +from .account_records import template_event + + +AuthenticationLoginEventMethods = MethodToEvent( + name="AuthenticationLoginEventMethods", + events={ + template_event.key: template_event, + }, + headers=[], + errors=[], + url="/login", + method="POST", + summary="Login via domain and access key : [email] | [phone]", + description="Login to the system via domain, access key : [email] | [phone]", ) -class AccountListEventMethod(MethodToEvent): - - event_type = "SELECT" - event_description = "" - event_category = "" - - __event_keys__ = { - "7192c2aa-5352-4e36-98b3-dafb7d036a3d": "account_records_list", - "208e6273-17ef-44f0-814a-8098f816b63a": "account_records_list_flt_res", - } - __event_validation__ = { - "7192c2aa-5352-4e36-98b3-dafb7d036a3d": ( - AccountRecordResponse, - [AccountRecords.__language_model__], - ), - "208e6273-17ef-44f0-814a-8098f816b63a": ( - AccountRecordResponse, - [AccountRecords.__language_model__], - ), - } - - @classmethod - def account_records_list( - cls, - list_options: ListOptionsRequestModel, - token_dict: Union[EmployeeTokenObject, OccupantTokenObject], - ): - db_session = AccountRecords.new_session() - if isinstance(token_dict, OccupantTokenObject): - AccountRecords.pre_query = AccountRecords.filter_all( - AccountRecords.company_id - == token_dict.selected_occupant.responsible_company_id, - db=db_session, - ).query - elif isinstance(token_dict, EmployeeTokenObject): - AccountRecords.pre_query = AccountRecords.filter_all( - AccountRecords.company_id == token_dict.selected_company.company_id, - db=db_session, - ).query - AccountRecords.filter_attr = list_options - records = AccountRecords.filter_all(db=db_session) - return AlchemyJsonResponse( - completed=True, - message="Account records listed successfully", - result=records, - cls_object=AccountRecords, - filter_attributes=list_options, - response_model=AccountRecordResponse, - ) - - @classmethod - def account_records_list_flt_res( - cls, - list_options: ListOptionsRequestModel, - token_dict: Union[EmployeeTokenObject, OccupantTokenObject], - ): - db_session = AccountRecords.new_session() - if not isinstance(token_dict, OccupantTokenObject): - raise AccountRecords.raise_http_exception( - status_code="HTTP_404_NOT_FOUND", - error_case="UNAUTHORIZED", - message="Only Occupant can see this data", - data={}, - ) - - return_list = [] - living_space: BuildLivingSpace = BuildLivingSpace.filter_by_one( - id=token_dict.selected_occupant.living_space_id - ).data - if not living_space: - raise AccountRecords.raise_http_exception( - status_code="HTTP_404_NOT_FOUND", - error_case="UNAUTHORIZED", - message="Living space not found", - data={}, - ) - - if not list_options: - list_options = ListOptions() - - main_filters = [ - AccountRecords.living_space_id - == token_dict.selected_occupant.living_space_id, - BuildDecisionBookPayments.process_date - >= str(system_arrow.now().shift(months=-3).date()), - BuildDecisionBookPayments.process_date - < str(system_arrow.find_last_day_of_month(living_space.expiry_ends)), - BuildDecisionBookPayments.process_date - >= str(system_arrow.get(living_space.expiry_starts)), - BuildDecisionBookPayments.is_confirmed == True, - AccountRecords.active == True, - ] - order_type = "desc" - if list_options.order_type: - order_type = "asc" if list_options.order_type[0] == "a" else "desc" - - order_by_list = BuildDecisionBookPayments.process_date.desc() - if list_options.order_field: - if list_options.order_field == "process_date": - order_by_list = ( - BuildDecisionBookPayments.process_date.asc() - if order_type == "asc" - else BuildDecisionBookPayments.process_date.desc() - ) - if list_options.order_field == "bank_date": - order_by_list = ( - AccountRecords.bank_date.desc() - if order_type == "asc" - else AccountRecords.bank_date.asc() - ) - if list_options.order_field == "currency_value": - order_by_list = ( - AccountRecords.currency_value.desc() - if order_type == "asc" - else AccountRecords.currency_value.asc() - ) - if list_options.order_field == "process_comment": - order_by_list = ( - AccountRecords.process_comment.desc() - if order_type == "asc" - else AccountRecords.process_comment.asc() - ) - if list_options.order_field == "payment_amount": - order_by_list = ( - BuildDecisionBookPayments.payment_amount.desc() - if order_type == "asc" - else BuildDecisionBookPayments.payment_amount.asc() - ) - - if list_options.query: - for key, value in list_options.query.items(): - if key == "process_date": - main_filters.append(BuildDecisionBookPayments.process_date == value) - if key == "bank_date": - main_filters.append(AccountRecords.bank_date == value) - if key == "currency": - main_filters.append(BuildDecisionBookPayments.currency == value) - if key == "currency_value": - main_filters.append(AccountRecords.currency_value == value) - if key == "process_comment": - main_filters.append(AccountRecords.process_comment == value) - if key == "payment_amount": - main_filters.append( - BuildDecisionBookPayments.payment_amount == value - ) - - query = ( - AccountRecords.session.query( - BuildDecisionBookPayments.process_date, - BuildDecisionBookPayments.payment_amount, - BuildDecisionBookPayments.currency, - AccountRecords.bank_date, - AccountRecords.currency_value, - AccountRecords.process_comment, - BuildDecisionBookPayments.uu_id, - ) - .join( - AccountRecords, - AccountRecords.id == BuildDecisionBookPayments.account_records_id, - ) - .filter(*main_filters) - ).order_by(order_by_list) - - query.limit(list_options.size or 5).offset( - (list_options.page or 1 - 1) * list_options.size or 5 - ) - for list_of_values in query.all() or []: - return_list.append( - { - "process_date": list_of_values[0], - "payment_amount": list_of_values[1], - "currency": list_of_values[2], - "bank_date": list_of_values[3], - "currency_value": list_of_values[4], - "process_comment": list_of_values[5], - } - ) - return AlchemyJsonResponse( - completed=True, - message="Account records listed successfully", - result=return_list, - cls_object=AccountRecords, - filter_attributes=list_options, - response_model=AccountRecordResponse, - ) +def authentication_login_with_domain_and_creds_endpoint( + request: Request, data: EndpointBaseRequestModel +) -> Dict[str, Any]: + event_2_catch = AuthenticationLoginEventMethods.retrieve_event( + event_function_code=f"{template_event.key}" + ) + data = event_2_catch.REQUEST_VALIDATOR(**data.data) + return event_2_catch.endpoint_callable(request=request, data=data) -class AccountCreateEventMethod(MethodToEvent): - - event_type = "CREATE" - event_description = "" - event_category = "" - - __event_keys__ = { - "31f4f32f-0cd4-4995-8a6a-f9f56335848a": "account_records_create", - } - __event_validation__ = { - "31f4f32f-0cd4-4995-8a6a-f9f56335848a": ( - InsertAccountRecord, - [AccountRecords.__language_model__], - ), - } - - @classmethod - def account_records_create( - cls, - data: InsertAccountRecordRequestModel, - token_dict: Union[EmployeeTokenObject, OccupantTokenObject], - ): - data_dict = data.excluded_dump() - if isinstance(token_dict, OccupantTokenObject): - db_session = AccountRecords.new_session() - build_iban = BuildIbans.filter_one( - BuildIbans.iban == data.iban, - BuildIbans.build_id == token_dict.selected_occupant.build_id, - db=db_session, - ).data - if not build_iban: - raise BuildIbans.raise_http_exception( - status_code="HTTP_404_NOT_FOUND", - error_case="UNAUTHORIZED", - message=f"{data.iban} is not found in company related to your organization", - data={ - "iban": data.iban, - }, - ) - account_record = AccountRecords.find_or_create(**data.excluded_dump()) - return AlchemyJsonResponse( - completed=True, - message="Account record created successfully", - result=account_record, - ) - elif isinstance(token_dict, EmployeeTokenObject): - # Build.pre_query = Build.select_action( - # employee_id=token_dict.selected_employee.employee_id, - # ) - # build_ids_list = Build.filter_all( - # ) - # build_iban = BuildIbans.filter_one( - # BuildIbans.iban == data.iban, - # BuildIbans.build_id.in_([build.id for build in build_ids_list.data]), - # ).data - # if not build_iban: - # BuildIbans.raise_http_exception( - # status_code="HTTP_404_NOT_FOUND", - # error_case="UNAUTHORIZED", - # message=f"{data.iban} is not found in company related to your organization", - # data={ - # "iban": data.iban, - # }, - # ) - bank_date = system_arrow.get(data.bank_date) - data_dict["bank_date_w"] = bank_date.weekday() - data_dict["bank_date_m"] = bank_date.month - data_dict["bank_date_d"] = bank_date.day - data_dict["bank_date_y"] = bank_date.year - - if int(data.currency_value) < 0: - debit_type = ApiEnumDropdown.filter_by_one( - system=True, enum_class="DebitTypes", key="DT-D" - ).data - data_dict["receive_debit"] = debit_type.id - data_dict["receive_debit_uu_id"] = str(debit_type.uu_id) - else: - debit_type = ApiEnumDropdown.filter_by_one( - system=True, enum_class="DebitTypes", key="DT-R" - ).data - data_dict["receive_debit"] = debit_type.id - data_dict["receive_debit_uu_id"] = str(debit_type.uu_id) - - account_record = AccountRecords.insert_one(data_dict).data - return AlchemyJsonResponse( - completed=True, - message="Account record created successfully", - result=account_record, - ) - - -class AccountUpdateEventMethod(MethodToEvent): - - event_type = "UPDATE" - event_description = "" - event_category = "" - - __event_keys__ = { - "ec98ef2c-bcd0-432d-a8f4-1822a56c33b2": "account_records_update", - } - __event_validation__ = { - "ec98ef2c-bcd0-432d-a8f4-1822a56c33b2": ( - UpdateAccountRecord, - [AccountRecords.__language_model__], - ), - } - - @classmethod - def account_records_update( - cls, - build_uu_id: str, - data: UpdateAccountRecordRequestModel, - token_dict: Union[EmployeeTokenObject, OccupantTokenObject], - ): - if isinstance(token_dict, OccupantTokenObject): - pass - elif isinstance(token_dict, EmployeeTokenObject): - pass - AccountRecords.build_parts_id = token_dict.selected_occupant.build_part_id - account_record = AccountRecords.update_one(build_uu_id, data).data - return AlchemyJsonResponse( - completed=True, - message="Account record updated successfully", - result=account_record, - cls_object=AccountRecords, - response_model=UpdateAccountRecord, - ) +AuthenticationLoginEventMethods.endpoint_callable = ( + authentication_login_with_domain_and_creds_endpoint +) diff --git a/Events/AllEvents/events/account/api_events.py b/Events/AllEvents/events/account/api_events.py new file mode 100644 index 0000000..e69de29 diff --git a/Events/AllEvents/events/account/cluster.py b/Events/AllEvents/events/account/cluster.py new file mode 100644 index 0000000..294aee0 --- /dev/null +++ b/Events/AllEvents/events/account/cluster.py @@ -0,0 +1,15 @@ +from Events.Engine.abstract_class import CategoryCluster + +# from info import template_page_info + + +AccountCluster = CategoryCluster( + name="AccountCluster", + tags=["template"], + prefix="/accounts", + description="Account Cluster", + pageinfo=None, + endpoints={}, + include_in_schema=True, + sub_category=[], +) diff --git a/Events/AllEvents/events/account/function_handlers.py b/Events/AllEvents/events/account/function_handlers.py new file mode 100644 index 0000000..0ad4bcc --- /dev/null +++ b/Events/AllEvents/events/account/function_handlers.py @@ -0,0 +1,350 @@ +""" +Account records service implementation. +""" + +from typing import Union +from pydantic import Field + +from Events.base_request_model import TokenDictType, BaseRouteModel +from ApiLayers.ApiValidations.Custom.token_objects import ( + OccupantTokenObject, + EmployeeTokenObject, +) +from ApiLayers.ApiLibrary import system_arrow +from ApiLayers.ApiValidations.Request.account_records import ( + InsertAccountRecord, + UpdateAccountRecord, +) +from ApiLayers.ApiValidations.Request.base_validations import ListOptions +from ApiLayers.Schemas import ( + BuildLivingSpace, + AccountRecords, + BuildIbans, + BuildDecisionBookPayments, + ApiEnumDropdown, +) +from Services.PostgresDb.Models.response import ( + PostgresResponse, +) +from ApiLayers.ApiValidations.Response import AccountRecordResponse +from .models import ( + InsertAccountRecordRequestModel, + UpdateAccountRecordRequestModel, + ListOptionsRequestModel, +) + + +class AccountListEventMethod(BaseRouteModel): + + event_type = "SELECT" + event_description = "" + event_category = "" + + __event_keys__ = { + "7192c2aa-5352-4e36-98b3-dafb7d036a3d": "account_records_list", + "208e6273-17ef-44f0-814a-8098f816b63a": "account_records_list_flt_res", + } + __event_validation__ = { + "7192c2aa-5352-4e36-98b3-dafb7d036a3d": ( + AccountRecordResponse, + [AccountRecords.__language_model__], + ), + "208e6273-17ef-44f0-814a-8098f816b63a": ( + AccountRecordResponse, + [AccountRecords.__language_model__], + ), + } + + @classmethod + def account_records_list( + cls, + list_options: ListOptionsRequestModel, + token_dict: Union[EmployeeTokenObject, OccupantTokenObject], + ): + db_session = AccountRecords.new_session() + if isinstance(token_dict, OccupantTokenObject): + AccountRecords.pre_query = AccountRecords.filter_all( + AccountRecords.company_id + == token_dict.selected_occupant.responsible_company_id, + db=db_session, + ).query + elif isinstance(token_dict, EmployeeTokenObject): + AccountRecords.pre_query = AccountRecords.filter_all( + AccountRecords.company_id == token_dict.selected_company.company_id, + db=db_session, + ).query + AccountRecords.filter_attr = list_options + records = AccountRecords.filter_all(db=db_session) + return AlchemyJsonResponse( + completed=True, + message="Account records listed successfully", + result=records, + cls_object=AccountRecords, + filter_attributes=list_options, + response_model=AccountRecordResponse, + ) + + @classmethod + def account_records_list_flt_res( + cls, + list_options: ListOptionsRequestModel, + token_dict: Union[EmployeeTokenObject, OccupantTokenObject], + ): + db_session = AccountRecords.new_session() + if not isinstance(token_dict, OccupantTokenObject): + raise AccountRecords.raise_http_exception( + status_code="HTTP_404_NOT_FOUND", + error_case="UNAUTHORIZED", + message="Only Occupant can see this data", + data={}, + ) + + return_list = [] + living_space: BuildLivingSpace = BuildLivingSpace.filter_by_one( + id=token_dict.selected_occupant.living_space_id + ).data + if not living_space: + raise AccountRecords.raise_http_exception( + status_code="HTTP_404_NOT_FOUND", + error_case="UNAUTHORIZED", + message="Living space not found", + data={}, + ) + + if not list_options: + list_options = ListOptions() + + main_filters = [ + AccountRecords.living_space_id + == token_dict.selected_occupant.living_space_id, + BuildDecisionBookPayments.process_date + >= str(system_arrow.now().shift(months=-3).date()), + BuildDecisionBookPayments.process_date + < str(system_arrow.find_last_day_of_month(living_space.expiry_ends)), + BuildDecisionBookPayments.process_date + >= str(system_arrow.get(living_space.expiry_starts)), + BuildDecisionBookPayments.is_confirmed == True, + AccountRecords.active == True, + ] + order_type = "desc" + if list_options.order_type: + order_type = "asc" if list_options.order_type[0] == "a" else "desc" + + order_by_list = BuildDecisionBookPayments.process_date.desc() + if list_options.order_field: + if list_options.order_field == "process_date": + order_by_list = ( + BuildDecisionBookPayments.process_date.asc() + if order_type == "asc" + else BuildDecisionBookPayments.process_date.desc() + ) + if list_options.order_field == "bank_date": + order_by_list = ( + AccountRecords.bank_date.desc() + if order_type == "asc" + else AccountRecords.bank_date.asc() + ) + if list_options.order_field == "currency_value": + order_by_list = ( + AccountRecords.currency_value.desc() + if order_type == "asc" + else AccountRecords.currency_value.asc() + ) + if list_options.order_field == "process_comment": + order_by_list = ( + AccountRecords.process_comment.desc() + if order_type == "asc" + else AccountRecords.process_comment.asc() + ) + if list_options.order_field == "payment_amount": + order_by_list = ( + BuildDecisionBookPayments.payment_amount.desc() + if order_type == "asc" + else BuildDecisionBookPayments.payment_amount.asc() + ) + + if list_options.query: + for key, value in list_options.query.items(): + if key == "process_date": + main_filters.append(BuildDecisionBookPayments.process_date == value) + if key == "bank_date": + main_filters.append(AccountRecords.bank_date == value) + if key == "currency": + main_filters.append(BuildDecisionBookPayments.currency == value) + if key == "currency_value": + main_filters.append(AccountRecords.currency_value == value) + if key == "process_comment": + main_filters.append(AccountRecords.process_comment == value) + if key == "payment_amount": + main_filters.append( + BuildDecisionBookPayments.payment_amount == value + ) + + query = ( + AccountRecords.session.query( + BuildDecisionBookPayments.process_date, + BuildDecisionBookPayments.payment_amount, + BuildDecisionBookPayments.currency, + AccountRecords.bank_date, + AccountRecords.currency_value, + AccountRecords.process_comment, + BuildDecisionBookPayments.uu_id, + ) + .join( + AccountRecords, + AccountRecords.id == BuildDecisionBookPayments.account_records_id, + ) + .filter(*main_filters) + ).order_by(order_by_list) + + query.limit(list_options.size or 5).offset( + (list_options.page or 1 - 1) * list_options.size or 5 + ) + for list_of_values in query.all() or []: + return_list.append( + { + "process_date": list_of_values[0], + "payment_amount": list_of_values[1], + "currency": list_of_values[2], + "bank_date": list_of_values[3], + "currency_value": list_of_values[4], + "process_comment": list_of_values[5], + } + ) + return AlchemyJsonResponse( + completed=True, + message="Account records listed successfully", + result=return_list, + cls_object=AccountRecords, + filter_attributes=list_options, + response_model=AccountRecordResponse, + ) + + +class AccountCreateEventMethod(BaseRouteModel): + + event_type = "CREATE" + event_description = "" + event_category = "" + + __event_keys__ = { + "31f4f32f-0cd4-4995-8a6a-f9f56335848a": "account_records_create", + } + __event_validation__ = { + "31f4f32f-0cd4-4995-8a6a-f9f56335848a": ( + InsertAccountRecord, + [AccountRecords.__language_model__], + ), + } + + @classmethod + def account_records_create( + cls, + data: InsertAccountRecordRequestModel, + token_dict: Union[EmployeeTokenObject, OccupantTokenObject], + ): + data_dict = data.excluded_dump() + if isinstance(token_dict, OccupantTokenObject): + db_session = AccountRecords.new_session() + build_iban = BuildIbans.filter_one( + BuildIbans.iban == data.iban, + BuildIbans.build_id == token_dict.selected_occupant.build_id, + db=db_session, + ).data + if not build_iban: + raise BuildIbans.raise_http_exception( + status_code="HTTP_404_NOT_FOUND", + error_case="UNAUTHORIZED", + message=f"{data.iban} is not found in company related to your organization", + data={ + "iban": data.iban, + }, + ) + account_record = AccountRecords.find_or_create(**data.excluded_dump()) + return AlchemyJsonResponse( + completed=True, + message="Account record created successfully", + result=account_record, + ) + elif isinstance(token_dict, EmployeeTokenObject): + # Build.pre_query = Build.select_action( + # employee_id=token_dict.selected_employee.employee_id, + # ) + # build_ids_list = Build.filter_all( + # ) + # build_iban = BuildIbans.filter_one( + # BuildIbans.iban == data.iban, + # BuildIbans.build_id.in_([build.id for build in build_ids_list.data]), + # ).data + # if not build_iban: + # BuildIbans.raise_http_exception( + # status_code="HTTP_404_NOT_FOUND", + # error_case="UNAUTHORIZED", + # message=f"{data.iban} is not found in company related to your organization", + # data={ + # "iban": data.iban, + # }, + # ) + bank_date = system_arrow.get(data.bank_date) + data_dict["bank_date_w"] = bank_date.weekday() + data_dict["bank_date_m"] = bank_date.month + data_dict["bank_date_d"] = bank_date.day + data_dict["bank_date_y"] = bank_date.year + + if int(data.currency_value) < 0: + debit_type = ApiEnumDropdown.filter_by_one( + system=True, enum_class="DebitTypes", key="DT-D" + ).data + data_dict["receive_debit"] = debit_type.id + data_dict["receive_debit_uu_id"] = str(debit_type.uu_id) + else: + debit_type = ApiEnumDropdown.filter_by_one( + system=True, enum_class="DebitTypes", key="DT-R" + ).data + data_dict["receive_debit"] = debit_type.id + data_dict["receive_debit_uu_id"] = str(debit_type.uu_id) + + account_record = AccountRecords.insert_one(data_dict).data + return AlchemyJsonResponse( + completed=True, + message="Account record created successfully", + result=account_record, + ) + + +class AccountUpdateEventMethod(BaseRouteModel): + + event_type = "UPDATE" + event_description = "" + event_category = "" + + __event_keys__ = { + "ec98ef2c-bcd0-432d-a8f4-1822a56c33b2": "account_records_update", + } + __event_validation__ = { + "ec98ef2c-bcd0-432d-a8f4-1822a56c33b2": ( + UpdateAccountRecord, + [AccountRecords.__language_model__], + ), + } + + @classmethod + def account_records_update( + cls, + build_uu_id: str, + data: UpdateAccountRecordRequestModel, + token_dict: Union[EmployeeTokenObject, OccupantTokenObject], + ): + if isinstance(token_dict, OccupantTokenObject): + pass + elif isinstance(token_dict, EmployeeTokenObject): + pass + AccountRecords.build_parts_id = token_dict.selected_occupant.build_part_id + account_record = AccountRecords.update_one(build_uu_id, data).data + return AlchemyJsonResponse( + completed=True, + message="Account record updated successfully", + result=account_record, + cls_object=AccountRecords, + response_model=UpdateAccountRecord, + ) diff --git a/Events/AllEvents/template/template/api_events.py b/Events/AllEvents/template/template/api_events.py index 000a7d5..be66917 100644 --- a/Events/AllEvents/template/template/api_events.py +++ b/Events/AllEvents/template/template/api_events.py @@ -18,6 +18,4 @@ template_event = Event( ) -template_event.endpoint_callable = ( - TemplateFunctions.template_example_function() -) +template_event.endpoint_callable = TemplateFunctions.template_example_function() diff --git a/Events/AllEvents/template/template/info.py b/Events/AllEvents/template/template/info.py index 4ace812..89d163f 100644 --- a/Events/AllEvents/template/template/info.py +++ b/Events/AllEvents/template/template/info.py @@ -7,5 +7,5 @@ template_page_info = PageInfo( description={"en": "template"}, icon="", parent="", - url="" + url="", ) diff --git a/Events/AllEvents/template/template/models.py b/Events/AllEvents/template/template/models.py index 07f361d..106b701 100644 --- a/Events/AllEvents/template/template/models.py +++ b/Events/AllEvents/template/template/models.py @@ -1,12 +1,9 @@ -from ApiLayers.ApiValidations.Request import ( - BaseModelRegular -) +from ApiLayers.ApiValidations.Request import BaseModelRegular class TemplateRequestModels: TemplateRequestModelX = BaseModelRegular - class TemplateResponseModels: TemplateResponseModelsX = BaseModelRegular diff --git a/Events/AllEvents/validations/__init__.py b/Events/AllEvents/validations/__init__.py index 3b48c96..f565e36 100644 --- a/Events/AllEvents/validations/__init__.py +++ b/Events/AllEvents/validations/__init__.py @@ -2,7 +2,6 @@ Validations package initialization. """ - from .validation.cluster import ValidationsCluster diff --git a/Events/AllEvents/validations/validation/function_handlers.py b/Events/AllEvents/validations/validation/function_handlers.py index cb08add..de81701 100644 --- a/Events/AllEvents/validations/validation/function_handlers.py +++ b/Events/AllEvents/validations/validation/function_handlers.py @@ -5,7 +5,10 @@ Validation function handlers from typing import Dict, Any from fastapi import Request -from ApiLayers.AllConfigs.Redis.configs import RedisValidationKeysAction, RedisCategoryKeys +from ApiLayers.AllConfigs.Redis.configs import ( + RedisValidationKeysAction, + RedisCategoryKeys, +) from Services.Redis.Actions.actions import RedisActions from Events.base_request_model import BaseRouteModel @@ -22,7 +25,7 @@ class ValidateBase: @property def function_codes(self): - redis_function_codes= RedisActions.get_json( + redis_function_codes = RedisActions.get_json( list_keys=[f"{self.redis_key}{self.url}"] ) if redis_function_codes.status: @@ -31,9 +34,13 @@ class ValidateBase: @property def intersection(self): - intersection = list(set(self.function_codes).intersection(set(self.reachable_codes))) + intersection = list( + set(self.function_codes).intersection(set(self.reachable_codes)) + ) if not len(intersection) == 1: - raise ValueError("Users reachable function codes does not match or match more than one.") + raise ValueError( + "Users reachable function codes does not match or match more than one." + ) return intersection[0] @@ -44,9 +51,11 @@ class RedisHeaderRetrieve(ValidateBase): @property def header(self): """ - Headers: Headers which is merged with response model && language models of event + Headers: Headers which is merged with response model && language models of event """ - redis_header= RedisActions.get_json(list_keys=[f"{self.redis_key}:{self.intersection}"]) + redis_header = RedisActions.get_json( + list_keys=[f"{self.redis_key}:{self.intersection}"] + ) if redis_header.status: return redis_header.first raise ValueError("Header not found") @@ -59,9 +68,11 @@ class RedisValidationRetrieve(ValidateBase): @property def validation(self): """ - Validation: Validation of event which is merged with response model && language models of event + Validation: Validation of event which is merged with response model && language models of event """ - redis_validation = RedisActions.get_json(list_keys=[f"{self.redis_key}:{self.intersection}"]) + redis_validation = RedisActions.get_json( + list_keys=[f"{self.redis_key}:{self.intersection}"] + ) if redis_validation.status: return redis_validation.first raise ValueError("Header not found") @@ -72,8 +83,8 @@ class ValidationsBoth(RedisHeaderRetrieve, RedisValidationRetrieve): @property def both(self) -> Dict[str, Any]: """ - Headers: Headers which is merged with response model && language models of event - Validation: Validation of event which is merged with response model && language models of event + Headers: Headers which is merged with response model && language models of event + Validation: Validation of event which is merged with response model && language models of event """ return {"headers": self.header, "validation": self.validation} @@ -85,14 +96,23 @@ class RetrieveValidation(BaseRouteModel): """ Retrieve validation by event function code """ - if getattr(data, 'asked_field', "") not in ValidationsConfig.SUPPORTED_VALIDATIONS: - raise ValueError(f"Invalid asked field please retry with valid fields {ValidationsConfig.SUPPORTED_VALIDATIONS}") + if ( + getattr(data, "asked_field", "") + not in ValidationsConfig.SUPPORTED_VALIDATIONS + ): + raise ValueError( + f"Invalid asked field please retry with valid fields {ValidationsConfig.SUPPORTED_VALIDATIONS}" + ) reachable_codes = [] if cls.context_retriever.token.is_employee: - reachable_codes = cls.context_retriever.token.selected_company.reachable_event_codes + reachable_codes = ( + cls.context_retriever.token.selected_company.reachable_event_codes + ) elif cls.context_retriever.token.is_occupant: - reachable_codes = cls.context_retriever.token.selected_occupant.reachable_event_codes + reachable_codes = ( + cls.context_retriever.token.selected_occupant.reachable_event_codes + ) validate_dict = dict(url=data.url, reachable_code=reachable_codes) if data.asked_field == "all": diff --git a/Events/AllEvents/validations/validation/info.py b/Events/AllEvents/validations/validation/info.py index 4ace812..89d163f 100644 --- a/Events/AllEvents/validations/validation/info.py +++ b/Events/AllEvents/validations/validation/info.py @@ -7,5 +7,5 @@ template_page_info = PageInfo( description={"en": "template"}, icon="", parent="", - url="" + url="", ) diff --git a/Events/AllEvents/validations/validation/models.py b/Events/AllEvents/validations/validation/models.py index e7e99e9..9052030 100644 --- a/Events/AllEvents/validations/validation/models.py +++ b/Events/AllEvents/validations/validation/models.py @@ -1,6 +1,7 @@ """ Validation records request and response models. """ + from typing import Optional from pydantic import BaseModel diff --git a/Events/AllEvents/validations/validation/validation.py b/Events/AllEvents/validations/validation/validation.py index 8d699ee..1854206 100644 --- a/Events/AllEvents/validations/validation/validation.py +++ b/Events/AllEvents/validations/validation/validation.py @@ -27,11 +27,15 @@ ValidationEventMethods = MethodToEvent( def authentication_login_with_domain_and_creds_endpoint( - request: Request, data: EndpointBaseRequestModel + request: Request, data: EndpointBaseRequestModel ) -> Dict[str, Any]: - function = ValidationEventMethods.retrieve_event(event_function_code=f"{validation_event.key}") + function = ValidationEventMethods.retrieve_event( + event_function_code=f"{validation_event.key}" + ) data = function.REQUEST_VALIDATOR(**data.data) - RetrieveValidation.context_retriever = ContextRetrievers(func=authentication_login_with_domain_and_creds_endpoint) + RetrieveValidation.context_retriever = ContextRetrievers( + func=authentication_login_with_domain_and_creds_endpoint + ) return function.endpoint_callable(request=request, data=data) diff --git a/Events/Engine/abstract_class.py b/Events/Engine/abstract_class.py index 21d914a..f0caa6a 100644 --- a/Events/Engine/abstract_class.py +++ b/Events/Engine/abstract_class.py @@ -188,15 +188,15 @@ class CategoryCluster: INCLUDE_IN_SCHEMA: Optional[bool] = True def __init__( - self, - name: str, - tags: list, - prefix: str, - description: str, - endpoints: dict[str, MethodToEvent], - sub_category: list, - pageinfo: Optional[PageInfo] = None, - include_in_schema: Optional[bool] = True, + self, + name: str, + tags: list, + prefix: str, + description: str, + endpoints: dict[str, MethodToEvent], + sub_category: list, + pageinfo: Optional[PageInfo] = None, + include_in_schema: Optional[bool] = True, ): self.NAME = name self.TAGS = tags diff --git a/Events/Engine/set_defaults/setClusters.py b/Events/Engine/set_defaults/setClusters.py index 79d751b..09600ca 100644 --- a/Events/Engine/set_defaults/setClusters.py +++ b/Events/Engine/set_defaults/setClusters.py @@ -88,11 +88,13 @@ class PrepareEvents(DecoratorModule): cluster.get_redis_cluster_index_value() ) # [SAVE]REDIS => CLUSTER_FUNCTION_CODES = {"ClusterToMethod"} : [FUNCTION_CODE, ...]} - self.valid_redis_items.CLUSTER_FUNCTION_CODES_VALUE.update({ - f"{self.valid_redis_items.CLUSTER_FUNCTION_CODES_KEY}:{cluster.name}": tuple( - cluster.retrieve_all_function_codes() - ) - }) + self.valid_redis_items.CLUSTER_FUNCTION_CODES_VALUE.update( + { + f"{self.valid_redis_items.CLUSTER_FUNCTION_CODES_KEY}:{cluster.name}": tuple( + cluster.retrieve_all_function_codes() + ) + } + ) for method_endpoint in list(cluster.ENDPOINTS.values()): # [SAVE]REDIS => ENDPOINT2CLASS = {MethodEvent: Endpoint("/.../.../..."), ...} diff --git a/Services/PostgresDb/Models/filter_functions.py b/Services/PostgresDb/Models/filter_functions.py index c288729..84dfb7e 100644 --- a/Services/PostgresDb/Models/filter_functions.py +++ b/Services/PostgresDb/Models/filter_functions.py @@ -140,7 +140,9 @@ class QueryModel(ArgumentModel): @classmethod def filter_all_system( - cls: Type[T], *args: Union[BinaryExpression, ColumnExpressionArgument], db: Session + cls: Type[T], + *args: Union[BinaryExpression, ColumnExpressionArgument], + db: Session, ) -> PostgresResponse: """ Filter multiple records by expressions without status filtering. @@ -158,7 +160,11 @@ class QueryModel(ArgumentModel): return PostgresResponse(pre_query=cls._query(db), query=query, is_array=True) @classmethod - def filter_all(cls: Type[T], *args: Union[BinaryExpression, ColumnExpressionArgument], db: Session) -> PostgresResponse: + def filter_all( + cls: Type[T], + *args: Union[BinaryExpression, ColumnExpressionArgument], + db: Session, + ) -> PostgresResponse: """ Filter multiple records by expressions. diff --git a/Services/PostgresDb/Models/pagination.py b/Services/PostgresDb/Models/pagination.py index ca11141..5dc6013 100644 --- a/Services/PostgresDb/Models/pagination.py +++ b/Services/PostgresDb/Models/pagination.py @@ -2,7 +2,8 @@ from __future__ import annotations from typing import Any, Dict, Optional, Union from sqlalchemy import desc, asc from pydantic import BaseModel -from AllConfigs.SqlDatabase.configs import PaginateConfig +from ApiLayers.AllConfigs.SqlDatabase.configs import PaginateConfig +from ApiLayers.ApiValidations.Request import ListOptions from Services.PostgresDb.Models.response import PostgresResponse @@ -183,3 +184,44 @@ class PaginationResult: if self.response_type else queried_data.get_dict() ) + + +class QueryOptions: + + def __init__(self, table, data: Union[dict, ListOptions] = None, model_query: Optional[Any] = None): + self.table = table + self.data = data + self.model_query = model_query + if isinstance(data, dict): + self.data = ListOptions(**data) + self.validate_query() + if not self.data.order_type: + self.data.order_type = ["created_at"] + if not self.data.order_field: + self.data.order_field = ["uu_id"] + + def validate_query(self): + if not self.data.query or not self.model_query: + return () + cleaned_query, cleaned_query_by_model, last_dict = {}, {}, {} + for key, value in self.data.query.items(): + cleaned_query[str(str(key).split("__")[0])] = value + cleaned_query_by_model[str(str(key).split("__")[0])] = (key, value) + cleaned_model = self.model_query(**cleaned_query) + for i in cleaned_query: + if hasattr(cleaned_model, i): + last_dict[str(cleaned_query_by_model[i][0])] = str(cleaned_query_by_model[i][1]) + self.data.query = last_dict + + def convert(self) -> tuple: + """ + self.table.convert(query) + (, ) + """ + if not self.data: + return () + if not self.data.query: + return () + print('query', self.data) + print('query', self.data.query) + return tuple(self.table.convert(self.data.query)) diff --git a/Services/PostgresDb/how_to.py b/Services/PostgresDb/how_to.py index e056ea2..02fa9ba 100644 --- a/Services/PostgresDb/how_to.py +++ b/Services/PostgresDb/how_to.py @@ -1,18 +1,26 @@ -from Schemas import AddressNeighborhood +from typing import Optional + +from ApiLayers.ApiValidations.Request import ListOptions +from ApiLayers.Schemas import AddressNeighborhood from Services.PostgresDb.Models.crud_alchemy import Credentials from Services.PostgresDb.Models.mixin import BasicMixin -from Services.PostgresDb.Models.pagination import Pagination, PaginationResult +from Services.PostgresDb.Models.pagination import Pagination, PaginationResult, QueryOptions +from pydantic import BaseModel -listing = False +listing = True creating = False -updating = True +updating = False new_session = AddressNeighborhood.new_session() new_session_test = AddressNeighborhood.new_session() BasicMixin.creds = Credentials(person_id=10, person_name="Berkay Super User") +class QueryModel(BaseModel): + neighborhood_name: Optional[str] + neighborhood_code: Optional[str] + if listing: """List Options and Queries""" @@ -20,25 +28,36 @@ if listing: AddressNeighborhood.neighborhood_code.icontains("10"), db=new_session, ).query - query_of_list_options = { - "neighborhood_name__ilike": "A%", - "neighborhood_code__contains": "3", - } - address_neighborhoods = AddressNeighborhood.filter_all( - *AddressNeighborhood.convert(query_of_list_options), - db=new_session, - ) - pagination = Pagination(data=address_neighborhoods) - pagination.page = 9 - pagination.size = 10 - pagination.orderField = ["type_code", "neighborhood_code"] - pagination.orderType = ["desc", "asc"] - pagination_result = PaginationResult( - data=address_neighborhoods, pagination=pagination - ) - print(pagination_result.pagination.as_dict()) - print(pagination_result.data) + list_options = { + "page": 1, + "size": 11, + "order_field": ["type_code", "neighborhood_code"], + "order_type": ["asc", "desc"], + "query": { + "neighborhood_name__ilike": "A%", + "neighborhood_code__contains": "3", + "my_other_field__ilike": "B%", + "other_other_field__ilike": "C%", + } + } + + query_options = QueryOptions(table=AddressNeighborhood, data=list_options, model_query=QueryModel) + address_neighborhoods = AddressNeighborhood.filter_all(*query_options.convert(), db=new_session) + + pagination = Pagination(data=address_neighborhoods) + pagination.change(**list_options) + + pagination_result = PaginationResult(data=address_neighborhoods, pagination=pagination) + print("as_dict", pagination_result.pagination.as_dict()) + for i, row in enumerate(pagination_result.data): + print(i+1, row) + + # list_options_valid = ListOptions(**list_options) + # pagination.page = 9 + # pagination.size = 10 + # pagination.orderField = ["type_code", "neighborhood_code"] + # pagination.orderType = ["asc", "asc"] if creating: """Create Queries""" @@ -62,9 +81,7 @@ if creating: if updating: """Update Queries""" - query_of_list_options = { - "uu_id": str("33a89767-d2dc-4531-8f66-7b650e22a8a7"), - } + query_of_list_options = {"uu_id": str("33a89767-d2dc-4531-8f66-7b650e22a8a7")} print("query_of_list_options", query_of_list_options) address_neighborhoods_one = AddressNeighborhood.filter_one( *AddressNeighborhood.convert(query_of_list_options),