updated last web service
This commit is contained in:
30
ServicesApi/Builds/TestApi/Dockerfile
Normal file
30
ServicesApi/Builds/TestApi/Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# 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 /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 /ServicesApi/Initializer /Initializer
|
||||
COPY /ServicesApi/Controllers /Controllers
|
||||
COPY /ServicesApi/Validations /Validations
|
||||
COPY /ServicesApi/Schemas /Schemas
|
||||
COPY /ServicesApi/Extensions /Extensions
|
||||
|
||||
COPY /ServicesApi/Builds/TestApi/endpoints /endpoints
|
||||
COPY /ServicesApi/Builds/TestApi/events /events
|
||||
# COPY /api_services/api_builds/test_api/validations /api_initializer/validations
|
||||
# COPY /api_services/api_builds/test_api/index.py /api_initializer/index.py
|
||||
|
||||
# Set Python path to include app directory
|
||||
ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# Run the application using the configured uvicorn server
|
||||
CMD ["poetry", "run", "python", "/Initializer/app.py"]
|
||||
18
ServicesApi/Builds/TestApi/endpoints/routes.py
Normal file
18
ServicesApi/Builds/TestApi/endpoints/routes.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from fastapi import APIRouter
|
||||
from .tester.router import tester_endpoint_route
|
||||
|
||||
|
||||
def get_routes() -> list[APIRouter]:
|
||||
return [tester_endpoint_route]
|
||||
|
||||
|
||||
def get_safe_endpoint_urls() -> list[tuple[str, str]]:
|
||||
return [
|
||||
("/", "GET"),
|
||||
("/docs", "GET"),
|
||||
("/redoc", "GET"),
|
||||
("/openapi.json", "GET"),
|
||||
("/metrics", "GET"),
|
||||
("/tester/list", "POST"),
|
||||
]
|
||||
|
||||
37
ServicesApi/Builds/TestApi/endpoints/tester/router.py
Normal file
37
ServicesApi/Builds/TestApi/endpoints/tester/router.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import datetime
|
||||
|
||||
from typing import Any
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from Validations.response import PaginateOnly, Pagination, PaginationResult, EndpointResponse
|
||||
from Validations.defaults.validations import CommonHeaders
|
||||
from Schemas import AccountRecords
|
||||
|
||||
|
||||
tester_endpoint_route = APIRouter(prefix="/tester", tags=["Tester Cluster"])
|
||||
|
||||
|
||||
class TestList(BaseModel):
|
||||
uu_id: str
|
||||
bank_date: datetime.datetime
|
||||
currency_value: float
|
||||
process_name: str
|
||||
|
||||
|
||||
tester_list = "TestList"
|
||||
@tester_endpoint_route.post(
|
||||
path="/list",
|
||||
description="List all tester endpoint",
|
||||
operation_id="4c38fab8-9b66-41cd-b87a-41175c9eea48",
|
||||
)
|
||||
def tester_list_route(
|
||||
list_options: PaginateOnly,
|
||||
headers: CommonHeaders = Depends(CommonHeaders.as_dependency),
|
||||
):
|
||||
with AccountRecords.new_session() as db_session:
|
||||
AccountRecords.set_session(db_session)
|
||||
tester_list = AccountRecords.query.filter(AccountRecords.currency_value > 0)
|
||||
pagination = Pagination(data=tester_list, base_query=AccountRecords.query.filter())
|
||||
pagination.change(**list_options.model_dump())
|
||||
pagination_result = PaginationResult(data=tester_list, pagination=pagination, response_model=TestList)
|
||||
return EndpointResponse(message="MSG0003-LIST", pagination_result=pagination_result).response
|
||||
3
ServicesApi/Builds/TestApi/events/__init__.py
Normal file
3
ServicesApi/Builds/TestApi/events/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
__all__ = []
|
||||
@@ -1,11 +1,12 @@
|
||||
from contextlib import contextmanager
|
||||
from functools import lru_cache
|
||||
from typing import Generator
|
||||
from api_controllers.postgres.config import postgres_configs
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker, scoped_session, Session
|
||||
|
||||
from Controllers.Postgres.config import postgres_configs
|
||||
|
||||
|
||||
# Configure the database engine with proper pooling
|
||||
engine = create_engine(
|
||||
|
||||
@@ -13,7 +13,7 @@ from sqlalchemy_mixins.repr import ReprMixin
|
||||
from sqlalchemy_mixins.smartquery import SmartQueryMixin
|
||||
from sqlalchemy_mixins.activerecord import ActiveRecordMixin
|
||||
|
||||
from api_controllers.postgres.engine import get_db, Base
|
||||
from Controllers.Postgres.engine import get_db, Base
|
||||
|
||||
|
||||
T = TypeVar("CrudMixin", bound="CrudMixin")
|
||||
@@ -72,7 +72,7 @@ class BasicMixin(Base, ActiveRecordMixin, SerializeMixin, ReprMixin, SmartQueryM
|
||||
return True, str(arrow.get(str(val)).format("YYYY-MM-DD HH:mm:ss"))
|
||||
elif isinstance(val, bool):
|
||||
return True, bool(val)
|
||||
elif isinstance(val, (float, Decimal)):
|
||||
elif isinstance(val, float) or isinstance(val, Decimal):
|
||||
return True, round(float(val), 3)
|
||||
elif isinstance(val, int):
|
||||
return True, int(val)
|
||||
@@ -81,7 +81,6 @@ class BasicMixin(Base, ActiveRecordMixin, SerializeMixin, ReprMixin, SmartQueryM
|
||||
elif val is None:
|
||||
return True, None
|
||||
return False, None
|
||||
|
||||
except Exception as e:
|
||||
err = e
|
||||
return False, None
|
||||
@@ -160,7 +159,6 @@ class BasicMixin(Base, ActiveRecordMixin, SerializeMixin, ReprMixin, SmartQueryM
|
||||
return {}
|
||||
|
||||
|
||||
|
||||
class CrudMixin(BasicMixin):
|
||||
"""
|
||||
Base mixin providing CRUD operations and common fields for PostgreSQL models.
|
||||
@@ -272,4 +270,3 @@ class CrudCollection(CrudMixin):
|
||||
is_email_send: Mapped[bool] = mapped_column(
|
||||
Boolean, server_default="0", comment="Email sent flag"
|
||||
)
|
||||
|
||||
|
||||
0
ServicesApi/Extensions/Middlewares/__init__.py
Normal file
0
ServicesApi/Extensions/Middlewares/__init__.py
Normal file
19
ServicesApi/Extensions/Middlewares/token_middleware.py
Normal file
19
ServicesApi/Extensions/Middlewares/token_middleware.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from fastapi import Request, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from config import api_config
|
||||
|
||||
from endpoints.routes import get_safe_endpoint_urls
|
||||
|
||||
|
||||
async def token_middleware(request: Request, call_next):
|
||||
base_url = request.url.path
|
||||
safe_endpoints = [_[0] for _ in get_safe_endpoint_urls()]
|
||||
if base_url in safe_endpoints:
|
||||
return await call_next(request)
|
||||
|
||||
token = request.headers.get(api_config.ACCESS_TOKEN_TAG, None)
|
||||
if not token:
|
||||
return JSONResponse(content={"error": "EYS_0002"}, status_code=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
response = await call_next(request)
|
||||
return response
|
||||
96
ServicesApi/Extensions/Middlewares/token_provider.py
Normal file
96
ServicesApi/Extensions/Middlewares/token_provider.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import enum
|
||||
|
||||
from typing import Optional, Union, Dict, Any, List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from api_controllers.redis.database import RedisActions
|
||||
from api_validations.token.validations import (
|
||||
TokenDictType,
|
||||
OccupantTokenObject,
|
||||
EmployeeTokenObject,
|
||||
UserType,
|
||||
)
|
||||
|
||||
class TokenProvider:
|
||||
|
||||
AUTH_TOKEN: str = "AUTH_TOKEN"
|
||||
|
||||
@classmethod
|
||||
def convert_redis_object_to_token(cls, redis_object: Dict[str, Any]) -> TokenDictType:
|
||||
"""
|
||||
Process Redis object and return appropriate token object.
|
||||
"""
|
||||
if redis_object.get("user_type") == UserType.employee.value:
|
||||
return EmployeeTokenObject(**redis_object)
|
||||
elif redis_object.get("user_type") == UserType.occupant.value:
|
||||
return OccupantTokenObject(**redis_object)
|
||||
raise ValueError("Invalid user type")
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_login_token_from_redis(
|
||||
cls, token: Optional[str] = None, user_uu_id: Optional[str] = None
|
||||
) -> Union[TokenDictType, List[TokenDictType]]:
|
||||
"""
|
||||
Retrieve token object from Redis using token and user_uu_id
|
||||
"""
|
||||
token_to_use, user_uu_id_to_use = token or "*", user_uu_id or "*"
|
||||
list_of_token_dict, auth_key_list = [], [cls.AUTH_TOKEN, token_to_use, user_uu_id_to_use]
|
||||
if token:
|
||||
result = RedisActions.get_json(list_keys=auth_key_list, limit=1)
|
||||
if first_record := result.first:
|
||||
return cls.convert_redis_object_to_token(first_record)
|
||||
elif user_uu_id:
|
||||
result = RedisActions.get_json(list_keys=auth_key_list)
|
||||
if all_records := result.all:
|
||||
for all_record in all_records:
|
||||
list_of_token_dict.append(cls.convert_redis_object_to_token(all_record))
|
||||
return list_of_token_dict
|
||||
raise ValueError("Token not found in Redis. Please check the token or user_uu_id.")
|
||||
|
||||
@classmethod
|
||||
def get_dict_from_redis(
|
||||
cls, token: Optional[str] = None, user_uu_id: Optional[str] = None
|
||||
) -> Union[TokenDictType, List[TokenDictType]]:
|
||||
"""
|
||||
Retrieve token object from Redis using token and user_uu_id
|
||||
"""
|
||||
token_to_use, user_uu_id_to_use = token or "*", user_uu_id or "*"
|
||||
list_of_token_dict, auth_key_list = [], [cls.AUTH_TOKEN, token_to_use, user_uu_id_to_use, "*"]
|
||||
if token:
|
||||
result = RedisActions.get_json(list_keys=auth_key_list, limit=1)
|
||||
if first_record := result.first:
|
||||
return cls.convert_redis_object_to_token(first_record)
|
||||
elif user_uu_id:
|
||||
result = RedisActions.get_json(list_keys=auth_key_list)
|
||||
if all_records := result.all:
|
||||
for all_record in all_records:
|
||||
list_of_token_dict.append(cls.convert_redis_object_to_token(all_record))
|
||||
return list_of_token_dict
|
||||
raise ValueError("Token not found in Redis. Please check the token or user_uu_id.")
|
||||
|
||||
@classmethod
|
||||
def retrieve_application_codes(cls, page_url: str, token: TokenDictType):
|
||||
"""
|
||||
Retrieve application code from the token object or list of token objects.
|
||||
"""
|
||||
if isinstance(token, EmployeeTokenObject):
|
||||
if application_codes := token.selected_company.reachable_app_codes.get(page_url, None):
|
||||
return application_codes
|
||||
elif isinstance(token, OccupantTokenObject):
|
||||
if application_codes := token.selected_occupant.reachable_app_codes.get(page_url, None):
|
||||
return application_codes
|
||||
raise ValueError("Invalid token type or no application code found.")
|
||||
|
||||
@classmethod
|
||||
def retrieve_event_codes(cls, endpoint_code: str, token: TokenDictType) -> str:
|
||||
"""
|
||||
Retrieve event code from the token object or list of token objects.
|
||||
"""
|
||||
if isinstance(token, EmployeeTokenObject):
|
||||
if event_codes := token.selected_company.reachable_event_codes.get(endpoint_code, None):
|
||||
return event_codes
|
||||
elif isinstance(token, OccupantTokenObject):
|
||||
if event_codes := token.selected_occupant.reachable_event_codes.get(endpoint_code, None):
|
||||
return event_codes
|
||||
raise ValueError("Invalid token type or no event code found.")
|
||||
@@ -1,7 +1,7 @@
|
||||
import uvicorn
|
||||
|
||||
from api_initializer.config import api_config
|
||||
from api_initializer.create_app import create_app
|
||||
from config import api_config
|
||||
from create_app import create_app
|
||||
|
||||
# from prometheus_fastapi_instrumentator import Instrumentator
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import events
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import RedirectResponse
|
||||
@@ -6,9 +8,8 @@ from config import api_config
|
||||
from open_api_creator import create_openapi_schema
|
||||
from create_route import RouteRegisterController
|
||||
|
||||
from api_middlewares.token_middleware import token_middleware
|
||||
from Extensions.Middlewares.token_middleware import token_middleware
|
||||
from endpoints.routes import get_routes
|
||||
import events
|
||||
|
||||
|
||||
cluster_is_set = False
|
||||
|
||||
@@ -10,7 +10,7 @@ class RouteRegisterController:
|
||||
|
||||
@staticmethod
|
||||
def add_router_with_event_to_database(router: APIRouter):
|
||||
from schemas import EndpointRestriction
|
||||
from Schemas import EndpointRestriction
|
||||
|
||||
with EndpointRestriction.new_session() as db_session:
|
||||
EndpointRestriction.set_session(db_session)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from ApiControllers.postgres.mixin import CrudCollection
|
||||
from Controllers.Postgres.mixin import CrudCollection
|
||||
from sqlalchemy.orm import mapped_column, Mapped, relationship
|
||||
from sqlalchemy import (
|
||||
String,
|
||||
|
||||
@@ -31,8 +31,8 @@ class EndpointResponse(BaseModel):
|
||||
return {
|
||||
"completed": self.completed,
|
||||
"message": self.message,
|
||||
"data": result_data,
|
||||
"pagination": pagination_dict,
|
||||
"data": result_data,
|
||||
}
|
||||
|
||||
model_config = {
|
||||
|
||||
@@ -26,7 +26,8 @@ class Pagination:
|
||||
MIN_SIZE = default_paginate_config.MIN_SIZE
|
||||
MAX_SIZE = default_paginate_config.MAX_SIZE
|
||||
|
||||
def __init__(self, data: PostgresResponse):
|
||||
def __init__(self, data: Query, base_query: Query):
|
||||
self.base_query = base_query
|
||||
self.query = data
|
||||
self.size: int = self.DEFAULT_SIZE
|
||||
self.page: int = 1
|
||||
@@ -60,7 +61,7 @@ class Pagination:
|
||||
"""Update page counts and validate current page."""
|
||||
if self.query:
|
||||
self.total_count = self.query.count()
|
||||
self.all_count = self.query.count()
|
||||
self.all_count = self.base_query.count()
|
||||
|
||||
self.size = (
|
||||
self.size
|
||||
@@ -151,24 +152,14 @@ class PaginationResult:
|
||||
Ordered query object.
|
||||
"""
|
||||
if not len(self.order_by) == len(self.pagination.orderType):
|
||||
raise ValueError(
|
||||
"Order by fields and order types must have the same length."
|
||||
)
|
||||
raise ValueError("Order by fields and order types must have the same length.")
|
||||
order_criteria = zip(self.order_by, self.pagination.orderType)
|
||||
for field, direction in order_criteria:
|
||||
if hasattr(self._query.column_descriptions[0]["entity"], field):
|
||||
if direction.lower().startswith("d"):
|
||||
self._query = self._query.order_by(
|
||||
desc(
|
||||
getattr(self._query.column_descriptions[0]["entity"], field)
|
||||
)
|
||||
)
|
||||
self._query = self._query.order_by(desc(getattr(self._query.column_descriptions[0]["entity"], field)))
|
||||
else:
|
||||
self._query = self._query.order_by(
|
||||
asc(
|
||||
getattr(self._query.column_descriptions[0]["entity"], field)
|
||||
)
|
||||
)
|
||||
self._query = self._query.order_by(asc(getattr(self._query.column_descriptions[0]["entity"], field)))
|
||||
return self._query
|
||||
|
||||
@property
|
||||
|
||||
Reference in New Issue
Block a user