diff --git a/ApiServices/AuthService/app.py b/ApiServices/AuthService/app.py index f60580f..113cd16 100644 --- a/ApiServices/AuthService/app.py +++ b/ApiServices/AuthService/app.py @@ -5,9 +5,11 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi import Request, HTTPException, status from fastapi.responses import JSONResponse +from api_objects.errors.errorMessages import EXCEPTION_DICTS, ERRORS_DICT, ERRORS_LANG +from api_objects.errors.errorHandlers import HTTPExceptionEvyos, HTTPExceptionAnyHandler, HTTPExceptionEvyosHandler + from middlewares.token_middleware import AuthHeaderMiddleware from application.create_file import create_app -from api_objects.errors.errors_dictionary import ErrorHandlers from prometheus_fastapi_instrumentator import Instrumentator app = create_app(routers=routers) @@ -24,17 +26,20 @@ app.add_middleware( ) app.add_middleware(AuthHeaderMiddleware) -# Initialize error handlers -error_handlers = ErrorHandlers.create( - requests=Request, - exceptions=HTTPException, - response_model=JSONResponse, - status=status, -) +# Initialize Exception and ExceptionInstance handlers +CustomExceptionHandler = HTTPExceptionEvyosHandler(**dict( + statuses=status, + exceptions=HTTPException, + response_model=JSONResponse, + exceptions_dict=EXCEPTION_DICTS, + errors_dict=ERRORS_DICT, + error_language_dict=ERRORS_LANG +)) +CustomExceptionAnyHandler = HTTPExceptionAnyHandler(response_model=JSONResponse) # Register error handlers with bound methods -app.add_exception_handler(HTTPException, error_handlers.exception_handler_http) -app.add_exception_handler(Exception, error_handlers.exception_handler_exception) +app.add_exception_handler(HTTPExceptionEvyos, CustomExceptionHandler.handle_exception) +app.add_exception_handler(Exception, CustomExceptionAnyHandler.any_exception_handler) if __name__ == "__main__": uvicorn_config = { diff --git a/ApiServices/AuthService/application/authentication.py b/ApiServices/AuthService/application/authentication.py index faac1af..7909178 100644 --- a/ApiServices/AuthService/application/authentication.py +++ b/ApiServices/AuthService/application/authentication.py @@ -13,7 +13,7 @@ from api_services.templates.password_templates import ( from api_services.token_service import TokenService from api_services.redis.functions import RedisActions from api_library.response_handlers import ResponseHandler -from api_library.date_time_actions.date_functions import system_arrow +from api_library.date_time_actions.date_functions import system_arrow, DateTimeLocal # from api_library.user_logger import UserLogger @@ -74,38 +74,41 @@ class AuthenticationLoginEventMethods(MethodToEvent): @classmethod def authentication_login_with_domain_and_creds(cls, data: Login, request: Request): - try: - access_dict = Users.login_user_with_credentials(data=data, request=request) - found_user = access_dict.get("user") - if not found_user: - # UserLogger.log_login_attempt( - # request, - # None, - # data.domain, - # data.access_key, - # success=False, - # error="Invalid credentials", - # ) - return ResponseHandler.unauthorized("Invalid credentials") + from api_objects import HTTPExceptionEvyos + raise HTTPExceptionEvyos( + error_code="UNKNOWN_ERROR", + lang="en", + ) + access_dict = Users.login_user_with_credentials(data=data, request=request) + found_user = access_dict.get("user") + Users.client_arrow = DateTimeLocal( + is_client=True, timezone=found_user.local_timezone + ) + if not found_user: # UserLogger.log_login_attempt( - # request, found_user.id, data.domain, data.access_key, success=True + # request, + # None, + # data.domain, + # data.access_key, + # success=False, + # error="Invalid credentials", # ) - response_data = { + return ResponseHandler.unauthorized("Invalid credentials") + return ResponseHandler.success( + message="User logged in successfully", + data={ "access_token": access_dict.get("access_token"), "refresh_token": access_dict.get("refresher_token"), "access_object": access_dict.get("access_object"), "user": found_user.get_dict(), - } - return ResponseHandler.success( - message="User logged in successfully", - data=response_data, - ) - except Exception as e: - # UserLogger.log_login_attempt( - # request, None, data.domain, data.access_key, success=False, error=str(e) - # ) - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e)) + }, + ) + # except Exception as e: + # # UserLogger.log_login_attempt( + # # request, None, data.domain, data.access_key, success=False, error=str(e) + # # ) + # raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e)) class AuthenticationSelectEventMethods(MethodToEvent): @@ -125,6 +128,9 @@ class AuthenticationSelectEventMethods(MethodToEvent): cls, data: EmployeeSelection, token_dict: EmployeeTokenObject, request: Request ): """Handle employee company selection""" + Users.client_arrow = DateTimeLocal( + is_client=True, timezone=token_dict.local_timezone + ) if data.company_uu_id not in token_dict.companies_uu_id_list: return ResponseHandler.unauthorized( "Company not found in user's company list" diff --git a/ApiServices/EventService/routers/rules/router.py b/ApiServices/EventService/routers/rules/router.py index bc9c0b3..efda770 100644 --- a/ApiServices/EventService/routers/rules/router.py +++ b/ApiServices/EventService/routers/rules/router.py @@ -79,7 +79,7 @@ def endpoint_restriction_available(request: Request, data: CheckEndpointAccess): system=True, ).data if not endpoint: - EndpointRestriction.raise_http_exception( + raise EndpointRestriction.raise_http_exception( status_code="HTTP_404_NOT_FOUND", error_case="UNAUTHORIZED", message="Only Occupant can see this data", @@ -94,7 +94,7 @@ def endpoint_restriction_available(request: Request, data: CheckEndpointAccess): == token_dict.selected_occupant.living_space_id, ).data if not event_occupant: - EndpointRestriction.raise_http_exception( + raise EndpointRestriction.raise_http_exception( status_code="HTTP_404_NOT_FOUND", error_case="UNAUTHORIZED", message="This endpoint is not available for this occupant", @@ -110,7 +110,7 @@ def endpoint_restriction_available(request: Request, data: CheckEndpointAccess): Event2Employee.employee_id == token_dict.selected_company.employee_id, ).data if not event_employee: - EndpointRestriction.raise_http_exception( + raise EndpointRestriction.raise_http_exception( status_code="HTTP_404_NOT_FOUND", error_case="UNAUTHORIZED", message="This endpoint is not available for this employee", diff --git a/ApiServices/ValidationService/app.py b/ApiServices/ValidationService/app.py index f60580f..219f619 100644 --- a/ApiServices/ValidationService/app.py +++ b/ApiServices/ValidationService/app.py @@ -10,6 +10,7 @@ from application.create_file import create_app from api_objects.errors.errors_dictionary import ErrorHandlers from prometheus_fastapi_instrumentator import Instrumentator + app = create_app(routers=routers) Instrumentator().instrument(app=app).expose(app=app) diff --git a/ApiServices/api_handlers/auth_actions/auth.py b/ApiServices/api_handlers/auth_actions/auth.py index a9a0491..1cad6e0 100644 --- a/ApiServices/api_handlers/auth_actions/auth.py +++ b/ApiServices/api_handlers/auth_actions/auth.py @@ -84,6 +84,10 @@ class AuthActions: person_uu_id=str(found_user.person.uu_id), request=dict(request.headers), available_occupants=occupants_selection_dict, + timezone=( + found_user.local_timezone if found_user.local_timezone else "GMT+0" + ), + lang=found_user.lang if found_user.lang else "tr", ), ) return dict( @@ -136,6 +140,7 @@ class AuthActions: company_address=company_address, ) ) + AccessObjectActions.save_object_to_redis( access_token=access_token, model_object=EmployeeTokenObject( @@ -151,6 +156,10 @@ class AuthActions: companies_id_list=companies_id_list, duty_uu_id_list=duty_uu_id_list, duty_id_list=duty_id_list, + timezone=( + found_user.local_timezone if found_user.local_timezone else "GMT+0" + ), + lang=found_user.lang if found_user.lang else "tr", ), ) return dict( @@ -185,11 +194,30 @@ class AuthActions: found_user.generate_access_token() if not access_token else access_token ) # Prepare the user's details to save in Redis Session - if found_user.is_occupant: # Check if user is NOT an occupant - return cls.do_occupant_login_token( + if found_user.is_occupant: # Check if user is an occupant + cls.do_occupant_login_token(request, found_user, domain, access_token) + + return { + "access_object": cls.do_occupant_login_token( + request, found_user, domain, access_token + ), + "access_token": access_token, + "refresher_token": ( + found_user.generate_refresh_token() + if found_user.remember_me + else None + ), + } + cls.do_employee_login_token(request, found_user, domain, access_token) + return { + "access_object": cls.do_employee_login_token( request, found_user, domain, access_token - ) - return cls.do_employee_login_token(request, found_user, domain, access_token) + ), + "access_token": access_token, + "refresher_token": ( + found_user.generate_refresh_token() if found_user.remember_me else None + ), + } @classmethod def update_selected_to_redis(cls, request, add_payload): diff --git a/ApiServices/api_handlers/auth_actions/token.py b/ApiServices/api_handlers/auth_actions/token.py index a9033d3..d6bff65 100644 --- a/ApiServices/api_handlers/auth_actions/token.py +++ b/ApiServices/api_handlers/auth_actions/token.py @@ -24,6 +24,7 @@ class AccessObjectActions: Raises: HTTPException: If save fails """ + try: RedisActions.save_object_to_redis( access_token=access_token, diff --git a/ApiServices/api_handlers/core_response.py b/ApiServices/api_handlers/core_response.py index 851b8da..ee9cca7 100644 --- a/ApiServices/api_handlers/core_response.py +++ b/ApiServices/api_handlers/core_response.py @@ -1,8 +1,100 @@ -from typing import Any, Union +from typing import Any, Union, Callable, Any from fastapi import status from fastapi.responses import JSONResponse +from pydantic import BaseModel +from api_validations.validations_request import PydanticBaseModel, BaseModelRegular from databases.sql_models.response_model import AlchemyResponse +from sqlalchemy.orm import Query + +MODEL_TYPE = Callable[[Any], Any] + +class Pagination: + size: int = 10 + page: int = 1 + orderField: str = "id" + orderType: str = "asc" + pageCount: int = 1 + totalCount: int = 1 + totalPage: int = 1 + + def change(self, page=None, size=None, order_field=None, order_type=None): + self.page = page or self.page + self.size = size or self.size + self.orderField = order_field or self.orderField + self.orderType = order_type or self.orderType + self.setter_page() + + def feed(self, data): + if isinstance(data, list): + self.totalCount = len(data) + elif isinstance(data, AlchemyResponse): + self.totalCount = data.count + elif isinstance(data, Query): + self.totalCount = data.count() + + def setter_page(self): + self.pageCount = self.size + self.totalPage = int(round(self.totalCount / self.size, 0)) + if self.totalCount % self.size > 0: + if self.page == self.totalPage: + self.pageCount = self.totalCount % self.size + self.totalPage = int(round(self.totalCount / self.size, 0)) + 1 + + def as_dict(self): + return { + "size": self.size, + "page": self.page, + "totalCount": self.totalCount, + "totalPage": self.totalPage, + "pageCount": self.pageCount, + "orderField": self.orderField, + "orderType": self.orderType, + } + + +class SingleAlchemyResponse: + status_code = "HTTP_200_OK" + result: AlchemyResponse + response_model: MODEL_TYPE + message: str + completed: bool + + def __new__( + cls, + message: str, + response_model: MODEL_TYPE, + status_code: str = "HTTP_200_OK", + result: AlchemyResponse = None, + completed: bool = True, + ): + cls.status_code = getattr(status, status_code, "HTTP_200_OK") + cls.message = message + cls.result = result + cls.completed = completed + cls.response_model = response_model + + if not isinstance(cls.result, AlchemyResponse): + raise Exception("Invalid response type 4 single alchemy response") + + if not cls.result.first: + raise Exception("Invalid data type 4 single alchemy response") + + pagination = Pagination() + pagination.change(page=1) + BaseModelRegular(**cls.result.data.get_dict()) + data = cls.result.data.get_dict() + if cls.response_model: + data = cls.response_model(**cls.result.data.get_dict()).dump() + return JSONResponse( + status_code=cls.status_code, + content=dict( + pagination=pagination.as_dict(), + completed=cls.completed, + message=cls.message, + data=data, + ), + ) class AlchemyJsonResponse: @@ -11,27 +103,16 @@ class AlchemyJsonResponse: result: AlchemyResponse completed: bool filter_attributes: Any = None - response_model: Any = None + response_model: MODEL_TYPE = None cls_object: Any = None - @staticmethod - def get_total_count(cls_object, filter_attributes): - total_page_number = 1 - count_to_use = cls_object.total_count / int(filter_attributes.size) - if cls_object.total_count > int(filter_attributes.size): - if isinstance(count_to_use, int): - total_page_number = round(count_to_use, 0) - elif isinstance(count_to_use, float): - total_page_number = round(count_to_use, 0) + 1 - return total_page_number - def __new__( cls, message: str, status_code: str = "HTTP_200_OK", - result: Union[Any, list] = None, + result: Union[BaseModelRegular, BaseModel, PydanticBaseModel] = None, completed: bool = True, - response_model: Any = None, + response_model: MODEL_TYPE = None, cls_object: Any = None, filter_attributes: Any = None, ): @@ -40,108 +121,121 @@ class AlchemyJsonResponse: cls.result = result cls.completed = completed cls.response_model = response_model - - pagination_dict = { - "size/total_count": [10, 10], - "page/total_page": [1, 1], - "order_field": "id", - "order_type": "asc", - } - if filter_attributes: - total_page_number = cls.get_total_count(cls_object, filter_attributes) - pagination_dict = { - "size/total_count": [filter_attributes.size, cls_object.total_count], - "page/total_page": [filter_attributes.page, total_page_number], - "order_field": filter_attributes.order_field, - "order_type": filter_attributes.order_type, - } - - if isinstance(cls.result, dict) or isinstance(cls.result, list): - return JSONResponse( - status_code=cls.status_code, - content=dict( - total_count=len(cls.result), - count=len(cls.result), - pagination=pagination_dict, - completed=cls.completed, - message=cls.message, - data=cls.result, - ), - ) - - first_item = getattr(cls.result, "data", None) - if not first_item: - return JSONResponse( - status_code=cls.status_code, - content=dict( - total_count=0, - count=0, - pagination=pagination_dict, - completed=cls.completed, - message=cls.message, - data=[], - ), - ) + cls.filter_attributes = filter_attributes + cls.cls_object = cls_object + pagination = Pagination() if cls.result.first: - return JSONResponse( - status_code=cls.status_code, - content=dict( - total_count=1, - count=1, - pagination=pagination_dict, - completed=cls.completed, - message=cls.message, - data=cls.result.data.get_dict(), - ), - ) + raise Exception("Invalid data type 4 alchemy response") - if not cls.result.get(1).filter_attr and isinstance(cls.result.data, list): - counts = cls.result.count - return JSONResponse( - status_code=cls.status_code, - content=dict( - total_count=counts, - count=counts, - pagination=pagination_dict, - completed=cls.completed, - message=cls.message, - data=[result_data.get_dict() for result_data in cls.result.data], - ), + if filter_attributes: + pagination.change( + page=filter_attributes.page, size=filter_attributes.size, + order_field=filter_attributes.order_field, + order_type=filter_attributes.order_type, ) - - # filter_model = cls.result.get(1).filter_attr - total_count = cls.result.get(1).query.limit(None).offset(None).count() - total_page_number = cls.get_total_count(cls_object, filter_attributes) - pagination_dict = { - "size/total_count": [filter_attributes.size, cls_object.total_count], - "page/total_page": [filter_attributes.page, total_page_number], - "order_field": filter_attributes.order_field, - "order_type": filter_attributes.order_type, - } - include_joins = dict( - include_joins=( - filter_attributes.include_joins - if filter_attributes.include_joins - else [] - ) - ) data = [] for data_object in cls.result.data: - data_dict = data_object.get_dict(include_joins=include_joins) + data_dict = data_object.get_dict() if cls.response_model: - data_dict = cls.response_model( - **data_object.get_dict(include_joins=include_joins) - ).dump() + data_dict = cls.response_model(**data_object.get_dict()).dump() data.append(data_dict) + pagination.feed(data) return JSONResponse( status_code=cls.status_code, content=dict( - total_count=total_count or 1, - count=cls.result.count, - pagination=pagination_dict, + pagination=pagination.as_dict(), message=cls.message, completed=cls.completed, data=data, ), ) + + +class ListJsonResponse: + status_code = "HTTP_200_OK" + result: list + message: str + completed: bool + filter_attributes: Any + response_model: MODEL_TYPE = None, + cls_object: Any = None, + + def __new__( + cls, + message: str, + status_code: str = "HTTP_200_OK", + result: Union[BaseModelRegular, BaseModel, PydanticBaseModel] = None, + completed: bool = True, + response_model: MODEL_TYPE = None, + cls_object: Any = None, + filter_attributes: Any = None, + ): + cls.status_code = getattr(status, status_code, "HTTP_200_OK") + cls.message = message + cls.result = result + cls.completed = completed + cls.filter_attributes = filter_attributes + cls.response_model: MODEL_TYPE = response_model + + if not isinstance(cls.result, list): + raise Exception("Invalid data type 4 list json response") + pagination = Pagination() + pagination.change(page=1) + data = list(cls.result) + if cls.response_model: + data = [cls.response_model(**data_object).dump() for data_object in cls.result] + pagination.feed(data) + return JSONResponse( + status_code=cls.status_code, + content=dict( + pagination=pagination.as_dict(), + completed=cls.completed, + message=cls.message, + data=cls.result, + ), + ) + +class DictJsonResponse: + status_code = "HTTP_200_OK" + result: dict + message: str + completed: bool + filter_attributes: Any + response_model: MODEL_TYPE = None, + cls_object: Any = None, + + def __new__( + cls, + message: str, + status_code: str = "HTTP_200_OK", + result: Union[BaseModelRegular, BaseModel, PydanticBaseModel] = None, + completed: bool = True, + response_model: MODEL_TYPE = None, + cls_object: Any = None, + filter_attributes: Any = None, + ): + cls.status_code = getattr(status, status_code, "HTTP_200_OK") + cls.message = message + cls.result = result + cls.completed = completed + cls.filter_attributes = filter_attributes + cls.response_model: MODEL_TYPE = response_model + + if not isinstance(cls.result, dict): + raise Exception("Invalid data type 4 dict json response") + + pagination = Pagination() + pagination.change(page=1) + data = cls.result + if cls.response_model: + data = cls.response_model(**cls.result).dump() + return JSONResponse( + status_code=cls.status_code, + content=dict( + pagination=pagination.as_dict(), + completed=cls.completed, + message=cls.message, + data=data, + ), + ) diff --git a/api-docker-compose.yml b/api-docker-compose.yml index 52fd34e..320ce82 100644 --- a/api-docker-compose.yml +++ b/api-docker-compose.yml @@ -13,6 +13,12 @@ services: - auth_venv:/service_app/.venv - auth_logs:/service_app/logs +# wag_management_init_service: +# container_name: wag_management_init_service +# build: +# context: . +# dockerfile: service_app_init/Dockerfile + # wag_management_event_service: # container_name: wag_management_event_service # # restart: on-failure @@ -41,11 +47,7 @@ services: # - validation_venv:/service_app/.venv # - validation_logs:/service_app/logs - # wag_management_init_service: - # container_name: wag_management_init_service - # build: - # context: . - # dockerfile: service_app_init/Dockerfile + volumes: auth_venv: diff --git a/api_events/events/__init__.py b/api_events/events/__init__.py index 415a028..5336d1c 100644 --- a/api_events/events/__init__.py +++ b/api_events/events/__init__.py @@ -14,20 +14,6 @@ from api_events.events.address.address import ( AddressPostCodeUpdateEventMethod, AddressPostCodeListEventMethod, ) -from api_events.events.application.authentication import ( - AuthenticationLoginEventMethod, - AuthenticationSelectEventMethod, - AuthenticationCheckTokenEventMethod, - AuthenticationRefreshEventMethod, - AuthenticationChangePasswordEventMethod, - AuthenticationCreatePasswordEventMethod, - AuthenticationResetPasswordEventMethod, - AuthenticationDisconnectUserEventMethod, - AuthenticationLogoutEventMethod, - AuthenticationRefreshTokenEventMethod, - AuthenticationForgotPasswordEventMethod, - AuthenticationDownloadAvatarEventMethod, -) from api_events.events.account.account_records import ( AccountRecordsListEventMethod, AccountRecordsCreateEventMethod, @@ -184,18 +170,6 @@ __all__ = [ "PeopleUpdateEventMethod", "PeoplePatchEventMethod", "PeopleCreateEventMethod", - "AuthenticationLoginEventMethod", - "AuthenticationSelectEventMethod", - "AuthenticationCheckTokenEventMethod", - "AuthenticationRefreshEventMethod", - "AuthenticationChangePasswordEventMethod", - "AuthenticationCreatePasswordEventMethod", - "AuthenticationResetPasswordEventMethod", - "AuthenticationDisconnectUserEventMethod", - "AuthenticationLogoutEventMethod", - "AuthenticationRefreshTokenEventMethod", - "AuthenticationForgotPasswordEventMethod", - "AuthenticationDownloadAvatarEventMethod", "AccountRecordsListEventMethod", "AccountRecordsCreateEventMethod", "AccountRecordsUpdateEventMethod", diff --git a/api_events/events/account/account_records.py b/api_events/events/account/account_records.py index 94616cf..eed53f3 100644 --- a/api_events/events/account/account_records.py +++ b/api_events/events/account/account_records.py @@ -64,7 +64,7 @@ class AccountRecordsListEventMethods(MethodToEvent): token_dict: typing.Union[EmployeeTokenObject, OccupantTokenObject], ): if not isinstance(token_dict, OccupantTokenObject): - raise AccountRecords().raise_http_exception( + raise AccountRecords.raise_http_exception( status_code="HTTP_404_NOT_FOUND", error_case="UNAUTHORIZED", message="Only Occupant can see this data", @@ -76,7 +76,7 @@ class AccountRecordsListEventMethods(MethodToEvent): id=token_dict.selected_occupant.living_space_id ).data if not living_space: - raise AccountRecords().raise_http_exception( + raise AccountRecords.raise_http_exception( status_code="HTTP_404_NOT_FOUND", error_case="UNAUTHORIZED", message="Living space not found", @@ -214,7 +214,7 @@ class AccountRecordsCreateEventMethods(MethodToEvent): BuildIbans.build_id == token_dict.selected_occupant.build_id, ).data if not build_iban: - BuildIbans.raise_http_exception( + 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", diff --git a/api_events/events/application/__init__.py b/api_events/events/application/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api_events/events/application/api.py b/api_events/events/application/api.py deleted file mode 100644 index d250455..0000000 --- a/api_events/events/application/api.py +++ /dev/null @@ -1,5 +0,0 @@ -from api_events.events.abstract_class import MethodToEvent, ActionsSchema -from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject - - -class DecisionBookEvents(MethodToEvent): ... diff --git a/api_events/events/application/application.py b/api_events/events/application/application.py deleted file mode 100644 index 9dbf257..0000000 --- a/api_events/events/application/application.py +++ /dev/null @@ -1,5 +0,0 @@ -from api_events.events.abstract_class import MethodToEvent, ActionsSchema -from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject - - -class ApplicationEvents(MethodToEvent): ... diff --git a/api_events/events/application/authentication.py b/api_events/events/application/authentication.py deleted file mode 100644 index 19f2e36..0000000 --- a/api_events/events/application/authentication.py +++ /dev/null @@ -1,750 +0,0 @@ -import json -from typing import Union -from fastapi import status -from fastapi.requests import Request -from fastapi.exceptions import HTTPException - -from api_objects import OccupantTokenObject, EmployeeTokenObject -from api_objects.auth.token_objects import CompanyToken, OccupantToken -from api_services.templates.password_templates import ( - password_is_changed_template, - change_your_password_template, -) -from api_services.token_service import TokenService -from api_services.redis.functions import RedisActions -from api_library.response_handlers import ResponseHandler -from api_library.date_time_actions.date_functions import system_arrow - -# from api_library.user_logger import UserLogger - -from api_validations.validations_request import ( - Login, - Logout, - ChangePassword, - EmployeeSelection, - OccupantSelection, - CreatePassword, - Forgot, - # ResetPassword, - # RefreshToken, -) -from api_validations.validations_response import ( - AuthenticationLoginResponse, - AuthenticationRefreshResponse, - AuthenticationUserInfoResponse, -) -from ApiServices.api_handlers.auth_actions.auth import AuthActions -from api_configs import Auth, ApiStatic -from api_events.events.abstract_class import MethodToEvent, ActionsSchema - -from databases import ( - Companies, - Staff, - Duties, - Departments, - Employees, - BuildLivingSpace, - BuildParts, - Build, - Duty, - Event2Occupant, - Event2Employee, - Users, - UsersTokens, - OccupantTypes, - RelationshipEmployee2Build, -) - -from api_services import ( - send_email, -) - - -class AuthenticationLoginEventMethods(MethodToEvent): - event_type = "LOGIN" - event_description = "Login via domain and access key : [email] | [phone]" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "e672846d-cc45-4d97-85d5-6f96747fac67": "authentication_login_with_domain_and_creds", - } - __event_validation__ = { - "e672846d-cc45-4d97-85d5-6f96747fac67": AuthenticationLoginResponse, - } - - @classmethod - def authentication_login_with_domain_and_creds(cls, data: Login, request: Request): - try: - access_dict = Users.login_user_with_credentials(data=data, request=request) - found_user = access_dict.get("user") - if not found_user: - # UserLogger.log_login_attempt( - # request, - # None, - # data.domain, - # data.access_key, - # success=False, - # error="Invalid credentials", - # ) - return ResponseHandler.unauthorized("Invalid credentials") - - # UserLogger.log_login_attempt( - # request, found_user.id, data.domain, data.access_key, success=True - # ) - response_data = { - "access_token": access_dict.get("access_token"), - "refresh_token": access_dict.get("refresher_token"), - "access_object": access_dict.get("access_object"), - "user": found_user.get_dict(), - } - return ResponseHandler.success( - message="User logged in successfully", - data=response_data, - ) - except Exception as e: - # UserLogger.log_login_attempt( - # request, None, data.domain, data.access_key, success=False, error=str(e) - # ) - raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e)) - - -class AuthenticationSelectEventMethods(MethodToEvent): - event_type = "LOGIN" - event_description = "Select Employee Duty or Occupant Type" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "cee96b9b-8487-4e9f-aaed-2e8c79687bf9": "authentication_select_company_or_occupant_type", - } - __event_validation__ = { - "cee96b9b-8487-4e9f-aaed-2e8c79687bf9": "authentication_select_company_or_occupant_type", - } - - @classmethod - def _handle_employee_selection( - cls, data: EmployeeSelection, token_dict: EmployeeTokenObject, request: Request - ): - """Handle employee company selection""" - if data.company_uu_id not in token_dict.companies_uu_id_list: - return ResponseHandler.unauthorized( - "Company not found in user's company list" - ) - - selected_company = Companies.filter_one( - Companies.uu_id == data.company_uu_id - ).data - if not selected_company: - return ResponseHandler.not_found("Company not found") - - # Get department IDs for the company - department_ids = [ - dept.id - for dept in Departments.filter_all( - Departments.company_id == selected_company.id - ).data - ] - - # Get duties IDs for the company - duties_ids = [ - duty.id - for duty in Duties.filter_all(Duties.company_id == selected_company.id).data - ] - - # Get staff IDs - staff_ids = [ - staff.id for staff in Staff.filter_all(Staff.duties_id.in_(duties_ids)).data - ] - - # Get employee - employee = Employees.filter_one( - Employees.people_id == token_dict.person_id, - Employees.staff_id.in_(staff_ids), - ).data - - if not employee: - return ResponseHandler.not_found("Employee not found") - - # Get reachable events - reachable_event_list_id = Event2Employee.get_event_id_by_employee_id( - employee_id=employee.id - ) - - # Get staff and duties - staff = Staff.filter_one(Staff.id == employee.staff_id).data - duties = Duties.filter_one(Duties.id == staff.duties_id).data - department = Departments.filter_one(Departments.id == duties.department_id).data - - # Get bulk duty - bulk_id = Duty.filter_by_one(system=True, duty_code="BULK").data - bulk_duty_id = Duties.filter_by_one( - company_id=selected_company.id, - duties_id=bulk_id.id, - **Duties.valid_record_dict, - ).data - - # Create company token - company_token = CompanyToken( - company_uu_id=selected_company.uu_id.__str__(), - company_id=selected_company.id, - department_id=department.id, - department_uu_id=department.uu_id.__str__(), - duty_id=duties.id, - duty_uu_id=duties.uu_id.__str__(), - bulk_duties_id=bulk_duty_id.id, - staff_id=staff.id, - staff_uu_id=staff.uu_id.__str__(), - employee_id=employee.id, - employee_uu_id=employee.uu_id.__str__(), - reachable_event_list_id=reachable_event_list_id, - ) - - # Update Redis - AuthActions.update_selected_to_redis(request=request, add_payload=company_token) - return ResponseHandler.success("Company selected successfully") - - @classmethod - def _handle_occupant_selection( - cls, data: OccupantSelection, token_dict: OccupantTokenObject, request: Request - ): - """Handle occupant type selection""" - # Get occupant type - occupant_type = OccupantTypes.filter_by_one( - system=True, uu_id=data.occupant_uu_id - ).data - if not occupant_type: - return ResponseHandler.not_found("Occupant Type not found") - - # Get build part - build_part = BuildParts.filter_by_one( - system=True, uu_id=data.build_part_uu_id - ).data - if not build_part: - return ResponseHandler.not_found("Build Part not found") - - # Get build and company info - build = Build.filter_one(Build.id == build_part.build_id).data - related_company = RelationshipEmployee2Build.filter_one( - RelationshipEmployee2Build.member_id == build.id - ).data - company_related = Companies.filter_one( - Companies.id == related_company.company_id - ).data - responsible_employee = Employees.filter_one( - Employees.id == related_company.employee_id - ).data - - # Get selected occupant type - selected_occupant_type = BuildLivingSpace.filter_one( - BuildLivingSpace.occupant_type == occupant_type.id, - BuildLivingSpace.person_id == token_dict.person_id, - BuildLivingSpace.build_parts_id == build_part.id, - ).data - if not selected_occupant_type: - return ResponseHandler.not_found("Selected occupant type not found") - - # Get reachable events - reachable_event_list_id = Event2Occupant.get_event_id_by_build_living_space_id( - build_living_space_id=selected_occupant_type.id - ) - - # Create occupant token - occupant_token = OccupantToken( - living_space_id=selected_occupant_type.id, - living_space_uu_id=selected_occupant_type.uu_id.__str__(), - occupant_type_id=occupant_type.id, - occupant_type_uu_id=occupant_type.uu_id.__str__(), - occupant_type=occupant_type.occupant_type, - build_id=build.id, - build_uuid=build.uu_id.__str__(), - build_part_id=build_part.id, - build_part_uuid=build_part.uu_id.__str__(), - responsible_employee_id=responsible_employee.id, - responsible_employee_uuid=responsible_employee.uu_id.__str__(), - responsible_company_id=company_related.id, - responsible_company_uuid=company_related.uu_id.__str__(), - reachable_event_list_id=reachable_event_list_id, - ) - - # Update Redis - AuthActions.update_selected_to_redis( - request=request, add_payload=occupant_token - ) - return ResponseHandler.success("Occupant selected successfully") - - @classmethod - def authentication_select_company_or_occupant_type( - cls, - request: Request, - data: Union[EmployeeSelection, OccupantSelection], - token_dict: Union[EmployeeTokenObject, OccupantTokenObject], - ): - """Handle selection of company or occupant type""" - try: - if isinstance(token_dict, EmployeeTokenObject): - return cls._handle_employee_selection(data, token_dict, request) - elif isinstance(token_dict, OccupantTokenObject): - return cls._handle_occupant_selection(data, token_dict, request) - return ResponseHandler.error( - "Invalid token type", status_code=status.HTTP_400_BAD_REQUEST - ) - except Exception as e: - return ResponseHandler.error( - str(e), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR - ) - - -class AuthenticationCheckTokenEventMethods(MethodToEvent): - event_type = "LOGIN" - event_description = "Check Token is valid for user" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "73d77e45-a33f-4f12-909e-3b56f00d8a12": "authentication_check_token_is_valid", - } - __event_validation__ = { - "73d77e45-a33f-4f12-909e-3b56f00d8a12": "authentication_check_token_is_valid", - } - - @classmethod - def authentication_check_token_is_valid(cls, request: Request): - try: - if RedisActions.get_object_via_access_key(request=request): - return ResponseHandler.success("Access Token is valid") - except HTTPException: - return ResponseHandler.unauthorized("Access Token is NOT valid") - - -class AuthenticationRefreshEventMethods(MethodToEvent): - event_type = "LOGIN" - event_description = "Refresh user info using access token" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "48379bb2-ba81-4d8e-a9dd-58837cfcbf67": "authentication_refresh_user_info", - } - __event_validation__ = { - "48379bb2-ba81-4d8e-a9dd-58837cfcbf67": AuthenticationRefreshResponse, - } - - @classmethod - def authentication_refresh_user_info( - cls, - request: Request, - token_dict: Union[EmployeeTokenObject, OccupantTokenObject], - ): - try: - access_token = request.headers.get(Auth.ACCESS_TOKEN_TAG) - if not access_token: - return ResponseHandler.unauthorized() - - # Get user and token info - found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data - if not found_user: - return ResponseHandler.not_found("User not found") - user_token = UsersTokens.filter_one( - UsersTokens.domain == found_user.domain_name, - UsersTokens.user_id == found_user.id, - UsersTokens.token_type == "RememberMe", - ).data - response_data = { - "access_token": access_token, - "refresh_token": getattr(user_token, "token", None), - "user": found_user.get_dict(), - } - return ResponseHandler.success( - "User info refreshed successfully", - data=response_data, - ) - except Exception as e: - return ResponseHandler.error(str(e)) - - -class AuthenticationChangePasswordEventMethods(MethodToEvent): - event_type = "LOGIN" - event_description = "Change password with access token" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "f09f7c1a-bee6-4e32-8444-962ec8f39091": "authentication_change_password", - } - __event_validation__ = { - "f09f7c1a-bee6-4e32-8444-962ec8f39091": "authentication_change_password", - } - - @classmethod - def authentication_change_password( - cls, - request: Request, - data: ChangePassword, - token_dict: Union[EmployeeTokenObject, OccupantTokenObject], - ): - try: - if not isinstance(token_dict, EmployeeTokenObject): - return ResponseHandler.unauthorized( - "Only employees can change password" - ) - - found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data - if not found_user: - return ResponseHandler.not_found("User not found") - - if not found_user.check_password(data.old_password): - # UserLogger.log_password_change( - # request, - # found_user.id, - # "change", - # success=False, - # error="Invalid old password", - # ) - return ResponseHandler.unauthorized("Old password is incorrect") - - found_user.set_password(data.new_password) - # UserLogger.log_password_change( - # request, found_user.id, "change", success=True - # ) - - return ResponseHandler.success("Password changed successfully") - except Exception as e: - # UserLogger.log_password_change( - # request, - # found_user.id if found_user else None, - # "change", - # success=False, - # error=str(e), - # ) - return ResponseHandler.error(str(e)) - - -class AuthenticationCreatePasswordEventMethods(MethodToEvent): - - event_type = "LOGIN" - event_description = "Create password with password reset token requested via email" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "c519f9af-92e1-47b2-abf7-5a3316d075f7": "authentication_create_password", - } - __event_validation__ = { - "c519f9af-92e1-47b2-abf7-5a3316d075f7": "authentication_create_password", - } - - @classmethod - def authentication_create_password(cls, data: CreatePassword): - - if not data.re_password == data.password: - raise HTTPException( - status_code=status.HTTP_406_NOT_ACCEPTABLE, detail="Password must match" - ) - if found_user := Users.filter_one( - Users.password_token == data.password_token - ).data: - found_user.create_password(found_user=found_user, password=data.password) - found_user.password_token = "" - found_user.save() - send_email_completed = send_email( - subject=f"Dear {found_user.user_tag}, your password has been changed.", - receivers=[str(found_user.email)], - html=password_is_changed_template(user_name=found_user.user_tag), - ) - if not send_email_completed: - raise HTTPException( - status_code=400, detail="Email can not be sent. Try again later" - ) - return ResponseHandler.success( - "Password is created successfully", - data=found_user.get_dict(), - ) - return ResponseHandler.not_found("Record not found") - - -class AuthenticationDisconnectUserEventMethods(MethodToEvent): - - event_type = "LOGIN" - event_description = "Disconnect all sessions of user in access token" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "8b586848-2fb3-4161-abbe-642157eec7ce": "authentication_disconnect_user", - } - __event_validation__ = { - "8b586848-2fb3-4161-abbe-642157eec7ce": "authentication_disconnect_user", - } - - @classmethod - def authentication_disconnect_user( - cls, data: Logout, token_dict: Union[EmployeeTokenObject, OccupantTokenObject] - ): - found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data - if not found_user: - return ResponseHandler.not_found("User not found") - if already_tokens := RedisActions.get_object_via_user_uu_id( - user_id=str(found_user.uu_id) - ): - for key, token_user in already_tokens.items(): - RedisActions.delete(key) - selected_user = Users.filter_one( - Users.uu_id == token_user.get("uu_id"), - ).data - selected_user.remove_refresher_token( - domain=data.domain, disconnect=True - ) - return ResponseHandler.success( - "All sessions are disconnected", - data=selected_user.get_dict(), - ) - return ResponseHandler.not_found("Invalid data") - - -class AuthenticationLogoutEventMethods(MethodToEvent): - - event_type = "LOGIN" - event_description = "Logout only single session of user which domain is provided" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "5cc22e4e-a0f7-4077-be41-1871feb3dfd1": "authentication_logout_user", - } - __event_validation__ = { - "5cc22e4e-a0f7-4077-be41-1871feb3dfd1": "authentication_logout_user", - } - - @classmethod - def authentication_logout_user( - cls, request: Request, data: Logout, token_dict: dict = None - ): - token_user = None - if already_tokens := RedisActions.get_object_via_access_key(request=request): - for key in already_tokens: - token_user = RedisActions.get_json(key) - if token_user.get("domain") == data.domain: - RedisActions.delete(key) - selected_user = Users.filter_one( - Users.uu_id == token_user.get("uu_id"), - ).data - selected_user.remove_refresher_token(domain=data.domain) - - return ResponseHandler.success( - "Session is logged out", - data=token_user, - ) - return ResponseHandler.not_found("Logout is not successfully completed") - - -class AuthenticationRefreshTokenEventMethods(MethodToEvent): - - event_type = "LOGIN" - event_description = "Refresh access token with refresher token" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "c90f3334-10c9-4181-b5ff-90d98a0287b2": "authentication_refresher_token", - } - __event_validation__ = { - "c90f3334-10c9-4181-b5ff-90d98a0287b2": AuthenticationRefreshResponse, - } - - @classmethod - def authentication_refresher_token( - # cls, request: Request, data: RefreshToken, token_dict: dict = None - cls, - request: Request, - data, - token_dict: dict = None, - ): - token_refresher = UsersTokens.filter_by_one( - token=data.refresh_token, - domain=data.domain, - **UsersTokens.valid_record_dict, - ).data - if not token_refresher: - return ResponseHandler.not_found("Invalid data") - if found_user := Users.filter_one( - Users.id == token_refresher.user_id, - ).data: - found_user: Users = found_user - access_key = AuthActions.save_access_token_to_redis( - request=request, found_user=found_user, domain=data.domain - ) - found_user.last_agent = request.headers.get("User-Agent", None) - found_user.last_platform = request.headers.get("Origin", None) - found_user.last_remote_addr = getattr( - request, "remote_addr", None - ) or request.headers.get("X-Forwarded-For", None) - found_user.last_seen = str(system_arrow.now()) - response_data = { - "access_token": access_key, - "refresh_token": data.refresh_token, - } - return ResponseHandler.success( - "User is logged in successfully via refresher token", - data=response_data, - ) - return ResponseHandler.not_found("Invalid data") - - -class AuthenticationForgotPasswordEventMethods(MethodToEvent): - - event_type = "LOGIN" - event_description = "Send an email to user for a valid password reset token" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "e3ca6e24-b9f8-4127-949c-3bfa364e3513": "authentication_forgot_password", - } - __event_validation__ = { - "e3ca6e24-b9f8-4127-949c-3bfa364e3513": "authentication_forgot_password", - } - - @classmethod - def authentication_forgot_password( - cls, - request: Request, - data: Forgot, - ): - found_user: Users = Users.check_user_exits( - access_key=data.access_key, domain=data.domain - ) - forgot_key = AuthActions.save_access_token_to_redis( - request=request, found_user=found_user, domain=data.domain - ) - forgot_link = ApiStatic.forgot_link(forgot_key=forgot_key) - send_email_completed = send_email( - subject=f"Dear {found_user.user_tag}, your forgot password link has been sent.", - receivers=[str(found_user.email)], - html=change_your_password_template( - user_name=found_user.user_tag, forgot_link=forgot_link - ), - ) - if not send_email_completed: - raise HTTPException( - status_code=400, detail="Email can not be sent. Try again later" - ) - found_user.password_token = forgot_key - found_user.password_token_is_valid = str(system_arrow.shift(days=1)) - found_user.save() - - return ResponseHandler.success( - "Password is change link is sent to your email or phone", - data={}, - ) - - -class AuthenticationResetPasswordEventMethods(MethodToEvent): - - event_type = "UPDATE" - __event_keys__ = { - "af9e121e-24bb-44ac-a616-471d5754360e": "authentication_reset_password", - } - - @classmethod - def authentication_reset_password(cls, data: Forgot): - from sqlalchemy import or_ - - found_user = Users.query.filter( - or_( - Users.email == str(data.access_key).lower(), - Users.phone_number == str(data.access_key).replace(" ", ""), - ), - ).first() - if not found_user: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Given access key or domain is not matching with the any user record.", - ) - - reset_password_token = found_user.reset_password_token(found_user=found_user) - send_email_completed = send_email( - subject=f"Dear {found_user.user_tag}, a password reset request has been received.", - receivers=[str(found_user.email)], - html=change_your_password_template( - user_name=found_user.user_tag, - forgot_link=ApiStatic.forgot_link(forgot_key=reset_password_token), - ), - ) - if not send_email_completed: - raise found_user.raise_http_exception( - status_code=400, message="Email can not be sent. Try again later" - ) - return ResponseHandler.success( - "Password change link is sent to your email or phone", - data=found_user.get_dict(), - ) - - -class AuthenticationDownloadAvatarEventMethods(MethodToEvent): - - event_type = "LOGIN" - event_description = "Download avatar icon and profile info of user" - event_category = "AUTHENTICATION" - - __event_keys__ = { - "c140cd5f-307f-4046-a93e-3ade032a57a7": "authentication_download_avatar", - } - __event_validation__ = { - "c140cd5f-307f-4046-a93e-3ade032a57a7": AuthenticationUserInfoResponse, - } - - @classmethod - def authentication_download_avatar( - cls, token_dict: Union[EmployeeTokenObject, OccupantTokenObject] - ): - if found_user := Users.filter_one(Users.id == token_dict.user_id).data: - expired_starts = str( - system_arrow.now() - system_arrow.get(str(found_user.expiry_ends)) - ) - expired_int = ( - system_arrow.now() - system_arrow.get(str(found_user.expiry_ends)) - ).days - - user_info = { - "lang": token_dict.lang, - "full_name": found_user.person.full_name, - "avatar": found_user.avatar, - "remember_me": found_user.remember_me, - "expiry_ends": str(found_user.expiry_ends), - "expired_str": expired_starts, - "expired_int": int(expired_int), - } - return ResponseHandler.success( - "Avatar and profile is shared via user credentials", - data=user_info, - ) - return ResponseHandler.not_found("Invalid data") - - -AuthenticationLoginEventMethod = AuthenticationLoginEventMethods( - action=ActionsSchema(endpoint="/authentication/login") -) -AuthenticationSelectEventMethod = AuthenticationSelectEventMethods( - action=ActionsSchema(endpoint="/authentication/select") -) -AuthenticationCheckTokenEventMethod = AuthenticationCheckTokenEventMethods( - action=ActionsSchema(endpoint="/authentication/valid") -) -AuthenticationRefreshEventMethod = AuthenticationRefreshEventMethods( - action=ActionsSchema(endpoint="/authentication/refresh") -) -AuthenticationChangePasswordEventMethod = AuthenticationChangePasswordEventMethods( - action=ActionsSchema(endpoint="/authentication/change_password") -) -AuthenticationCreatePasswordEventMethod = AuthenticationCreatePasswordEventMethods( - action=ActionsSchema(endpoint="/authentication/create_password") -) -AuthenticationDisconnectUserEventMethod = AuthenticationDisconnectUserEventMethods( - action=ActionsSchema(endpoint="/authentication/disconnect") -) -AuthenticationLogoutEventMethod = AuthenticationLogoutEventMethods( - action=ActionsSchema(endpoint="/authentication/logout") -) -AuthenticationRefreshTokenEventMethod = AuthenticationRefreshTokenEventMethods( - action=ActionsSchema(endpoint="/authentication/refresher") -) -AuthenticationForgotPasswordEventMethod = AuthenticationForgotPasswordEventMethods( - action=ActionsSchema(endpoint="/authentication/forgot") -) -AuthenticationDownloadAvatarEventMethod = AuthenticationDownloadAvatarEventMethods( - action=ActionsSchema(endpoint="/authentication/avatar") -) -AuthenticationResetPasswordEventMethod = AuthenticationResetPasswordEventMethods( - action=ActionsSchema(endpoint="/authentication/reset_password") -) diff --git a/api_events/events/application/rules.py b/api_events/events/application/rules.py deleted file mode 100644 index 61e684a..0000000 --- a/api_events/events/application/rules.py +++ /dev/null @@ -1,5 +0,0 @@ -from api_events.events.abstract_class import MethodToEvent, ActionsSchema -from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject - - -class RulesEvents(MethodToEvent): ... diff --git a/api_events/events/building/building_build_area.py b/api_events/events/building/building_build_area.py index 590501d..aceaf3f 100644 --- a/api_events/events/building/building_build_area.py +++ b/api_events/events/building/building_build_area.py @@ -80,7 +80,7 @@ class BuildAreaCreateEventMethods(MethodToEvent): selected_build = None if isinstance(token_dict, OccupantTokenObject): if not token_dict.selected_occupant.build_uuid == data.build_uu_id: - BuildArea.raise_http_exception( + raise BuildArea.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"Occupant can not create build area for {data.build_uu_id}", @@ -96,7 +96,7 @@ class BuildAreaCreateEventMethods(MethodToEvent): employee_id=token_dict.selected_company.employee_id ).all() if not str(data.build_uu_id) in [str(build.uu_id) for build in build_ids]: - BuildArea.raise_http_exception( + raise BuildArea.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"Employee can not create build area for {data.build_uu_id}", diff --git a/api_events/events/building/building_build_sites.py b/api_events/events/building/building_build_sites.py index 857e512..b8b3aea 100644 --- a/api_events/events/building/building_build_sites.py +++ b/api_events/events/building/building_build_sites.py @@ -47,7 +47,7 @@ class BuildSitesListEventMethods(MethodToEvent): ) employees_build_list = [build.address_id for build in employees_build.all()] if not employees_build_list: - BuildSites.raise_http_exception( + raise BuildSites.raise_http_exception( status_code="HTTP_404_NOT_FOUND", error_case="NOT_FOUND", message="Employee has no build sites registered", @@ -86,7 +86,7 @@ class BuildSitesCreateEventMethods(MethodToEvent): ): if isinstance(token_dict, OccupantTokenObject): if not token_dict.selected_occupant.build_uuid == data.build_uu_id: - BuildSites.raise_http_exception( + raise BuildSites.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"Occupant can not create build sites for {data.build_uu_id}", @@ -99,7 +99,7 @@ class BuildSitesCreateEventMethods(MethodToEvent): employee_id=token_dict.selected_company.employee_id ).all() if not str(data.build_uu_id) in [str(build.uu_id) for build in build_ids]: - BuildSites.raise_http_exception( + raise BuildSites.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"Employee can not create build sites for {data.build_uu_id}", diff --git a/api_events/events/building/building_living_spaces.py b/api_events/events/building/building_living_spaces.py index 8b7287c..f57b733 100644 --- a/api_events/events/building/building_living_spaces.py +++ b/api_events/events/building/building_living_spaces.py @@ -47,7 +47,7 @@ class BuildingLivingSpacesListEventMethods(MethodToEvent): Build.id == token_dict.selected_occupant.build_id, ).data if not occupants_build_id: - Build.raise_http_exception( + raise Build.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"Occupant has no build registered in the system. Contact with your company.", @@ -57,7 +57,7 @@ class BuildingLivingSpacesListEventMethods(MethodToEvent): BuildParts.build_id.in_(occupants_build_id.id), ).data if not occupants_build_parts: - Build.raise_http_exception( + raise Build.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"Occupant has no build parts registered in the system. Contact with your company.", @@ -80,7 +80,7 @@ class BuildingLivingSpacesListEventMethods(MethodToEvent): employee_id=token_dict.selected_company.employee_id ) if not build_id_list_query: - Build.raise_http_exception( + raise Build.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"Employee has no build registered in the system. Contact with your supervisor.", @@ -92,7 +92,7 @@ class BuildingLivingSpacesListEventMethods(MethodToEvent): ), ).data if not build_part_id_list_query: - Build.raise_http_exception( + raise Build.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"Employee has no build parts registered in the system. Contact with your supervisor.", @@ -142,7 +142,7 @@ class BuildingLivingSpacesCreateEventMethods(MethodToEvent): BuildParts.build_id.in_([build.id for build in build_id_list_query.all()]), ).data if not build_part: - BuildLivingSpace.raise_http_exception( + raise BuildLivingSpace.raise_http_exception( status_code="HTTP_404_NOT_FOUND", error_case="UNAUTHORIZED", message=f"{data.build_parts_uu_id} - Build Part is not found in database. Check build part uu_id", @@ -154,7 +154,7 @@ class BuildingLivingSpacesCreateEventMethods(MethodToEvent): People.uu_id == data.person_uu_id, ).data if not life_person: - BuildLivingSpace.raise_http_exception( + raise BuildLivingSpace.raise_http_exception( status_code="HTTP_404_NOT_FOUND", error_case="UNAUTHORIZED", message=f"{data.person_uu_id} - Living Person is not found in database.", @@ -164,7 +164,7 @@ class BuildingLivingSpacesCreateEventMethods(MethodToEvent): ) occupant_type = OccupantTypes.filter_by_one(uu_id=data.occupant_type_uu_id).data if not occupant_type: - BuildLivingSpace.raise_http_exception( + raise BuildLivingSpace.raise_http_exception( status_code="HTTP_404_NOT_FOUND", error_case="UNAUTHORIZED", message=f"{data.occupant_type_uu_id} - Occupant Type is not found in database. Check occupant type uu_id", @@ -252,7 +252,7 @@ class BuildingLivingSpacesUpdateEventMethods(MethodToEvent): ), ).data if not build_part: - BuildLivingSpace.raise_http_exception( + raise BuildLivingSpace.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"{data.life_person_uu_id} - Living Person is not found in database.", @@ -264,7 +264,7 @@ class BuildingLivingSpacesUpdateEventMethods(MethodToEvent): People.uu_id == data.life_person_uu_id or "" ).data if not life_person: - BuildLivingSpace.raise_http_exception( + raise BuildLivingSpace.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message=f"{data.life_person_uu_id} - Living Person is not found in database.", @@ -290,7 +290,7 @@ class BuildingLivingSpacesUpdateEventMethods(MethodToEvent): if data_dict["is_tenant_live"]: owner_person = getattr(last_living_space, "owner_person_id", None) if not owner_person: - BuildLivingSpace.raise_http_exception( + raise BuildLivingSpace.raise_http_exception( status_code="HTTP_403_FORBIDDEN", error_case="UNAUTHORIZED", message="Owner person of build part is not defined. Please register owner of part first.", diff --git a/api_objects/__init__.py b/api_objects/__init__.py index b196ce1..6c42231 100644 --- a/api_objects/__init__.py +++ b/api_objects/__init__.py @@ -6,6 +6,7 @@ from api_objects.auth.token_objects import ( OccupantToken, ApplicationToken, ) +from api_objects.errors.errorHandlers import HTTPExceptionEvyos __all__ = [ "OccupantTokenObject", @@ -14,4 +15,5 @@ __all__ = [ "CompanyToken", "OccupantToken", "ApplicationToken", + "HTTPExceptionEvyos", ] diff --git a/api_objects/auth/token_objects.py b/api_objects/auth/token_objects.py index c29b520..92fb845 100644 --- a/api_objects/auth/token_objects.py +++ b/api_objects/auth/token_objects.py @@ -23,7 +23,7 @@ class ApplicationToken(BaseModel): domain: Optional[str] = "app.evyos.com.tr" lang: Optional[str] = "TR" - timezone: Optional[str] = "Europe/Istanbul" + timezone: Optional[str] = "GMT+3" user_type: int = UserType.occupant.value credentials: dict = None diff --git a/api_objects/errors/alchemy_errors.py b/api_objects/errors/alchemy_errors.py index c5ac9eb..cf5de2a 100644 --- a/api_objects/errors/alchemy_errors.py +++ b/api_objects/errors/alchemy_errors.py @@ -1,74 +1,6 @@ -from .errors_dictionary import ErrorMessages -class AlchemyError: - ERRORS_DICT = { - "100": "HTTP_100_CONTINUE", - "101": "HTTP_101_SWITCHING_PROTOCOLS", - "102": "HTTP_102_PROCESSING", - "103": "HTTP_103_EARLY_HINTS", - "200": "HTTP_200_OK", - "201": "HTTP_201_CREATED", - "202": "HTTP_202_ACCEPTED", - "203": "HTTP_203_NON_AUTHORITATIVE_INFORMATION", - "204": "HTTP_204_NO_CONTENT", - "205": "HTTP_205_RESET_CONTENT", - "206": "HTTP_206_PARTIAL_CONTENT", - "207": "HTTP_207_MULTI_STATUS", - "208": "HTTP_208_ALREADY_REPORTED", - "226": "HTTP_226_IM_USED", - "300": "HTTP_300_MULTIPLE_CHOICES", - "301": "HTTP_301_MOVED_PERMANENTLY", - "302": "HTTP_302_FOUND", - "303": "HTTP_303_SEE_OTHER", - "304": "HTTP_304_NOT_MODIFIED", - "305": "HTTP_305_USE_PROXY", - "306": "HTTP_306_RESERVED", - "307": "HTTP_307_TEMPORARY_REDIRECT", - "308": "HTTP_308_PERMANENT_REDIRECT", - "400": "HTTP_400_BAD_REQUEST", - "401": "HTTP_401_UNAUTHORIZED", - "402": "HTTP_402_PAYMENT_REQUIRED", - "403": "HTTP_403_FORBIDDEN", - "404": "HTTP_404_NOT_FOUND", - "405": "HTTP_405_METHOD_NOT_ALLOWED", - "406": "HTTP_406_NOT_ACCEPTABLE", - "407": "HTTP_407_PROXY_AUTHENTICATION_REQUIRED", - "408": "HTTP_408_REQUEST_TIMEOUT", - "409": "HTTP_409_CONFLICT", - "410": "HTTP_410_GONE", - "411": "HTTP_411_LENGTH_REQUIRED", - "412": "HTTP_412_PRECONDITION_FAILED", - "413": "HTTP_413_REQUEST_ENTITY_TOO_LARGE", - "414": "HTTP_414_REQUEST_URI_TOO_LONG", - "415": "HTTP_415_UNSUPPORTED_MEDIA_TYPE", - "416": "HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE", - "417": "HTTP_417_EXPECTATION_FAILED", - "418": "HTTP_418_IM_A_TEAPOT", - "421": "HTTP_421_MISDIRECTED_REQUEST", - "422": "HTTP_422_UNPROCESSABLE_ENTITY", - "423": "HTTP_423_LOCKED", - "424": "HTTP_424_FAILED_DEPENDENCY", - "426": "HTTP_426_UPGRADE_REQUIRED", - "428": "HTTP_428_PRECONDITION_REQUIRED", - "429": "HTTP_429_TOO_MANY_REQUESTS", - "431": "HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE", - "451": "HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS", - "500": "HTTP_500_INTERNAL_SERVER_ERROR", - } - ERRORS_KEYS = { - "delete": "DeletedRecord", - "update": "UpdatedRecord", - "create": "CreatedRecord", - "list": "ListedRecords", - "not_found": "RecordNotFound", - "already_exist": "AlreadyExists", - "not_deleted": "RecordNotDeleted", - "not_updated": "RecordNotUpdated", - "not_created": "RecordNotCreated", - "not_listed": "RecordsNotListed", - "not_confirm": "IsNotConfirmed", - } +class HTTPExceptionError: def __init__(self, lang): self.lang = lang diff --git a/api_objects/errors/errorHandlers.py b/api_objects/errors/errorHandlers.py new file mode 100644 index 0000000..c9897f7 --- /dev/null +++ b/api_objects/errors/errorHandlers.py @@ -0,0 +1,60 @@ +from typing import Any + + +# class HTTPExceptionInstance: + +# def __init__(self, statuses, exceptions, exceptions_dict, errors_dict, response_model, error_language_dict): +# self.EXCEPTIONS = exceptions # from fastapi.exceptions import HTTPException +# self.STATUSES = statuses # from fastapi import status +# self.EXCEPTION_DICTS: dict = exceptions_dict +# self.ERRORS_DICT: dict = errors_dict +# self.ERRORS_LANG: dict = error_language_dict +# self.RESPONSE_MODEL: Any = response_model + + +class HTTPExceptionEvyos(Exception): + + def __init__(self, error_code: str, lang: str): + self.error_code = error_code + self.lang = lang + + +class HTTPExceptionEvyosHandler: + + def __init__(self, statuses, exceptions, exceptions_dict, errors_dict, response_model, error_language_dict): + self.EXCEPTIONS = exceptions # from fastapi.exceptions import HTTPException + self.STATUSES = statuses # from fastapi import status + self.EXCEPTION_DICTS: dict = exceptions_dict + self.ERRORS_DICT: dict = errors_dict + self.ERRORS_LANG: dict = error_language_dict + self.RESPONSE_MODEL: Any = response_model + + def retrieve_error_status_code(self, exc: HTTPExceptionEvyos): + grab_status = self.ERRORS_DICT.get(str(exc.error_code).upper(), "") + grab_status_code = self.EXCEPTION_DICTS.get(str(grab_status).upper(), "500") + return getattr(self.STATUSES, str(grab_status_code), getattr(self.STATUSES, "HTTP_500_INTERNAL_SERVER_ERROR")) + + def retrieve_error_message(self, exc: HTTPExceptionEvyos): + message_by_lang = self.ERRORS_LANG.get(str(exc.lang).lower(), {}) + message_by_code = message_by_lang.get(str(exc.error_code).upper(), "Unknown error") + return message_by_code + + def handle_exception(self, request, exc: HTTPExceptionEvyos): + headers = getattr(request, "headers", {}) + status_code = self.retrieve_error_status_code(exc) + error_message = self.retrieve_error_message(exc) + return self.RESPONSE_MODEL( + status_code=int(status_code), + content={"message": error_message, "lang": exc.lang}, + ) + +class HTTPExceptionAnyHandler: + + def __init__(self, response_model): + self.RESPONSE_MODEL: Any = response_model + + def any_exception_handler(self, request, exc: Exception): + return self.RESPONSE_MODEL( + status_code=200, + content={"message": str(exc), "lang": None, "status_code": 417}, + ) diff --git a/api_objects/errors/errorMessages/__init__.py b/api_objects/errors/errorMessages/__init__.py new file mode 100644 index 0000000..99b03d1 --- /dev/null +++ b/api_objects/errors/errorMessages/__init__.py @@ -0,0 +1,17 @@ +from .baseErrorCluster import ( + BASE_ERRORS, + BASE_ERROR_LANGUAGE, + EXCEPTION_DICTS, +) + +ERRORS_LANG = { + "tr": { + **BASE_ERROR_LANGUAGE["tr"], + }, + "en": { + **BASE_ERROR_LANGUAGE["en"], + } +} +ERRORS_DICT = { + **BASE_ERRORS, +} diff --git a/api_objects/errors/errorMessages/baseErrorCluster.py b/api_objects/errors/errorMessages/baseErrorCluster.py new file mode 100644 index 0000000..0cff5c4 --- /dev/null +++ b/api_objects/errors/errorMessages/baseErrorCluster.py @@ -0,0 +1,104 @@ + +BASE_ERRORS = { + "NOT_CREATED": 405, + "NOT_DELETED": 405, + "NOT_UPDATED": 405, + "NOT_LISTED": 404, + "NOT_FOUND": 404, + "ALREADY_EXISTS": 400, + "IS_NOT_CONFIRMED": 405, + "NOT_AUTHORIZED": 401, + "NOT_VALID": 406, + "NOT_ACCEPTABLE": 406, + "INVALID_DATA": 422, + "UNKNOWN_ERROR": 502, +} + +BASE_ERROR_LANGUAGE = { + "tr": { + "NOT_CREATED": "Kayıt oluşturulamadı.", + "NOT_DELETED": "Kayıt silinemedi.", + "NOT_UPDATED": "Kayıt güncellenemedi.", + "NOT_LISTED": "Kayıt listelenemedi.", + "NOT_FOUND": "Kayıt bulunamadı.", + "ALREADY_EXISTS": "Kayıt zaten mevcut.", + "IS_NOT_CONFIRMED": "Kayıt onaylanmadı.", + "NOT_AUTHORIZED": "Yetkisiz kullanıcı.", + "NOT_VALID": "Gecersiz veri.", + "NOT_ACCEPTABLE": "Gecersiz veri.", + "INVALID_DATA": "Gecersiz veri.", + "UNKNOWN_ERROR": "Bilinmeyen bir hata oluştu.", + }, + "en": { + "NOT_CREATED": "Not Created.", + "NOT_DELETED": "Not Deleted.", + "NOT_UPDATED": "Not Updated.", + "NOT_LISTED": "Not Listed.", + "NOT_FOUND": "Not Found.", + "ALREADY_EXISTS": "Already Exists.", + "IS_NOT_CONFIRMED": "Not Confirmed.", + "NOT_AUTHORIZED": "Not Authorized.", + "NOT_VALID": "Not Valid.", + "NOT_ACCEPTABLE": "Not Acceptable.", + "INVALID_DATA": "Invalid Data.", + "UNKNOWN_ERROR": "Unknown Error occured.", + }, +} + +from fastapi import status +EXCEPTION_DICTS = { + "100": "HTTP_100_CONTINUE", + "101": "HTTP_101_SWITCHING_PROTOCOLS", + "102": "HTTP_102_PROCESSING", + "103": "HTTP_103_EARLY_HINTS", + "200": "HTTP_200_OK", + "201": "HTTP_201_CREATED", + "202": "HTTP_202_ACCEPTED", + "203": "HTTP_203_NON_AUTHORITATIVE_INFORMATION", + "204": "HTTP_204_NO_CONTENT", + "205": "HTTP_205_RESET_CONTENT", + "206": "HTTP_206_PARTIAL_CONTENT", + "207": "HTTP_207_MULTI_STATUS", + "208": "HTTP_208_ALREADY_REPORTED", + "226": "HTTP_226_IM_USED", + "300": "HTTP_300_MULTIPLE_CHOICES", + "301": "HTTP_301_MOVED_PERMANENTLY", + "302": "HTTP_302_FOUND", + "303": "HTTP_303_SEE_OTHER", + "304": "HTTP_304_NOT_MODIFIED", + "305": "HTTP_305_USE_PROXY", + "306": "HTTP_306_RESERVED", + "307": "HTTP_307_TEMPORARY_REDIRECT", + "308": "HTTP_308_PERMANENT_REDIRECT", + "400": "HTTP_400_BAD_REQUEST", + "401": "HTTP_401_UNAUTHORIZED", + "402": "HTTP_402_PAYMENT_REQUIRED", + "403": "HTTP_403_FORBIDDEN", + "404": "HTTP_404_NOT_FOUND", + "405": "HTTP_405_METHOD_NOT_ALLOWED", + "406": "HTTP_406_NOT_ACCEPTABLE", + "407": "HTTP_407_PROXY_AUTHENTICATION_REQUIRED", + "408": "HTTP_408_REQUEST_TIMEOUT", + "409": "HTTP_409_CONFLICT", + "410": "HTTP_410_GONE", + "411": "HTTP_411_LENGTH_REQUIRED", + "412": "HTTP_412_PRECONDITION_FAILED", + "413": "HTTP_413_REQUEST_ENTITY_TOO_LARGE", + "414": "HTTP_414_REQUEST_URI_TOO_LONG", + "415": "HTTP_415_UNSUPPORTED_MEDIA_TYPE", + "416": "HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE", + "417": "HTTP_417_EXPECTATION_FAILED", + "418": "HTTP_418_IM_A_TEAPOT", + "421": "HTTP_421_MISDIRECTED_REQUEST", + "422": "HTTP_422_UNPROCESSABLE_ENTITY", + "423": "HTTP_423_LOCKED", + "424": "HTTP_424_FAILED_DEPENDENCY", + "426": "HTTP_426_UPGRADE_REQUIRED", + "428": "HTTP_428_PRECONDITION_REQUIRED", + "429": "HTTP_429_TOO_MANY_REQUESTS", + "431": "HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE", + "451": "HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS", + "500": "HTTP_500_INTERNAL_SERVER_ERROR", + "502": "HTTP_502_BAD_GATEWAY", + } + diff --git a/api_objects/errors/errors_dictionary.py b/api_objects/errors/errors_dictionary.py deleted file mode 100644 index 5371ffb..0000000 --- a/api_objects/errors/errors_dictionary.py +++ /dev/null @@ -1,49 +0,0 @@ -from json import loads - - -class ErrorMessages: - __messages__ = {} - - @classmethod - def get_message(cls, message_key, lang): - return cls.__messages__[lang][message_key] - - -class ErrorHandlers: - def __init__(self, requests, exceptions, response_model, status): - self.requests = requests # from fastapi.requests import Request - self.exceptions = exceptions # from fastapi.exceptions import HTTPException - self.response_model = ( - response_model # from fastapi.responses import JSONResponse - ) - self.status = status # from fastapi import status - - @classmethod - def create(cls, requests, exceptions, response_model, status): - return cls(requests, exceptions, response_model, status) - - def exception_handler_http(self, request, exc): - exc_detail = getattr(exc, "detail", None) - try: - detail = loads(str(exc_detail)) - return self.response_model( - status_code=exc.status_code, - content={ - "Data": detail.get("data", {}), - "Error": detail.get("error_case", "UNKNOWN"), - "Message": detail.get( - "message", "An error occurred while processing the request" - ), - }, - ) - except Exception as e: - return self.response_model( - status_code=exc.status_code, - content={"Error": str(exc_detail), "Message": f"{str(e)}", "Data": {}}, - ) - - def exception_handler_exception(self, request, exc): - return self.response_model( - status_code=self.status.HTTP_417_EXPECTATION_FAILED, - content={"message": exc.__str__()}, - ) diff --git a/databases/extensions/auth.py b/databases/extensions/auth.py index 9970b8b..0973f33 100644 --- a/databases/extensions/auth.py +++ b/databases/extensions/auth.py @@ -1,21 +1,22 @@ import hashlib import uuid import requests - +import secrets from datetime import timedelta -from fastapi.exceptions import HTTPException + +from api_services.redis.functions import RedisActions from databases.no_sql_models.validations import ( PasswordHistoryViaUser, AccessHistoryViaUser, ) -from api_library.date_time_actions.date_functions import system_arrow, client_arrow +from api_library.date_time_actions.date_functions import system_arrow from api_configs import ApiStatic, Auth class PasswordModule: @staticmethod def generate_token(length=32): - return uuid.uuid4().__str__()[:length] + return secrets.token_urlsafe(length) @staticmethod def create_hashed_password(domain: str, id_: str, password: str): @@ -25,6 +26,10 @@ class PasswordModule: def check_password(cls, domain, id_, password, password_hashed): return cls.create_hashed_password(domain, id_, password) == password_hashed + @classmethod + def generate_access_token(cls): + return secrets.token_urlsafe(Auth.ACCESS_TOKEN_LENGTH) + class AuthModule(PasswordModule): @@ -33,6 +38,7 @@ class AuthModule(PasswordModule): from databases import Users from sqlalchemy import or_ from fastapi import status + from fastapi.exceptions import HTTPException found_user: Users = Users.query.filter( or_( @@ -56,9 +62,6 @@ class AuthModule(PasswordModule): ) return found_user - def generate_access_token(self): - return self.generate_token(Auth.ACCESS_TOKEN_LENGTH) - def remove_refresher_token(self, domain, disconnect: bool = False): from databases import UsersTokens @@ -76,6 +79,8 @@ class AuthModule(PasswordModule): UsersTokens.save() def check_password_is_different(self, password): + from fastapi.exceptions import HTTPException + main_domain = self.get_main_domain_and_other_domains(get_main_domain=True) if self.hash_password == self.create_hashed_password( domain=main_domain, id_=self.uu_id, password=password @@ -88,9 +93,9 @@ class AuthModule(PasswordModule): @staticmethod def create_password(found_user, password, password_token=None): from databases import MongoQueryIdentity, Users + from fastapi.exceptions import HTTPException found_user: Users = found_user - if found_user.password_token: replace_day = 0 try: @@ -145,9 +150,7 @@ class AuthModule(PasswordModule): return found_user.password_token def generate_refresher_token(self, domain: str, remember_me=False): - from databases import ( - UsersTokens, - ) + from databases import UsersTokens if remember_me: refresh_token = self.generate_token(Auth.REFRESHER_TOKEN_LENGTH) @@ -183,6 +186,7 @@ class AuthModule(PasswordModule): class UserLoginModule(AuthModule): + @classmethod def set_login_details_to_mongo_database( cls, found_user, headers_request, access_token, record_id @@ -212,8 +216,8 @@ class UserLoginModule(AuthModule): "address": address_package, "user_id": found_user.id, } - already_exits = mongo_db.mongo_engine.filter_by(filter_query) or None - no_address_validates = mongo_db.mongo_engine.get_all()[0] == 0 + already_exits = mongo_db.mongo_engine.filter_by(filter_query).data + no_address_validates = mongo_db.mongo_engine.get_all().data access_via_user = query_engine.update_access_history_via_user( AccessHistoryViaUser( **{ @@ -241,7 +245,6 @@ class UserLoginModule(AuthModule): } }, ) - print("update_mongo", update_mongo) else: insert_mongo = mongo_db.mongo_engine.insert( payload={ @@ -257,14 +260,14 @@ class UserLoginModule(AuthModule): "is_first": True if no_address_validates else False, } ) - print("insert_mongo", insert_mongo) @classmethod def login_user_with_credentials(cls, data, request): from databases.sql_models.identity.identity import Users from ApiServices.api_handlers.auth_actions.auth import AuthActions + from fastapi.exceptions import HTTPException - found_user = Users.check_user_exits( + found_user: Users = Users.check_user_exits( access_key=data.access_key, domain=data.domain ) if not found_user: @@ -280,19 +283,20 @@ class UserLoginModule(AuthModule): ) if found_user.check_password( - domain=data.domain, - id_=found_user.uu_id, - password_hashed=found_user.hash_password, - password=data.password, - ): + domain=data.domain, + id_=found_user.uu_id, + password_hashed=found_user.hash_password, + password=data.password, + ): access_object_to_redis = AuthActions.save_access_token_to_redis( request=request, found_user=found_user, domain=data.domain, # remember_me=data.remember_me, ) - print("access_object_to_redis", access_object_to_redis) access_token = access_object_to_redis.get("access_token") + + access_object_to_redis["user"] = found_user headers_request = dict(request.headers) headers_request["evyos-user-agent"] = headers_request.get("user-agent") headers_request["evyos-platform"] = headers_request.get("user-agent") diff --git a/databases/sql_models/building/decision_book.py b/databases/sql_models/building/decision_book.py index d2c51be..9e70ec3 100644 --- a/databases/sql_models/building/decision_book.py +++ b/databases/sql_models/building/decision_book.py @@ -17,7 +17,7 @@ from sqlalchemy import ( Numeric, Integer, ) -from sqlalchemy.orm import mapped_column, Mapped, relationship +from sqlalchemy.orm import Mapped, mapped_column, relationship from api_validations.validations_request import ( InsertDecisionBook, @@ -26,6 +26,19 @@ from api_validations.validations_request import ( InsertBuildDecisionBookProjects, ) from databases.sql_models.core_mixin import CrudCollection +from databases.language_models.building.decision_book import ( + BuildDecisionBookLanguageModel, + BuildDecisionBookInvitationsLanguageModel, + BuildDecisionBookPersonLanguageModel, + BuildDecisionBookPersonOccupantsLanguageModel, + BuildDecisionBookItemsLanguageModel, + BuildDecisionBookItemsUnapprovedLanguageModel, + BuildDecisionBookPaymentsLanguageModel, + BuildDecisionBookLegalLanguageModel, + BuildDecisionBookProjectsLanguageModel, + BuildDecisionBookProjectPersonLanguageModel, + BuildDecisionBookProjectItemsLanguageModel, +) class BuildDecisionBook(CrudCollection): @@ -44,6 +57,7 @@ class BuildDecisionBook(CrudCollection): __tablename__ = "build_decision_book" __exclude__fields__ = [] + __language_model__ = BuildDecisionBookLanguageModel decision_book_pdf_path: Mapped[str] = mapped_column( String, server_default="", nullable=True @@ -242,6 +256,7 @@ class BuildDecisionBookInvitations(CrudCollection): __tablename__ = "build_decision_book_invitations" __exclude__fields__ = [] + __language_model__ = BuildDecisionBookInvitationsLanguageModel build_id: Mapped[int] = mapped_column(Integer, nullable=False) build_uu_id: Mapped[str] = mapped_column( @@ -341,6 +356,7 @@ class BuildDecisionBookPerson(CrudCollection): __tablename__ = "build_decision_book_person" __exclude__fields__ = [] __enum_list__ = [("management_typecode", "BuildManagementType", "bm")] + __language_model__ = BuildDecisionBookPersonLanguageModel dues_percent_discount: Mapped[int] = mapped_column(SmallInteger, server_default="0") dues_fix_discount: Mapped[float] = mapped_column(Numeric(10, 2), server_default="0") @@ -517,6 +533,7 @@ class BuildDecisionBookPersonOccupants(CrudCollection): __tablename__ = "build_decision_book_person_occupants" __exclude__fields__ = [] + __language_model__ = BuildDecisionBookPersonOccupantsLanguageModel build_decision_book_person_id: Mapped[int] = mapped_column( ForeignKey("build_decision_book_person.id"), nullable=False @@ -559,6 +576,7 @@ class BuildDecisionBookItems(CrudCollection): __tablename__ = "build_decision_book_items" __exclude__fields__ = [] + __language_model__ = BuildDecisionBookItemsLanguageModel item_order: Mapped[int] = mapped_column( SmallInteger, nullable=False, comment="Order Number of Item" @@ -799,6 +817,7 @@ class BuildDecisionBookItemsUnapproved(CrudCollection): __tablename__ = "build_decision_book_items_unapproved" __exclude__fields__ = [] + __language_model__ = BuildDecisionBookItemsUnapprovedLanguageModel item_objection: Mapped[str] = mapped_column( Text, nullable=False, comment="Objection Content" @@ -841,6 +860,7 @@ class BuildDecisionBookPayments(CrudCollection): __tablename__ = "build_decision_book_payments" __exclude__fields__ = [] __enum_list__ = [("receive_debit", "DebitTypes", "D")] + __language_model__ = BuildDecisionBookPaymentsLanguageModel payment_plan_time_periods: Mapped[str] = mapped_column( String(10), nullable=False, comment="Payment Plan Time Periods" @@ -951,6 +971,7 @@ class BuildDecisionBookLegal(CrudCollection): __tablename__ = "build_decision_book_legal" __exclude__fields__ = [] + __language_model__ = BuildDecisionBookLegalLanguageModel period_start_date: Mapped[TIMESTAMP] = mapped_column( TIMESTAMP(timezone=True), nullable=False, comment="Start Date of Legal Period" @@ -1027,6 +1048,7 @@ class BuildDecisionBookProjects(CrudCollection): __tablename__ = "build_decision_book_projects" __exclude__fields__ = [] + __language_model__ = BuildDecisionBookProjectsLanguageModel project_no: Mapped[str] = mapped_column( String(12), nullable=True, comment="Project Number of Decision Book" @@ -1194,6 +1216,7 @@ class BuildDecisionBookProjectPerson(CrudCollection): __tablename__ = "build_decision_book_project_person" __exclude__fields__ = [] + __language_model__ = BuildDecisionBookProjectPersonLanguageModel # __enum_list__ = [("management_typecode", "ProjectTeamTypes", "PTT-EMP")] dues_percent_discount: Mapped[int] = mapped_column(SmallInteger, server_default="0") @@ -1226,6 +1249,7 @@ class BuildDecisionBookProjectItems(CrudCollection): __tablename__ = "build_decision_book_project_items" __exclude__fields__ = [] + __language_model__ = BuildDecisionBookProjectItemsLanguageModel item_header: Mapped[str] = mapped_column( String, nullable=False, comment="Item Header" diff --git a/databases/sql_models/company/employee.py b/databases/sql_models/company/employee.py index 82e48cc..a9fb8f2 100644 --- a/databases/sql_models/company/employee.py +++ b/databases/sql_models/company/employee.py @@ -5,6 +5,13 @@ from sqlalchemy import ( Numeric, ) from sqlalchemy.orm import mapped_column, Mapped + +from databases.language_models.company.employee import ( +StaffLanguageModel, +EmployeesLanguageModel, +EmployeeHistoryLanguageModel, +EmployeesSalariesLanguageModel, +) from databases.sql_models.core_mixin import CrudCollection from api_validations.validations_request import InsertCompanyEmployees @@ -14,6 +21,7 @@ class Staff(CrudCollection): __tablename__ = "staff" __exclude__fields__ = [] + __language_model__ = StaffLanguageModel staff_description: Mapped[str] = mapped_column( String, server_default="", comment="Staff Description" @@ -61,6 +69,7 @@ class Employees(CrudCollection): __tablename__ = "employees" __exclude__fields__ = [] + __language_model__ = EmployeesLanguageModel staff_id: Mapped[int] = mapped_column(ForeignKey("staff.id")) staff_uu_id: Mapped[str] = mapped_column( @@ -81,6 +90,7 @@ class EmployeeHistory(CrudCollection): __tablename__ = "employee_history" __exclude__fields__ = [] + __language_model__ = EmployeeHistoryLanguageModel staff_id: Mapped[int] = mapped_column( ForeignKey("staff.id"), nullable=False, comment="Staff ID" @@ -105,6 +115,7 @@ class EmployeesSalaries(CrudCollection): __tablename__ = "employee_salaries" __exclude__fields__ = [] + __language_model__ = EmployeesSalariesLanguageModel gross_salary: Mapped[float] = mapped_column( Numeric(20, 6), nullable=False, comment="Gross Salary" diff --git a/databases/sql_models/core_mixin.py b/databases/sql_models/core_mixin.py index 86b8709..2a575a0 100644 --- a/databases/sql_models/core_mixin.py +++ b/databases/sql_models/core_mixin.py @@ -21,7 +21,7 @@ from sqlalchemy_mixins.serialize import SerializeMixin from sqlalchemy_mixins.repr import ReprMixin from sqlalchemy_mixins.smartquery import SmartQueryMixin -from api_library import DateTimeLocal, client_arrow, system_arrow +from api_library import DateTimeLocal, system_arrow from databases.sql_models.sql_operations import FilterAttributes from databases.sql_models.postgres_database import Base @@ -138,14 +138,14 @@ class CrudMixin(Base, SmartQueryMixin, SessionMixin, FilterAttributes): return True, int(val) elif key_ == Mapped[TIMESTAMP]: return True, str( - cls.client_arrow.get(str(val)).format("DD-MM-YYYY HH:mm:ss") + cls.client_arrow.get(str(val)).format("DD-MM-YYYY HH:mm:ss +0") ) elif key_ == Mapped[str]: return True, str(val) else: if isinstance(val, datetime.datetime): return True, str( - cls.client_arrow.get(str(val)).format("DD-MM-YYYY HH:mm:ss") + cls.client_arrow.get(str(val)).format("DD-MM-YYYY HH:mm:ss +0") ) elif isinstance(value_type, bool): return True, bool(val) diff --git a/databases/sql_models/identity/identity.py b/databases/sql_models/identity/identity.py index 3dedbe1..49399be 100644 --- a/databases/sql_models/identity/identity.py +++ b/databases/sql_models/identity/identity.py @@ -122,6 +122,9 @@ class Users(CrudCollection, UserLoginModule, SelectAction): person_uu_id: Mapped[str] = mapped_column( String, server_default="", comment="Person UUID", index=True ) + local_timezone = mapped_column( + String, server_default="GMT+3", comment="Local timezone of user" + ) person = relationship("People", back_populates="user", foreign_keys=[person_id]) @property @@ -183,11 +186,6 @@ class Users(CrudCollection, UserLoginModule, SelectAction): @classmethod def credentials(cls): person_object = People.filter_by_one(system=True, id=cls.person_id).data - # if not person_object: - # raise HTTPException( - # status_code=401, - # detail="Person not found. Please contact the admin.", - # ) if person_object: return { "person_id": person_object.id, diff --git a/databases/sql_models/others/enums.py b/databases/sql_models/others/enums.py index f8dd442..472e750 100644 --- a/databases/sql_models/others/enums.py +++ b/databases/sql_models/others/enums.py @@ -15,6 +15,7 @@ from databases.sql_models.core_mixin import CrudCollection class ApiEnumDropdown(CrudCollection): __tablename__ = "api_enum_dropdown" __exclude__fields__ = ["enum_class"] + __language_model__ = None id: Mapped[int] = mapped_column(primary_key=True) uu_id: Mapped[str] = mapped_column( diff --git a/databases/sql_models/rules/rules.py b/databases/sql_models/rules/rules.py index 6619d5e..f8e91e2 100644 --- a/databases/sql_models/rules/rules.py +++ b/databases/sql_models/rules/rules.py @@ -1,7 +1,9 @@ -from sqlalchemy import String, Boolean -from databases.sql_models.core_mixin import CrudCollection +from sqlalchemy import String from sqlalchemy.orm import mapped_column, Mapped +from databases.language_models.rules.rules import EndpointRestrictionLanguageModel +from databases.sql_models.core_mixin import CrudCollection + class EndpointRestriction(CrudCollection): """ @@ -10,6 +12,7 @@ class EndpointRestriction(CrudCollection): __tablename__ = "endpoint_restriction" __exclude__fields__ = [] + __language_model__ = EndpointRestrictionLanguageModel endpoint_function: Mapped[str] = mapped_column( String, server_default="", comment="Function name of the API endpoint" diff --git a/databases/sql_models/sql_operations.py b/databases/sql_models/sql_operations.py index 328995b..3103f84 100644 --- a/databases/sql_models/sql_operations.py +++ b/databases/sql_models/sql_operations.py @@ -50,18 +50,9 @@ class FilterAttributes: """Save the data via the metadata.""" try: meta_data = getattr(cls, "meta_data", {}) - meta_data_created = meta_data.get("created", False) - if meta_data_created: - print("meta_data_created commit", meta_data_created) + if meta_data.get("created", False): cls.__session__.commit() - print("meta_data_created rollback", meta_data_created) cls.__session__.rollback() - # cls.raise_http_exception( - # status_code="HTTP_304_NOT_MODIFIED", - # error_case=meta_data.get("error_case", "Error on save and commit"), - # data={}, - # message=meta_data.get("message", "Error on save and commit"), - # ) except SQLAlchemyError as e: cls.raise_http_exception( status_code="HTTP_304_NOT_MODIFIED", @@ -233,8 +224,9 @@ class FilterAttributes: for smart_iter in cls.filter_expr(**filter_list.get("query", {})): if key := arg_left(smart_iter): args = cls.add_new_arg_to_args(args, key, smart_iter) - query = cls._query().filter(*args) + query = cls._query() cls.total_count = query.count() + query = query.filter(*args) if cls.filter_attr: data_query = cls.add_query_to_filter(query, filter_list) cls.filter_attr = None diff --git a/service_app_init/runner.py b/service_app_init/runner.py index cb2fdb6..790d0d3 100644 --- a/service_app_init/runner.py +++ b/service_app_init/runner.py @@ -157,6 +157,6 @@ def create_application_defaults_func(create_address=False): if __name__ == "__main__": print("Service App Initial Default Runner is running") - # do_alembic() - create_application_defaults_func(create_address=True) + do_alembic() + # create_application_defaults_func(create_address=True) print("Service App Initial Default Runner is completed")