updated last web service

This commit is contained in:
2025-06-02 21:11:15 +03:00
parent df3f59bd8e
commit 0cd0eb0f22
106 changed files with 1061 additions and 50 deletions

View 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"]

View 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"),
]

View 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

View File

@@ -0,0 +1,3 @@
__all__ = []

View File

@@ -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(

View File

@@ -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"
)

View 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

View 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.")

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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 = {

View File

@@ -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