From 3d5a43220eb9d96c0874b175ab929a936e628248 Mon Sep 17 00:00:00 2001 From: berkay Date: Sat, 25 Jan 2025 20:59:47 +0300 Subject: [PATCH] redis implemntations and api setup completed --- .idea/inspectionProfiles/Project_Default.xml | 35 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/wag-managment-api-service-version-5.iml | 15 + .idea/workspace.xml | 185 ++++++++++ ApiLayers/AllApiNeeds/application/__init__.py | 1 - ApiLayers/AllApiNeeds/application/app.py | 21 -- ApiLayers/AllApiNeeds/create_file.py | 94 ----- ApiLayers/AllApiNeeds/create_routes.py | 127 ------- ApiLayers/AllConfigs/Email/configs.py | 2 +- ApiLayers/AllConfigs/NoSqlDatabase/configs.py | 2 +- ApiLayers/AllConfigs/Redis/configs.py | 10 +- ApiLayers/AllConfigs/SqlDatabase/configs.py | 2 +- ApiLayers/AllConfigs/Token/config.py | 44 --- ApiLayers/ApiLibrary/__init__.py | 9 +- .../date_time_actions/date_functions.py | 3 +- ApiLayers/ApiLibrary/token/password_module.py | 2 +- .../ApiServices/Cluster/create_router.py | 39 ++ .../ApiServices/Cluster/handle_cluster.py | 5 + .../ApiServices/Login/user_login_handler.py | 13 +- ApiLayers/ApiServices/Token/token_handler.py | 19 +- ApiLayers/ApiServices/__init__.py | 2 +- .../ApiValidations/Request/account_records.py | 2 +- ApiLayers/ApiValidations/Request/address.py | 4 +- .../ApiValidations/Request/application.py | 2 +- ApiLayers/ApiValidations/Request/area.py | 2 +- .../ApiValidations/Request/authentication.py | 2 +- .../Request/base_validations.py | 2 +- .../Request/build_living_space.py | 4 +- .../ApiValidations/Request/build_part.py | 2 +- ApiLayers/ApiValidations/Request/building.py | 2 +- ApiLayers/ApiValidations/Request/company.py | 2 +- .../Request/core_request_validations.py | 2 +- .../ApiValidations/Request/decision_book.py | 2 +- .../ApiValidations/Request/departments.py | 4 +- ApiLayers/ApiValidations/Request/employee.py | 2 +- ApiLayers/ApiValidations/Request/events.py | 2 +- ApiLayers/ApiValidations/Request/modules.py | 2 +- ApiLayers/ApiValidations/Request/people.py | 2 +- .../Request/project_decision_book.py | 2 +- ApiLayers/ApiValidations/Request/rules.py | 4 +- ApiLayers/ApiValidations/Request/services.py | 2 +- ApiLayers/ApiValidations/Request/staff.py | 2 +- ApiLayers/ApiValidations/Request/user.py | 2 +- .../Response/default_response.py | 65 ++-- .../ErrorHandlers/api_exc_handler.py | 12 +- ApiLayers/ErrorHandlers/__init__.py | 7 +- ApiLayers/ErrorHandlers/bases.py | 4 +- .../Database/account/account.py | 2 +- .../LanguageModels/Database/account/iban.py | 2 +- .../Database/building/budget.py | 2 +- .../LanguageModels/Database/building/build.py | 2 +- .../Database/building/decision_book.py | 2 +- .../Database/company/company.py | 2 +- .../Database/company/department.py | 2 +- .../Database/company/employee.py | 2 +- .../LanguageModels/Database/event/event.py | 2 +- .../Database/identity/identity.py | 2 +- .../LanguageModels/Database/rules/rules.py | 2 +- .../Errors/merge_all_error_languages.py | 2 +- .../middleware => Middleware}/__init__.py | 0 .../auth_middleware.py | 33 +- .../function_wrappers.py | 0 .../token_event_middleware.py | 246 +++++++------ ApiLayers/Schemas/account/account.py | 2 +- ApiLayers/Schemas/building/build.py | 8 +- ApiLayers/Schemas/building/decision_book.py | 6 +- ApiLayers/Schemas/company/company.py | 8 +- ApiLayers/Schemas/company/employee.py | 4 +- ApiLayers/Schemas/event/event.py | 2 +- ApiLayers/Schemas/identity/identity.py | 15 +- ApiLayers/Schemas/rules/rules.py | 2 +- DockerApiServices/AuthServiceApi/Dockerfile | 9 +- .../AuthServiceApi}/app.py | 8 +- .../AuthServiceApi}/app_handler.py | 27 +- DockerApiServices/AuthServiceApi/config.py | 70 ++++ .../AuthServiceApi/create_file.py | 48 +++ .../AuthServiceApi/create_routes.py | 31 ++ .../AuthServiceApi/events_file.py | 3 + .../AuthServiceApi}/open_api_creator.py | 68 ++-- DockerApiServices/EventServiceApi/Dockerfile | 24 +- DockerApiServices/EventServiceApi/app.py | 27 ++ .../EventServiceApi/app_handler.py | 77 ++++ DockerApiServices/EventServiceApi/config.py | 60 +++ .../EventServiceApi/create_file.py | 48 +++ .../EventServiceApi/create_routes.py | 33 ++ .../EventServiceApi/events_file.py | 3 + .../EventServiceApi/open_api_creator.py | 249 +++++++++++++ DockerApiServices/InitServiceApi/Dockerfile | 40 ++ .../ValidationServiceApi/Dockerfile | 26 +- DockerApiServices/ValidationServiceApi/app.py | 27 ++ .../ValidationServiceApi/app_handler.py | 77 ++++ .../ValidationServiceApi/config.py | 58 +++ .../ValidationServiceApi/create_file.py | 48 +++ .../ValidationServiceApi/create_routes.py | 33 ++ .../ValidationServiceApi/events_file.py | 3 + .../ValidationServiceApi/open_api_creator.py | 249 +++++++++++++ Events/AllEvents/authentication/__init__.py | 4 +- .../authentication/auth/api_events.py | 89 +++-- Events/AllEvents/authentication/auth/auth.py | 211 +++++++++-- .../AllEvents/authentication/auth/cluster.py | 1 + .../authentication/auth/function_handlers.py | 63 ++-- Events/AllEvents/authentication/auth/info.py | 2 - .../AllEvents/authentication/auth/models.py | 1 - Events/AllEvents/events/__init__.py | 7 + Events/AllEvents/events_file.py | 6 + .../validations/validation/validation.py | 20 +- Events/Engine/__init__.py | 5 +- Events/Engine/abstract_class.py | 344 ++++++++++-------- .../set_defaults/category_cluster_models.py | 42 +++ .../set_defaults/prepare_redis_items.py | 84 +++++ Events/Engine/set_defaults/run.py | 14 + Events/Engine/set_defaults/setClusters.py | 174 ++++++++- Events/abstract_class.py | 38 -- Scratches/endpoint.py | 33 +- Services/PostgresDb/Models/core_alchemy.py | 6 +- Services/PostgresDb/Models/crud_alchemy.py | 4 +- .../PostgresDb/Models/filter_functions.py | 6 +- Services/PostgresDb/__init__.py | 2 +- Services/PostgresDb/database.py | 2 +- Services/Redis/Actions/actions.py | 2 +- Services/Redis/Models/access.py | 30 ++ Services/Redis/Models/cluster.py | 17 + Services/Redis/Models/row.py | 36 +- Services/Redis/__init__.py | 2 +- Services/Redis/conn.py | 2 +- docker-compose-services.yml | 9 +- .../backend/unified_schema_service.py | 57 +-- .../Models_old/alchemy_response.py | 0 .../Models_old/base_model.py | 0 .../Models_old/filter_functions.py | 0 .../PostgresDb => trash}/Models_old/mixins.py | 0 .../PostgresDb => trash}/Models_old/query.py | 0 .../Models_old/response.py | 0 trash/abstract_class.py | 33 ++ {Events => trash}/abstract_class_old.py | 162 +++++---- trash/auth_old.py | 11 +- 138 files changed, 2888 insertions(+), 1117 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/wag-managment-api-service-version-5.iml create mode 100644 .idea/workspace.xml delete mode 100644 ApiLayers/AllApiNeeds/application/__init__.py delete mode 100644 ApiLayers/AllApiNeeds/application/app.py delete mode 100644 ApiLayers/AllApiNeeds/create_file.py delete mode 100644 ApiLayers/AllApiNeeds/create_routes.py create mode 100644 ApiLayers/ApiServices/Cluster/create_router.py create mode 100644 ApiLayers/ApiServices/Cluster/handle_cluster.py rename ApiLayers/{AllApiNeeds/middleware => Middleware}/__init__.py (100%) rename ApiLayers/{AllApiNeeds/middleware => Middleware}/auth_middleware.py (86%) rename ApiLayers/{AllApiNeeds/middleware => Middleware}/function_wrappers.py (100%) rename ApiLayers/{AllApiNeeds/middleware => Middleware}/token_event_middleware.py (79%) rename {ApiLayers/AllApiNeeds => DockerApiServices/AuthServiceApi}/app.py (77%) rename {ApiLayers/AllApiNeeds => DockerApiServices/AuthServiceApi}/app_handler.py (75%) create mode 100644 DockerApiServices/AuthServiceApi/config.py create mode 100644 DockerApiServices/AuthServiceApi/create_file.py create mode 100644 DockerApiServices/AuthServiceApi/create_routes.py create mode 100644 DockerApiServices/AuthServiceApi/events_file.py rename {ApiLayers/AllApiNeeds => DockerApiServices/AuthServiceApi}/open_api_creator.py (82%) create mode 100644 DockerApiServices/EventServiceApi/app.py create mode 100644 DockerApiServices/EventServiceApi/app_handler.py create mode 100644 DockerApiServices/EventServiceApi/config.py create mode 100644 DockerApiServices/EventServiceApi/create_file.py create mode 100644 DockerApiServices/EventServiceApi/create_routes.py create mode 100644 DockerApiServices/EventServiceApi/events_file.py create mode 100644 DockerApiServices/EventServiceApi/open_api_creator.py create mode 100644 DockerApiServices/InitServiceApi/Dockerfile create mode 100644 DockerApiServices/ValidationServiceApi/app.py create mode 100644 DockerApiServices/ValidationServiceApi/app_handler.py create mode 100644 DockerApiServices/ValidationServiceApi/config.py create mode 100644 DockerApiServices/ValidationServiceApi/create_file.py create mode 100644 DockerApiServices/ValidationServiceApi/create_routes.py create mode 100644 DockerApiServices/ValidationServiceApi/events_file.py create mode 100644 DockerApiServices/ValidationServiceApi/open_api_creator.py create mode 100644 Events/AllEvents/events_file.py create mode 100644 Events/Engine/set_defaults/category_cluster_models.py create mode 100644 Events/Engine/set_defaults/prepare_redis_items.py delete mode 100644 Events/abstract_class.py create mode 100644 Services/Redis/Models/access.py create mode 100644 Services/Redis/Models/cluster.py rename {Services/PostgresDb => trash}/Models_old/alchemy_response.py (100%) rename {Services/PostgresDb => trash}/Models_old/base_model.py (100%) rename {Services/PostgresDb => trash}/Models_old/filter_functions.py (100%) rename {Services/PostgresDb => trash}/Models_old/mixins.py (100%) rename {Services/PostgresDb => trash}/Models_old/query.py (100%) rename {Services/PostgresDb => trash}/Models_old/response.py (100%) create mode 100644 trash/abstract_class.py rename {Events => trash}/abstract_class_old.py (89%) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..4129666 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,35 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0653e14 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/wag-managment-api-service-version-5.iml b/.idea/wag-managment-api-service-version-5.iml new file mode 100644 index 0000000..5fdd65b --- /dev/null +++ b/.idea/wag-managment-api-service-version-5.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..48fba99 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1737819471396 + + + + \ No newline at end of file diff --git a/ApiLayers/AllApiNeeds/application/__init__.py b/ApiLayers/AllApiNeeds/application/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/ApiLayers/AllApiNeeds/application/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ApiLayers/AllApiNeeds/application/app.py b/ApiLayers/AllApiNeeds/application/app.py deleted file mode 100644 index 257add1..0000000 --- a/ApiLayers/AllApiNeeds/application/app.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Base FastAPI application configuration. -""" - -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware - - -def create_app() -> FastAPI: - app = FastAPI(title="API Service") - - # Configure CORS - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - - return app diff --git a/ApiLayers/AllApiNeeds/create_file.py b/ApiLayers/AllApiNeeds/create_file.py deleted file mode 100644 index 8ce7074..0000000 --- a/ApiLayers/AllApiNeeds/create_file.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -FastAPI Application Factory Module - -This module provides functionality to create and configure a FastAPI application with: -- Custom OpenAPI schema configuration -- Security scheme configuration for Bearer authentication -- Automatic router registration -- Response class configuration -- Security requirements for protected endpoints -""" - -from typing import Any, Dict, List, Tuple -from fastapi import FastAPI, APIRouter -from fastapi.responses import JSONResponse, RedirectResponse -from fastapi.openapi.utils import get_openapi - -from AllConfigs.Token.config import Auth -from AllConfigs.main import MainConfig as Config - -from create_routes import get_all_routers - - -def setup_security_schema() -> Dict[str, Any]: - """ - Configure security schema for the OpenAPI documentation. - - Returns: - Dict[str, Any]: Security schema configuration - """ - return { - "components": { - "securitySchemes": { - "Bearer Auth": { - "type": "apiKey", - "in": "header", - "name": Auth.ACCESS_TOKEN_TAG, - "description": "Enter: **'Bearer <JWT>'**, where JWT is the access token", - } - } - } - } - - -def configure_route_security( - path: str, method: str, schema: Dict[str, Any], protected_paths: List[str] -) -> None: - """ - Configure security requirements for a specific route. - - Args: - path: Route path - method: HTTP method - schema: OpenAPI schema to modify - protected_paths: List of paths that require authentication - """ - if path in protected_paths: - if "paths" in schema and path in schema["paths"]: - if method.lower() in schema["paths"][path]: - schema["paths"][path][method.lower()]["security"] = [{"Bearer": []}] - - -def create_app() -> FastAPI: - """ - Create and configure a FastAPI application with dynamic route creation. - - Returns: - FastAPI: Configured FastAPI application instance - """ - - from open_api_creator import create_openapi_schema - - # Get all routers and protected routes using the dynamic route creation - - app = FastAPI( - title=Config.TITLE, - description=Config.DESCRIPTION, - default_response_class=JSONResponse, - ) # Initialize FastAPI app - - @app.get("/", include_in_schema=False, summary=str(Config.DESCRIPTION)) - async def home() -> RedirectResponse: - """Redirect root path to API documentation.""" - return RedirectResponse(url="/docs") - - # Get all routers and protected routes using the dynamic route creation - routers, protected_routes = get_all_routers() - - # Include all routers - for router in routers: - app.include_router(router) - - app.openapi = lambda app=app: create_openapi_schema(app) - - return app diff --git a/ApiLayers/AllApiNeeds/create_routes.py b/ApiLayers/AllApiNeeds/create_routes.py deleted file mode 100644 index b969985..0000000 --- a/ApiLayers/AllApiNeeds/create_routes.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -Route configuration and factory module. -Handles dynamic route creation based on configurations. -""" - -from typing import Optional, Dict, Any, List, Callable, TypeVar, ParamSpec - -P = ParamSpec("P") # For function parameters -R = TypeVar("R") # For return type - -from dataclasses import dataclass -from functools import wraps -from fastapi import APIRouter, Request -from fastapi.routing import APIRoute -from middleware.auth_middleware import MiddlewareModule -from pydantic import BaseModel -from AllConfigs.main import MainConfig as Config - - -@dataclass -class EndpointFactoryConfig: - endpoint: str - method: str - summary: str - description: str - endpoint_function: Callable[P, R] # Now accepts any parameters and return type - is_auth_required: bool = True - is_event_required: bool = False - extra_options: Dict[str, Any] = None - - def __post_init__(self): - if self.extra_options is None: - self.extra_options = {} - - -class EnhancedEndpointFactory: - def __init__(self, router_config: dict): - self.router = APIRouter( - prefix=router_config["prefix"], - tags=router_config["tags"], - include_in_schema=router_config.get("include_in_schema", True), - ) - self.endpoints = router_config["endpoints"] - self.protected_routes: Dict[str, List[str]] = {} - - def create_endpoint(self, config: EndpointFactoryConfig): - """ - Create an endpoint directly from the configuration. - - Args: - config: EndpointFactoryConfig instance containing endpoint configuration - """ - endpoint_path = config.endpoint - endpoint_function = config.endpoint_function - - if config.is_auth_required: - # endpoint_function = MiddlewareModule.auth_required(endpoint_function) - # Track protected routes - full_path = f"{self.router.prefix}{endpoint_path}" - if full_path not in self.protected_routes: - self.protected_routes[full_path] = [] - self.protected_routes[full_path].append(config.method.lower()) - - # Register the endpoint with FastAPI router - getattr(self.router, config.method.lower())( - endpoint_path, - summary=config.summary, - description=config.description, - **config.extra_options, - )(endpoint_function) - - def get_router(self) -> APIRouter: - """Get the configured router.""" - return self.router - - def get_protected_routes(self) -> Dict[str, List[str]]: - """Get the protected routes mapping.""" - return self.protected_routes - - -async def health_check(request: Request): - """Default health check endpoint.""" - return {"status": "healthy", "message": "Service is running"} - - -async def ping_test(request: Request, service_name: str = "base-router"): - """Default ping test endpoint.""" - return {"ping": "pong", "service": service_name} - - -def get_all_routers() -> tuple[List[APIRouter], Dict[str, List[str]]]: - """ - Get all routers and protected routes from route configurations. - - Returns: - tuple: (routers, protected_routes) - """ - from ApiEvents.route_configs import get_route_configs - - routers = [] - all_protected_routes = {} - - # Get route configurations from the registry - route_configs = get_route_configs() - factory_all = [] - for config in route_configs: - factory = EnhancedEndpointFactory(config) - - # Create endpoints from configuration - for endpoint_dict in config["endpoints"]: - endpoint_config = EndpointFactoryConfig( - endpoint=endpoint_dict["endpoint"], - method=endpoint_dict["method"], - summary=endpoint_dict["summary"], - description=endpoint_dict["description"], - endpoint_function=endpoint_dict["endpoint_function"], - is_auth_required=endpoint_dict["is_auth_required"], - is_event_required=endpoint_dict["is_event_required"], - extra_options=endpoint_dict.get("extra_options", {}), - ) - factory.create_endpoint(endpoint_config) - factory_all.append(endpoint_config.__dict__) - - # Add router and protected routes - routers.append(factory.get_router()) - all_protected_routes.update(factory.get_protected_routes()) - return routers, all_protected_routes diff --git a/ApiLayers/AllConfigs/Email/configs.py b/ApiLayers/AllConfigs/Email/configs.py index 2cd61c9..ce92382 100644 --- a/ApiLayers/AllConfigs/Email/configs.py +++ b/ApiLayers/AllConfigs/Email/configs.py @@ -1,4 +1,4 @@ -from AllConfigs import HostConfig +from ApiLayers.AllConfigs import HostConfig class EmailConfig: diff --git a/ApiLayers/AllConfigs/NoSqlDatabase/configs.py b/ApiLayers/AllConfigs/NoSqlDatabase/configs.py index 8dcc208..813b12f 100644 --- a/ApiLayers/AllConfigs/NoSqlDatabase/configs.py +++ b/ApiLayers/AllConfigs/NoSqlDatabase/configs.py @@ -1,4 +1,4 @@ -from AllConfigs import HostConfig +from ApiLayers.AllConfigs import HostConfig class MongoConfig: diff --git a/ApiLayers/AllConfigs/Redis/configs.py b/ApiLayers/AllConfigs/Redis/configs.py index cebf5cf..1303782 100644 --- a/ApiLayers/AllConfigs/Redis/configs.py +++ b/ApiLayers/AllConfigs/Redis/configs.py @@ -1,4 +1,4 @@ -from AllConfigs import HostConfig +from ApiLayers.AllConfigs import HostConfig class WagRedis: @@ -16,14 +16,18 @@ class WagRedis: db=WagRedis.REDIS_DB, ) + class RedisCategoryKeys: + REBUILD: str = "REBUILD" + ENDPOINT2CLASS: str = "ENDPOINT2CLASS" LANGUAGE_MODELS: str = "LANGUAGE_MODELS" VALIDATION_USER: str = "VALIDATION_USER" + CLUSTER_INDEX: str = "CLUSTER_INDEX" CLUSTER_FUNCTION_CODES: str = "CLUSTER_FUNCTION_CODES" METHOD_FUNCTION_CODES: str = "METHOD_FUNCTION_CODES" MENU_FIRST_LAYER: str = "MENU_FIRST_LAYER" PAGE_MAPPER: str = "PAGE_MAPPER" MENU_MAPPER: str = "MENU_MAPPER" AUTH: str = "AUTH" - OCC: str = "Occupant" - EMP: str = "Employee" + OCCUPANT: str = "OCCUPANT" + EMPLOYEE: str = "EMPLOYEE" \ No newline at end of file diff --git a/ApiLayers/AllConfigs/SqlDatabase/configs.py b/ApiLayers/AllConfigs/SqlDatabase/configs.py index f33684b..c36f662 100644 --- a/ApiLayers/AllConfigs/SqlDatabase/configs.py +++ b/ApiLayers/AllConfigs/SqlDatabase/configs.py @@ -1,4 +1,4 @@ -from AllConfigs import HostConfig +from ApiLayers.AllConfigs import HostConfig class WagDatabase: diff --git a/ApiLayers/AllConfigs/Token/config.py b/ApiLayers/AllConfigs/Token/config.py index e95c300..9793055 100644 --- a/ApiLayers/AllConfigs/Token/config.py +++ b/ApiLayers/AllConfigs/Token/config.py @@ -1,21 +1,6 @@ import datetime -class ApiStatic: - PLACEHOLDER = "https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg" - FORGOT_LINK = "https://www.evyos.com.tr/password/create?tokenUrl=" - BLACKLIST_LINK = "https://www.evyos.com.tr/support/unknown-login-notice/" - APP_DIR = "/home/berkay/git-evyos/api-managment-backend/" - - @classmethod - def forgot_link(cls, forgot_key): - return cls.FORGOT_LINK + forgot_key - - @classmethod - def blacklist_login(cls, record_id): - return cls.BLACKLIST_LINK + record_id - - class Auth: ACCESS_EMAIL_EXT = "evyos.com.tr" ACCESS_TOKEN_TAG = "evyos-session-key" @@ -40,32 +25,3 @@ class Auth: TOKEN_EXPIRE_DAY_5 = datetime.timedelta(days=5) TOKEN_EXPIRE_DAY_15 = datetime.timedelta(days=15) TOKEN_EXPIRE_DAY_30 = datetime.timedelta(days=30) - - -class Routers: - NO_TOKEN_REQUIRES = [ - "/", - "/metrics", - "/openapi.json", - "/docs", - "/redoc", - "/auth/login", - "/favicon.ico", - "/docs/oauth2-redirect", - "/authentication/select", - "/authentication/login", - "/authentication/logout", - "/authentication/refresher", - "/authentication/refresh", - "/authentication/disconnect", - "/authentication/create_password", - "/authentication/reset_password", - "/authentication/forgot", - "/authentication/valid", - ] - NO_EVENT_REQUIRES = [ - "/access/endpoints/available", - "/access/endpoint/available", - "/validations/endpoint", - "/authentication/avatar", - ] diff --git a/ApiLayers/ApiLibrary/__init__.py b/ApiLayers/ApiLibrary/__init__.py index 9da6e7d..b64093b 100644 --- a/ApiLayers/ApiLibrary/__init__.py +++ b/ApiLayers/ApiLibrary/__init__.py @@ -1,10 +1,13 @@ -from ApiLibrary.date_time_actions.date_functions import ( +from ApiLayers.ApiLibrary.date_time_actions.date_functions import ( DateTimeLocal, system_arrow, client_arrow, ) -from ApiLibrary.extensions.select import SelectActionWithEmployee, SelectAction -from ApiLibrary.common.line_number import get_line_number_for_error +from ApiLayers.ApiLibrary.extensions.select import ( + SelectActionWithEmployee, + SelectAction, +) +from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error __all__ = [ "DateTimeLocal", diff --git a/ApiLayers/ApiLibrary/date_time_actions/date_functions.py b/ApiLayers/ApiLibrary/date_time_actions/date_functions.py index 533cf08..3071d23 100644 --- a/ApiLayers/ApiLibrary/date_time_actions/date_functions.py +++ b/ApiLayers/ApiLibrary/date_time_actions/date_functions.py @@ -1,6 +1,7 @@ import arrow import calendar -from AllConfigs.main import MainConfig as Config + +from ApiLayers.AllConfigs.main import MainConfig as Config class DateTimeLocal: diff --git a/ApiLayers/ApiLibrary/token/password_module.py b/ApiLayers/ApiLibrary/token/password_module.py index d2ebbf0..8032f93 100644 --- a/ApiLayers/ApiLibrary/token/password_module.py +++ b/ApiLayers/ApiLibrary/token/password_module.py @@ -3,7 +3,7 @@ import uuid import secrets import random -from AllConfigs.Token.config import Auth +from ApiLayers.AllConfigs.Token.config import Auth class PasswordModule: diff --git a/ApiLayers/ApiServices/Cluster/create_router.py b/ApiLayers/ApiServices/Cluster/create_router.py new file mode 100644 index 0000000..f59dd5f --- /dev/null +++ b/ApiLayers/ApiServices/Cluster/create_router.py @@ -0,0 +1,39 @@ +from fastapi import APIRouter +import uuid +from Events.Engine.abstract_class import CategoryCluster, MethodToEvent + + +class CreateRouterFromCluster: + + def __init__(self, **kwargs): + self.prefix = kwargs.get("prefix") + self.tags = kwargs.get("tags") + self.router = APIRouter(prefix=self.prefix, tags=self.tags) + + +class CreateEndpointFromCluster: + + def __init__(self, **kwargs): + self.router: CategoryCluster = kwargs.get("router") + self.method_endpoint: MethodToEvent = kwargs.get("method_endpoint") + self.unique_id = str(uuid.uuid4())[:8] # Use first 8 chars of UUID for brevity + self.attach_router() + + def attach_router(self): + method = getattr(self.router, self.method_endpoint.METHOD.lower()) + + # Create a unique operation ID based on the endpoint path, method, and a unique identifier + base_path = self.method_endpoint.URL.strip('/').replace('/', '_').replace('-', '_') + operation_id = f"{base_path}_{self.method_endpoint.METHOD.lower()}_{self.unique_id}" + + kwargs = { + "path": self.method_endpoint.URL, + "summary": self.method_endpoint.SUMMARY, + "description": self.method_endpoint.DESCRIPTION, + "operation_id": operation_id + } + + if hasattr(self.method_endpoint, 'RESPONSE_MODEL') and self.method_endpoint.RESPONSE_MODEL is not None: + kwargs["response_model"] = self.method_endpoint.RESPONSE_MODEL + + method(**kwargs)(self.method_endpoint.endpoint_callable) diff --git a/ApiLayers/ApiServices/Cluster/handle_cluster.py b/ApiLayers/ApiServices/Cluster/handle_cluster.py new file mode 100644 index 0000000..b7f1884 --- /dev/null +++ b/ApiLayers/ApiServices/Cluster/handle_cluster.py @@ -0,0 +1,5 @@ +from Services.Redis import RedisActions, AccessToken +from Services.Redis.Models.cluster import RedisList + +redis_list = RedisList(redis_key="test") + diff --git a/ApiLayers/ApiServices/Login/user_login_handler.py b/ApiLayers/ApiServices/Login/user_login_handler.py index ec416c7..4ca7889 100644 --- a/ApiLayers/ApiServices/Login/user_login_handler.py +++ b/ApiLayers/ApiServices/Login/user_login_handler.py @@ -1,11 +1,12 @@ from typing import Any, ClassVar, Dict from sqlalchemy import or_ -from ApiLibrary.common.line_number import get_line_number_for_error -from Schemas import Users -from ErrorHandlers import HTTPExceptionApi -from ApiValidations.Request.authentication import Login -from ApiLibrary.token.password_module import PasswordModule -from ApiServices.Token.token_handler import TokenService + +from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error +from ApiLayers.ErrorHandlers import HTTPExceptionApi +from ApiLayers.ApiValidations.Request.authentication import Login +from ApiLayers.ApiLibrary.token.password_module import PasswordModule +from ApiLayers.ApiServices.Token.token_handler import TokenService +from ApiLayers.Schemas import Users class UserLoginModule: diff --git a/ApiLayers/ApiServices/Token/token_handler.py b/ApiLayers/ApiServices/Token/token_handler.py index 871fa63..7f8538f 100644 --- a/ApiLayers/ApiServices/Token/token_handler.py +++ b/ApiLayers/ApiServices/Token/token_handler.py @@ -2,21 +2,20 @@ from typing import List, Union, TypeVar, Dict, Any, Optional, TYPE_CHECKING -from AllConfigs.Token.config import Auth -from ApiLibrary.common.line_number import get_line_number_for_error -from ApiLibrary.date_time_actions.date_functions import DateTimeLocal -from ApiLibrary.token.password_module import PasswordModule -from ErrorHandlers import HTTPExceptionApi -from Schemas.identity.identity import UsersTokens, People -from Services.Redis import RedisActions, AccessToken -from ApiValidations.Custom.token_objects import ( +from ApiLayers.AllConfigs.Token.config import Auth +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, UserType, CompanyToken, OccupantToken, ) -from Schemas import ( +from ApiLayers.Schemas import ( Users, BuildLivingSpace, BuildParts, @@ -30,6 +29,8 @@ from Schemas import ( OccupantTypes, ) from Services.Redis.Models.response import RedisResponse +from Services.Redis import RedisActions, AccessToken + if TYPE_CHECKING: from fastapi import Request diff --git a/ApiLayers/ApiServices/__init__.py b/ApiLayers/ApiServices/__init__.py index be0bc54..7dec7ae 100644 --- a/ApiLayers/ApiServices/__init__.py +++ b/ApiLayers/ApiServices/__init__.py @@ -1,4 +1,4 @@ -from ApiServices.Token.token_handler import TokenService +from ApiLayers.ApiServices.Token.token_handler import TokenService __all__ = [ "TokenService", diff --git a/ApiLayers/ApiValidations/Request/account_records.py b/ApiLayers/ApiValidations/Request/account_records.py index 5dca43c..cdbf515 100644 --- a/ApiLayers/ApiValidations/Request/account_records.py +++ b/ApiLayers/ApiValidations/Request/account_records.py @@ -1,4 +1,4 @@ -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel from typing import Optional diff --git a/ApiLayers/ApiValidations/Request/address.py b/ApiLayers/ApiValidations/Request/address.py index 8939943..5482b5e 100644 --- a/ApiLayers/ApiValidations/Request/address.py +++ b/ApiLayers/ApiValidations/Request/address.py @@ -1,7 +1,7 @@ from typing import Optional -from ApiValidations.Request import PydanticBaseModel, ListOptions -from ApiValidations.handler import BaseModelRegular +from ApiLayers.ApiValidations.Request import PydanticBaseModel, ListOptions +from ApiLayers.ApiValidations.handler import BaseModelRegular class PostCodeValidation: diff --git a/ApiLayers/ApiValidations/Request/application.py b/ApiLayers/ApiValidations/Request/application.py index 3a1147b..f9e4739 100644 --- a/ApiLayers/ApiValidations/Request/application.py +++ b/ApiLayers/ApiValidations/Request/application.py @@ -1,4 +1,4 @@ -from ApiValidations.Request import BaseModelRegular +from ApiLayers.ApiValidations.Request import BaseModelRegular class SingleEnumClassKeyValidation: diff --git a/ApiLayers/ApiValidations/Request/area.py b/ApiLayers/ApiValidations/Request/area.py index 635adbf..1cc63aa 100644 --- a/ApiLayers/ApiValidations/Request/area.py +++ b/ApiLayers/ApiValidations/Request/area.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class BuildAreaValidation: diff --git a/ApiLayers/ApiValidations/Request/authentication.py b/ApiLayers/ApiValidations/Request/authentication.py index 8f3b7ad..abbb249 100644 --- a/ApiLayers/ApiValidations/Request/authentication.py +++ b/ApiLayers/ApiValidations/Request/authentication.py @@ -1,4 +1,4 @@ -from ApiValidations.Request import BaseModelRegular +from ApiLayers.ApiValidations.Request import BaseModelRegular from typing import Optional from pydantic import BaseModel, ConfigDict, Field diff --git a/ApiLayers/ApiValidations/Request/base_validations.py b/ApiLayers/ApiValidations/Request/base_validations.py index 27eb461..832193f 100644 --- a/ApiLayers/ApiValidations/Request/base_validations.py +++ b/ApiLayers/ApiValidations/Request/base_validations.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.handler import BaseModelRegular +from ApiLayers.ApiValidations.handler import BaseModelRegular class ListOptions(BaseModelRegular): diff --git a/ApiLayers/ApiValidations/Request/build_living_space.py b/ApiLayers/ApiValidations/Request/build_living_space.py index d39dd2a..b13e503 100644 --- a/ApiLayers/ApiValidations/Request/build_living_space.py +++ b/ApiLayers/ApiValidations/Request/build_living_space.py @@ -1,11 +1,11 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular +from ApiLayers.ApiValidations.Request import BaseModelRegular # from api_validations.validations_request import ( # PydanticBaseModel, # PydanticBaseModelValidation, # ) -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class InsertBuildLivingSpace(BaseModelRegular): diff --git a/ApiLayers/ApiValidations/Request/build_part.py b/ApiLayers/ApiValidations/Request/build_part.py index ae6d104..9d9d2bb 100644 --- a/ApiLayers/ApiValidations/Request/build_part.py +++ b/ApiLayers/ApiValidations/Request/build_part.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class InsertBuildTypes(BaseModelRegular): diff --git a/ApiLayers/ApiValidations/Request/building.py b/ApiLayers/ApiValidations/Request/building.py index 7d81bdc..042b62e 100644 --- a/ApiLayers/ApiValidations/Request/building.py +++ b/ApiLayers/ApiValidations/Request/building.py @@ -1,6 +1,6 @@ from typing import Optional from datetime import datetime -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class InsertBuild(BaseModelRegular): diff --git a/ApiLayers/ApiValidations/Request/company.py b/ApiLayers/ApiValidations/Request/company.py index 59cf061..50b394a 100644 --- a/ApiLayers/ApiValidations/Request/company.py +++ b/ApiLayers/ApiValidations/Request/company.py @@ -1,5 +1,5 @@ from typing import Optional, List -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class InsertCompany(BaseModelRegular): diff --git a/ApiLayers/ApiValidations/Request/core_request_validations.py b/ApiLayers/ApiValidations/Request/core_request_validations.py index 0c303a9..aa08831 100644 --- a/ApiLayers/ApiValidations/Request/core_request_validations.py +++ b/ApiLayers/ApiValidations/Request/core_request_validations.py @@ -1,6 +1,6 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular +from ApiLayers.ApiValidations.Request import BaseModelRegular class ListOptionsValidation: diff --git a/ApiLayers/ApiValidations/Request/decision_book.py b/ApiLayers/ApiValidations/Request/decision_book.py index 61fc3a4..e3d14fa 100644 --- a/ApiLayers/ApiValidations/Request/decision_book.py +++ b/ApiLayers/ApiValidations/Request/decision_book.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel, ListOptions +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel, ListOptions class DecisionBookDecisionBookInvitations(BaseModelRegular): diff --git a/ApiLayers/ApiValidations/Request/departments.py b/ApiLayers/ApiValidations/Request/departments.py index 377b119..dbdaa01 100644 --- a/ApiLayers/ApiValidations/Request/departments.py +++ b/ApiLayers/ApiValidations/Request/departments.py @@ -1,7 +1,5 @@ from typing import Optional -from ApiValidations.Request import ( - PydanticBaseModel, -) +from ApiLayers.ApiValidations.Request import PydanticBaseModel class DepartmentsPydantic(PydanticBaseModel): diff --git a/ApiLayers/ApiValidations/Request/employee.py b/ApiLayers/ApiValidations/Request/employee.py index a1d5c24..9a68b46 100644 --- a/ApiLayers/ApiValidations/Request/employee.py +++ b/ApiLayers/ApiValidations/Request/employee.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class BindEmployees2People(PydanticBaseModel): diff --git a/ApiLayers/ApiValidations/Request/events.py b/ApiLayers/ApiValidations/Request/events.py index 98f4db9..83a5ba6 100644 --- a/ApiLayers/ApiValidations/Request/events.py +++ b/ApiLayers/ApiValidations/Request/events.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular +from ApiLayers.ApiValidations.Request import BaseModelRegular class RegisterEvents2EmployeeValidation: diff --git a/ApiLayers/ApiValidations/Request/modules.py b/ApiLayers/ApiValidations/Request/modules.py index e6fcc67..26658ca 100644 --- a/ApiLayers/ApiValidations/Request/modules.py +++ b/ApiLayers/ApiValidations/Request/modules.py @@ -1,4 +1,4 @@ -from ApiValidations.Request import BaseModelRegular +from ApiLayers.ApiValidations.Request import BaseModelRegular class RegisterModules2OccupantValidation: diff --git a/ApiLayers/ApiValidations/Request/people.py b/ApiLayers/ApiValidations/Request/people.py index 281f355..b898ab1 100644 --- a/ApiLayers/ApiValidations/Request/people.py +++ b/ApiLayers/ApiValidations/Request/people.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class InsertPerson(BaseModelRegular): diff --git a/ApiLayers/ApiValidations/Request/project_decision_book.py b/ApiLayers/ApiValidations/Request/project_decision_book.py index 709852a..1bd4c2b 100644 --- a/ApiLayers/ApiValidations/Request/project_decision_book.py +++ b/ApiLayers/ApiValidations/Request/project_decision_book.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class InsertBuildDecisionBookProjectItems(BaseModelRegular): diff --git a/ApiLayers/ApiValidations/Request/rules.py b/ApiLayers/ApiValidations/Request/rules.py index c9f9845..cc8ef6f 100644 --- a/ApiLayers/ApiValidations/Request/rules.py +++ b/ApiLayers/ApiValidations/Request/rules.py @@ -1,6 +1,6 @@ from typing import Optional, List -from ApiValidations.Request import BaseModelRegular -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class CheckEndpointAccess(BaseModelRegular): diff --git a/ApiLayers/ApiValidations/Request/services.py b/ApiLayers/ApiValidations/Request/services.py index 54712c9..c5d71ef 100644 --- a/ApiLayers/ApiValidations/Request/services.py +++ b/ApiLayers/ApiValidations/Request/services.py @@ -1,4 +1,4 @@ -from ApiValidations.Request import BaseModelRegular +from ApiLayers.ApiValidations.Request import BaseModelRegular class RegisterServices2OccupantValidation: diff --git a/ApiLayers/ApiValidations/Request/staff.py b/ApiLayers/ApiValidations/Request/staff.py index 03cdadb..a98af03 100644 --- a/ApiLayers/ApiValidations/Request/staff.py +++ b/ApiLayers/ApiValidations/Request/staff.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.Request import BaseModelRegular, PydanticBaseModel +from ApiLayers.ApiValidations.Request import BaseModelRegular, PydanticBaseModel class InsertStaffValidation: diff --git a/ApiLayers/ApiValidations/Request/user.py b/ApiLayers/ApiValidations/Request/user.py index 7583a1b..c527684 100644 --- a/ApiLayers/ApiValidations/Request/user.py +++ b/ApiLayers/ApiValidations/Request/user.py @@ -1,5 +1,5 @@ from typing import Optional -from ApiValidations.Request import PydanticBaseModel +from ApiLayers.ApiValidations.Request import PydanticBaseModel class InsertUsersValidation: diff --git a/ApiLayers/ApiValidations/Response/default_response.py b/ApiLayers/ApiValidations/Response/default_response.py index 117ff2c..ab32299 100644 --- a/ApiLayers/ApiValidations/Response/default_response.py +++ b/ApiLayers/ApiValidations/Response/default_response.py @@ -3,6 +3,7 @@ from typing import Any, Optional from fastapi import status from fastapi.responses import JSONResponse + class BaseEndpointResponse: def __init__(self, code: str, lang: str): @@ -12,7 +13,7 @@ class BaseEndpointResponse: def retrieve_message(self): messages = {} return messages[self.code][self.lang] - + # 1. 200 OK class EndpointSuccessResponse(BaseEndpointResponse): @@ -24,8 +25,8 @@ class EndpointSuccessResponse(BaseEndpointResponse): completed=True, message=self.retrieve_message(), lang=self.lang, - data=data - ) + data=data, + ), ) @@ -39,15 +40,14 @@ class EndpointCreatedResponse(BaseEndpointResponse): completed=True, message=self.retrieve_message(), lang=self.lang, - data=data - ) + data=data, + ), ) # 3. 202 Accepted class EndpointAcceptedResponse(BaseEndpointResponse): - def as_dict(self, data: Optional[Dict[str, Any]] = None): return JSONResponse( status_code=status.HTTP_202_ACCEPTED, @@ -55,8 +55,8 @@ class EndpointAcceptedResponse(BaseEndpointResponse): completed=True, message=self.retrieve_message(), lang=self.lang, - data=data - ) + data=data, + ), ) @@ -70,8 +70,8 @@ class EndpointBadRequestResponse(BaseEndpointResponse): completed=False, message=self.retrieve_message(), lang=self.lang, - data=data - ) + data=data, + ), ) @@ -82,10 +82,8 @@ class EndpointUnauthorizedResponse(BaseEndpointResponse): return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, content=dict( - completed=False, - message=self.retrieve_message(), - lang=self.lang - ) + completed=False, message=self.retrieve_message(), lang=self.lang + ), ) @@ -96,10 +94,8 @@ class EndpointNotFoundResponse(BaseEndpointResponse): return JSONResponse( status_code=status.HTTP_404_NOT_FOUND, content=dict( - completed=False, - message=self.retrieve_message(), - lang=self.lang - ) + completed=False, message=self.retrieve_message(), lang=self.lang + ), ) @@ -110,10 +106,8 @@ class EndpointForbiddenResponse(BaseEndpointResponse): return JSONResponse( status_code=status.HTTP_403_FORBIDDEN, content=dict( - completed=False, - message=self.retrieve_message(), - lang=self.lang - ) + completed=False, message=self.retrieve_message(), lang=self.lang + ), ) @@ -124,15 +118,14 @@ class EndpointConflictResponse(BaseEndpointResponse): return JSONResponse( status_code=status.HTTP_409_CONFLICT, content=dict( - completed=False, - message=self.retrieve_message(), - lang=self.lang - ) + completed=False, message=self.retrieve_message(), lang=self.lang + ), ) + # 7. 429 Too Many Requests class EndpointTooManyRequestsResponse(BaseEndpointResponse): - + def __init__(self, retry_after: int): self.retry_after = retry_after @@ -141,10 +134,8 @@ class EndpointTooManyRequestsResponse(BaseEndpointResponse): status_code=status.HTTP_429_TOO_MANY_REQUESTS, headers={"Retry-After": str(self.retry_after)}, content=dict( - completed=False, - message=self.retrieve_message(), - lang=self.lang - ) + completed=False, message=self.retrieve_message(), lang=self.lang + ), ) @@ -155,10 +146,8 @@ class EndpointInternalErrorResponse(BaseEndpointResponse): return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=dict( - completed=False, - message=self.retrieve_message(), - lang=self.lang - ) + completed=False, message=self.retrieve_message(), lang=self.lang + ), ) @@ -168,8 +157,6 @@ class EndpointErrorResponse(BaseEndpointResponse): return JSONResponse( status_code=status.HTTP_304_NOT_MODIFIED, content=dict( - completed=False, - message=self.retrieve_message(), - lang=self.lang - ) + completed=False, message=self.retrieve_message(), lang=self.lang + ), ) diff --git a/ApiLayers/ErrorHandlers/ErrorHandlers/api_exc_handler.py b/ApiLayers/ErrorHandlers/ErrorHandlers/api_exc_handler.py index 96d8f5f..6745534 100644 --- a/ApiLayers/ErrorHandlers/ErrorHandlers/api_exc_handler.py +++ b/ApiLayers/ErrorHandlers/ErrorHandlers/api_exc_handler.py @@ -1,10 +1,12 @@ -from typing import Any, Dict, Union, Awaitable +from typing import Any, Union, Awaitable from fastapi import Request, WebSocket from fastapi.responses import Response -from LanguageModels.Errors.merge_all_error_languages import MergedErrorLanguageModels -from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi -from ErrorHandlers.bases import BaseErrorModelClass +from ApiLayers.LanguageModels.Errors.merge_all_error_languages import ( + MergedErrorLanguageModels, +) +from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi +from ApiLayers.ErrorHandlers.bases import BaseErrorModelClass class HTTPExceptionApiHandler: @@ -23,7 +25,7 @@ class HTTPExceptionApiHandler: @staticmethod def retrieve_error_message(exc: HTTPExceptionApi, error_languages) -> str: - from ErrorHandlers import DEFAULT_ERROR + from ApiLayers.ErrorHandlers import DEFAULT_ERROR return error_languages.get(str(exc.error_code).upper(), DEFAULT_ERROR) diff --git a/ApiLayers/ErrorHandlers/__init__.py b/ApiLayers/ErrorHandlers/__init__.py index fae403a..c83ecf8 100644 --- a/ApiLayers/ErrorHandlers/__init__.py +++ b/ApiLayers/ErrorHandlers/__init__.py @@ -1,9 +1,8 @@ -from ErrorHandlers.ErrorHandlers.api_exc_handler import ( +from ApiLayers.ErrorHandlers.ErrorHandlers.api_exc_handler import ( HTTPExceptionApiHandler, ) -from ErrorHandlers.Exceptions.api_exc import ( - HTTPExceptionApi, -) +from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi + DEFAULT_ERROR = "UNKNOWN_ERROR" diff --git a/ApiLayers/ErrorHandlers/bases.py b/ApiLayers/ErrorHandlers/bases.py index e7ee6a3..c045b80 100644 --- a/ApiLayers/ErrorHandlers/bases.py +++ b/ApiLayers/ErrorHandlers/bases.py @@ -1,5 +1,5 @@ -from ErrorHandlers.base import BaseError -from ErrorHandlers.statuses import Statuses +from ApiLayers.ErrorHandlers.base import BaseError +from ApiLayers.ErrorHandlers.statuses import Statuses class BaseErrorModelClass: diff --git a/ApiLayers/LanguageModels/Database/account/account.py b/ApiLayers/LanguageModels/Database/account/account.py index 8bb0857..57924b3 100644 --- a/ApiLayers/LanguageModels/Database/account/account.py +++ b/ApiLayers/LanguageModels/Database/account/account.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel AccountBooksLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Database/account/iban.py b/ApiLayers/LanguageModels/Database/account/iban.py index ccc1b49..8adc724 100644 --- a/ApiLayers/LanguageModels/Database/account/iban.py +++ b/ApiLayers/LanguageModels/Database/account/iban.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel BuildIbansLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Database/building/budget.py b/ApiLayers/LanguageModels/Database/building/budget.py index 515d6d5..b1b9890 100644 --- a/ApiLayers/LanguageModels/Database/building/budget.py +++ b/ApiLayers/LanguageModels/Database/building/budget.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel DecisionBookBudgetBooksLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Database/building/build.py b/ApiLayers/LanguageModels/Database/building/build.py index 06aaa7e..e71c9d0 100644 --- a/ApiLayers/LanguageModels/Database/building/build.py +++ b/ApiLayers/LanguageModels/Database/building/build.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel BuildTypesLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Database/building/decision_book.py b/ApiLayers/LanguageModels/Database/building/decision_book.py index fb2f6f5..8209b91 100644 --- a/ApiLayers/LanguageModels/Database/building/decision_book.py +++ b/ApiLayers/LanguageModels/Database/building/decision_book.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel BuildDecisionBookLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Database/company/company.py b/ApiLayers/LanguageModels/Database/company/company.py index 94bdb14..be9047b 100644 --- a/ApiLayers/LanguageModels/Database/company/company.py +++ b/ApiLayers/LanguageModels/Database/company/company.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel RelationshipDutyCompanyLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Database/company/department.py b/ApiLayers/LanguageModels/Database/company/department.py index 77b79f1..1ce4c33 100644 --- a/ApiLayers/LanguageModels/Database/company/department.py +++ b/ApiLayers/LanguageModels/Database/company/department.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel DepartmentsLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Database/company/employee.py b/ApiLayers/LanguageModels/Database/company/employee.py index 391d1f2..ca96af7 100644 --- a/ApiLayers/LanguageModels/Database/company/employee.py +++ b/ApiLayers/LanguageModels/Database/company/employee.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel StaffLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Database/event/event.py b/ApiLayers/LanguageModels/Database/event/event.py index 184608e..f30fd85 100644 --- a/ApiLayers/LanguageModels/Database/event/event.py +++ b/ApiLayers/LanguageModels/Database/event/event.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel EventsLanguageModel = dict( diff --git a/ApiLayers/LanguageModels/Database/identity/identity.py b/ApiLayers/LanguageModels/Database/identity/identity.py index aeb7869..3b3c685 100644 --- a/ApiLayers/LanguageModels/Database/identity/identity.py +++ b/ApiLayers/LanguageModels/Database/identity/identity.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel UsersTokensLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Database/rules/rules.py b/ApiLayers/LanguageModels/Database/rules/rules.py index 4d3f52e..bb79123 100644 --- a/ApiLayers/LanguageModels/Database/rules/rules.py +++ b/ApiLayers/LanguageModels/Database/rules/rules.py @@ -1,4 +1,4 @@ -from LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel +from ApiLayers.LanguageModels.Database.Mixins.crud_mixin import CrudCollectionLanguageModel EndpointRestrictionLanguageModel = dict( tr={ diff --git a/ApiLayers/LanguageModels/Errors/merge_all_error_languages.py b/ApiLayers/LanguageModels/Errors/merge_all_error_languages.py index 5f83f27..17c6c2d 100644 --- a/ApiLayers/LanguageModels/Errors/merge_all_error_languages.py +++ b/ApiLayers/LanguageModels/Errors/merge_all_error_languages.py @@ -1,4 +1,4 @@ -from LanguageModels.Errors.base_languages import BaseErrorLanguageModels +from ApiLayers.LanguageModels.Errors.base_languages import BaseErrorLanguageModels class MergedErrorLanguageModels: diff --git a/ApiLayers/AllApiNeeds/middleware/__init__.py b/ApiLayers/Middleware/__init__.py similarity index 100% rename from ApiLayers/AllApiNeeds/middleware/__init__.py rename to ApiLayers/Middleware/__init__.py diff --git a/ApiLayers/AllApiNeeds/middleware/auth_middleware.py b/ApiLayers/Middleware/auth_middleware.py similarity index 86% rename from ApiLayers/AllApiNeeds/middleware/auth_middleware.py rename to ApiLayers/Middleware/auth_middleware.py index c23d3d3..51b62a0 100644 --- a/ApiLayers/AllApiNeeds/middleware/auth_middleware.py +++ b/ApiLayers/Middleware/auth_middleware.py @@ -5,17 +5,19 @@ This module provides authentication decorator for protecting endpoints and a middleware for request timing measurements. """ +import inspect + from time import perf_counter -from typing import Callable, Optional, Dict, Any, Tuple, Union +from typing import Callable from functools import wraps from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware -from ApiLibrary.common.line_number import get_line_number_for_error -from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi -from AllConfigs.Token.config import Auth -import inspect +from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error +from ApiLayers.ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi +from ApiLayers.AllConfigs.Token.config import Auth +from ApiLayers.ApiServices.Token.token_handler import TokenService class MiddlewareModule: @@ -39,7 +41,6 @@ class MiddlewareModule: Raises: HTTPExceptionApi: If token is missing, invalid, or user not found """ - from ApiServices.Token.token_handler import TokenService # Get token and validate - will raise HTTPExceptionApi if invalid redis_token = TokenService.get_access_token_from_request(request=request) @@ -86,14 +87,22 @@ class MiddlewareModule: @wraps(func) async def wrapper(request: Request, *args, **kwargs): # Get and validate token context from request - # Create auth context and Attach auth context to both wrapper and original function - func.auth = cls.get_user_from_request(request) - wrapper.auth = func.auth + auth_context = { + "is_employee": False, + "is_occupant": False, + "context": {} + } + + # Set auth context on the wrapper function itself + setattr(wrapper, 'auth', auth_context) + # Call the original endpoint function if inspect.iscoroutinefunction(func): - return await func(request, *args, **kwargs) - return func(request, *args, **kwargs) - + result = await func(request, *args, **kwargs) + else: + result = func(request, *args, **kwargs) + + return result return wrapper diff --git a/ApiLayers/AllApiNeeds/middleware/function_wrappers.py b/ApiLayers/Middleware/function_wrappers.py similarity index 100% rename from ApiLayers/AllApiNeeds/middleware/function_wrappers.py rename to ApiLayers/Middleware/function_wrappers.py diff --git a/ApiLayers/AllApiNeeds/middleware/token_event_middleware.py b/ApiLayers/Middleware/token_event_middleware.py similarity index 79% rename from ApiLayers/AllApiNeeds/middleware/token_event_middleware.py rename to ApiLayers/Middleware/token_event_middleware.py index 3db8774..1129762 100644 --- a/ApiLayers/AllApiNeeds/middleware/token_event_middleware.py +++ b/ApiLayers/Middleware/token_event_middleware.py @@ -9,13 +9,12 @@ from typing import Callable, Dict, Any, Optional, Union from fastapi import Request from pydantic import BaseModel -from ApiLibrary.common.line_number import get_line_number_for_error -from ApiServices.Token.token_handler import TokenService -from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi -from Schemas.rules.rules import EndpointRestriction +from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error +from ApiLayers.ApiServices.Token.token_handler import TokenService +from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi +from ApiLayers.Schemas import Events, EndpointRestriction from .auth_middleware import MiddlewareModule -from Schemas import Events class EventFunctions: @@ -24,88 +23,7 @@ class EventFunctions: self.endpoint = endpoint self.request = request - def match_endpoint_with_accesiable_event(self) -> Optional[Dict[str, Any]]: - """ - Match an endpoint with accessible events. - Args: - endpoint: The endpoint to match - - Returns: - Dict containing the endpoint registration data - None if endpoint is not found in database - """ - access_token = TokenService.get_access_token_from_request(self.request) - token_context = TokenService.get_object_via_access_key( - access_token=access_token - ) - if token_context.is_employee: - reachable_event_codes: list[str] = ( - token_context.selected_company.reachable_event_codes - ) - elif token_context.is_occupant: - reachable_event_codes: list[str] = ( - token_context.selected_occupant.reachable_event_codes - ) - else: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Token not found", - ) - - if not access_token: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Token not found", - ) - - db = EndpointRestriction.new_session() - restriction = EndpointRestriction.filter_one( - EndpointRestriction.endpoint_name == self.endpoint, - db=db, - ).data - if not restriction: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Function code not found", - ) - - event_related = Events.filter_all( - Events.endpoint_id == restriction.id, - db=db, - ).data - if not event_related: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="No event is registered for this user.", - ) - an_event = event_related[0] - event_related_codes: list[str] = [ - event.function_code for event in event_related - ] - intersected_code: set = set(reachable_event_codes).intersection( - set(event_related_codes) - ) - if not len(list(intersected_code)) == 1: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="No event is registered for this user.", - ) - return { - "endpoint_url": self.endpoint, - "reachable_event_code": list(intersected_code)[0], - "class": an_event.function_class, - } def retrieve_function_dict(self) -> Optional[Dict[str, Any]]: """ @@ -201,9 +119,91 @@ class TokenEventMiddleware: """ @staticmethod - def event_required( - func: Callable[..., Dict[str, Any]] - ) -> Callable[..., Dict[str, Any]]: + def match_endpoint_with_accessible_event(request_from_scope, endpoint_from_scope) -> Optional[Dict[str, Any]]: + """ + Match an endpoint with accessible events. + + Args: + request_from_scope: The endpoint to match + + Returns: + Dict containing the endpoint registration data + None if endpoint is not found in database + """ + access_token = TokenService.get_access_token_from_request(request_from_scope) + token_context = TokenService.get_object_via_access_key( + access_token=access_token + ) + if token_context.is_employee: + reachable_event_codes: list[str] = ( + token_context.selected_company.reachable_event_codes + ) + elif token_context.is_occupant: + reachable_event_codes: list[str] = ( + token_context.selected_occupant.reachable_event_codes + ) + else: + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Token not found", + ) + + if not access_token: + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Token not found", + ) + + db = EndpointRestriction.new_session() + restriction = EndpointRestriction.filter_one( + EndpointRestriction.endpoint_name == endpoint_from_scope, + db=db, + ).data + if not restriction: + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="Function code not found", + ) + + event_related = Events.filter_all( + Events.endpoint_id == restriction.id, + db=db, + ).data + if not event_related: + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="No event is registered for this user.", + ) + an_event = event_related[0] + event_related_codes: list[str] = [ + event.function_code for event in event_related + ] + intersected_code: set = set(reachable_event_codes).intersection( + set(event_related_codes) + ) + if not len(list(intersected_code)) == 1: + raise HTTPExceptionApi( + error_code="", + lang="en", + loc=get_line_number_for_error(), + sys_msg="No event is registered for this user.", + ) + return { + "endpoint_url": endpoint_from_scope, + "reachable_event_code": list(intersected_code)[0], + "class": an_event.function_class, + } + + @classmethod + def event_required(cls, func: Callable) -> Callable: """ Decorator for endpoints with token and event requirements. This decorator: @@ -216,31 +216,36 @@ class TokenEventMiddleware: Returns: Callable: The wrapped function with both auth and event handling """ - # # First apply authentication - # authenticated_func = MiddlewareModule.auth_required(func) - authenticated_func = func + # First apply authentication + authenticated_func = MiddlewareModule.auth_required(func) @wraps(authenticated_func) async def wrapper(request: Request, *args, **kwargs) -> Dict[str, Any]: - - # Get function code from the function's metadata - endpoint_url = getattr(authenticated_func, "url_of_endpoint", {}) - if not endpoint_url: - raise HTTPExceptionApi( - error_code="", - lang="en", - loc=get_line_number_for_error(), - sys_msg="Function code not found", - ) - - # Make handler available to all functions in the chain - func.func_code = EventFunctions( - endpoint_url, request - ).match_endpoint_with_accesiable_event() - # Call the authenticated function + # Get the endpoint URL for matching with events + endpoint_url = str(request.url.path) + + # Set func_code first + func_code = "8aytr-" + setattr(wrapper, 'func_code', func_code) + + # Get auth context from the authenticated function's wrapper + auth_context = getattr(authenticated_func, 'auth', None) + print('auth_context', auth_context) + if auth_context is not None: + setattr(wrapper, 'auth', auth_context) + + # Execute the authenticated function and get its result if inspect.iscoroutinefunction(authenticated_func): - return await authenticated_func(request, *args, **kwargs) - return authenticated_func(request, *args, **kwargs) + result = await authenticated_func(request, *args, **kwargs) + else: + result = authenticated_func(request, *args, **kwargs) + + return result + + # Copy any existing attributes from the authenticated function + for attr in dir(authenticated_func): + if not attr.startswith('__'): + setattr(wrapper, attr, getattr(authenticated_func, attr)) return wrapper @@ -261,8 +266,8 @@ class TokenEventMiddleware: Callable: The wrapped function with both auth and event handling """ # First apply authentication - authenticated_func = MiddlewareModule.auth_required(func) - + # authenticated_func = MiddlewareModule.auth_required(func) + authenticated_func = func @wraps(authenticated_func) async def wrapper( request: Request, *args: Any, **kwargs: Any @@ -278,9 +283,9 @@ class TokenEventMiddleware: loc=get_line_number_for_error(), sys_msg="Endpoint not found", ) - wrapper.validation_code = EventFunctions( - endpoint_asked, request - ).retrieve_function_dict() + func.func_code = cls.match_endpoint_with_accessible_event( + endpoint_url, request + ) if inspect.iscoroutinefunction(authenticated_func): result = await authenticated_func(request, *args, **kwargs) else: @@ -289,9 +294,14 @@ class TokenEventMiddleware: wrapper.auth = function_auth func.auth = function_auth authenticated_func.auth = function_auth - # If result is a coroutine, await it - if inspect.iscoroutine(result): - result = await result + if inspect.iscoroutinefunction(authenticated_func): + result = await authenticated_func(request, *args, **kwargs) + else: + result = authenticated_func(request, *args, **kwargs) + if inspect.iscoroutinefunction(func): + result = await func(request, *args, **kwargs) + else: + result = func(request, *args, **kwargs) return result return wrapper diff --git a/ApiLayers/Schemas/account/account.py b/ApiLayers/Schemas/account/account.py index 707a233..2a30db6 100644 --- a/ApiLayers/Schemas/account/account.py +++ b/ApiLayers/Schemas/account/account.py @@ -11,7 +11,7 @@ from sqlalchemy import ( ) from Services.PostgresDb import CrudCollection -from LanguageModels.Database.account.account import ( +from ApiLayers.LanguageModels.Database.account.account import ( AccountBooksLanguageModel, AccountCodesLanguageModel, AccountRecordsLanguageModel, diff --git a/ApiLayers/Schemas/building/build.py b/ApiLayers/Schemas/building/build.py index 0efcdee..3366451 100644 --- a/ApiLayers/Schemas/building/build.py +++ b/ApiLayers/Schemas/building/build.py @@ -15,17 +15,17 @@ from sqlalchemy import ( or_, ) -from ApiLibrary import system_arrow, SelectActionWithEmployee +from ApiLayers.ApiLibrary import system_arrow, SelectActionWithEmployee from Services.PostgresDb import CrudCollection -from ApiValidations.Request import ( +from ApiLayers.ApiValidations.Request import ( InsertBuild, InsertBuildParts, InsertBuildLivingSpace, UpdateBuild, ) -from ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTokenObject -from LanguageModels.Database.building.build import ( +from ApiLayers.ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTokenObject +from ApiLayers.LanguageModels.Database.building.build import ( BuildTypesLanguageModel, Part2EmployeeLanguageModel, BuildPartsLanguageModel, diff --git a/ApiLayers/Schemas/building/decision_book.py b/ApiLayers/Schemas/building/decision_book.py index 12cd710..de8e53c 100644 --- a/ApiLayers/Schemas/building/decision_book.py +++ b/ApiLayers/Schemas/building/decision_book.py @@ -4,7 +4,7 @@ from decimal import Decimal from typing import List from fastapi import HTTPException, status -from ApiLibrary.date_time_actions.date_functions import system_arrow +from ApiLayers.ApiLibrary.date_time_actions.date_functions import system_arrow from sqlalchemy import ( String, @@ -19,14 +19,14 @@ from sqlalchemy import ( ) from sqlalchemy.orm import Mapped, mapped_column, relationship -from ApiValidations.Request import ( +from ApiLayers.ApiValidations.Request import ( InsertDecisionBook, InsertBuildDecisionBookItems, InsertBuildDecisionBookItemDebits, InsertBuildDecisionBookProjects, ) from Services.PostgresDb import CrudCollection -from LanguageModels.Database.building.decision_book import ( +from ApiLayers.LanguageModels.Database.building.decision_book import ( BuildDecisionBookLanguageModel, BuildDecisionBookInvitationsLanguageModel, BuildDecisionBookPersonLanguageModel, diff --git a/ApiLayers/Schemas/company/company.py b/ApiLayers/Schemas/company/company.py index dea49af..73752a2 100644 --- a/ApiLayers/Schemas/company/company.py +++ b/ApiLayers/Schemas/company/company.py @@ -11,14 +11,14 @@ from sqlalchemy import ( ) from sqlalchemy.orm import mapped_column, relationship, Mapped -from ApiLibrary.extensions.select import SelectAction -from ApiValidations.Custom.token_objects import EmployeeTokenObject -from ApiValidations.Request import ( +from ApiLayers.ApiLibrary.extensions.select import SelectAction +from ApiLayers.ApiValidations.Custom.token_objects import EmployeeTokenObject +from ApiLayers.ApiValidations.Request import ( InsertCompany, UpdateCompany, MatchCompany2Company, ) -from LanguageModels.Database.company.company import ( +from ApiLayers.LanguageModels.Database.company.company import ( RelationshipDutyCompanyLanguageModel, CompaniesLanguageModel, # CompanyDutiesLanguageModel, diff --git a/ApiLayers/Schemas/company/employee.py b/ApiLayers/Schemas/company/employee.py index dd892ca..748fc93 100644 --- a/ApiLayers/Schemas/company/employee.py +++ b/ApiLayers/Schemas/company/employee.py @@ -6,7 +6,7 @@ from sqlalchemy import ( ) from sqlalchemy.orm import mapped_column, Mapped -from LanguageModels.Database.company.employee import ( +from ApiLayers.LanguageModels.Database.company.employee import ( StaffLanguageModel, EmployeesLanguageModel, EmployeeHistoryLanguageModel, @@ -14,7 +14,7 @@ from LanguageModels.Database.company.employee import ( ) from Services.PostgresDb import CrudCollection -from ApiValidations.Request import InsertCompanyEmployees +from ApiLayers.ApiValidations.Request import InsertCompanyEmployees class Staff(CrudCollection): diff --git a/ApiLayers/Schemas/event/event.py b/ApiLayers/Schemas/event/event.py index d30b6ce..3f6cf9e 100644 --- a/ApiLayers/Schemas/event/event.py +++ b/ApiLayers/Schemas/event/event.py @@ -1,5 +1,5 @@ from Services.PostgresDb import CrudCollection -from LanguageModels.Database.event.event import ( +from ApiLayers.LanguageModels.Database.event.event import ( EventsLanguageModel, ModulesLanguageModel, ServicesLanguageModel, diff --git a/ApiLayers/Schemas/identity/identity.py b/ApiLayers/Schemas/identity/identity.py index eef7ce6..133ebe1 100644 --- a/ApiLayers/Schemas/identity/identity.py +++ b/ApiLayers/Schemas/identity/identity.py @@ -8,24 +8,23 @@ from sqlalchemy import ( ForeignKey, Index, TIMESTAMP, - func, Text, BigInteger, Numeric, + func, or_, ) from sqlalchemy.orm import mapped_column, relationship, Mapped -from ApiLibrary.date_time_actions.date_functions import system_arrow -from AllConfigs.Token.config import Auth, ApiStatic -from ApiLibrary.extensions.select import SelectAction, SelectActionWithEmployee +from ApiLayers.ApiLibrary.date_time_actions.date_functions import system_arrow +from ApiLayers.ApiLibrary.extensions.select import SelectAction, SelectActionWithEmployee +from ApiLayers.AllConfigs.Token.config import Auth from Services.PostgresDb import CrudCollection +from config import ApiStatic -# from databases.extensions import SelectAction, SelectActionWithEmployee -# from databases.extensions.auth import UserLoginModule -from ApiValidations.Request import InsertUsers, InsertPerson -from LanguageModels.Database.identity.identity import ( +from ApiLayers.ApiValidations.Request import InsertUsers, InsertPerson +from ApiLayers.LanguageModels.Database.identity.identity import ( UsersTokensLanguageModel, UsersLanguageModel, PeopleLanguageModel, diff --git a/ApiLayers/Schemas/rules/rules.py b/ApiLayers/Schemas/rules/rules.py index 95285a3..a2bcb12 100644 --- a/ApiLayers/Schemas/rules/rules.py +++ b/ApiLayers/Schemas/rules/rules.py @@ -1,7 +1,7 @@ from sqlalchemy import String from sqlalchemy.orm import mapped_column, Mapped -from LanguageModels.Database.rules.rules import EndpointRestrictionLanguageModel +from ApiLayers.LanguageModels.Database.rules.rules import EndpointRestrictionLanguageModel from Services.PostgresDb import CrudCollection diff --git a/DockerApiServices/AuthServiceApi/Dockerfile b/DockerApiServices/AuthServiceApi/Dockerfile index 082ced1..a406c4f 100644 --- a/DockerApiServices/AuthServiceApi/Dockerfile +++ b/DockerApiServices/AuthServiceApi/Dockerfile @@ -19,15 +19,18 @@ RUN poetry config virtualenvs.create false \ && rm -rf ~/.cache/pypoetry # Copy application code -COPY DockerApiServices/AllApiNeeds /app +COPY DockerApiServices/AuthServiceApi /app # Copy application code COPY ApiLayers /app/ApiLayers +COPY Services /app/Services # Events -COPY Events/AllEvents/auth /app/Events/AllEvents/auth +# COPY Events/base_request_model.py /app/Events/base_request_model.py +COPY Events/Engine /app/Events/Engine COPY Events/base_request_model.py /app/Events/base_request_model.py -COPY Events/abstract_class.py /app/Events/abstract_class.py +COPY Events/AllEvents/authentication /app/Events/AllEvents/authentication +COPY DockerApiServices/AuthServiceApi/events_file.py /app/Events/AllEvents/events_file.py # Set Python path to include app directory ENV PYTHONPATH=/app \ diff --git a/ApiLayers/AllApiNeeds/app.py b/DockerApiServices/AuthServiceApi/app.py similarity index 77% rename from ApiLayers/AllApiNeeds/app.py rename to DockerApiServices/AuthServiceApi/app.py index 1d12a8b..68ed880 100644 --- a/ApiLayers/AllApiNeeds/app.py +++ b/DockerApiServices/AuthServiceApi/app.py @@ -10,9 +10,11 @@ This module initializes and configures the FastAPI application with: """ import uvicorn + from prometheus_fastapi_instrumentator import Instrumentator -from app_handler import setup_middleware, get_uvicorn_config +from app_handler import setup_middleware from create_file import create_app +from config import ApiConfig app = create_app() # Initialize FastAPI application @@ -21,5 +23,5 @@ setup_middleware(app) # Configure middleware and exception handlers if __name__ == "__main__": - uvicorn_config = get_uvicorn_config() # Run the application with Uvicorn - uvicorn.Server(uvicorn.Config(**uvicorn_config)).run() + # Run the application with Uvicorn + uvicorn.Server(uvicorn.Config(**ApiConfig.as_dict())).run() diff --git a/ApiLayers/AllApiNeeds/app_handler.py b/DockerApiServices/AuthServiceApi/app_handler.py similarity index 75% rename from ApiLayers/AllApiNeeds/app_handler.py rename to DockerApiServices/AuthServiceApi/app_handler.py index 7c1b5f2..3156e6a 100644 --- a/ApiLayers/AllApiNeeds/app_handler.py +++ b/DockerApiServices/AuthServiceApi/app_handler.py @@ -7,13 +7,12 @@ This module contains all the handler functions for configuring and setting up th - Uvicorn server configuration """ -from typing import Dict, Any - +from fastapi import FastAPI, Request, status from fastapi.middleware.cors import CORSMiddleware -from fastapi import FastAPI, Request, HTTPException, status from fastapi.responses import JSONResponse -from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi -from middleware.auth_middleware import RequestTimingMiddleware, LoggerTimingMiddleware + +from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi +from ApiLayers.Middleware.auth_middleware import RequestTimingMiddleware, LoggerTimingMiddleware def setup_cors_middleware(app: FastAPI) -> None: @@ -56,7 +55,7 @@ def setup_exception_handlers(app: FastAPI) -> None: Args: app: FastAPI application instance """ - from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApiHandler + from ApiLayers.ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApiHandler custom_exception_handler = HTTPExceptionApiHandler(response_model=JSONResponse) app.add_exception_handler( @@ -76,19 +75,3 @@ def setup_middleware(app: FastAPI) -> None: app.add_middleware(RequestTimingMiddleware) app.add_middleware(LoggerTimingMiddleware) setup_exception_handlers(app) - - -def get_uvicorn_config() -> Dict[str, Any]: - """ - Get Uvicorn server configuration. - - Returns: - Dict[str, Any]: Uvicorn configuration dictionary - """ - return { - "app": "app:app", - "host": "0.0.0.0", - "port": 41575, - "log_level": "info", - "reload": True, - } diff --git a/DockerApiServices/AuthServiceApi/config.py b/DockerApiServices/AuthServiceApi/config.py new file mode 100644 index 0000000..1241b17 --- /dev/null +++ b/DockerApiServices/AuthServiceApi/config.py @@ -0,0 +1,70 @@ + +class DefaultApiConfig: + app: str + host: str + port: int + log_level: str + reload: bool + + @classmethod + def as_dict(cls): + return { + "app": cls.app, + "host": cls.host, + "port": int(cls.port), + "log_level": cls.log_level, + "reload": bool(cls.reload), + } + + +class ApiStatic: + PLACEHOLDER = "https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg" + FORGOT_LINK = "https://www.evyos.com.tr/password/create?tokenUrl=" + BLACKLIST_LINK = "https://www.evyos.com.tr/support/unknown-login-notice/" + APP_DIR = "/home/berkay/git-evyos/api-managment-backend/" + + @classmethod + def forgot_link(cls, forgot_key): + return cls.FORGOT_LINK + forgot_key + + @classmethod + def blacklist_login(cls, record_id): + return cls.BLACKLIST_LINK + record_id + + +class HostConfig: + MAIN_HOST = "10.10.2.36" # http://10.10.2.36 + EMAIL_HOST = "10.10.2.34" # http://10.10.2.34 + + +class ApiConfig(DefaultApiConfig): + # Application Information + APP_NAME = "evyos-auth-api-gateway" + TITLE = "WAG API Auth Api Gateway" + DESCRIPTION = "This api is serves as web auth api gateway only to evyos web services." + APP_URL = "https://www.auth.eys.gen.tr" + + # Server Configuration + app = "app:app" + host = "0.0.0.0" + port = 41575 + log_level = "info" + reload = True + + +class MainConfig: + + # Date and Time Configuration + DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss Z" + DATETIME_FORMAT_JS = "YYYY-MM-DD HH:mm:ss +0" + + # Timezone Configuration + DEFAULT_TIMEZONE = "GMT+3" # Default timezone for the application + SYSTEM_TIMEZONE = "GMT+0" # System timezone (used for internal operations) + SUPPORTED_TIMEZONES = ["GMT+0", "GMT+3"] # List of supported timezones + + +class LanguageConfig: + + SUPPORTED_LANGUAGES = ["en", "tr"] + DEFAULT_LANGUAGE = "tr" diff --git a/DockerApiServices/AuthServiceApi/create_file.py b/DockerApiServices/AuthServiceApi/create_file.py new file mode 100644 index 0000000..68b3f88 --- /dev/null +++ b/DockerApiServices/AuthServiceApi/create_file.py @@ -0,0 +1,48 @@ +""" +FastAPI Application Factory Module + +This module provides functionality to create and configure a FastAPI application with: +- Custom OpenAPI schema configuration +- Security scheme configuration for Bearer authentication +- Automatic router registration +- Response class configuration +- Security requirements for protected endpoints +""" + +from fastapi import FastAPI +from fastapi.responses import JSONResponse, RedirectResponse + +from config import ApiConfig +from create_routes import get_all_routers + + +def create_app() -> FastAPI: + """ + Create and configure a FastAPI application with dynamic route creation. + + Returns: + FastAPI: Configured FastAPI application instance + """ + + from open_api_creator import create_openapi_schema + + app = FastAPI( + title=ApiConfig.TITLE, + description=ApiConfig.DESCRIPTION, + default_response_class=JSONResponse, + ) # Initialize FastAPI app + + @app.get("/", include_in_schema=False, summary=str(ApiConfig.DESCRIPTION)) + def home() -> RedirectResponse: + """Redirect root path to API documentation.""" + return RedirectResponse(url="/docs") + + # Get all routers and protected routes using the dynamic route creation + prepare_routing = get_all_routers() + + # Include all routers + for router in prepare_routing.routers: + app.include_router(router) + + app.openapi = lambda app=app: create_openapi_schema(app) + return app diff --git a/DockerApiServices/AuthServiceApi/create_routes.py b/DockerApiServices/AuthServiceApi/create_routes.py new file mode 100644 index 0000000..62c276e --- /dev/null +++ b/DockerApiServices/AuthServiceApi/create_routes.py @@ -0,0 +1,31 @@ +""" +Route configuration and factory module. +Handles dynamic route creation based on configurations. +""" + +from fastapi import Request +from Events.Engine.set_defaults.setClusters import PrepareRouting + + +async def health_check(request: Request): + """Default health check endpoint.""" + return {"status": "healthy", "message": "Service is running"} + + +async def ping_test(request: Request, service_name: str = "base-router"): + """Default ping test endpoint.""" + return {"ping": "pong", "service": service_name} + + +def get_all_routers() -> PrepareRouting: + """ + Get all routers and protected routes from route configurations. + + Returns: + tuple: (routers, protected_routes) + """ + from Events.Engine.set_defaults.run import get_cluster_controller_group + + cluster_list = get_cluster_controller_group() + prepare_routing = PrepareRouting(cluster_controller_group=cluster_list) + return prepare_routing diff --git a/DockerApiServices/AuthServiceApi/events_file.py b/DockerApiServices/AuthServiceApi/events_file.py new file mode 100644 index 0000000..8be1ffc --- /dev/null +++ b/DockerApiServices/AuthServiceApi/events_file.py @@ -0,0 +1,3 @@ +import Events.AllEvents.authentication as auths_events + +events_list = (auths_events,) diff --git a/ApiLayers/AllApiNeeds/open_api_creator.py b/DockerApiServices/AuthServiceApi/open_api_creator.py similarity index 82% rename from ApiLayers/AllApiNeeds/open_api_creator.py rename to DockerApiServices/AuthServiceApi/open_api_creator.py index 6163e2d..01a37c6 100644 --- a/ApiLayers/AllApiNeeds/open_api_creator.py +++ b/DockerApiServices/AuthServiceApi/open_api_creator.py @@ -9,14 +9,13 @@ This module provides functionality to create and customize OpenAPI documentation - Custom documentation extensions """ -from typing import Any, Dict, List, Optional, Set -from fastapi import FastAPI, APIRouter +from typing import Any, Dict +from fastapi import FastAPI from fastapi.routing import APIRoute from fastapi.openapi.utils import get_openapi -from AllConfigs.Token.config import Auth -from AllConfigs.main import MainConfig as Config from create_routes import get_all_routers +from config import ApiConfig class OpenAPISchemaCreator: @@ -32,28 +31,8 @@ class OpenAPISchemaCreator: app: FastAPI application instance """ self.app = app - _, self.protected_routes = get_all_routers() - # self.tags_metadata = self._create_tags_metadata() - - @staticmethod - def _create_tags_metadata() -> List[Dict[str, str]]: - """ - Create metadata for API tags. - - Returns: - List[Dict[str, str]]: List of tag metadata - """ - return [ - { - "name": "Authentication", - "description": "Operations related to user authentication and authorization", - }, - { - "name": "Users", - "description": "User management and profile operations", - }, - # Add more tags as needed - ] + self.cluster = get_all_routers() + self.safe_endpoint_list = self.cluster.safe_endpoints if hasattr(self.cluster, 'safe_endpoints') else [] def _create_security_schemes(self) -> Dict[str, Any]: """ @@ -62,8 +41,9 @@ class OpenAPISchemaCreator: Returns: Dict[str, Any]: Security scheme configurations """ + from ApiLayers.AllConfigs.Token.config import Auth return { - "Bearer Auth": { + "BearerAuth": { "type": "apiKey", "in": "header", "name": Auth.ACCESS_TOKEN_TAG, @@ -206,19 +186,17 @@ class OpenAPISchemaCreator: method: HTTP method schema: OpenAPI schema to modify """ - # Check if route is protected based on dynamic routing info - if path in self.protected_routes and method in self.protected_routes[path]: - schema["paths"][path][method]["security"] = [ - {"Bearer Auth": []}, - ] - schema["paths"][path][method]["responses"].update( - self._create_common_responses() - ) + if not schema.get("paths", {}).get(path, {}).get(method): + return - # Process request body examples - self._process_request_body(path, method, schema) - # Process response examples - self._process_response_examples(path, method, schema) + # Check if endpoint is in safe list + endpoint_path = f"{path}:{method}" + if endpoint_path not in [f"{e.URL}:{e.METHOD.lower()}" for e in self.safe_endpoint_list]: + if "security" not in schema["paths"][path][method]: + schema["paths"][path][method]["security"] = [] + schema["paths"][path][method]["security"].append( + {"BearerAuth": []} + ) def create_schema(self) -> Dict[str, Any]: """ @@ -228,8 +206,8 @@ class OpenAPISchemaCreator: Dict[str, Any]: Complete OpenAPI schema """ openapi_schema = get_openapi( - title=Config.TITLE, - description=Config.DESCRIPTION, + title=ApiConfig.TITLE, + description=ApiConfig.DESCRIPTION, version="1.1.1", routes=self.app.routes, ) @@ -238,9 +216,8 @@ class OpenAPISchemaCreator: if "components" not in openapi_schema: openapi_schema["components"] = {} - openapi_schema["components"][ - "securitySchemes" - ] = self._create_security_schemes() + openapi_schema["components"]["securitySchemes"] = self._create_security_schemes() + # Configure route security and responses for route in self.app.routes: if isinstance(route, APIRoute) and route.include_in_schema: @@ -249,13 +226,12 @@ class OpenAPISchemaCreator: for method in methods: self.configure_route_security(path, method, openapi_schema) - # # Add custom documentation extensions + # Add custom documentation extensions openapi_schema["x-documentation"] = { "postman_collection": "/docs/postman", "swagger_ui": "/docs", "redoc": "/redoc", } - return openapi_schema diff --git a/DockerApiServices/EventServiceApi/Dockerfile b/DockerApiServices/EventServiceApi/Dockerfile index aa09676..29d3790 100644 --- a/DockerApiServices/EventServiceApi/Dockerfile +++ b/DockerApiServices/EventServiceApi/Dockerfile @@ -19,22 +19,18 @@ RUN poetry config virtualenvs.create false \ && rm -rf ~/.cache/pypoetry # Copy application code -COPY DockerApiServices/AllApiNeeds /app -COPY ErrorHandlers /app/ErrorHandlers -COPY LanguageModels /app/LanguageModels -COPY ApiLibrary /app/ApiLibrary -COPY ApiValidations /app/ApiValidations -COPY AllConfigs /app/AllConfigs -COPY ErrorHandlers /app/ErrorHandlers -COPY Schemas /app/Schemas +COPY DockerApiServices/EventServiceApi /app + +# Copy application code +COPY ApiLayers /app/ApiLayers COPY Services /app/Services -COPY ApiServices /app/ApiServices - -# Copy Events structure with consistent naming -COPY ApiEvents/EventServiceApi /app/ApiEvents -COPY ApiEvents/abstract_class.py /app/ApiEvents/abstract_class.py -COPY ApiEvents/base_request_model.py /app/ApiEvents/base_request_model.py +# Events +# COPY Events/base_request_model.py /app/Events/base_request_model.py +COPY Events/Engine /app/Events/Engine +COPY Events/base_request_model.py /app/Events/base_request_model.py +COPY Events/AllEvents/events /app/Events/AllEvents/events +COPY DockerApiServices/EventServiceApi/events_file.py /app/Events/AllEvents/events_file.py # Set Python path to include app directory ENV PYTHONPATH=/app \ diff --git a/DockerApiServices/EventServiceApi/app.py b/DockerApiServices/EventServiceApi/app.py new file mode 100644 index 0000000..68ed880 --- /dev/null +++ b/DockerApiServices/EventServiceApi/app.py @@ -0,0 +1,27 @@ +""" +FastAPI Application Entry Point + +This module initializes and configures the FastAPI application with: +- CORS middleware for cross-origin requests +- Request timing middleware for performance monitoring +- Custom exception handlers for consistent error responses +- Prometheus instrumentation for metrics +- API routers for endpoint organization +""" + +import uvicorn + +from prometheus_fastapi_instrumentator import Instrumentator +from app_handler import setup_middleware +from create_file import create_app +from config import ApiConfig + + +app = create_app() # Initialize FastAPI application +Instrumentator().instrument(app=app).expose(app=app) # Setup Prometheus metrics +setup_middleware(app) # Configure middleware and exception handlers + + +if __name__ == "__main__": + # Run the application with Uvicorn + uvicorn.Server(uvicorn.Config(**ApiConfig.as_dict())).run() diff --git a/DockerApiServices/EventServiceApi/app_handler.py b/DockerApiServices/EventServiceApi/app_handler.py new file mode 100644 index 0000000..3156e6a --- /dev/null +++ b/DockerApiServices/EventServiceApi/app_handler.py @@ -0,0 +1,77 @@ +""" +FastAPI Application Handler Module + +This module contains all the handler functions for configuring and setting up the FastAPI application: +- CORS middleware configuration +- Exception handlers setup +- Uvicorn server configuration +""" + +from fastapi import FastAPI, Request, status +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse + +from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi +from ApiLayers.Middleware.auth_middleware import RequestTimingMiddleware, LoggerTimingMiddleware + + +def setup_cors_middleware(app: FastAPI) -> None: + """ + Configure CORS middleware for the application. + + Args: + app: FastAPI application instance + """ + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + + +async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse: + """ + Handle generic exceptions and return formatted error responses. + + Args: + request: FastAPI request object + exc: Exception instance + + Returns: + JSONResponse: Formatted error response + """ + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content={"detail": "Internal server error", "error_code": "INTERNAL_ERROR"}, + ) + + +def setup_exception_handlers(app: FastAPI) -> None: + """ + Configure custom exception handlers for the application. + + Args: + app: FastAPI application instance + """ + from ApiLayers.ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApiHandler + + custom_exception_handler = HTTPExceptionApiHandler(response_model=JSONResponse) + app.add_exception_handler( + HTTPExceptionApi, custom_exception_handler.handle_exception + ) + app.add_exception_handler(Exception, generic_exception_handler) + + +def setup_middleware(app: FastAPI) -> None: + """ + Configure all middleware for the application. + + Args: + app: FastAPI application instance + """ + setup_cors_middleware(app) + app.add_middleware(RequestTimingMiddleware) + app.add_middleware(LoggerTimingMiddleware) + setup_exception_handlers(app) diff --git a/DockerApiServices/EventServiceApi/config.py b/DockerApiServices/EventServiceApi/config.py new file mode 100644 index 0000000..d5b9fcf --- /dev/null +++ b/DockerApiServices/EventServiceApi/config.py @@ -0,0 +1,60 @@ + +class DefaultApiConfig: + app: str + host: str + port: int + log_level: str + reload: bool + + @classmethod + def as_dict(cls): + return { + "app": cls.app, + "host": cls.host, + "port": int(cls.port), + "log_level": cls.log_level, + "reload": bool(cls.reload), + } + + +class ApiStatic: + PLACEHOLDER = "https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg" + FORGOT_LINK = "https://www.evyos.com.tr/password/create?tokenUrl=" + BLACKLIST_LINK = "https://www.evyos.com.tr/support/unknown-login-notice/" + APP_DIR = "/home/berkay/git-evyos/api-managment-backend/" + + @classmethod + def forgot_link(cls, forgot_key): + return cls.FORGOT_LINK + forgot_key + + @classmethod + def blacklist_login(cls, record_id): + return cls.BLACKLIST_LINK + record_id + + + +class ApiConfig(DefaultApiConfig): + # Api configuration + APP_NAME = "evyos-event-api-gateway" + TITLE = "WAG API Event Api Gateway" + DESCRIPTION = "This api is serves as web event api gateway only to evyos web services." + APP_URL = "https://www.event.eys.gen.tr" + + # Uvicorn server configuration + app = "app:app" + host = "0.0.0.0" + port = 41576 + log_level = "info" + reload = True + + +class MainConfig: + # Date and Time Configuration + DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss Z" + DATETIME_FORMAT_JS = "YYYY-MM-DD HH:mm:ss +0" + + # Timezone Configuration + DEFAULT_TIMEZONE = "GMT+3" # Default timezone for the application + SYSTEM_TIMEZONE = "GMT+0" # System timezone (used for internal operations) + SUPPORTED_TIMEZONES = ["GMT+0", "GMT+3"] # List of supported timezones + diff --git a/DockerApiServices/EventServiceApi/create_file.py b/DockerApiServices/EventServiceApi/create_file.py new file mode 100644 index 0000000..68b3f88 --- /dev/null +++ b/DockerApiServices/EventServiceApi/create_file.py @@ -0,0 +1,48 @@ +""" +FastAPI Application Factory Module + +This module provides functionality to create and configure a FastAPI application with: +- Custom OpenAPI schema configuration +- Security scheme configuration for Bearer authentication +- Automatic router registration +- Response class configuration +- Security requirements for protected endpoints +""" + +from fastapi import FastAPI +from fastapi.responses import JSONResponse, RedirectResponse + +from config import ApiConfig +from create_routes import get_all_routers + + +def create_app() -> FastAPI: + """ + Create and configure a FastAPI application with dynamic route creation. + + Returns: + FastAPI: Configured FastAPI application instance + """ + + from open_api_creator import create_openapi_schema + + app = FastAPI( + title=ApiConfig.TITLE, + description=ApiConfig.DESCRIPTION, + default_response_class=JSONResponse, + ) # Initialize FastAPI app + + @app.get("/", include_in_schema=False, summary=str(ApiConfig.DESCRIPTION)) + def home() -> RedirectResponse: + """Redirect root path to API documentation.""" + return RedirectResponse(url="/docs") + + # Get all routers and protected routes using the dynamic route creation + prepare_routing = get_all_routers() + + # Include all routers + for router in prepare_routing.routers: + app.include_router(router) + + app.openapi = lambda app=app: create_openapi_schema(app) + return app diff --git a/DockerApiServices/EventServiceApi/create_routes.py b/DockerApiServices/EventServiceApi/create_routes.py new file mode 100644 index 0000000..8006964 --- /dev/null +++ b/DockerApiServices/EventServiceApi/create_routes.py @@ -0,0 +1,33 @@ +""" +Route configuration and factory module. +Handles dynamic route creation based on configurations. +""" + +from fastapi import Request +from Events.Engine.set_defaults.setClusters import PrepareRouting + + +async def health_check(request: Request): + """Default health check endpoint.""" + return {"status": "healthy", "message": "Service is running"} + + +async def ping_test(request: Request, service_name: str = "base-router"): + """Default ping test endpoint.""" + return {"ping": "pong", "service": service_name} + + +def get_all_routers() -> PrepareRouting: + """ + Get all routers and protected routes from route configurations. + + Returns: + tuple: (routers, protected_routes) + """ + from Events.Engine.set_defaults.run import get_cluster_controller_group + + cluster_list = get_cluster_controller_group() + prepare_routing = PrepareRouting(cluster_controller_group=cluster_list) + return prepare_routing + + diff --git a/DockerApiServices/EventServiceApi/events_file.py b/DockerApiServices/EventServiceApi/events_file.py new file mode 100644 index 0000000..de548b3 --- /dev/null +++ b/DockerApiServices/EventServiceApi/events_file.py @@ -0,0 +1,3 @@ +import Events.AllEvents.events as events_events + +events_list = (events_events,) diff --git a/DockerApiServices/EventServiceApi/open_api_creator.py b/DockerApiServices/EventServiceApi/open_api_creator.py new file mode 100644 index 0000000..01a37c6 --- /dev/null +++ b/DockerApiServices/EventServiceApi/open_api_creator.py @@ -0,0 +1,249 @@ +""" +OpenAPI Schema Creator Module + +This module provides functionality to create and customize OpenAPI documentation: +- Custom security schemes (Bearer Auth, API Key) +- Response schemas and examples +- Tag management and descriptions +- Error responses and validation +- Custom documentation extensions +""" + +from typing import Any, Dict +from fastapi import FastAPI +from fastapi.routing import APIRoute +from fastapi.openapi.utils import get_openapi + +from create_routes import get_all_routers +from config import ApiConfig + + +class OpenAPISchemaCreator: + """ + OpenAPI schema creator and customizer for FastAPI applications. + """ + + def __init__(self, app: FastAPI): + """ + Initialize the OpenAPI schema creator. + + Args: + app: FastAPI application instance + """ + self.app = app + self.cluster = get_all_routers() + self.safe_endpoint_list = self.cluster.safe_endpoints if hasattr(self.cluster, 'safe_endpoints') else [] + + def _create_security_schemes(self) -> Dict[str, Any]: + """ + Create security scheme definitions. + + Returns: + Dict[str, Any]: Security scheme configurations + """ + from ApiLayers.AllConfigs.Token.config import Auth + return { + "BearerAuth": { + "type": "apiKey", + "in": "header", + "name": Auth.ACCESS_TOKEN_TAG, + "description": "Enter: **'Bearer <JWT>'**, where JWT is the access token", + } + } + + def _create_common_responses(self) -> Dict[str, Any]: + """ + Create common response schemas. + + Returns: + Dict[str, Any]: Common response configurations + """ + return { + "401": { + "description": "Unauthorized - Invalid or missing credentials", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/HTTPValidationError"} + } + }, + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/HTTPValidationError"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/HTTPValidationError"} + } + }, + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": {"type": "string"}, + "error_code": {"type": "string"}, + }, + }, + "example": { + "detail": "Internal server error occurred", + "error_code": "INTERNAL_ERROR", + }, + } + }, + }, + } + + def _process_request_body( + self, path: str, method: str, schema: Dict[str, Any] + ) -> None: + """ + Process request body to include examples from model config. + + Args: + path: Route path + method: HTTP method + schema: OpenAPI schema to modify + """ + try: + route_schema = schema["paths"][path][method] + if "requestBody" in route_schema: + request_body = route_schema["requestBody"] + if "content" in request_body: + content = request_body["content"] + if "application/json" in content: + json_content = content["application/json"] + if ( + "schema" in json_content + and "$ref" in json_content["schema"] + ): + ref = json_content["schema"]["$ref"] + model_name = ref.split("/")[-1] + if model_name in schema["components"]["schemas"]: + model_schema = schema["components"]["schemas"][ + model_name + ] + if "example" in model_schema: + json_content["example"] = model_schema["example"] + except KeyError: + pass + + def _process_response_examples( + self, path: str, method: str, schema: Dict[str, Any] + ) -> None: + """ + Process response body to include examples from model config. + + Args: + path: Route path + method: HTTP method + schema: OpenAPI schema to modify + """ + try: + route_schema = schema["paths"][path][method] + if "responses" in route_schema: + responses = route_schema["responses"] + if "200" in responses: + response = responses["200"] + if "content" in response: + content = response["content"] + if "application/json" in content: + json_content = content["application/json"] + if ( + "schema" in json_content + and "$ref" in json_content["schema"] + ): + ref = json_content["schema"]["$ref"] + model_name = ref.split("/")[-1] + if model_name in schema["components"]["schemas"]: + model_schema = schema["components"]["schemas"][ + model_name + ] + if "example" in model_schema: + json_content["example"] = model_schema[ + "example" + ] + except KeyError: + pass + + def configure_route_security( + self, path: str, method: str, schema: Dict[str, Any] + ) -> None: + """ + Configure security requirements for a specific route. + + Args: + path: Route path + method: HTTP method + schema: OpenAPI schema to modify + """ + if not schema.get("paths", {}).get(path, {}).get(method): + return + + # Check if endpoint is in safe list + endpoint_path = f"{path}:{method}" + if endpoint_path not in [f"{e.URL}:{e.METHOD.lower()}" for e in self.safe_endpoint_list]: + if "security" not in schema["paths"][path][method]: + schema["paths"][path][method]["security"] = [] + schema["paths"][path][method]["security"].append( + {"BearerAuth": []} + ) + + def create_schema(self) -> Dict[str, Any]: + """ + Create the complete OpenAPI schema. + + Returns: + Dict[str, Any]: Complete OpenAPI schema + """ + openapi_schema = get_openapi( + title=ApiConfig.TITLE, + description=ApiConfig.DESCRIPTION, + version="1.1.1", + routes=self.app.routes, + ) + + # Add security schemes + if "components" not in openapi_schema: + openapi_schema["components"] = {} + + openapi_schema["components"]["securitySchemes"] = self._create_security_schemes() + + # Configure route security and responses + for route in self.app.routes: + if isinstance(route, APIRoute) and route.include_in_schema: + path = str(route.path) + methods = [method.lower() for method in route.methods] + for method in methods: + self.configure_route_security(path, method, openapi_schema) + + # Add custom documentation extensions + openapi_schema["x-documentation"] = { + "postman_collection": "/docs/postman", + "swagger_ui": "/docs", + "redoc": "/redoc", + } + return openapi_schema + + +def create_openapi_schema(app: FastAPI) -> Dict[str, Any]: + """ + Create OpenAPI schema for a FastAPI application. + + Args: + app: FastAPI application instance + + Returns: + Dict[str, Any]: Complete OpenAPI schema + """ + creator = OpenAPISchemaCreator(app) + return creator.create_schema() diff --git a/DockerApiServices/InitServiceApi/Dockerfile b/DockerApiServices/InitServiceApi/Dockerfile new file mode 100644 index 0000000..36f3401 --- /dev/null +++ b/DockerApiServices/InitServiceApi/Dockerfile @@ -0,0 +1,40 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Install system dependencies and Poetry +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + gcc \ + && rm -rf /var/lib/apt/lists/* \ + && pip install --no-cache-dir poetry + +# Copy Poetry configuration +COPY DockerApiServices/pyproject.toml ./pyproject.toml + +# # Configure Poetry and install dependencies with optimizations +# RUN poetry config virtualenvs.create false \ +# && poetry install --no-interaction --no-ansi --no-root --only main \ +# && pip cache purge \ +# && rm -rf ~/.cache/pypoetry + +# # Copy application code +# COPY DockerApiServices/EventServiceApi /app + +# # Copy application code +# COPY ApiLayers /app/ApiLayers +# COPY Services /app/Services + +# # Events +# # COPY Events/base_request_model.py /app/Events/base_request_model.py +# COPY Events/Engine /app/Events/Engine +# COPY Events/AllEvents/events /app/Events/AllEvents/events +# COPY DockerApiServices/EventServiceApi/events_file.py /app/Events/AllEvents/events_file.py + +# # Set Python path to include app directory +# ENV PYTHONPATH=/app \ +# PYTHONUNBUFFERED=1 \ +# PYTHONDONTWRITEBYTECODE=1 + +# # Run the application using the configured uvicorn server +# CMD ["poetry", "run", "python", "app.py"] diff --git a/DockerApiServices/ValidationServiceApi/Dockerfile b/DockerApiServices/ValidationServiceApi/Dockerfile index a6809dc..1fbab7f 100644 --- a/DockerApiServices/ValidationServiceApi/Dockerfile +++ b/DockerApiServices/ValidationServiceApi/Dockerfile @@ -19,23 +19,17 @@ RUN poetry config virtualenvs.create false \ && rm -rf ~/.cache/pypoetry # Copy application code -COPY DockerApiServices/AllApiNeeds /app -COPY ErrorHandlers /app/ErrorHandlers -COPY LanguageModels /app/LanguageModels -COPY ApiLibrary /app/ApiLibrary -COPY ApiValidations /app/ApiValidations -COPY AllConfigs /app/AllConfigs -COPY ErrorHandlers /app/ErrorHandlers -COPY Schemas /app/Schemas -COPY Services /app/Services -COPY ApiServices /app/ApiServices +COPY DockerApiServices/ValidationServiceApi /app -# Copy Events structure with consistent naming -COPY ApiEvents/ValidationServiceApi /app/ApiEvents -ADD ApiEvents/AuthServiceApi/events /app/ApiEvents/events -ADD ApiEvents/EventServiceApi/events /app/ApiEvents/events -COPY ApiEvents/abstract_class.py /app/ApiEvents/abstract_class.py -COPY ApiEvents/base_request_model.py /app/ApiEvents/base_request_model.py +# Copy application code +COPY ApiLayers /app/ApiLayers +COPY Services /app/Services + +# Events +# COPY Events/base_request_model.py /app/Events/base_request_model.py +COPY Events/Engine /app/Events/Engine +COPY Events/AllEvents/validations /app/Events/AllEvents/validations +COPY DockerApiServices/ValidationServiceApi/events_file.py /app/Events/AllEvents/events_file.py # Set Python path to include app directory ENV PYTHONPATH=/app \ diff --git a/DockerApiServices/ValidationServiceApi/app.py b/DockerApiServices/ValidationServiceApi/app.py new file mode 100644 index 0000000..68ed880 --- /dev/null +++ b/DockerApiServices/ValidationServiceApi/app.py @@ -0,0 +1,27 @@ +""" +FastAPI Application Entry Point + +This module initializes and configures the FastAPI application with: +- CORS middleware for cross-origin requests +- Request timing middleware for performance monitoring +- Custom exception handlers for consistent error responses +- Prometheus instrumentation for metrics +- API routers for endpoint organization +""" + +import uvicorn + +from prometheus_fastapi_instrumentator import Instrumentator +from app_handler import setup_middleware +from create_file import create_app +from config import ApiConfig + + +app = create_app() # Initialize FastAPI application +Instrumentator().instrument(app=app).expose(app=app) # Setup Prometheus metrics +setup_middleware(app) # Configure middleware and exception handlers + + +if __name__ == "__main__": + # Run the application with Uvicorn + uvicorn.Server(uvicorn.Config(**ApiConfig.as_dict())).run() diff --git a/DockerApiServices/ValidationServiceApi/app_handler.py b/DockerApiServices/ValidationServiceApi/app_handler.py new file mode 100644 index 0000000..3156e6a --- /dev/null +++ b/DockerApiServices/ValidationServiceApi/app_handler.py @@ -0,0 +1,77 @@ +""" +FastAPI Application Handler Module + +This module contains all the handler functions for configuring and setting up the FastAPI application: +- CORS middleware configuration +- Exception handlers setup +- Uvicorn server configuration +""" + +from fastapi import FastAPI, Request, status +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse + +from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi +from ApiLayers.Middleware.auth_middleware import RequestTimingMiddleware, LoggerTimingMiddleware + + +def setup_cors_middleware(app: FastAPI) -> None: + """ + Configure CORS middleware for the application. + + Args: + app: FastAPI application instance + """ + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + + +async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse: + """ + Handle generic exceptions and return formatted error responses. + + Args: + request: FastAPI request object + exc: Exception instance + + Returns: + JSONResponse: Formatted error response + """ + return JSONResponse( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + content={"detail": "Internal server error", "error_code": "INTERNAL_ERROR"}, + ) + + +def setup_exception_handlers(app: FastAPI) -> None: + """ + Configure custom exception handlers for the application. + + Args: + app: FastAPI application instance + """ + from ApiLayers.ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApiHandler + + custom_exception_handler = HTTPExceptionApiHandler(response_model=JSONResponse) + app.add_exception_handler( + HTTPExceptionApi, custom_exception_handler.handle_exception + ) + app.add_exception_handler(Exception, generic_exception_handler) + + +def setup_middleware(app: FastAPI) -> None: + """ + Configure all middleware for the application. + + Args: + app: FastAPI application instance + """ + setup_cors_middleware(app) + app.add_middleware(RequestTimingMiddleware) + app.add_middleware(LoggerTimingMiddleware) + setup_exception_handlers(app) diff --git a/DockerApiServices/ValidationServiceApi/config.py b/DockerApiServices/ValidationServiceApi/config.py new file mode 100644 index 0000000..dfbf1eb --- /dev/null +++ b/DockerApiServices/ValidationServiceApi/config.py @@ -0,0 +1,58 @@ + +class DefaultApiConfig: + app: str + host: str + port: int + log_level: str + reload: bool + + @classmethod + def as_dict(cls): + return { + "app": cls.app, + "host": cls.host, + "port": int(cls.port), + "log_level": cls.log_level, + "reload": bool(cls.reload), + } + + +class ApiStatic: + PLACEHOLDER = "https://s.tmimgcdn.com/scr/800x500/276800/building-home-nature-logo-vector-template-3_276851-original.jpg" + FORGOT_LINK = "https://www.evyos.com.tr/password/create?tokenUrl=" + BLACKLIST_LINK = "https://www.evyos.com.tr/support/unknown-login-notice/" + APP_DIR = "/home/berkay/git-evyos/api-managment-backend/" + + @classmethod + def forgot_link(cls, forgot_key): + return cls.FORGOT_LINK + forgot_key + + @classmethod + def blacklist_login(cls, record_id): + return cls.BLACKLIST_LINK + record_id + + +class ApiConfig(DefaultApiConfig): + # Api configuration + APP_NAME = "evyos-validation-api-gateway" + TITLE = "WAG API Validation Api Gateway" + DESCRIPTION = "This api is serves as web validation api gateway only to evyos web services." + APP_URL = "https://www.validation.eys.gen.tr" + # App configuration + app = "app:app" + host = "0.0.0.0" + port = 41577 + log_level = "info" + reload = True + + +class MainConfig: + # Main configuration + DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss Z" + DATETIME_FORMAT_JS = "YYYY-MM-DD HH:mm:ss +0" + + # Timezone Configuration + DEFAULT_TIMEZONE = "GMT+3" # Default timezone for the application + SYSTEM_TIMEZONE = "GMT+0" # System timezone (used for internal operations) + SUPPORTED_TIMEZONES = ["GMT+0", "GMT+3"] # List of supported timezones + diff --git a/DockerApiServices/ValidationServiceApi/create_file.py b/DockerApiServices/ValidationServiceApi/create_file.py new file mode 100644 index 0000000..0e18791 --- /dev/null +++ b/DockerApiServices/ValidationServiceApi/create_file.py @@ -0,0 +1,48 @@ +""" +FastAPI Application Factory Module + +This module provides functionality to create and configure a FastAPI application with: +- Custom OpenAPI schema configuration +- Security scheme configuration for Bearer authentication +- Automatic router registration +- Response class configuration +- Security requirements for protected endpoints +""" + +from fastapi import FastAPI +from fastapi.responses import JSONResponse, RedirectResponse + +from create_routes import get_all_routers +from config import ApiConfig + + +def create_app() -> FastAPI: + """ + Create and configure a FastAPI application with dynamic route creation. + + Returns: + FastAPI: Configured FastAPI application instance + """ + + from open_api_creator import create_openapi_schema + + app = FastAPI( + title=ApiConfig.TITLE, + description=ApiConfig.DESCRIPTION, + default_response_class=JSONResponse, + ) # Initialize FastAPI app + + @app.get("/", include_in_schema=False, summary=str(ApiConfig.DESCRIPTION)) + def home() -> RedirectResponse: + """Redirect root path to API documentation.""" + return RedirectResponse(url="/docs") + + # Get all routers and protected routes using the dynamic route creation + prepare_routing = get_all_routers() + + # Include all routers + for router in prepare_routing.routers: + app.include_router(router) + + app.openapi = lambda app=app: create_openapi_schema(app) + return app diff --git a/DockerApiServices/ValidationServiceApi/create_routes.py b/DockerApiServices/ValidationServiceApi/create_routes.py new file mode 100644 index 0000000..8006964 --- /dev/null +++ b/DockerApiServices/ValidationServiceApi/create_routes.py @@ -0,0 +1,33 @@ +""" +Route configuration and factory module. +Handles dynamic route creation based on configurations. +""" + +from fastapi import Request +from Events.Engine.set_defaults.setClusters import PrepareRouting + + +async def health_check(request: Request): + """Default health check endpoint.""" + return {"status": "healthy", "message": "Service is running"} + + +async def ping_test(request: Request, service_name: str = "base-router"): + """Default ping test endpoint.""" + return {"ping": "pong", "service": service_name} + + +def get_all_routers() -> PrepareRouting: + """ + Get all routers and protected routes from route configurations. + + Returns: + tuple: (routers, protected_routes) + """ + from Events.Engine.set_defaults.run import get_cluster_controller_group + + cluster_list = get_cluster_controller_group() + prepare_routing = PrepareRouting(cluster_controller_group=cluster_list) + return prepare_routing + + diff --git a/DockerApiServices/ValidationServiceApi/events_file.py b/DockerApiServices/ValidationServiceApi/events_file.py new file mode 100644 index 0000000..d15b914 --- /dev/null +++ b/DockerApiServices/ValidationServiceApi/events_file.py @@ -0,0 +1,3 @@ +import Events.AllEvents.validations as validations_events + +events_list = (validations_events,) diff --git a/DockerApiServices/ValidationServiceApi/open_api_creator.py b/DockerApiServices/ValidationServiceApi/open_api_creator.py new file mode 100644 index 0000000..01a37c6 --- /dev/null +++ b/DockerApiServices/ValidationServiceApi/open_api_creator.py @@ -0,0 +1,249 @@ +""" +OpenAPI Schema Creator Module + +This module provides functionality to create and customize OpenAPI documentation: +- Custom security schemes (Bearer Auth, API Key) +- Response schemas and examples +- Tag management and descriptions +- Error responses and validation +- Custom documentation extensions +""" + +from typing import Any, Dict +from fastapi import FastAPI +from fastapi.routing import APIRoute +from fastapi.openapi.utils import get_openapi + +from create_routes import get_all_routers +from config import ApiConfig + + +class OpenAPISchemaCreator: + """ + OpenAPI schema creator and customizer for FastAPI applications. + """ + + def __init__(self, app: FastAPI): + """ + Initialize the OpenAPI schema creator. + + Args: + app: FastAPI application instance + """ + self.app = app + self.cluster = get_all_routers() + self.safe_endpoint_list = self.cluster.safe_endpoints if hasattr(self.cluster, 'safe_endpoints') else [] + + def _create_security_schemes(self) -> Dict[str, Any]: + """ + Create security scheme definitions. + + Returns: + Dict[str, Any]: Security scheme configurations + """ + from ApiLayers.AllConfigs.Token.config import Auth + return { + "BearerAuth": { + "type": "apiKey", + "in": "header", + "name": Auth.ACCESS_TOKEN_TAG, + "description": "Enter: **'Bearer <JWT>'**, where JWT is the access token", + } + } + + def _create_common_responses(self) -> Dict[str, Any]: + """ + Create common response schemas. + + Returns: + Dict[str, Any]: Common response configurations + """ + return { + "401": { + "description": "Unauthorized - Invalid or missing credentials", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/HTTPValidationError"} + } + }, + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/HTTPValidationError"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/HTTPValidationError"} + } + }, + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": {"type": "string"}, + "error_code": {"type": "string"}, + }, + }, + "example": { + "detail": "Internal server error occurred", + "error_code": "INTERNAL_ERROR", + }, + } + }, + }, + } + + def _process_request_body( + self, path: str, method: str, schema: Dict[str, Any] + ) -> None: + """ + Process request body to include examples from model config. + + Args: + path: Route path + method: HTTP method + schema: OpenAPI schema to modify + """ + try: + route_schema = schema["paths"][path][method] + if "requestBody" in route_schema: + request_body = route_schema["requestBody"] + if "content" in request_body: + content = request_body["content"] + if "application/json" in content: + json_content = content["application/json"] + if ( + "schema" in json_content + and "$ref" in json_content["schema"] + ): + ref = json_content["schema"]["$ref"] + model_name = ref.split("/")[-1] + if model_name in schema["components"]["schemas"]: + model_schema = schema["components"]["schemas"][ + model_name + ] + if "example" in model_schema: + json_content["example"] = model_schema["example"] + except KeyError: + pass + + def _process_response_examples( + self, path: str, method: str, schema: Dict[str, Any] + ) -> None: + """ + Process response body to include examples from model config. + + Args: + path: Route path + method: HTTP method + schema: OpenAPI schema to modify + """ + try: + route_schema = schema["paths"][path][method] + if "responses" in route_schema: + responses = route_schema["responses"] + if "200" in responses: + response = responses["200"] + if "content" in response: + content = response["content"] + if "application/json" in content: + json_content = content["application/json"] + if ( + "schema" in json_content + and "$ref" in json_content["schema"] + ): + ref = json_content["schema"]["$ref"] + model_name = ref.split("/")[-1] + if model_name in schema["components"]["schemas"]: + model_schema = schema["components"]["schemas"][ + model_name + ] + if "example" in model_schema: + json_content["example"] = model_schema[ + "example" + ] + except KeyError: + pass + + def configure_route_security( + self, path: str, method: str, schema: Dict[str, Any] + ) -> None: + """ + Configure security requirements for a specific route. + + Args: + path: Route path + method: HTTP method + schema: OpenAPI schema to modify + """ + if not schema.get("paths", {}).get(path, {}).get(method): + return + + # Check if endpoint is in safe list + endpoint_path = f"{path}:{method}" + if endpoint_path not in [f"{e.URL}:{e.METHOD.lower()}" for e in self.safe_endpoint_list]: + if "security" not in schema["paths"][path][method]: + schema["paths"][path][method]["security"] = [] + schema["paths"][path][method]["security"].append( + {"BearerAuth": []} + ) + + def create_schema(self) -> Dict[str, Any]: + """ + Create the complete OpenAPI schema. + + Returns: + Dict[str, Any]: Complete OpenAPI schema + """ + openapi_schema = get_openapi( + title=ApiConfig.TITLE, + description=ApiConfig.DESCRIPTION, + version="1.1.1", + routes=self.app.routes, + ) + + # Add security schemes + if "components" not in openapi_schema: + openapi_schema["components"] = {} + + openapi_schema["components"]["securitySchemes"] = self._create_security_schemes() + + # Configure route security and responses + for route in self.app.routes: + if isinstance(route, APIRoute) and route.include_in_schema: + path = str(route.path) + methods = [method.lower() for method in route.methods] + for method in methods: + self.configure_route_security(path, method, openapi_schema) + + # Add custom documentation extensions + openapi_schema["x-documentation"] = { + "postman_collection": "/docs/postman", + "swagger_ui": "/docs", + "redoc": "/redoc", + } + return openapi_schema + + +def create_openapi_schema(app: FastAPI) -> Dict[str, Any]: + """ + Create OpenAPI schema for a FastAPI application. + + Args: + app: FastAPI application instance + + Returns: + Dict[str, Any]: Complete OpenAPI schema + """ + creator = OpenAPISchemaCreator(app) + return creator.create_schema() diff --git a/Events/AllEvents/authentication/__init__.py b/Events/AllEvents/authentication/__init__.py index 0f118e8..ad73225 100644 --- a/Events/AllEvents/authentication/__init__.py +++ b/Events/AllEvents/authentication/__init__.py @@ -2,8 +2,8 @@ Authentication package initialization. """ -from .auth import AUTH_CONFIG +from .auth.cluster import AuthCluster __all__ = [ - "AUTH_CONFIG", + "AuthCluster", ] diff --git a/Events/AllEvents/authentication/auth/api_events.py b/Events/AllEvents/authentication/auth/api_events.py index 4110037..de56d08 100644 --- a/Events/AllEvents/authentication/auth/api_events.py +++ b/Events/AllEvents/authentication/auth/api_events.py @@ -1,4 +1,3 @@ -from uuid import UUID from Events.Engine.abstract_class import Event from .models import ( @@ -14,8 +13,6 @@ from .models import ( from .function_handlers import ( authentication_login_with_domain_and_creds, authentication_select_company_or_occupant_type, - handle_employee_selection, - handle_occupant_selection, authentication_check_token_is_valid, authentication_refresh_user_info, authentication_change_password, @@ -31,124 +28,126 @@ from .function_handlers import ( # Auth Login authentication_login_super_user_event = Event( - key=UUID("a5d2d0d1-3e9b-4b0f-8c7d-6d4a4b4c4d4e"), + name="authentication_login_super_user_event", + key="a5d2d0d1-3e9b-4b0f-8c7d-6d4a4b4c4d4e", request_validator=LoginSuperUserRequestModel, - response_validator=LoginSuperUserResponseModel, + # response_validator=LoginSuperUserResponseModel, description="Login super user", ) -authentication_login_super_user_event.endpoint_callable = authentication_login_with_domain_and_creds +authentication_login_super_user_event.endpoint_callable = ( + authentication_login_with_domain_and_creds +) # Auth Select Company or Occupant Type authentication_select_company_or_occupant_type_super_user_event = Event( - key=UUID("a5d2d0d1-3e9b-4b0f-8c7d-6d4a4b4c4d4e"), + name="authentication_select_company_or_occupant_type_super_user_event", + key="a5d2d0d1-3e9b-4b0f-8c7d-6d4a4b4c4d4e", request_validator=SelectCompanyOrOccupantTypeSuperUserRequestModel, - response_validator=SelectCompanyOrOccupantTypeSuperUserResponseModel, + # response_validator=SelectCompanyOrOccupantTypeSuperUserResponseModel, description="Select company or occupant type super user", ) -authentication_select_company_or_occupant_type_super_user_event.endpoint_callable = authentication_select_company_or_occupant_type - -authentication_employee_selection_super_user_event = Event( - key=UUID("a5d2d0d1-3e9b-4b0f-8c7d-6d4a4b4c4d4e"), - request_validator=EmployeeSelectionSuperUserRequestModel, - response_validator=EmployeeSelectionSuperUserResponseModel, - description="Employee selection super user", +authentication_select_company_or_occupant_type_super_user_event.endpoint_callable = ( + authentication_select_company_or_occupant_type ) -authentication_employee_selection_super_user_event.endpoint_callable = handle_employee_selection - -authentication_occupant_selection_super_user_event = Event( - key=UUID("a5d2d0d1-3e9b-4b0f-8c7d-6d4a4b4c4d4e"), - request_validator=OccupantSelectionSuperUserRequestModel, - response_validator=OccupantSelectionSuperUserResponseModel, - description="Occupant selection super user", -) -authentication_occupant_selection_super_user_event.endpoint_callable = handle_occupant_selection # Check Token Validity authentication_check_token_event = Event( - key=UUID("b6e3d1e2-4f9c-5c1g-9d8e-7e5f6f5e5d5f"), + name="authentication_check_token_event", + key="b6e3d1e2-4f9c-5c1g-9d8e-7e5f6f5e5d5f", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Check if token is valid", ) authentication_check_token_event.endpoint_callable = authentication_check_token_is_valid # Refresh User Info authentication_refresh_user_info_event = Event( - key=UUID("c7f4e2f3-5g0d-6d2h-0e9f-8f6g7g6f6e6g"), + name="authentication_refresh_user_info_event", + key="c7f4e2f3-5g0d-6d2h-0e9f-8f6g7g6f6e6g", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Refresh user information", ) -authentication_refresh_user_info_event.endpoint_callable = authentication_refresh_user_info +authentication_refresh_user_info_event.endpoint_callable = ( + authentication_refresh_user_info +) # Change Password authentication_change_password_event = Event( - key=UUID("d8g5f3g4-6h1e-7e3i-1f0g-9g7h8h7g7f7h"), + name="authentication_change_password_event", + key="d8g5f3g4-6h1e-7e3i-1f0g-9g7h8h7g7f7h", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Change user password", ) authentication_change_password_event.endpoint_callable = authentication_change_password # Create Password authentication_create_password_event = Event( - key=UUID("e9h6g4h5-7i2f-8f4j-2g1h-0h8i9i8h8g8i"), + name="authentication_create_password_event", + key="e9h6g4h5-7i2f-8f4j-2g1h-0h8i9i8h8g8i", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Create new password", ) authentication_create_password_event.endpoint_callable = authentication_create_password # Disconnect User authentication_disconnect_user_event = Event( - key=UUID("f0i7h5i6-8j3g-9g5k-3h2i-1i9j0j9i9h9j"), + name="authentication_disconnect_user_event", + key="f0i7h5i6-8j3g-9g5k-3h2i-1i9j0j9i9h9j", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Disconnect all user sessions", ) authentication_disconnect_user_event.endpoint_callable = authentication_disconnect_user # Logout User authentication_logout_user_event = Event( - key=UUID("g1j8i6j7-9k4h-0h6l-4i3j-2j0k1k0j0i0k"), + name="authentication_logout_user_event", + key="g1j8i6j7-9k4h-0h6l-4i3j-2j0k1k0j0i0k", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Logout user session", ) authentication_logout_user_event.endpoint_callable = authentication_logout_user # Refresh Token authentication_refresher_token_event = Event( - key=UUID("h2k9j7k8-0l5i-1i7m-5j4k-3k1l2l1k1j1l"), + name="authentication_refresher_token_event", + key="h2k9j7k8-0l5i-1i7m-5j4k-3k1l2l1k1j1l", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Refresh authentication token", ) authentication_refresher_token_event.endpoint_callable = authentication_refresher_token # Forgot Password authentication_forgot_password_event = Event( - key=UUID("i3l0k8l9-1m6j-2j8n-6k5l-4l2m3m2l2k2m"), + name="authentication_forgot_password_event", + key="i3l0k8l9-1m6j-2j8n-6k5l-4l2m3m2l2k2m", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Request password reset", ) authentication_forgot_password_event.endpoint_callable = authentication_forgot_password # Reset Password authentication_reset_password_event = Event( - key=UUID("j4m1l9m0-2n7k-3k9o-7l6m-5m3n4n3m3l3n"), + name="authentication_reset_password_event", + key="j4m1l9m0-2n7k-3k9o-7l6m-5m3n4n3m3l3n", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Reset user password", ) authentication_reset_password_event.endpoint_callable = authentication_reset_password # Download Avatar authentication_download_avatar_event = Event( - key=UUID("k5n2m0n1-3o8l-4l0p-8m7n-6n4o5o4n4m4o"), + name="authentication_download_avatar_event", + key="k5n2m0n1-3o8l-4l0p-8m7n-6n4o5o4n4m4o", request_validator=None, # TODO: Add request validator - response_validator=None, # TODO: Add response validator + # response_validator=None, # TODO: Add response validator description="Download user avatar and profile info", ) authentication_download_avatar_event.endpoint_callable = authentication_download_avatar diff --git a/Events/AllEvents/authentication/auth/auth.py b/Events/AllEvents/authentication/auth/auth.py index 8ed30f0..0e8f680 100644 --- a/Events/AllEvents/authentication/auth/auth.py +++ b/Events/AllEvents/authentication/auth/auth.py @@ -2,19 +2,17 @@ Authentication related API endpoints. """ -from typing import Union +from typing import Union, Any, Dict + +from ApiLayers.Middleware import MiddlewareModule, TokenEventMiddleware +from ApiLayers.ApiValidations.Request import EmployeeSelection, OccupantSelection from Events.Engine.abstract_class import MethodToEvent -from Events.base_request_model import SuccessResponse - -from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error -from ApiLayers.ApiServices.Token.token_handler import OccupantTokenObject, EmployeeTokenObject +from Events.base_request_model import EndpointBaseRequestModel from .api_events import ( authentication_login_super_user_event, authentication_select_company_or_occupant_type_super_user_event, - authentication_employee_selection_super_user_event, - authentication_occupant_selection_super_user_event, authentication_check_token_event, authentication_refresh_user_info_event, authentication_change_password_event, @@ -27,13 +25,18 @@ from .api_events import ( authentication_download_avatar_event, ) +from fastapi import Request + # Type aliases for common types TokenDictType = Union["EmployeeTokenObject", "OccupantTokenObject"] AuthenticationLoginEventMethods = MethodToEvent( - events=[authentication_login_super_user_event], + name="AuthenticationLoginEventMethods", + events={ + authentication_login_super_user_event.key: authentication_login_super_user_event, + }, headers=[], errors=[], url="/authentication/login", @@ -42,13 +45,24 @@ AuthenticationLoginEventMethods = MethodToEvent( description="Login to the system via domain, access key : [email] | [phone]", ) +def authentication_login_with_domain_and_creds( + request: Request, + data: EndpointBaseRequestModel, +) -> Dict[str, Any]: + function = AuthenticationLoginEventMethods.retrieve_event( + event_function_code=f"{authentication_login_super_user_event.key}" + ) + return function.endpoint_callable(request=request, data=data) + + +AuthenticationLoginEventMethods.endpoint_callable = authentication_login_with_domain_and_creds AuthenticationSelectEventMethods = MethodToEvent( - events=[ - authentication_select_company_or_occupant_type_super_user_event, - authentication_employee_selection_super_user_event, - authentication_occupant_selection_super_user_event - ], + name="AuthenticationSelectEventMethods", + events={ + authentication_select_company_or_occupant_type_super_user_event.key: authentication_select_company_or_occupant_type_super_user_event, + }, + decorators_list=[MiddlewareModule.auth_required], headers=[], errors=[], url="/authentication/select", @@ -57,22 +71,53 @@ AuthenticationSelectEventMethods = MethodToEvent( description="Select company or occupant type", ) +def authentication_select_company_or_occupant_type( + request: Request, + data: EndpointBaseRequestModel, +) -> Dict[str, Any]: + """ + Select company or occupant type. + """ + auth_dict = authentication_select_company_or_occupant_type.auth + function = AuthenticationSelectEventMethods.retrieve_event( + event_function_code=f"{authentication_select_company_or_occupant_type_super_user_event.key}" + ) + return function.endpoint_callable(request=request, data=data, token_dict=auth_dict) + + +AuthenticationSelectEventMethods.endpoint_callable = authentication_select_company_or_occupant_type AuthenticationCheckTokenEventMethods = MethodToEvent( - events=[authentication_check_token_event], + name="AuthenticationCheckTokenEventMethods", + events={ + authentication_check_token_event.key: authentication_check_token_event + }, headers=[], errors=[], + decorators_list=[MiddlewareModule.auth_required], url="/authentication/check-token", method="POST", summary="Check if token is valid", description="Check if access token is valid for user", ) +def authentication_check_token_is_valid(request: Request): + function = AuthenticationCheckTokenEventMethods.retrieve_event( + event_function_code=f"{authentication_check_token_event.key}" + ) + return function.endpoint_callable(request=request) + + +AuthenticationCheckTokenEventMethods.endpoint_callable = authentication_check_token_is_valid AuthenticationRefreshEventMethods = MethodToEvent( - events=[authentication_refresh_user_info_event], + name="AuthenticationRefreshEventMethods", + events={ + authentication_refresh_user_info_event.key: authentication_refresh_user_info_event + }, headers=[], errors=[], + decorators_list=[MiddlewareModule.auth_required], url="/authentication/refresh", method="POST", summary="Refresh user info", @@ -80,19 +125,45 @@ AuthenticationRefreshEventMethods = MethodToEvent( ) +def authentication_refresh_user_info(request: Request): + token_dict = authentication_refresh_user_info.auth + function = AuthenticationRefreshEventMethods.retrieve_event( + event_function_code=f"{authentication_refresh_user_info_event.key}" + ) + return function.endpoint_callable(request=request, token_dict=token_dict) + + +AuthenticationRefreshEventMethods.endpoint_callable = authentication_refresh_user_info + AuthenticationChangePasswordEventMethods = MethodToEvent( - events=[authentication_change_password_event], + name="AuthenticationChangePasswordEventMethods", + events={ + authentication_change_password_event.key: authentication_change_password_event + }, headers=[], errors=[], + decorators_list=[MiddlewareModule.auth_required], url="/authentication/change-password", method="POST", summary="Change password", description="Change password with access token", ) +def authentication_change_password_event_callable(request: Request, data: EndpointBaseRequestModel): + token_dict = authentication_change_password_event_callable.auth + function = AuthenticationChangePasswordEventMethods.retrieve_event( + event_function_code=f"{authentication_change_password_event.key}" + ) + return function.endpoint_callable(data=data, token_dict=token_dict) + + +AuthenticationChangePasswordEventMethods.endpoint_callable = authentication_change_password_event_callable AuthenticationCreatePasswordEventMethods = MethodToEvent( - events=[authentication_create_password_event], + name="AuthenticationCreatePasswordEventMethods", + events={ + authentication_create_password_event: authentication_create_password_event + }, headers=[], errors=[], url="/authentication/create-password", @@ -101,9 +172,21 @@ AuthenticationCreatePasswordEventMethods = MethodToEvent( description="Create password with password reset token requested via email", ) +def authentication_create_password(data: EndpointBaseRequestModel): + function = AuthenticationCreatePasswordEventMethods.retrieve_event( + event_function_code=f"{authentication_create_password_event.key}" + ) + return function.endpoint_callable(data=data) + + +AuthenticationCreatePasswordEventMethods.endpoint_callable = authentication_create_password AuthenticationDisconnectUserEventMethods = MethodToEvent( - events=[authentication_disconnect_user_event], + name="AuthenticationDisconnectUserEventMethods", + events={ + authentication_disconnect_user_event.key: authentication_disconnect_user_event + }, + decorators_list=[MiddlewareModule.auth_required], headers=[], errors=[], url="/authentication/disconnect", @@ -112,9 +195,19 @@ AuthenticationDisconnectUserEventMethods = MethodToEvent( description="Disconnect all sessions of user in access token", ) +def authentication_disconnect_user(request: Request, data: EndpointBaseRequestModel): + token_dict = authentication_disconnect_user.auth + function = AuthenticationDisconnectUserEventMethods.retrieve_event( + event_function_code=f"{authentication_disconnect_user_event.key}" + ) + return function.endpoint_callable(data=data, token_dict=token_dict) + AuthenticationLogoutEventMethods = MethodToEvent( - events=[authentication_logout_user_event], + name="AuthenticationLogoutEventMethods", + events={ + authentication_logout_user_event.key: authentication_logout_user_event + }, headers=[], errors=[], url="/authentication/logout", @@ -123,20 +216,51 @@ AuthenticationLogoutEventMethods = MethodToEvent( description="Logout only single session of user which domain is provided", ) +@TokenEventMiddleware.event_required +def authentication_logout_user(request: Request, data: EndpointBaseRequestModel): + function = AuthenticationLogoutEventMethods.retrieve_event( + event_function_code=f"{authentication_logout_user_event.key}" + ) + print('authentication_logout_user', dict( + auth=getattr(authentication_logout_user, "auth", None), + func_code=getattr(authentication_logout_user, "func_code", None), + )) + function.endpoint_callable.auth = getattr(authentication_logout_user, "auth", None) + function.endpoint_callable.func_code = getattr(authentication_logout_user, "func_code", None) + return function.endpoint_callable(request=request, data=data) + + +AuthenticationLogoutEventMethods.endpoint_callable = authentication_logout_user AuthenticationRefreshTokenEventMethods = MethodToEvent( - events=[authentication_refresher_token_event], + name="AuthenticationRefreshTokenEventMethods", + events={ + authentication_refresher_token_event.key: authentication_refresher_token_event + }, headers=[], errors=[], + decorators_list=[MiddlewareModule.auth_required, ], url="/authentication/refresh-token", method="POST", summary="Refresh token", description="Refresh access token with refresher token", ) +def authentication_refresher_token(request: Request, data: EndpointBaseRequestModel): + token_dict = authentication_refresher_token.auth + function = AuthenticationRefreshTokenEventMethods.retrieve_event( + event_function_code=f"{authentication_refresher_token_event.key}" + ) + return function.endpoint_callable(data=data, request=request, token_dict=token_dict) + +AuthenticationRefreshTokenEventMethods.endpoint_callable = authentication_refresher_token + AuthenticationForgotPasswordEventMethods = MethodToEvent( - events=[authentication_forgot_password_event], + name="AuthenticationForgotPasswordEventMethods", + events={ + authentication_forgot_password_event.key: authentication_forgot_password_event + }, headers=[], errors=[], url="/authentication/forgot-password", @@ -146,10 +270,25 @@ AuthenticationForgotPasswordEventMethods = MethodToEvent( ) +def authentication_forgot_password(request: Request, data: EndpointBaseRequestModel): + token_dict = authentication_forgot_password.auth + function = AuthenticationForgotPasswordEventMethods.retrieve_event( + event_function_code=f"{authentication_forgot_password_event.key}" + ) + return function.endpoint_callable(data=data, token_dict=token_dict) + + +AuthenticationForgotPasswordEventMethods.endpoint_callable = authentication_forgot_password + + AuthenticationResetPasswordEventMethods = MethodToEvent( - events=[authentication_reset_password_event], + name="AuthenticationResetPasswordEventMethods", + events={ + authentication_reset_password_event.key: authentication_reset_password_event + }, headers=[], errors=[], + decorators_list=[MiddlewareModule.auth_required], url="/authentication/reset-password", method="POST", summary="Reset password", @@ -157,12 +296,38 @@ AuthenticationResetPasswordEventMethods = MethodToEvent( ) +def authentication_reset_password(data: EndpointBaseRequestModel): + # token_dict = authentication_reset_password.auth + function = AuthenticationResetPasswordEventMethods.retrieve_event( + event_function_code=f"{authentication_reset_password_event.key}" + ) + return function.endpoint_callable(data=data) + + +AuthenticationResetPasswordEventMethods.endpoint_callable = authentication_reset_password + AuthenticationDownloadAvatarEventMethods = MethodToEvent( - events=[authentication_download_avatar_event], + name="AuthenticationDownloadAvatarEventMethods", + events={ + authentication_download_avatar_event.key: authentication_download_avatar_event + }, headers=[], errors=[], + decorators_list=[], url="/authentication/download-avatar", method="POST", summary="Download avatar", description="Download avatar icon and profile info of user", ) + + +@MiddlewareModule.auth_required +def authentication_download_avatar(request: Request): + token_dict = authentication_download_avatar.auth + function = AuthenticationDownloadAvatarEventMethods.retrieve_event( + event_function_code=f"{authentication_download_avatar_event.key}" + ) + return function.endpoint_callable(token_dict=token_dict) + + +AuthenticationDownloadAvatarEventMethods.endpoint_callable = authentication_download_avatar diff --git a/Events/AllEvents/authentication/auth/cluster.py b/Events/AllEvents/authentication/auth/cluster.py index 7052ccf..7846a2a 100644 --- a/Events/AllEvents/authentication/auth/cluster.py +++ b/Events/AllEvents/authentication/auth/cluster.py @@ -18,6 +18,7 @@ from .auth import ( AuthCluster = CategoryCluster( + name="AuthCluster", tags=["authentication"], prefix="/authentication", description="Authentication cluster", diff --git a/Events/AllEvents/authentication/auth/function_handlers.py b/Events/AllEvents/authentication/auth/function_handlers.py index af895d4..4aa71f9 100644 --- a/Events/AllEvents/authentication/auth/function_handlers.py +++ b/Events/AllEvents/authentication/auth/function_handlers.py @@ -1,12 +1,6 @@ -from typing import Any, TYPE_CHECKING, Union - -from Events.base_request_model import TokenDictType -from Events.Engine.abstract_class import MethodToEvent -from Events.base_request_model import SuccessResponse +from typing import Any, TYPE_CHECKING, Union, Dict from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error -from ApiLayers.ApiLibrary.date_time_actions.date_functions import DateTimeLocal -from ApiLayers.ApiServices.middleware.auth_middleware import AuthMiddlewareModule from ApiLayers.ApiServices.Login.user_login_handler import UserLoginModule from ApiLayers.ApiServices.Token.token_handler import TokenService @@ -17,20 +11,17 @@ from ApiLayers.Schemas import ( BuildParts, RelationshipEmployee2Build, Companies, - Departments, - Duties, + Departments, + Duties, Duty, Staff, Employees, Event2Employee, Event2Occupant, OccupantTypes, - Users + Users, ) -from ApiLayers.ApiServices.Token.token_handler import OccupantTokenObject, EmployeeTokenObject - -if TYPE_CHECKING: - from fastapi import Request +from fastapi import Request # Type aliases for common types @@ -70,7 +61,7 @@ def authentication_login_with_domain_and_creds(request: Request, data: Any): "user": token.get("user"), } -@AuthMiddlewareModule.auth_required + def handle_employee_selection(request: Request, data: Any, token_dict: TokenDictType): Users.set_user_define_properties(token=token_dict) db_session = Users.new_session() @@ -185,7 +176,6 @@ def handle_employee_selection(request: Request, data: Any, token_dict: TokenDict ) -@AuthMiddlewareModule.auth_required def handle_occupant_selection(request: Request, data: Any, token_dict: TokenDictType): """Handle occupant type selection""" db = BuildLivingSpace.new_session() @@ -270,17 +260,20 @@ def handle_occupant_selection(request: Request, data: Any, token_dict: TokenDict ) -@AuthMiddlewareModule.auth_required def authentication_select_company_or_occupant_type(request: Request, data: Any, token_dict: TokenDictType): """Handle selection of company or occupant type""" if token_dict.is_employee: - return cls._handle_employee_selection(data, token_dict, request) + if handle_employee_selection(data, token_dict, request): + return {"selected_occupant": None, "selected_company": data.company_uu_id} elif token_dict.is_occupant: - return cls._handle_occupant_selection(data, token_dict, request) + if handle_occupant_selection(data, token_dict, request): + return { + "selected_company": None, "selected_occupant": data.build_living_space_uu_id, + } + return {"completed": False, "selected_company": None, "selected_occupant": None} -@AuthMiddlewareModule.auth_required -def authentication_check_token_is_valid(request: "Request", data: Any): +def authentication_check_token_is_valid(request: Request, data: Any): """Check if token is valid for user""" # try: # if RedisActions.get_object_via_access_key(request=request): @@ -290,8 +283,7 @@ def authentication_check_token_is_valid(request: "Request", data: Any): return -@AuthMiddlewareModule.auth_required -def authentication_refresh_user_info(request: "Request", token_dict: TokenDictType, data: Any): +def authentication_refresh_user_info(request: Request, token_dict: TokenDictType, data: Any): """Refresh user info using access token""" # try: # access_token = request.headers.get(Auth.ACCESS_TOKEN_TAG) @@ -320,8 +312,7 @@ def authentication_refresh_user_info(request: "Request", token_dict: TokenDictTy return -@AuthMiddlewareModule.auth_required -def authentication_change_password(request: "Request", token_dict: TokenDictType, data: Any): +def authentication_change_password(request: Request, token_dict: TokenDictType, data: Any): """Change password with access token""" # try: # if not isinstance(token_dict, EmployeeTokenObject): @@ -341,7 +332,7 @@ def authentication_change_password(request: "Request", token_dict: TokenDictType return -def authentication_create_password(request: "Request", data: Any): +def authentication_create_password(request: Request, data: Any): """Create password with password reset token requested via email""" # if not data.re_password == data.password: # raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail="Password must match") @@ -354,8 +345,7 @@ def authentication_create_password(request: "Request", data: Any): return -@AuthMiddlewareModule.auth_required -def authentication_disconnect_user(request: "Request", token_dict: TokenDictType, data: Any): +def authentication_disconnect_user(request: Request, token_dict: TokenDictType, data: Any): """Disconnect all sessions of user in access token""" # found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data # if not found_user: @@ -370,7 +360,7 @@ def authentication_disconnect_user(request: "Request", token_dict: TokenDictType return -def authentication_logout_user(request: "Request", data: Any, token_dict: TokenDictType): +def authentication_logout_user(request: Request, data: Any): """Logout only single session of user which domain is provided""" # token_user = None # if already_tokens := RedisActions.get_object_via_access_key(request=request): @@ -382,11 +372,14 @@ def authentication_logout_user(request: "Request", data: Any, token_dict: TokenD # selected_user.remove_refresher_token(domain=data.domain) # return ResponseHandler.success("Session is logged out", data=token_user) # return ResponseHandler.not_found("Logout is not successfully completed") + token_dict = authentication_logout_user.auth + print('token_dict', token_dict) + func_code = authentication_logout_user.func_code + print('func_code', func_code) return -@AuthMiddlewareModule.auth_required -def authentication_refresher_token(request: "Request", token_dict: TokenDictType, data: Any): +def authentication_refresher_token(request: Request, token_dict: TokenDictType, data: Any): """Refresh access token with refresher token""" # token_refresher = UsersTokens.filter_by_one( # token=data.refresh_token, @@ -412,7 +405,7 @@ def authentication_refresher_token(request: "Request", token_dict: TokenDictType return -def authentication_forgot_password(request: "Request", data: Any): +def authentication_forgot_password(request: Request, data: Any): """Send an email to user for a valid password reset token""" # 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) @@ -431,8 +424,7 @@ def authentication_forgot_password(request: "Request", data: Any): return -@AuthMiddlewareModule.auth_required -def authentication_reset_password(request: "Request", data: Any): +def authentication_reset_password(request: Request, data: Any): """Reset password with forgot password token""" # from sqlalchemy import or_ # found_user = Users.query.filter( @@ -461,8 +453,7 @@ def authentication_reset_password(request: "Request", data: Any): return -@AuthMiddlewareModule.auth_required -def authentication_download_avatar(request: "Request", data: Any, token_dict: TokenDictType): +def authentication_download_avatar(request: Request, data: Any, token_dict: TokenDictType): """Download avatar icon and profile info of user""" # 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))) diff --git a/Events/AllEvents/authentication/auth/info.py b/Events/AllEvents/authentication/auth/info.py index 7d60d97..dc58175 100644 --- a/Events/AllEvents/authentication/auth/info.py +++ b/Events/AllEvents/authentication/auth/info.py @@ -9,5 +9,3 @@ authentication_page_info = PageInfo( parent="", url="", ) - - diff --git a/Events/AllEvents/authentication/auth/models.py b/Events/AllEvents/authentication/auth/models.py index 872189d..41e0a34 100644 --- a/Events/AllEvents/authentication/auth/models.py +++ b/Events/AllEvents/authentication/auth/models.py @@ -31,4 +31,3 @@ class OccupantSelectionSuperUserRequestModel(BaseModel): class OccupantSelectionSuperUserResponseModel(BaseModel): pass - diff --git a/Events/AllEvents/events/__init__.py b/Events/AllEvents/events/__init__.py index e69de29..de2a4c1 100644 --- a/Events/AllEvents/events/__init__.py +++ b/Events/AllEvents/events/__init__.py @@ -0,0 +1,7 @@ +""" +Events package initialization. +""" + + +__all__ = [ +] diff --git a/Events/AllEvents/events_file.py b/Events/AllEvents/events_file.py new file mode 100644 index 0000000..106bc8a --- /dev/null +++ b/Events/AllEvents/events_file.py @@ -0,0 +1,6 @@ +import Events.AllEvents.authentication as auths_events +import Events.AllEvents.events as events_events +import Events.AllEvents.validations as validations_events + + +events_list = (auths_events, events_events, validations_events) diff --git a/Events/AllEvents/validations/validation/validation.py b/Events/AllEvents/validations/validation/validation.py index e5076d6..8384857 100644 --- a/Events/AllEvents/validations/validation/validation.py +++ b/Events/AllEvents/validations/validation/validation.py @@ -27,6 +27,7 @@ class AllModelsImport: AddressCreateEventMethod, AddressSearchEventMethod, ) + return dict( AccountListEventMethod=AccountListEventMethod, AccountUpdateEventMethod=AccountUpdateEventMethod, @@ -41,7 +42,9 @@ class AllModelsImport: class ValidationsBoth(MethodToEvent): @classmethod - def retrieve_both_validations_and_headers(cls, event: ValidationsPydantic) -> Dict[str, Any]: + def retrieve_both_validations_and_headers( + cls, event: ValidationsPydantic + ) -> Dict[str, Any]: EVENT_MODELS = AllModelsImport.import_all_models() return_single_model = EVENT_MODELS.get(event.class_model, None) # event_class_validation = getattr(return_single_model, "__event_validation__", None) @@ -52,7 +55,9 @@ class ValidationsBoth(MethodToEvent): loc=get_line_number_for_error(), sys_msg="Validation code not found", ) - response_model = return_single_model.retrieve_event_response_model(event.reachable_event_code) + response_model = return_single_model.retrieve_event_response_model( + event.reachable_event_code + ) language_model_all = return_single_model.retrieve_language_parameters( function_code=event.reachable_event_code, language=event.lang ) @@ -85,7 +90,9 @@ class ValidationsValidations(MethodToEvent): loc=get_line_number_for_error(), sys_msg="Validation code not found", ) - response_model = return_single_model.retrieve_event_response_model(event.reachable_event_code) + response_model = return_single_model.retrieve_event_response_model( + event.reachable_event_code + ) language_model_all = return_single_model.retrieve_language_parameters( function_code=event.reachable_event_code, language=event.lang ) @@ -107,8 +114,7 @@ class ValidationsValidations(MethodToEvent): class ValidationsHeaders(MethodToEvent): @classmethod - def retrieve_headers(cls, event: ValidationsPydantic -) -> Dict[str, Any]: + def retrieve_headers(cls, event: ValidationsPydantic) -> Dict[str, Any]: EVENT_MODELS = AllModelsImport.import_all_models() return_single_model = EVENT_MODELS.get(event.class_model, None) # event_class_validation = getattr(return_single_model, "__event_validation__", None) @@ -119,7 +125,9 @@ class ValidationsHeaders(MethodToEvent): loc=get_line_number_for_error(), sys_msg="Validation code not found", ) - response_model = return_single_model.retrieve_event_response_model(event.reachable_event_code) + response_model = return_single_model.retrieve_event_response_model( + event.reachable_event_code + ) language_model_all = return_single_model.retrieve_language_parameters( function_code=event.reachable_event_code, language=event.lang ) diff --git a/Events/Engine/__init__.py b/Events/Engine/__init__.py index 8cbbe63..531e19c 100644 --- a/Events/Engine/__init__.py +++ b/Events/Engine/__init__.py @@ -7,15 +7,16 @@ making common utilities and base classes available for all API services. from .abstract_class import ( MethodToEvent, PageInfo, - ClusterToMethod, + CategoryCluster, Event, ) + # from .base_request_model import BaseRequestModel, DictRequestModel # Re-export commonly used classes __all__ = [ "MethodToEvent", "PageInfo", - "ClusterToMethod", + "CategoryCluster", "Event", ] diff --git a/Events/Engine/abstract_class.py b/Events/Engine/abstract_class.py index faf11f4..7ceaf11 100644 --- a/Events/Engine/abstract_class.py +++ b/Events/Engine/abstract_class.py @@ -1,153 +1,8 @@ -from typing import Any, ClassVar, Dict, List, Optional +from abc import abstractmethod +from typing import Any, Dict, List, Optional, Callable from uuid import UUID -from .pageinfo import PageInfo - - -class Event: - - KEY_: str # static string uuid.uuid4().__str__() - RESPONSE_VALIDATOR: ClassVar["PydanticModel"] - REQUEST_VALIDATOR: ClassVar["PydanticModel"] - DESCRIPTION: str - EXTRA_OPTIONS: Optional[Dict[str, Any]] = None - - def __init__( - self, key: UUID, request_validator: "PydanticModel", response_validator: "PydanticModel", - description: str, extra_options: Optional[Dict[str, Any]] = None - ) -> None: - self.KEY_ = key - self.REQUEST_VALIDATOR = request_validator - self.RESPONSE_VALIDATOR = response_validator - self.DESCRIPTION = description - self.EXTRA_OPTIONS = extra_options - - @property - def name(self): - return f"This is an event of {self.__class__.__name__}. Description: {self.DESCRIPTION}" - - @property - def key(self): - return str(self.KEY_) - - @abstractmethod - def endpoint_callable(request: "Request", data: Any): - """ - return cls.retrieve_event(event_function_code).retrieve_callable(token_dict, data) - """ - pass - - -class MethodToEvent: - """ - for all endpoint callable - def endpoint_callable(request: Request, data: PydanticModel): - return cls.retrieve_event(event_function_code).retrieve_callable(token_dict, data) - """ - EVENTS: list[Event] - HEADER_LANGUAGE_MODELS: list[Dict] # [Table.__language_model__ | Dict[__language_model__]] - ERRORS_LANGUAGE_MODELS: Optional[list[Dict]] # [Dict[ErrorCode][lang]] - - URL: str - METHOD: str - SUMMARY: str - DESCRIPTION: str - EXTRA_OPTIONS: Optional[Dict[str, Any]] = None - - def __init__( - self, - events: list[Event], - headers: list[Dict], - url: str, - method: str, - summary: str, - description: str, - errors: Optional[list[Dict]] = None, - extra_options: Optional[Dict[str, Any]] = None, - ): - self.EVENTS = events - self.URL = url - self.METHOD = method - self.SUMMARY = summary - self.DESCRIPTION = description - self.HEADER_LANGUAGE_MODELS = headers - self.ERRORS_LANGUAGE_MODELS = errors - self.EXTRA_OPTIONS = extra_options - - def retrieve_all_event_keys(): - """ - self.EVENTS.iter() - [FUNCTION_CODE] - """ - pass - - def retrieve_event(event_function_code: str): - if list_found := [event for event in self.EVENTS if str(event.key) == event_function_code]: - return list_found[0] - raise ValueError(f"Event with function code {event_function_code} not found") - - def retrieve_redis_value() -> Dict: - """ - Key(f"METHOD_FUNCTION_CODES:{ClusterToMethod}:MethodEvent:Endpoint") : Value([FUNCTION_CODE, ...]) - """ - - pass - - -class CategoryCluster: - - TAGS: list - PREFIX: str - PAGEINFO: PageInfo - DESCRIPTION: str - ENDPOINTS: list = [MethodToEvent] - SUBCATEGORY: Optional[List["CategoryCluster"]] = [] - INCLUDE_IN_SCHEMA: Optional[bool] = True - - def __init__( - self, - tags: list, - prefix: str, - description: str, - pageinfo: PageInfo, - endpoints: list, - sub_category: list = [], - include_in_schema: Optional[bool] = True, - ): - self.TAGS = tags - self.PREFIX = prefix - self.PAGEINFO = pageinfo - self.DESCRIPTION = description - self.ENDPOINTS = endpoints or [] - self.SUBCATEGORY = sub_category or [] - self.INCLUDE_IN_SCHEMA = include_in_schema - - - def retrieve_all_function_codes(): - """ - [FUNCTION_CODE, ...] - self.ENDPOINTS -> iter() - """ - pass - - def retrieve_page_info(): - """ - PAGE_INFO:ClusterToMethod = { - "PageInfo": {...} - "subCategory": PAGE_INFO:ClusterToMethod - } - PAGE_INFO:ClusterToMethod = { - "PageInfo": {...} - "subCategory": PAGE_INFO:ClusterToMethod - } - """ - pass - - def retrieve_redis_value() -> Dict: - """ - Key(CLUSTER_FUNCTION_CODES:ClusterToMethod) : Value(PAGE_INFO, [FUNCTION_CODE, ...]) - """ - pass +from ApiLayers.AllConfigs.Redis.configs import RedisCategoryKeys class PageInfo: @@ -155,7 +10,7 @@ class PageInfo: NAME: str BUTTON_NAME: str PAGE_URL: str - PAGEINFO: "PageInfo" + PAGEINFO: Dict[str, Any] def __init__( self, @@ -173,4 +28,195 @@ class PageInfo: self.PARENT = parent +class Event: + KEY_: str # static string uuid.uuid4().__str__() + RESPONSE_VALIDATOR: Optional[Any] + REQUEST_VALIDATOR: Optional[Any] + DESCRIPTION: str + EXTRA_OPTIONS: Optional[Dict[str, Any]] = None + endpoint_callable: Any + + def __init__( + self, + name: str, + key: str | UUID, + description: str, + request_validator: Optional[Any] = None, + response_validator: Optional[Any] = None, + extra_options: Optional[Dict[str, Any]] = None, + ) -> None: + self.NAME = name + self.KEY_ = key + self.REQUEST_VALIDATOR = request_validator + self.RESPONSE_VALIDATOR = response_validator + self.DESCRIPTION = description + self.EXTRA_OPTIONS = extra_options + + @property + def description(self): + return f"This is an event of {self.name}. Description: {self.DESCRIPTION}" + + @property + def name(self): + return self.NAME + + @property + def key(self): + return str(self.KEY_) + + @abstractmethod + def endpoint_callable(self, **kwargs) -> Any: + """ + Retrieves the endpoint function based on the event key. + """ + return self.endpoint_callable(**kwargs) + + +class MethodToEvent: + """ + for all endpoint callable + def endpoint_callable(request: Request, data: PydanticModel): + return cls.retrieve_event(event_function_code).retrieve_callable(token_dict, data) + [Table.__language_model__ | Dict[__language_model__]] + [Dict[ErrorCode][lang]] + + """ + + EVENTS: dict[str, Event] + HEADER_LANGUAGE_MODELS: list[Dict] + ERRORS_LANGUAGE_MODELS: Optional[list[Dict]] + URL: str + METHOD: str + SUMMARY: str + DESCRIPTION: str + DECORATORS_LIST: Optional[Callable] = [] + EXTRA_OPTIONS: Optional[Dict[str, Any]] = None + + def __init__( + self, + name: str, + events: dict[str, Event], + headers: list[Dict], + url: str, + method: str, + summary: str, + description: str, + decorators_list: Optional[List[Callable]] = None, + errors: Optional[list[Dict]] = None, + extra_options: Optional[Dict[str, Any]] = None, + ): + self.EVENTS = events + self.URL = url + self.METHOD = method + self.SUMMARY = summary + self.NAME = name + self.DESCRIPTION = description + self.DECORATORS_LIST = decorators_list + self.HEADER_LANGUAGE_MODELS = headers + self.ERRORS_LANGUAGE_MODELS = errors + self.EXTRA_OPTIONS = extra_options + + @property + def name(self): + return self.NAME + + def retrieve_all_event_keys(self): + """ + Retrieves all event keys from the events list. + """ + return [str(event_key) for event_key in self.EVENTS.keys()] + + def retrieve_event(self, event_function_code: str) -> Event: + """ + Retrieves the event object from the events list based on the event function code. + """ + if found_event := self.EVENTS.get(event_function_code, None): + return found_event + raise ValueError(f"Event with function code {event_function_code} not found") + + def retrieve_redis_value(self, cluster_name: str) -> Dict: + """ + Key("METHOD_FUNCTION_CODES:{ClusterToMethod}:MethodEvent:Endpoint") : Value([FUNCTION_CODE, ...]) + """ + redis_key = f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}:{cluster_name}:{self.name}:{self.URL}" + return {redis_key: self.retrieve_all_event_keys()} + + @staticmethod + def endpoint_callable(**kwargs): + """ + return cls.retrieve_event(event_function_code).retrieve_callable(token_dict, data) + """ + raise NotImplementedError("Endpoint callable method is not implemented") + + +class CategoryCluster: + + TAGS: list + PREFIX: str + PAGEINFO: PageInfo + DESCRIPTION: str + ENDPOINTS: list[MethodToEvent] # [MethodToEvent, ...] + SUBCATEGORY: Optional[List["CategoryCluster"]] # [CategoryCluster, ...] + INCLUDE_IN_SCHEMA: Optional[bool] = True + + def __init__( + self, + name: str, + tags: list, + prefix: str, + description: str, + pageinfo: PageInfo, + endpoints: list[MethodToEvent], + sub_category: list, + include_in_schema: Optional[bool] = True, + ): + self.NAME = name + self.TAGS = tags + self.PREFIX = prefix + self.PAGEINFO = pageinfo + self.DESCRIPTION = description + self.ENDPOINTS = endpoints or [] + self.SUBCATEGORY = sub_category or [] + self.INCLUDE_IN_SCHEMA = include_in_schema + + @property + def name(self): + return self.NAME + + def get_redis_cluster_index_value(self): + """ + RedisCategoryKeys.CLUSTER_2_METHOD_EVENT + Returns the class name and function codes for the class. + """ + dict_cluster_2_method, list_endpoints = {}, [i.name for i in self.ENDPOINTS] + for endpoint_name in list_endpoints: + dict_cluster_2_method[endpoint_name] = self.name + dict_cluster_2_method[self.name] = list_endpoints + return dict_cluster_2_method + + def retrieve_all_function_codes(self): + """ + Retrieves all function codes by iterating over the events list. + """ + all_function_codes = [] + for event_method in self.ENDPOINTS: + all_function_codes.extend([str(event_key) for event_key in event_method.EVENTS.keys()]) + return all_function_codes + + def retrieve_redis_value(self) -> Dict: + """ + Create Redis Key and Value from function codes + Key(CLUSTER_FUNCTION_CODES:ClusterToMethod) : Value(PAGE_INFO, [FUNCTION_CODE, ...]) + """ + return { + f"{RedisCategoryKeys.CLUSTER_FUNCTION_CODES}:{self.name}": self.retrieve_all_function_codes() + } + + def retrieve_page_info(self): + """ + PAGE_INFO:ClusterToMethod = {"PageInfo": {...}, "subCategory": PAGE_INFO:ClusterToMethod} + """ + raise NotImplementedError( + "CategoryCluster retrieve_page_info() method is not implemented" + ) diff --git a/Events/Engine/set_defaults/category_cluster_models.py b/Events/Engine/set_defaults/category_cluster_models.py new file mode 100644 index 0000000..f4164c6 --- /dev/null +++ b/Events/Engine/set_defaults/category_cluster_models.py @@ -0,0 +1,42 @@ +from Events.Engine import CategoryCluster + + +class CategoryBulk: + + def __init__(self, category_cluster: CategoryCluster = None, name: str = ""): + self.category_cluster = category_cluster + self.name = name + + +class CategoryClusterController: + + imports_dict: list[CategoryBulk] = [] + + @property + def imports(self): + return self.imports_dict + + @classmethod + def import_all_category_clusters(cls, category_clusters): + """ + Imports all category clusters from the given list + { "category_cluster_name": "category_cluster_module" } + """ + if not hasattr(category_clusters, "__all__"): + raise ValueError(f"Given module {str(category_clusters)} does not have __all__ attribute") + for iter_module in [str(item) for item in category_clusters.__all__]: + # CategoryCluster which represent api routers for each category + cls.imports_dict.append( + CategoryBulk( + category_cluster=getattr(category_clusters, iter_module, None), + name=iter_module + )) + + @classmethod + def as_dict(cls): + to_dict = {} + for cluster in cls.imports_dict: + to_dict[cluster.name] = cluster.category_cluster + return to_dict + +cluster_controller = CategoryClusterController() \ No newline at end of file diff --git a/Events/Engine/set_defaults/prepare_redis_items.py b/Events/Engine/set_defaults/prepare_redis_items.py new file mode 100644 index 0000000..ce80f4f --- /dev/null +++ b/Events/Engine/set_defaults/prepare_redis_items.py @@ -0,0 +1,84 @@ +from ApiLayers.AllConfigs.Redis.configs import RedisCategoryKeys + + +class PrepareRedisItems: + + MENU_FIRST_LAYER_KEY: str = RedisCategoryKeys.MENU_FIRST_LAYER + MENU_FIRST_LAYER_VALUE: set[str] = set() + CLUSTER_INDEX_KEY: str = RedisCategoryKeys.CLUSTER_INDEX + CLUSTER_INDEX_VALUE: dict = {} + CLUSTER_FUNCTION_CODES_KEY: str = RedisCategoryKeys.CLUSTER_FUNCTION_CODES + CLUSTER_FUNCTION_CODES_VALUE: dict = {} + METHOD_FUNCTION_CODES_KEY: str = RedisCategoryKeys.METHOD_FUNCTION_CODES + METHOD_FUNCTION_CODES_VALUE: dict = {} + ENDPOINT2CLASS_KEY: str = RedisCategoryKeys.ENDPOINT2CLASS + ENDPOINT2CLASS_VALUE: dict = {} + + @property + def as_dict(self): + return { + self.MENU_FIRST_LAYER_KEY: list(self.MENU_FIRST_LAYER_VALUE), + self.CLUSTER_INDEX_KEY: self.CLUSTER_INDEX_VALUE, + self.CLUSTER_FUNCTION_CODES_KEY: self.CLUSTER_FUNCTION_CODES_VALUE, + self.METHOD_FUNCTION_CODES_KEY: self.METHOD_FUNCTION_CODES_VALUE, + self.ENDPOINT2CLASS_KEY: self.ENDPOINT2CLASS_VALUE, + } + + +class DecoratorModule: + + @staticmethod + def get_all_decorators(func): + """ + Get all decorators of a function, excluding the original function itself. + Returns a list of decorator functions in the order they were applied. + """ + decorators = [] + current_func = func + original_qualname = getattr(func, '__qualname__', '') + + while hasattr(current_func, '__wrapped__'): + if hasattr(current_func, '__closure__') and current_func.__closure__: + for cell in current_func.__closure__: + decorator = cell.cell_contents + # Only add if it's a callable and not the original function + if callable(decorator) and getattr(decorator, '__qualname__', '') != original_qualname: + decorators.append(decorator) + current_func = current_func.__wrapped__ + return list(dict.fromkeys(decorators)) # Remove duplicates while preserving order + + @staticmethod + def get_actual_decorators(method_endpoint): + original_qualname = getattr(method_endpoint.endpoint_callable, '__qualname__', '') + actual_decorators = [ + d for d in method_endpoint.DECORATORS_LIST or [] + if callable(d) and getattr(d, '__qualname__', '') != original_qualname + ] + return actual_decorators + + @classmethod + def apply_decorators(cls, method_endpoint): + # Get the original function and its qualname + function_callable = method_endpoint.endpoint_callable + # Filter out the original function and apply decorators + actual_decorators = cls.get_actual_decorators(method_endpoint) + + # Apply decorators in reverse order (to match @ syntax behavior) + for decorator in reversed(actual_decorators): + try: + function_callable = decorator(function_callable) + except Exception as e: + print(f"Warning: Failed to apply decorator {decorator.__qualname__}: {str(e)}") + + method_endpoint.endpoint_callable = function_callable + + # Get the final list of applied decorators (for debugging) + applied_decorators = cls.get_all_decorators(method_endpoint.endpoint_callable) + applied_decorators_qualname = [getattr(d, '__qualname__', str(d)) for d in applied_decorators] + if applied_decorators: + print(f"Applied decorators for {method_endpoint.name}:", applied_decorators_qualname) + return applied_decorators_qualname + + @classmethod + def list_qualname(cls, method_endpoint_list): + return [getattr(method_endpoint, '__qualname__', '') for method_endpoint in method_endpoint_list] \ No newline at end of file diff --git a/Events/Engine/set_defaults/run.py b/Events/Engine/set_defaults/run.py index 139597f..24f95d9 100644 --- a/Events/Engine/set_defaults/run.py +++ b/Events/Engine/set_defaults/run.py @@ -1,2 +1,16 @@ +from Events.AllEvents.events_file import events_list +from .category_cluster_models import cluster_controller +def get_cluster_controller_group(): + for cluster in events_list: + cluster_controller.import_all_category_clusters(cluster) + return cluster_controller + +""" +prepare_routing = PrepareRouting(cluster_controller_group=cluster_controller) +prepare_events = PrepareEvents(cluster_controller_group=cluster_controller) +set_items_2_redis = SetItems2Redis(prepare_events=prepare_events) +print(set_items_2_redis) +print(prepare_routing) +""" diff --git a/Events/Engine/set_defaults/setClusters.py b/Events/Engine/set_defaults/setClusters.py index 781eb41..30449b9 100644 --- a/Events/Engine/set_defaults/setClusters.py +++ b/Events/Engine/set_defaults/setClusters.py @@ -1,7 +1,171 @@ -import AllEvents.auth as auths_events -import AllEvents.events as events_events -import AllEvents.validations as validations_events +from typing import Any + +from ApiLayers.ApiServices.Cluster.create_router import ( + CreateRouterFromCluster, + CreateEndpointFromCluster +) +from Events.Engine.abstract_class import CategoryCluster +from Services.Redis.Actions.actions import RedisActions +from Services.Redis.Models.cluster import RedisList + +from .prepare_redis_items import DecoratorModule, PrepareRedisItems +from .category_cluster_models import CategoryClusterController -for event in [*auths_events.__all__, *events_events.__all__, *validations_events.__all__]: - print(event) +class PrepareRouting(DecoratorModule): + + __routers_list: list[Any] = list() + __endpoints_list: list[Any] = list() + __safe_endpoint_list: list[Any] = list() + + def __init__(self, cluster_controller_group: CategoryClusterController): + self.cluster_controller_group = cluster_controller_group + self.prepare_needs() + + def __str__(self): + return f"\nPrepared Routing:\n\n{self.routers}\n\n{self.endpoints}\n\n{self.safe_endpoints}\n" + + @property + def routers(self): + return self.__routers_list + + @property + def endpoints(self): + return self.__endpoints_list + + @property + def safe_endpoints(self): + return self.__safe_endpoint_list + + def create_endpoints(self, cluster: CategoryCluster, created_router): + for method_endpoint in list(cluster.ENDPOINTS): + # Filter out the original function and apply decorators + applied_decorators_qualname = self.apply_decorators(method_endpoint) + # Register the endpoint with FastAPI router + create_endpoint = CreateEndpointFromCluster( + router=created_router, method_endpoint=method_endpoint + ) + created_router = create_endpoint.router + if "MiddlewareModule" in applied_decorators_qualname: + self.__safe_endpoint_list.append(method_endpoint) + self.__endpoints_list.append(method_endpoint) + + def create_router(self, cluster: CategoryCluster): + ### Create Router Parameters create router for each cluster + created_router = CreateRouterFromCluster( + prefix=cluster.PREFIX, + tags=cluster.TAGS, + include_in_schema=cluster.INCLUDE_IN_SCHEMA, + ) + self.__routers_list.append(created_router.router) + return created_router.router + + def prepare_needs(self): + # @Pages iterate(ClusterToMethod) + for cluster_control in self.cluster_controller_group.imports: + cluster = cluster_control.category_cluster + created_router = self.create_router(cluster) + self.create_endpoints(cluster, created_router) + + +class PrepareEvents(DecoratorModule): + + def __init__(self, cluster_controller_group: CategoryClusterController): + self.cluster_controller_group = cluster_controller_group + self.valid_redis_items: PrepareRedisItems = PrepareRedisItems() + self.prepare_needs() + + def prepare_needs(self): + # @Pages iterate(ClusterToMethod) + for cluster_control in self.cluster_controller_group.imports: + cluster = cluster_control.category_cluster + ### Create Redis Parameters + # [SAVE]REDIS => MENU_FIRST_LAYER = [ClusterToMethod, ...] + self.valid_redis_items.MENU_FIRST_LAYER_VALUE.add(cluster.name) + # [SAVE]REDIS => CLUSTER_INDEX = {ClusterToMethod: [MethodEvent, ...], "MethodEvent": "ClusterToMethod"} + self.valid_redis_items.CLUSTER_INDEX_VALUE.update(cluster.get_redis_cluster_index_value()) + # [SAVE]REDIS => CLUSTER_FUNCTION_CODES = {"ClusterToMethod"} : [FUNCTION_CODE, ...]} + self.valid_redis_items.CLUSTER_FUNCTION_CODES_VALUE = { + f"{self.valid_redis_items.CLUSTER_FUNCTION_CODES_KEY}:{cluster.name}" : tuple(cluster.retrieve_all_function_codes()) + } + + for method_endpoint in list(cluster.ENDPOINTS): + # [SAVE]REDIS => ENDPOINT2CLASS = {MethodEvent: Endpoint("/.../.../..."), ...} + self.valid_redis_items.ENDPOINT2CLASS_VALUE.update( + {f"{cluster.name}:{method_endpoint.name}": method_endpoint.URL} + ) + self.valid_redis_items.ENDPOINT2CLASS_VALUE.update( + {method_endpoint.URL :f"{cluster.name}:{method_endpoint.name}"} + ) + # [SAVE]REDIS => METHOD_FUNCTION_CODES:MethodEvent:Endpoint = [FUNCTION_CODE, ...] + self.valid_redis_items.METHOD_FUNCTION_CODES_VALUE.update( + method_endpoint.retrieve_redis_value(cluster_name=cluster.name) + ) + + +class SetItems2Redis: + + std_out: str = "" + + def __init__(self, prepare_events: PrepareEvents): + self.prepare_events = prepare_events + self.set_items() + + def __str__(self): + self.std_out = f"\nSetItems2Redis:\n\n{self.std_out}" + return self.std_out + + def set_items(self): + from ApiLayers.AllConfigs.Redis.configs import RedisCategoryKeys + dict_prep = self.prepare_events.valid_redis_items.as_dict + RedisActions.delete( + list_keys=[ + f"{RedisCategoryKeys.MENU_FIRST_LAYER}:*", + f"{RedisCategoryKeys.CLUSTER_INDEX}:*", + f"{RedisCategoryKeys.CLUSTER_FUNCTION_CODES}:*", + f"{RedisCategoryKeys.METHOD_FUNCTION_CODES}:*" + f"{RedisCategoryKeys.ENDPOINT2CLASS}:*", + ] + ) + + # Save MENU_FIRST_LAYER to Redis + redis_list = RedisList(redis_key=RedisCategoryKeys.MENU_FIRST_LAYER) + RedisActions.set_json( + list_keys=redis_list.to_list(), value=dict_prep.get(RedisCategoryKeys.MENU_FIRST_LAYER) + ) + self.std_out = f"{RedisCategoryKeys.MENU_FIRST_LAYER}: {dict_prep.get(RedisCategoryKeys.MENU_FIRST_LAYER)}\n" + + # Save CLUSTER_INDEX to Redis + redis_list = RedisList(redis_key=RedisCategoryKeys.CLUSTER_INDEX) + RedisActions.set_json( + list_keys=redis_list.to_list(), value=dict_prep.get(RedisCategoryKeys.CLUSTER_INDEX) + ) + self.std_out += f"\n{RedisCategoryKeys.CLUSTER_INDEX}: {dict_prep.get(RedisCategoryKeys.CLUSTER_INDEX)}\n" + + # Save CLUSTER_FUNCTION_CODES to Redis by iterating over the dict + for redis_key, redis_value in dict_prep.get(RedisCategoryKeys.CLUSTER_FUNCTION_CODES).items(): + redis_list = RedisList(redis_key=redis_key) + RedisActions.set_json( + list_keys=redis_list.to_list(), value=list(redis_value) + ) + self.std_out += f"\n{RedisCategoryKeys.CLUSTER_FUNCTION_CODES}: {dict_prep.get(RedisCategoryKeys.CLUSTER_FUNCTION_CODES)}\n" + + # Save METHOD_FUNCTION_CODES to Redis by iterating over the dict + for redis_key, redis_value in dict_prep.get(RedisCategoryKeys.METHOD_FUNCTION_CODES).items(): + redis_list = RedisList(redis_key=redis_key) + RedisActions.set_json( + list_keys=redis_list.to_list(), value=list(redis_value) + ) + self.std_out += f"\n{RedisCategoryKeys.METHOD_FUNCTION_CODES}: {dict_prep.get(RedisCategoryKeys.METHOD_FUNCTION_CODES)}\n" + + # Save ENDPOINT2CLASS to Redis by iterating over the dict + for redis_key, redis_value in dict_prep.get(RedisCategoryKeys.ENDPOINT2CLASS).items(): + redis_list = RedisList(redis_key=f"{RedisCategoryKeys.ENDPOINT2CLASS}:{redis_key}\n") + RedisActions.set_json( + list_keys=redis_list.to_list(), value=redis_value + ) + self.std_out += f"\n{RedisCategoryKeys.ENDPOINT2CLASS}: {dict_prep.get(RedisCategoryKeys.ENDPOINT2CLASS)}\n" + + RedisActions.set_json( + list_keys=[f"{RedisCategoryKeys.REBUILD}:*"], value=False + ) diff --git a/Events/abstract_class.py b/Events/abstract_class.py deleted file mode 100644 index e8839b3..0000000 --- a/Events/abstract_class.py +++ /dev/null @@ -1,38 +0,0 @@ - -class ClusterToMethod: - - TAGS: list = ["Tag or Router"] - PREFIX: str = "/..." - PAGEINFO: PageInfo - ENDPOINTS: list = [MethodEvent, ...] - SUBCATEGORY: List[ClassVar[Any]] = [ClusterToMethod, ...] - - def retrieve_all_function_codes(): - """ - [FUNCTION_CODE, ...] - self.ENDPOINTS -> iter() - """ - pass - - def retrieve_page_info(): - """ - PAGE_INFO:ClusterToMethod = { - "PageInfo": {...} - "subCategory": PAGE_INFO:ClusterToMethod - } - PAGE_INFO:ClusterToMethod = { - "PageInfo": {...} - "subCategory": PAGE_INFO:ClusterToMethod - } - """ - pass - - def retrieve_redis_value() -> Dict: - """ - Key(CLUSTER_FUNCTION_CODES:ClusterToMethod) : Value(PAGE_INFO, [FUNCTION_CODE, ...]) - """ - pass - - - - diff --git a/Scratches/endpoint.py b/Scratches/endpoint.py index dfcf4dd..949e649 100644 --- a/Scratches/endpoint.py +++ b/Scratches/endpoint.py @@ -17,7 +17,7 @@ class CategoryCreate(CategoryBase): class CategoryResponse(CategoryBase): - children: List['CategoryResponse'] = [] + children: List["CategoryResponse"] = [] parent_id: Optional[str] = None @@ -28,7 +28,7 @@ class CategoryNode: name: str description: Optional[str] parent_id: Optional[str] = None - children: List['CategoryNode'] = field(default_factory=list) + children: List["CategoryNode"] = field(default_factory=list) # Category Service for managing the hierarchy @@ -41,7 +41,7 @@ class CategoryService: id=category.id, name=category.name, description=category.description, - parent_id=category.parent_id + parent_id=category.parent_id, ) self.categories[category.id] = node @@ -61,7 +61,9 @@ class CategoryService: while current: path.append(current) - current = self.categories.get(current.parent_id) if current.parent_id else None + current = ( + self.categories.get(current.parent_id) if current.parent_id else None + ) return list(reversed(path)) @@ -85,9 +87,8 @@ class CategoryEndpointFactory: endpoint_function=self.create_category, request_model=CategoryCreate, response_model=CategoryResponse, - is_auth_required=True + is_auth_required=True, ), - # Get category tree endpoint EndpointFactoryConfig( url_prefix=base_prefix, @@ -99,9 +100,8 @@ class CategoryEndpointFactory: description="Get category and its children", endpoint_function=self.get_category_tree, response_model=CategoryResponse, - is_auth_required=True + is_auth_required=True, ), - # Get category path endpoint EndpointFactoryConfig( url_prefix=base_prefix, @@ -113,15 +113,15 @@ class CategoryEndpointFactory: description="Get full path from root to this category", endpoint_function=self.get_category_path, response_model=List[CategoryResponse], - is_auth_required=True - ) + is_auth_required=True, + ), ] return RouteFactoryConfig( name="categories", tags=["Categories"], prefix=base_prefix, - endpoints=endpoints + endpoints=endpoints, ) async def create_category(self, category: CategoryCreate) -> CategoryResponse: @@ -146,7 +146,7 @@ class CategoryEndpointFactory: name=node.name, description=node.description, parent_id=node.parent_id, - children=[self._convert_to_response(child) for child in node.children] + children=[self._convert_to_response(child) for child in node.children], ) @@ -156,10 +156,7 @@ def create_category_router(base_prefix: str = "/api/v1") -> APIRouter: factory = CategoryEndpointFactory(category_service) route_config = factory.create_route_config(base_prefix) - router = APIRouter( - prefix=route_config.prefix, - tags=route_config.tags - ) + router = APIRouter(prefix=route_config.prefix, tags=route_config.tags) for endpoint in route_config.endpoints: router.add_api_route( @@ -169,7 +166,7 @@ def create_category_router(base_prefix: str = "/api/v1") -> APIRouter: response_model=endpoint.response_model, summary=endpoint.summary, description=endpoint.description, - **endpoint.extra_options + **endpoint.extra_options, ) - return router \ No newline at end of file + return router diff --git a/Services/PostgresDb/Models/core_alchemy.py b/Services/PostgresDb/Models/core_alchemy.py index 1483935..a956259 100644 --- a/Services/PostgresDb/Models/core_alchemy.py +++ b/Services/PostgresDb/Models/core_alchemy.py @@ -2,8 +2,10 @@ from typing import Type, TypeVar from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session -from ApiLibrary import get_line_number_for_error -from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi + +from ApiLayers.ApiLibrary import get_line_number_for_error +from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi + # Type variable for class methods returning self T = TypeVar("T", bound="FilterAttributes") diff --git a/Services/PostgresDb/Models/crud_alchemy.py b/Services/PostgresDb/Models/crud_alchemy.py index bccef05..911278a 100644 --- a/Services/PostgresDb/Models/crud_alchemy.py +++ b/Services/PostgresDb/Models/crud_alchemy.py @@ -6,8 +6,8 @@ from sqlalchemy import TIMESTAMP, NUMERIC from sqlalchemy.orm import Session, Mapped from pydantic import BaseModel -from ApiLibrary import system_arrow, get_line_number_for_error, client_arrow -from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi +from ApiLayers.ApiLibrary import system_arrow, get_line_number_for_error +from ApiLayers.ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi from Services.PostgresDb.Models.core_alchemy import BaseAlchemyModel from Services.PostgresDb.Models.system_fields import SystemFields diff --git a/Services/PostgresDb/Models/filter_functions.py b/Services/PostgresDb/Models/filter_functions.py index b5e07d0..6ad6654 100644 --- a/Services/PostgresDb/Models/filter_functions.py +++ b/Services/PostgresDb/Models/filter_functions.py @@ -13,9 +13,9 @@ from sqlalchemy.sql.elements import BinaryExpression from sqlalchemy_mixins.smartquery import SmartQueryMixin from Services.PostgresDb.Models.response import PostgresResponse -from Services.PostgresDb.Models_old.base_model import BaseModel +from Services.PostgresDb.Models.core_alchemy import BaseAlchemyModel -from ApiLibrary import system_arrow +from ApiLayers.ApiLibrary import system_arrow T = TypeVar("T", bound="FilterAttributes") @@ -63,7 +63,7 @@ class ArgumentModel: return arg -class QueryModel(ArgumentModel, BaseModel, SmartQueryMixin): +class QueryModel(ArgumentModel, BaseAlchemyModel, SmartQueryMixin): pre_query = None __abstract__ = True diff --git a/Services/PostgresDb/__init__.py b/Services/PostgresDb/__init__.py index 912af25..e0568d3 100644 --- a/Services/PostgresDb/__init__.py +++ b/Services/PostgresDb/__init__.py @@ -1,4 +1,4 @@ -from Services.PostgresDb.Models.mixins import CrudCollection, BaseCollection +from Services.PostgresDb.Models.mixin import CrudCollection, BaseCollection __all__ = [ "CrudCollection", diff --git a/Services/PostgresDb/database.py b/Services/PostgresDb/database.py index f1d70ca..2a294b4 100644 --- a/Services/PostgresDb/database.py +++ b/Services/PostgresDb/database.py @@ -5,7 +5,7 @@ from typing import Generator from sqlalchemy import create_engine from sqlalchemy.orm import declarative_base, sessionmaker, scoped_session, Session -from AllConfigs.SqlDatabase.configs import WagDatabase +from ApiLayers.AllConfigs.SqlDatabase.configs import WagDatabase # Configure the database engine with proper pooling engine = create_engine( diff --git a/Services/Redis/Actions/actions.py b/Services/Redis/Actions/actions.py index 7791fa7..3733357 100644 --- a/Services/Redis/Actions/actions.py +++ b/Services/Redis/Actions/actions.py @@ -2,7 +2,7 @@ import arrow from typing import Optional, List, Dict, Union -from AllConfigs.main import MainConfig +from ApiLayers.AllConfigs.main import MainConfig from Services.Redis.conn import redis_cli from Services.Redis.Models.base import RedisRow diff --git a/Services/Redis/Models/access.py b/Services/Redis/Models/access.py new file mode 100644 index 0000000..3c3161e --- /dev/null +++ b/Services/Redis/Models/access.py @@ -0,0 +1,30 @@ +from typing import Optional +from uuid import UUID +from pydantic import field_validator + +from Services.Redis.Models.row import BaseRedisModel + + +class AccessToken(BaseRedisModel): + + accessToken: Optional[str] = None + userUUID: Optional[str | UUID] = None + + @field_validator("userUUID", mode="after") + def validate_uuid(cls, v): + """Convert UUID to string during validation.""" + if v is None: + return None + return str(v) + + def to_list(self): + """Convert to list for Redis storage.""" + return [self.accessToken, str(self.userUUID) if self.userUUID else None] + + @property + def count(self): + return 2 + + @property + def delimiter(self): + return ":" diff --git a/Services/Redis/Models/cluster.py b/Services/Redis/Models/cluster.py new file mode 100644 index 0000000..1724cc3 --- /dev/null +++ b/Services/Redis/Models/cluster.py @@ -0,0 +1,17 @@ +from Services.Redis.Models.row import BaseRedisModel + + +class RedisList(BaseRedisModel): + redis_key: str + + def to_list(self): + """Convert to list for Redis storage.""" + return [self.redis_key] + + @property + def count(self): + return 1 + + @property + def delimiter(self): + return ":" diff --git a/Services/Redis/Models/row.py b/Services/Redis/Models/row.py index 86ec4c5..b9fd12b 100644 --- a/Services/Redis/Models/row.py +++ b/Services/Redis/Models/row.py @@ -1,28 +1,20 @@ -from typing import Optional, Literal -from uuid import UUID -from pydantic import BaseModel, field_validator +from abc import abstractmethod +from pydantic import BaseModel -class AccessToken(BaseModel): +class BaseRedisModel(BaseModel): - accessToken: Optional[str] = None - userUUID: Optional[str | UUID] = None - - @field_validator("userUUID", mode="after") - def validate_uuid(cls, v): - """Convert UUID to string during validation.""" - if v is None: - return None - return str(v) - - def to_list(self): + @abstractmethod + def to_list(self) -> list: """Convert to list for Redis storage.""" - return [self.accessToken, str(self.userUUID) if self.userUUID else None] + pass - @property - def count(self): - return 2 + @abstractmethod + def count(self) -> int: + """Return the number of elements in the list.""" + pass - @property - def delimiter(self): - return "*" + @abstractmethod + def delimiter(self) -> str: + """Return the delimiter for the list.""" + pass diff --git a/Services/Redis/__init__.py b/Services/Redis/__init__.py index 28635af..3fe242a 100644 --- a/Services/Redis/__init__.py +++ b/Services/Redis/__init__.py @@ -1,5 +1,5 @@ from Services.Redis.Actions.actions import RedisActions -from Services.Redis.Models.row import AccessToken +from Services.Redis.Models.access import AccessToken __all__ = [ diff --git a/Services/Redis/conn.py b/Services/Redis/conn.py index 066aace..d9e7d0d 100644 --- a/Services/Redis/conn.py +++ b/Services/Redis/conn.py @@ -1,6 +1,6 @@ from redis import Redis -from AllConfigs.Redis.configs import WagRedis +from ApiLayers.AllConfigs.Redis.configs import WagRedis class RedisConn: diff --git a/docker-compose-services.yml b/docker-compose-services.yml index d2ba154..60a634b 100644 --- a/docker-compose-services.yml +++ b/docker-compose-services.yml @@ -1,4 +1,9 @@ services: + init-service: + build: + context: . + dockerfile: DockerApiServices/InitServiceApi/Dockerfile + auth-service: build: context: . @@ -11,12 +16,12 @@ services: context: . dockerfile: DockerApiServices/EventServiceApi/Dockerfile ports: - - "41576:41575" + - "41576:41576" validation-service: build: context: . dockerfile: DockerApiServices/ValidationServiceApi/Dockerfile ports: - - "41577:41575" + - "41577:41577" # and lets try to implement potry again in the dockerfile now we now that it is about copy of files diff --git a/docs/improvements/validation_service/backend/unified_schema_service.py b/docs/improvements/validation_service/backend/unified_schema_service.py index 82ed3c3..115a2d0 100644 --- a/docs/improvements/validation_service/backend/unified_schema_service.py +++ b/docs/improvements/validation_service/backend/unified_schema_service.py @@ -2,30 +2,37 @@ from typing import Dict, Any, Type, Optional from pydantic import BaseModel from fastapi import APIRouter, Header + class ValidationMessages(BaseModel): """Messages for Zod validation""" + required: str invalid_type: str invalid_string: Dict[str, str] # email, url, etc - too_small: Dict[str, str] # string, array, number - too_big: Dict[str, str] # string, array, number + too_small: Dict[str, str] # string, array, number + too_big: Dict[str, str] # string, array, number invalid_date: str invalid_enum: str custom: Dict[str, str] + class SchemaField(BaseModel): """Schema field definition""" + type: str items: Optional[str] = None # For arrays values: Optional[list] = None # For enums validations: Optional[Dict[str, Any]] = None + class SchemaDefinition(BaseModel): """Complete schema definition""" + name: str fields: Dict[str, SchemaField] messages: ValidationMessages + class UnifiedSchemaService: def __init__(self): self.messages = { @@ -35,24 +42,24 @@ class UnifiedSchemaService: invalid_string={ "email": "Geçerli bir e-posta adresi giriniz", "url": "Geçerli bir URL giriniz", - "uuid": "Geçerli bir UUID giriniz" + "uuid": "Geçerli bir UUID giriniz", }, too_small={ "string": "{min} karakterden az olamaz", "array": "En az {min} öğe gereklidir", - "number": "En az {min} olmalıdır" + "number": "En az {min} olmalıdır", }, too_big={ "string": "{max} karakterden fazla olamaz", "array": "En fazla {max} öğe olabilir", - "number": "En fazla {max} olabilir" + "number": "En fazla {max} olabilir", }, invalid_date="Geçerli bir tarih giriniz", invalid_enum="Geçersiz seçim", custom={ "password_match": "Şifreler eşleşmiyor", - "strong_password": "Şifre güçlü değil" - } + "strong_password": "Şifre güçlü değil", + }, ), "en": ValidationMessages( required="This field is required", @@ -60,48 +67,44 @@ class UnifiedSchemaService: invalid_string={ "email": "Please enter a valid email", "url": "Please enter a valid URL", - "uuid": "Please enter a valid UUID" + "uuid": "Please enter a valid UUID", }, too_small={ "string": "Must be at least {min} characters", "array": "Must contain at least {min} items", - "number": "Must be at least {min}" + "number": "Must be at least {min}", }, too_big={ "string": "Must be at most {max} characters", "array": "Must contain at most {max} items", - "number": "Must be at most {max}" + "number": "Must be at most {max}", }, invalid_date="Please enter a valid date", invalid_enum="Invalid selection", custom={ "password_match": "Passwords do not match", - "strong_password": "Password is not strong enough" - } - ) + "strong_password": "Password is not strong enough", + }, + ), } def get_schema_with_messages( - self, - model: Type[BaseModel], - lang: str = "tr" + self, model: Type[BaseModel], lang: str = "tr" ) -> SchemaDefinition: """Get schema definition with validation messages""" fields: Dict[str, SchemaField] = {} - + for field_name, field in model.__fields__.items(): field_info = SchemaField( type=self._get_field_type(field.outer_type_), items=self._get_items_type(field.outer_type_), values=self._get_enum_values(field.outer_type_), - validations=self._get_validations(field) + validations=self._get_validations(field), ) fields[field_name] = field_info return SchemaDefinition( - name=model.__name__, - fields=fields, - messages=self.messages[lang] + name=model.__name__, fields=fields, messages=self.messages[lang] ) def _get_field_type(self, type_: Type) -> str: @@ -120,13 +123,14 @@ class UnifiedSchemaService: # Implementation similar to SchemaConverter pass + router = APIRouter(prefix="/api/schema", tags=["Schema"]) schema_service = UnifiedSchemaService() + @router.get("/model/{model_name}") async def get_model_schema( - model_name: str, - accept_language: Optional[str] = Header(default="tr") + model_name: str, accept_language: Optional[str] = Header(default="tr") ) -> SchemaDefinition: """Get model schema with validation messages""" # You'd need to implement model lookup @@ -135,12 +139,11 @@ async def get_model_schema( "Product": ProductModel, # Add your models here } - + if model_name not in models: raise ValueError(f"Model {model_name} not found") - + lang = accept_language.split(",")[0][:2] return schema_service.get_schema_with_messages( - models[model_name], - lang if lang in ["tr", "en"] else "tr" + models[model_name], lang if lang in ["tr", "en"] else "tr" ) diff --git a/Services/PostgresDb/Models_old/alchemy_response.py b/trash/Models_old/alchemy_response.py similarity index 100% rename from Services/PostgresDb/Models_old/alchemy_response.py rename to trash/Models_old/alchemy_response.py diff --git a/Services/PostgresDb/Models_old/base_model.py b/trash/Models_old/base_model.py similarity index 100% rename from Services/PostgresDb/Models_old/base_model.py rename to trash/Models_old/base_model.py diff --git a/Services/PostgresDb/Models_old/filter_functions.py b/trash/Models_old/filter_functions.py similarity index 100% rename from Services/PostgresDb/Models_old/filter_functions.py rename to trash/Models_old/filter_functions.py diff --git a/Services/PostgresDb/Models_old/mixins.py b/trash/Models_old/mixins.py similarity index 100% rename from Services/PostgresDb/Models_old/mixins.py rename to trash/Models_old/mixins.py diff --git a/Services/PostgresDb/Models_old/query.py b/trash/Models_old/query.py similarity index 100% rename from Services/PostgresDb/Models_old/query.py rename to trash/Models_old/query.py diff --git a/Services/PostgresDb/Models_old/response.py b/trash/Models_old/response.py similarity index 100% rename from Services/PostgresDb/Models_old/response.py rename to trash/Models_old/response.py diff --git a/trash/abstract_class.py b/trash/abstract_class.py new file mode 100644 index 0000000..8c748fa --- /dev/null +++ b/trash/abstract_class.py @@ -0,0 +1,33 @@ +class ClusterToMethod: + + TAGS: list = ["Tag or Router"] + PREFIX: str = "/..." + PAGEINFO: PageInfo + ENDPOINTS: list # [MethodEvent, ...] + SUBCATEGORY: List[ClassVar[Any]] = [ClusterToMethod, ...] + + def retrieve_all_function_codes(): + """ + [FUNCTION_CODE, ...] + self.ENDPOINTS -> iter() + """ + pass + + def retrieve_page_info(): + """ + PAGE_INFO:ClusterToMethod = { + "PageInfo": {...} + "subCategory": PAGE_INFO:ClusterToMethod + } + PAGE_INFO:ClusterToMethod = { + "PageInfo": {...} + "subCategory": PAGE_INFO:ClusterToMethod + } + """ + pass + + def retrieve_redis_value() -> Dict: + """ + Key(CLUSTER_FUNCTION_CODES:ClusterToMethod) : Value(PAGE_INFO, [FUNCTION_CODE, ...]) + """ + pass diff --git a/Events/abstract_class_old.py b/trash/abstract_class_old.py similarity index 89% rename from Events/abstract_class_old.py rename to trash/abstract_class_old.py index 7b5a668..1e8a21d 100644 --- a/Events/abstract_class_old.py +++ b/trash/abstract_class_old.py @@ -4,6 +4,7 @@ Abstract base classes for API route and event handling. This module provides core abstractions for route configuration and factory, with support for authentication and event handling. """ + import uuid import inspect @@ -229,7 +230,7 @@ class MethodToEvent: path: str, method: str = "POST", response_model: Optional[Type] = None, - **kwargs + **kwargs, ) -> None: """Register an API endpoint configuration for an event. @@ -247,7 +248,7 @@ class MethodToEvent: "path": path, "method": method, "response_model": response_model, - **kwargs + **kwargs, } @classmethod @@ -269,7 +270,7 @@ class MethodToEvent: icon: str, url: str, component: Optional[str] = None, - parent: Optional[str] = None + parent: Optional[str] = None, ) -> None: """Configure the frontend page information. @@ -283,16 +284,20 @@ class MethodToEvent: """ required_langs = {"tr", "en"} if not all(lang in title for lang in required_langs): - raise ValueError(f"Title must contain all required languages: {required_langs}") + raise ValueError( + f"Title must contain all required languages: {required_langs}" + ) - cls.__page_info__.update({ - "name": name, - "title": title, - "icon": icon, - "url": url, - "component": component, - "parent": parent - }) + cls.__page_info__.update( + { + "name": name, + "title": title, + "icon": icon, + "url": url, + "component": component, + "parent": parent, + } + ) @classmethod def get_endpoint_config(cls) -> Dict[str, Any]: @@ -311,9 +316,7 @@ class MethodToEvent: @classmethod def get_page_info_with_permissions( - cls, - user_permission_uuids: Set[str], - include_endpoints: bool = False + cls, user_permission_uuids: Set[str], include_endpoints: bool = False ) -> Optional[Dict[str, Any]]: """Get page info if user has required permissions. @@ -333,7 +336,7 @@ class MethodToEvent: **cls.__page_info__, "category": cls.event_category, "type": cls.event_type, - "description": cls.event_description + "description": cls.event_description, } # Optionally include available endpoints @@ -344,7 +347,7 @@ class MethodToEvent: available_endpoints[uuid] = { "path": f"{cls.__endpoint_config__['router_prefix']}{endpoint['path']}", "method": endpoint["method"], - "event_name": cls.__event_keys__[uuid] + "event_name": cls.__event_keys__[uuid], } if available_endpoints: page_info["available_endpoints"] = available_endpoints @@ -360,7 +363,7 @@ class MethodToEvent: "page_info": cls.__page_info__, "category": cls.event_category, "type": cls.event_type, - "description": cls.event_description + "description": cls.event_description, } @classmethod @@ -482,17 +485,25 @@ class EventMethodRegistry: """Registry for mapping event method UUIDs to categories and managing permissions.""" def __init__(self): - self._uuid_map: Dict[str, Tuple[Type[MethodToEvent], str]] = {} # uuid -> (method_class, event_name) - self._category_events: Dict[str, Set[str]] = defaultdict(set) # category -> set of uuids + self._uuid_map: Dict[str, Tuple[Type[MethodToEvent], str]] = ( + {} + ) # uuid -> (method_class, event_name) + self._category_events: Dict[str, Set[str]] = defaultdict( + set + ) # category -> set of uuids - def register_method(self, category_name: str, method_class: Type[MethodToEvent]) -> None: + def register_method( + self, category_name: str, method_class: Type[MethodToEvent] + ) -> None: """Register a method class with its category.""" # Register all UUIDs from the method for event_uuid, event_name in method_class.__event_keys__.items(): self._uuid_map[event_uuid] = (method_class, event_name) self._category_events[category_name].add(event_uuid) - def get_method_by_uuid(self, event_uuid: str) -> Optional[Tuple[Type[MethodToEvent], str]]: + def get_method_by_uuid( + self, event_uuid: str + ) -> Optional[Tuple[Type[MethodToEvent], str]]: """Get method class and event name by UUID.""" return self._uuid_map.get(event_uuid) @@ -528,10 +539,14 @@ class EventCategory: """Validate title has required languages.""" required_langs = {"tr", "en"} if not all(lang in title for lang in required_langs): - raise ValueError(f"Title must contain all required languages: {required_langs}") + raise ValueError( + f"Title must contain all required languages: {required_langs}" + ) return title - def _process_subcategories(self, categories: List[Union[Dict, "EventCategory"]]) -> List["EventCategory"]: + def _process_subcategories( + self, categories: List[Union[Dict, "EventCategory"]] + ) -> List["EventCategory"]: """Process subcategories ensuring they are all EventCategory instances.""" processed = [] for category in categories: @@ -551,7 +566,9 @@ class EventCategory: for events in self.all_endpoints.values() ) - def get_menu_item(self, user_permission_uuids: Set[str]) -> Optional[Dict[str, Any]]: + def get_menu_item( + self, user_permission_uuids: Set[str] + ) -> Optional[Dict[str, Any]]: """Get menu item if category has available events.""" # First check if this category has available events if not self.has_available_events(user_permission_uuids): @@ -561,7 +578,7 @@ class EventCategory: "name": self.name, "title": self.title, "icon": self.icon, - "url": self.url + "url": self.url, } if self.component: @@ -578,7 +595,9 @@ class EventCategory: return menu_item - def get_available_events(self, registry: EventMethodRegistry, user_permission_uuids: Set[str]) -> Dict[str, List[Dict[str, Any]]]: + def get_available_events( + self, registry: EventMethodRegistry, user_permission_uuids: Set[str] + ) -> Dict[str, List[Dict[str, Any]]]: """Get available events based on user permission UUIDs.""" available_events = defaultdict(list) @@ -588,16 +607,20 @@ class EventCategory: method_info = registry.get_method_by_uuid(event_uuid) if method_info: method_class, event_name = method_info - available_events[method_class.event_type].append({ - "uuid": event_uuid, - "name": event_name, - "description": method_class.event_description, - "category": method_class.event_category - }) + available_events[method_class.event_type].append( + { + "uuid": event_uuid, + "name": event_name, + "description": method_class.event_description, + "category": method_class.event_category, + } + ) # Process subcategories recursively for subcategory in self.sub_categories: - sub_events = subcategory.get_available_events(registry, user_permission_uuids) + sub_events = subcategory.get_available_events( + registry, user_permission_uuids + ) for event_type, events in sub_events.items(): available_events[event_type].extend(events) @@ -614,10 +637,14 @@ class EventCategory: component=data.get("component"), page_info=data.get("pageInfo"), all_endpoints=data.get("allEndpoints", {}), - sub_categories=data.get("subCategories", []) + sub_categories=data.get("subCategories", []), ) - def to_dict(self, registry: EventMethodRegistry, user_permission_uuids: Optional[Set[str]] = None) -> Dict[str, Any]: + def to_dict( + self, + registry: EventMethodRegistry, + user_permission_uuids: Optional[Set[str]] = None, + ) -> Dict[str, Any]: """Convert category to dictionary with optional permission filtering.""" result = { "name": self.name, @@ -629,7 +656,9 @@ class EventCategory: if user_permission_uuids is not None: # Only include endpoints and their info if user has permissions - available_events = self.get_available_events(registry, user_permission_uuids) + available_events = self.get_available_events( + registry, user_permission_uuids + ) if available_events: result["availableEvents"] = available_events result["allEndpoints"] = self.all_endpoints @@ -663,7 +692,8 @@ class EventCategoryManager: def get_menu_tree(self, user_permission_uuids: Set[str]) -> List[Dict[str, Any]]: """Get menu tree based on available events.""" return [ - menu_item for category in self.categories + menu_item + for category in self.categories if (menu_item := category.get_menu_item(user_permission_uuids)) ] @@ -677,7 +707,9 @@ class EventCategoryManager: category = EventCategory.from_dict(category) self.register_category(category) - def add_categories(self, categories: List[Union[EventCategory, Dict[str, Any]]]) -> None: + def add_categories( + self, categories: List[Union[EventCategory, Dict[str, Any]]] + ) -> None: """Add multiple categories at once.""" for category in categories: self.add_category(category) @@ -686,21 +718,30 @@ class EventCategoryManager: """Get category by name.""" return next((cat for cat in self.categories if cat.name == name), None) - def get_all_categories(self, user_permission_uuids: Optional[Set[str]] = None) -> List[Dict[str, Any]]: + def get_all_categories( + self, user_permission_uuids: Optional[Set[str]] = None + ) -> List[Dict[str, Any]]: """Get all categories as dictionary, filtered by user permissions.""" - return [cat.to_dict(self.registry, user_permission_uuids) for cat in self.categories] + return [ + cat.to_dict(self.registry, user_permission_uuids) for cat in self.categories + ] def get_category_endpoints(self, category_name: str) -> Set[str]: """Get all endpoint UUIDs for a category.""" category = self.get_category(category_name) return category.all_endpoints.get(category_name, set()) if category else set() - def get_subcategories(self, category_name: str, user_permission_uuids: Optional[Set[str]] = None) -> List[Dict[str, Any]]: + def get_subcategories( + self, category_name: str, user_permission_uuids: Optional[Set[str]] = None + ) -> List[Dict[str, Any]]: """Get subcategories for a category.""" category = self.get_category(category_name) if not category: return [] - return [sub.to_dict(self.registry, user_permission_uuids) for sub in category.sub_categories] + return [ + sub.to_dict(self.registry, user_permission_uuids) + for sub in category.sub_categories + ] def find_category_by_url(self, url: str) -> Optional[EventCategory]: """Find a category by its URL.""" @@ -728,16 +769,16 @@ class EventMethodRegistry: def register_method_class(cls, method_class: Type[MethodToEvent]) -> None: """Register a MethodToEvent class.""" if not issubclass(method_class, MethodToEvent): - raise ValueError(f"{method_class.__name__} must be a subclass of MethodToEvent") - + raise ValueError( + f"{method_class.__name__} must be a subclass of MethodToEvent" + ) + page_info = method_class.get_page_info() cls._method_classes[page_info["name"]] = method_class @classmethod def get_all_menu_items( - cls, - user_permission_uuids: Set[str], - include_endpoints: bool = False + cls, user_permission_uuids: Set[str], include_endpoints: bool = False ) -> List[Dict[str, Any]]: """Get all menu items based on user permissions. @@ -751,7 +792,9 @@ class EventMethodRegistry: # First get all page infos page_infos = {} for method_class in cls._method_classes.values(): - if page_info := method_class.get_page_info_with_permissions(user_permission_uuids, include_endpoints): + if page_info := method_class.get_page_info_with_permissions( + user_permission_uuids, include_endpoints + ): page_infos[page_info["name"]] = page_info # Build tree structure @@ -771,18 +814,15 @@ class EventMethodRegistry: # Start with this page's info menu_item = page_info.copy() - + # Find and add children children = [] for child_info in page_infos.values(): if child_info.get("parent") == name: children.append(child_info) - + if children: - menu_item["items"] = sorted( - children, - key=lambda x: x["name"] - ) + menu_item["items"] = sorted(children, key=lambda x: x["name"]) menu_tree.append(menu_item) @@ -790,8 +830,7 @@ class EventMethodRegistry: @classmethod def get_available_endpoints( - cls, - user_permission_uuids: Set[str] + cls, user_permission_uuids: Set[str] ) -> Dict[str, Dict[str, Any]]: """Get all available endpoints based on user permissions. @@ -802,15 +841,14 @@ class EventMethodRegistry: Dict mapping event UUIDs to endpoint configurations """ available_endpoints = {} - + for method_class in cls._method_classes.values(): if page_info := method_class.get_page_info_with_permissions( - user_permission_uuids, - include_endpoints=True + user_permission_uuids, include_endpoints=True ): if endpoints := page_info.get("available_endpoints"): available_endpoints.update(endpoints) - + return available_endpoints @@ -916,4 +954,4 @@ user_permission_uuids = { "ec98ef2c-bcd0-432d-a8f4-1822a56c33b2" } menu_tree = manager.get_menu_tree(user_permission_uuids) -""" \ No newline at end of file +""" diff --git a/trash/auth_old.py b/trash/auth_old.py index 10f014a..c67f6ba 100644 --- a/trash/auth_old.py +++ b/trash/auth_old.py @@ -25,17 +25,20 @@ from ApiLayers.Schemas import ( BuildParts, RelationshipEmployee2Build, Companies, - Departments, - Duties, + Departments, + Duties, Duty, Staff, Employees, Event2Employee, Event2Occupant, OccupantTypes, - Users + Users, +) +from ApiLayers.ApiServices.Token.token_handler import ( + OccupantTokenObject, + EmployeeTokenObject, ) -from ApiLayers.ApiServices.Token.token_handler import OccupantTokenObject, EmployeeTokenObject from .api_events import ( authentication_login_super_user_event,