error raise now locates loc
This commit is contained in:
parent
56b693989d
commit
fbd0e336e0
|
|
@ -6,7 +6,11 @@ from fastapi import Request, HTTPException, status
|
|||
from fastapi.responses import JSONResponse
|
||||
|
||||
from api_objects.errors.errorMessages import EXCEPTION_DICTS, ERRORS_DICT, ERRORS_LANG
|
||||
from api_objects.errors.errorHandlers import HTTPExceptionEvyos, HTTPExceptionAnyHandler, HTTPExceptionEvyosHandler
|
||||
from api_objects.errors.errorHandlers import (
|
||||
HTTPExceptionEvyos,
|
||||
HTTPExceptionAnyHandler,
|
||||
HTTPExceptionEvyosHandler,
|
||||
)
|
||||
|
||||
from middlewares.token_middleware import AuthHeaderMiddleware
|
||||
from application.create_file import create_app
|
||||
|
|
@ -27,14 +31,16 @@ app.add_middleware(
|
|||
app.add_middleware(AuthHeaderMiddleware)
|
||||
|
||||
# Initialize Exception and ExceptionInstance handlers
|
||||
CustomExceptionHandler = HTTPExceptionEvyosHandler(**dict(
|
||||
CustomExceptionHandler = HTTPExceptionEvyosHandler(
|
||||
**dict(
|
||||
statuses=status,
|
||||
exceptions=HTTPException,
|
||||
response_model=JSONResponse,
|
||||
exceptions_dict=EXCEPTION_DICTS,
|
||||
errors_dict=ERRORS_DICT,
|
||||
error_language_dict=ERRORS_LANG
|
||||
))
|
||||
error_language_dict=ERRORS_LANG,
|
||||
)
|
||||
)
|
||||
CustomExceptionAnyHandler = HTTPExceptionAnyHandler(response_model=JSONResponse)
|
||||
|
||||
# Register error handlers with bound methods
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import json
|
||||
from typing import Union
|
||||
from fastapi import status
|
||||
from fastapi.requests import Request
|
||||
from fastapi.exceptions import HTTPException
|
||||
|
||||
from api_objects import OccupantTokenObject, EmployeeTokenObject
|
||||
from api_objects.auth.token_objects import CompanyToken, OccupantToken
|
||||
from api_services.templates.password_templates import (
|
||||
password_is_changed_template,
|
||||
change_your_password_template,
|
||||
|
|
@ -36,6 +33,13 @@ from api_validations.validations_response import (
|
|||
from ApiServices.api_handlers.auth_actions.auth import AuthActions
|
||||
from api_configs import Auth, ApiStatic
|
||||
from api_events.events.abstract_class import MethodToEvent, ActionsSchema
|
||||
from api_objects import (
|
||||
OccupantTokenObject,
|
||||
EmployeeTokenObject,
|
||||
CompanyToken,
|
||||
OccupantToken,
|
||||
HTTPExceptionEvyos,
|
||||
)
|
||||
|
||||
from databases import (
|
||||
Companies,
|
||||
|
|
@ -55,9 +59,7 @@ from databases import (
|
|||
RelationshipEmployee2Build,
|
||||
)
|
||||
|
||||
from api_services import (
|
||||
send_email,
|
||||
)
|
||||
from api_services import send_email
|
||||
|
||||
|
||||
class AuthenticationLoginEventMethods(MethodToEvent):
|
||||
|
|
@ -74,26 +76,12 @@ class AuthenticationLoginEventMethods(MethodToEvent):
|
|||
|
||||
@classmethod
|
||||
def authentication_login_with_domain_and_creds(cls, data: Login, request: Request):
|
||||
from api_objects import HTTPExceptionEvyos
|
||||
|
||||
raise HTTPExceptionEvyos(
|
||||
error_code="UNKNOWN_ERROR",
|
||||
lang="en",
|
||||
)
|
||||
access_dict = Users.login_user_with_credentials(data=data, request=request)
|
||||
found_user = access_dict.get("user")
|
||||
Users.client_arrow = DateTimeLocal(
|
||||
is_client=True, timezone=found_user.local_timezone
|
||||
)
|
||||
if not found_user:
|
||||
# UserLogger.log_login_attempt(
|
||||
# request,
|
||||
# None,
|
||||
# data.domain,
|
||||
# data.access_key,
|
||||
# success=False,
|
||||
# error="Invalid credentials",
|
||||
# )
|
||||
return ResponseHandler.unauthorized("Invalid credentials")
|
||||
return ResponseHandler.success(
|
||||
message="User logged in successfully",
|
||||
|
|
@ -104,11 +92,6 @@ class AuthenticationLoginEventMethods(MethodToEvent):
|
|||
"user": found_user.get_dict(),
|
||||
},
|
||||
)
|
||||
# except Exception as e:
|
||||
# # UserLogger.log_login_attempt(
|
||||
# # request, None, data.domain, data.access_key, success=False, error=str(e)
|
||||
# # )
|
||||
# raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e))
|
||||
|
||||
|
||||
class AuthenticationSelectEventMethods(MethodToEvent):
|
||||
|
|
@ -128,9 +111,7 @@ class AuthenticationSelectEventMethods(MethodToEvent):
|
|||
cls, data: EmployeeSelection, token_dict: EmployeeTokenObject, request: Request
|
||||
):
|
||||
"""Handle employee company selection"""
|
||||
Users.client_arrow = DateTimeLocal(
|
||||
is_client=True, timezone=token_dict.local_timezone
|
||||
)
|
||||
Users.client_arrow = DateTimeLocal(is_client=True, timezone=token_dict.timezone)
|
||||
if data.company_uu_id not in token_dict.companies_uu_id_list:
|
||||
return ResponseHandler.unauthorized(
|
||||
"Company not found in user's company list"
|
||||
|
|
|
|||
|
|
@ -5,9 +5,15 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||
from fastapi import Request, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from api_objects.errors.errorMessages import EXCEPTION_DICTS, ERRORS_DICT, ERRORS_LANG
|
||||
from api_objects.errors.errorHandlers import (
|
||||
HTTPExceptionEvyos,
|
||||
HTTPExceptionAnyHandler,
|
||||
HTTPExceptionEvyosHandler,
|
||||
)
|
||||
|
||||
from middlewares.token_middleware import AuthHeaderMiddleware
|
||||
from application.create_file import create_app
|
||||
from api_objects.errors.errors_dictionary import ErrorHandlers
|
||||
from prometheus_fastapi_instrumentator import Instrumentator
|
||||
|
||||
app = create_app(routers=routers)
|
||||
|
|
@ -24,17 +30,22 @@ app.add_middleware(
|
|||
)
|
||||
app.add_middleware(AuthHeaderMiddleware)
|
||||
|
||||
# Initialize error handlers
|
||||
error_handlers = ErrorHandlers.create(
|
||||
requests=Request,
|
||||
# Initialize Exception and ExceptionInstance handlers
|
||||
CustomExceptionHandler = HTTPExceptionEvyosHandler(
|
||||
**dict(
|
||||
statuses=status,
|
||||
exceptions=HTTPException,
|
||||
response_model=JSONResponse,
|
||||
status=status,
|
||||
exceptions_dict=EXCEPTION_DICTS,
|
||||
errors_dict=ERRORS_DICT,
|
||||
error_language_dict=ERRORS_LANG,
|
||||
)
|
||||
)
|
||||
CustomExceptionAnyHandler = HTTPExceptionAnyHandler(response_model=JSONResponse)
|
||||
|
||||
# Register error handlers with bound methods
|
||||
app.add_exception_handler(HTTPException, error_handlers.exception_handler_http)
|
||||
app.add_exception_handler(Exception, error_handlers.exception_handler_exception)
|
||||
app.add_exception_handler(HTTPExceptionEvyos, CustomExceptionHandler.handle_exception)
|
||||
app.add_exception_handler(Exception, CustomExceptionAnyHandler.any_exception_handler)
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn_config = {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ from .decision_book.project_decision_book_items.router import (
|
|||
from .decision_book.project_decision_book_person.router import (
|
||||
build_decision_book_project_people_route,
|
||||
)
|
||||
from .validations.router import validations_route
|
||||
|
||||
__all__ = [
|
||||
"account_records_router",
|
||||
|
|
@ -97,5 +96,4 @@ __all__ = [
|
|||
"build_decision_book_project_route",
|
||||
"build_decision_book_project_items_route",
|
||||
"build_decision_book_project_people_route",
|
||||
"validations_route",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,123 +0,0 @@
|
|||
from fastapi import status
|
||||
from fastapi.routing import APIRouter
|
||||
from fastapi.requests import Request
|
||||
from fastapi.exceptions import HTTPException
|
||||
from api_validations.validations_request import (
|
||||
EndpointValidation,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
validations_route = APIRouter(prefix="/validations", tags=["Validations"])
|
||||
validations_route.include_router(validations_route, include_in_schema=True)
|
||||
|
||||
|
||||
class EndpointValidationResponse(BaseModel):
|
||||
language: str
|
||||
headers: dict
|
||||
validation: dict
|
||||
|
||||
|
||||
class ValidationParser:
|
||||
|
||||
def __init__(self, active_validation):
|
||||
self.annotations = (
|
||||
active_validation.__annotations__.items() if active_validation else None
|
||||
)
|
||||
self.schema = {}
|
||||
self.parse()
|
||||
|
||||
def parse(self):
|
||||
for key, value in self.annotations or {}:
|
||||
field_type, required = "string", False
|
||||
if str(value) == "<class 'str'>" or str(value) == "typing.Optional[str]":
|
||||
field_type = "string"
|
||||
required = not str(value) == "typing.Optional[str]"
|
||||
elif str(value) == "<class 'int'>" or str(value) == "typing.Optional[int]":
|
||||
field_type = "integer"
|
||||
required = not str(value) == "typing.Optional[int]"
|
||||
elif (
|
||||
str(value) == "<class 'bool'>" or str(value) == "typing.Optional[bool]"
|
||||
):
|
||||
field_type = "boolean"
|
||||
required = not str(value) == "typing.Optional[bool]"
|
||||
elif (
|
||||
str(value) == "<class 'float'>"
|
||||
or str(value) == "typing.Optional[float]"
|
||||
):
|
||||
field_type = "float"
|
||||
required = not str(value) == "typing.Optional[bool]"
|
||||
elif (
|
||||
str(value) == "<class 'datetime.datetime'>"
|
||||
or str(value) == "typing.Optional[datetime.datetime]"
|
||||
):
|
||||
field_type = "datetime"
|
||||
required = not str(value) == "typing.Optional[datetime.datetime]"
|
||||
self.schema[key] = {"type": field_type, "required": required}
|
||||
|
||||
|
||||
def retrieve_validation_from_class(selected_event, events):
|
||||
event_function_class = getattr(selected_event, "function_class", None)
|
||||
event_function_code = getattr(selected_event, "function_code", None)
|
||||
function_class = getattr(events, event_function_class, None)
|
||||
return function_class.__event_validation__.get(event_function_code, None)
|
||||
|
||||
|
||||
@validations_route.post(path="/endpoint", summary="Retrieve validation of endpoint")
|
||||
def user_list(request: Request, validation: EndpointValidation):
|
||||
import api_events.events as events
|
||||
from api_services.redis.functions import get_object_via_access_key
|
||||
from databases import (
|
||||
EndpointRestriction,
|
||||
Events,
|
||||
)
|
||||
|
||||
valid_token = get_object_via_access_key(request=request)
|
||||
if not valid_token:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"No valid token found in the request.",
|
||||
)
|
||||
endpoint_active = EndpointRestriction.filter_one(
|
||||
EndpointRestriction.endpoint_name.ilike(f"%{str(validation.endpoint)}%"),
|
||||
system=True,
|
||||
).data
|
||||
if not endpoint_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"This endpoint {str(validation.endpoint)} is not active for this user, please contact your responsible company for further information.",
|
||||
)
|
||||
if valid_token.user_type == 1 and not valid_token.selected_company:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Selected company is not found in the token object.",
|
||||
)
|
||||
elif valid_token.user_type == 2 and not valid_token.selected_occupant:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_418_IM_A_TEAPOT,
|
||||
detail="Selected occupant is not found in the token object.",
|
||||
)
|
||||
selected_event = Events.filter_one(
|
||||
Events.endpoint_id == endpoint_active.id,
|
||||
Events.id.in_(valid_token.selected_company.reachable_event_list_id),
|
||||
).data
|
||||
if not selected_event:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="This endpoint requires event validation. Please contact your responsible company to use this event.",
|
||||
)
|
||||
active_validation = retrieve_validation_from_class(selected_event, events)
|
||||
if not active_validation:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="No validation found for this endpoint.",
|
||||
)
|
||||
headers = getattr(
|
||||
active_validation, str(valid_token.lang).lower(), active_validation.tr
|
||||
)
|
||||
validation_parse = ValidationParser(active_validation=active_validation)
|
||||
return EndpointValidationResponse(
|
||||
language=valid_token.lang,
|
||||
headers=headers,
|
||||
validation=validation_parse.schema,
|
||||
)
|
||||
|
|
@ -5,12 +5,17 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||
from fastapi import Request, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from api_objects.errors.errorMessages import EXCEPTION_DICTS, ERRORS_DICT, ERRORS_LANG
|
||||
from api_objects.errors.errorHandlers import (
|
||||
HTTPExceptionEvyos,
|
||||
HTTPExceptionAnyHandler,
|
||||
HTTPExceptionEvyosHandler,
|
||||
)
|
||||
|
||||
from middlewares.token_middleware import AuthHeaderMiddleware
|
||||
from application.create_file import create_app
|
||||
from api_objects.errors.errors_dictionary import ErrorHandlers
|
||||
from prometheus_fastapi_instrumentator import Instrumentator
|
||||
|
||||
|
||||
app = create_app(routers=routers)
|
||||
Instrumentator().instrument(app=app).expose(app=app)
|
||||
|
||||
|
|
@ -25,17 +30,22 @@ app.add_middleware(
|
|||
)
|
||||
app.add_middleware(AuthHeaderMiddleware)
|
||||
|
||||
# Initialize error handlers
|
||||
error_handlers = ErrorHandlers.create(
|
||||
requests=Request,
|
||||
# Initialize Exception and ExceptionInstance handlers
|
||||
CustomExceptionHandler = HTTPExceptionEvyosHandler(
|
||||
**dict(
|
||||
statuses=status,
|
||||
exceptions=HTTPException,
|
||||
response_model=JSONResponse,
|
||||
status=status,
|
||||
exceptions_dict=EXCEPTION_DICTS,
|
||||
errors_dict=ERRORS_DICT,
|
||||
error_language_dict=ERRORS_LANG,
|
||||
)
|
||||
)
|
||||
CustomExceptionAnyHandler = HTTPExceptionAnyHandler(response_model=JSONResponse)
|
||||
|
||||
# Register error handlers with bound methods
|
||||
app.add_exception_handler(HTTPException, error_handlers.exception_handler_http)
|
||||
app.add_exception_handler(Exception, error_handlers.exception_handler_exception)
|
||||
app.add_exception_handler(HTTPExceptionEvyos, CustomExceptionHandler.handle_exception)
|
||||
app.add_exception_handler(Exception, CustomExceptionAnyHandler.any_exception_handler)
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn_config = {
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
__all__ = []
|
||||
from .validations_by_endpoint.router import validations_router
|
||||
|
||||
__all__ = ["validations_router"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
import json
|
||||
|
||||
from fastapi.routing import APIRouter
|
||||
from fastapi.requests import Request
|
||||
|
||||
from api_validations.validations_request import EndpointValidation
|
||||
from ApiServices.api_handlers.core_response import HTTPExceptionEvyos
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
validations_router = APIRouter(prefix="/validations", tags=["Validations"])
|
||||
validations_router.include_router(validations_router, include_in_schema=True)
|
||||
|
||||
|
||||
class EndpointValidationResponse(BaseModel):
|
||||
language: str
|
||||
headers: dict
|
||||
validation: dict
|
||||
|
||||
|
||||
class ValidationParser:
|
||||
|
||||
def __init__(self, active_validation):
|
||||
self.core_validation = active_validation
|
||||
self.annotations = (
|
||||
active_validation.model_json_schema() if active_validation else None
|
||||
)
|
||||
self.annotations = json.loads(json.dumps(self.annotations))
|
||||
self.schema = {}
|
||||
self.parse()
|
||||
|
||||
def parse(self):
|
||||
from api_validations.validations_request import PydanticBaseModel, CrudRecords
|
||||
properties = self.annotations.get("properties").items()
|
||||
for key, value in properties:
|
||||
default, title, type_field = value.get("default", None), value.get("title", ""), value.get("type", None)
|
||||
field_type, required, possible_types = "string", False, []
|
||||
if not type_field:
|
||||
for _ in value.get("anyOf") or []:
|
||||
type_opt = json.loads(json.dumps(_))
|
||||
if not type_opt.get("type") == "null":
|
||||
possible_types.append(type_opt.get("type"))
|
||||
field_type = possible_types[0]
|
||||
else:
|
||||
field_type, required = type_field, True
|
||||
total_class_annotations = {
|
||||
**self.core_validation.__annotations__
|
||||
**PydanticBaseModel.__annotations__,
|
||||
**CrudRecords.__annotations__,
|
||||
}
|
||||
attribute_of_class = total_class_annotations.get(key, None)
|
||||
if (
|
||||
str(attribute_of_class) == "<class 'str'>"
|
||||
or str(attribute_of_class) == "typing.Optional[str]"
|
||||
):
|
||||
field_type = "string"
|
||||
required = not str(attribute_of_class) == "typing.Optional[str]"
|
||||
elif (
|
||||
str(attribute_of_class) == "<class 'int'>"
|
||||
or str(attribute_of_class) == "typing.Optional[int]"
|
||||
):
|
||||
field_type = "integer"
|
||||
required = not str(attribute_of_class) == "typing.Optional[int]"
|
||||
elif (
|
||||
str(attribute_of_class) == "<class 'bool'>"
|
||||
or str(attribute_of_class) == "typing.Optional[bool]"
|
||||
):
|
||||
field_type = "boolean"
|
||||
required = not str(attribute_of_class) == "typing.Optional[bool]"
|
||||
elif (
|
||||
str(attribute_of_class) == "<class 'float'>"
|
||||
or str(attribute_of_class) == "typing.Optional[float]"
|
||||
):
|
||||
field_type = "float"
|
||||
required = not str(attribute_of_class) == "typing.Optional[bool]"
|
||||
elif (
|
||||
str(attribute_of_class) == "<class 'datetime.datetime'>"
|
||||
or str(attribute_of_class) == "typing.Optional[datetime.datetime]"
|
||||
):
|
||||
field_type = "datetime"
|
||||
required = (
|
||||
not str(attribute_of_class) == "typing.Optional[datetime.datetime]"
|
||||
)
|
||||
self.schema[key] = {"type": field_type, "required": required, "default": default}
|
||||
|
||||
@classmethod
|
||||
def retrieve_validation_from_class(cls, selected_event, events, lang):
|
||||
event_function_class = getattr(selected_event, "function_class", None)
|
||||
event_function_code = getattr(selected_event, "function_code", None)
|
||||
function_class = getattr(events, event_function_class, None)
|
||||
event_headers = function_class.retrieve_language_parameters(
|
||||
language=lang, function_code=event_function_code
|
||||
)
|
||||
event_validation = function_class.__event_validation__.get(
|
||||
event_function_code, [None]
|
||||
)[0]
|
||||
if not event_validation:
|
||||
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=lang)
|
||||
return event_validation, event_headers
|
||||
|
||||
|
||||
@validations_router.post(path="/validations", summary="Get validations by endpoint")
|
||||
def validation_by_endpoint(request: Request, validation: EndpointValidation):
|
||||
import api_events.events as events
|
||||
|
||||
from api_services.redis.functions import RedisActions
|
||||
from databases import EndpointRestriction, Events
|
||||
|
||||
valid_token = RedisActions.get_object_via_access_key(request=request)
|
||||
language = str(valid_token.lang).lower()
|
||||
if not valid_token:
|
||||
raise HTTPExceptionEvyos(error_code="", lang=language)
|
||||
endpoint_active = EndpointRestriction.filter_one(
|
||||
EndpointRestriction.endpoint_name.ilike(f"%{str(validation.endpoint)}%"),
|
||||
system=True,
|
||||
).data
|
||||
if not endpoint_active:
|
||||
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
|
||||
if valid_token.user_type == 1 and not valid_token.selected_company:
|
||||
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
|
||||
elif valid_token.user_type == 2 and not valid_token.selected_occupant:
|
||||
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
|
||||
selected_event = Events.filter_one(
|
||||
Events.endpoint_id == endpoint_active.id,
|
||||
Events.id.in_(valid_token.selected_company.reachable_event_list_id),
|
||||
).data
|
||||
if not selected_event:
|
||||
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
|
||||
active_validation, active_headers = ValidationParser.retrieve_validation_from_class(
|
||||
selected_event=selected_event, events=events, lang=language
|
||||
)
|
||||
if not active_validation:
|
||||
raise HTTPExceptionEvyos(error_code="HTTP_400_BAD_REQUEST", lang=language)
|
||||
validation_parse = ValidationParser(active_validation=active_validation)
|
||||
return EndpointValidationResponse(
|
||||
language=language,
|
||||
headers=active_headers,
|
||||
validation=validation_parse.schema,
|
||||
)
|
||||
|
|
@ -6,8 +6,8 @@ from pydantic import BaseModel
|
|||
from api_validations.validations_request import PydanticBaseModel, BaseModelRegular
|
||||
from databases.sql_models.response_model import AlchemyResponse
|
||||
from sqlalchemy.orm import Query
|
||||
from api_objects.errors.errorHandlers import HTTPExceptionEvyos
|
||||
|
||||
MODEL_TYPE = Callable[[Any], Any]
|
||||
|
||||
class Pagination:
|
||||
size: int = 10
|
||||
|
|
@ -18,12 +18,24 @@ class Pagination:
|
|||
totalCount: int = 1
|
||||
totalPage: int = 1
|
||||
|
||||
def change(self, page=None, size=None, order_field=None, order_type=None):
|
||||
def change(
|
||||
self,
|
||||
page: int = 1,
|
||||
size: int = 10,
|
||||
order_field: str = "id",
|
||||
order_type: str = "asc",
|
||||
):
|
||||
self.size = size if 10 < size < 40 else 10
|
||||
self.page = page or self.page
|
||||
self.size = size or self.size
|
||||
self.orderField = order_field or self.orderField
|
||||
self.orderType = order_type or self.orderType
|
||||
self.setter_page()
|
||||
if self.page > self.totalPage:
|
||||
self.page = self.totalPage
|
||||
elif self.page < 1:
|
||||
self.page = 1
|
||||
self.setter_page()
|
||||
|
||||
def feed(self, data):
|
||||
if isinstance(data, list):
|
||||
|
|
@ -32,14 +44,16 @@ class Pagination:
|
|||
self.totalCount = data.count
|
||||
elif isinstance(data, Query):
|
||||
self.totalCount = data.count()
|
||||
self.setter_page()
|
||||
|
||||
def setter_page(self):
|
||||
self.pageCount = self.size
|
||||
self.totalPage = int(round(self.totalCount / self.size, 0))
|
||||
if self.totalCount % self.size > 0:
|
||||
if self.page == self.totalPage:
|
||||
self.pageCount = self.totalCount % self.size
|
||||
remainder = self.totalCount % self.size
|
||||
if remainder > 0:
|
||||
self.totalPage = int(round(self.totalCount / self.size, 0)) + 1
|
||||
if self.page == self.totalPage:
|
||||
self.pageCount = remainder
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
|
|
@ -56,17 +70,19 @@ class Pagination:
|
|||
class SingleAlchemyResponse:
|
||||
status_code = "HTTP_200_OK"
|
||||
result: AlchemyResponse
|
||||
response_model: MODEL_TYPE
|
||||
response_model: Any
|
||||
message: str
|
||||
completed: bool
|
||||
cls_object: Any = (None,)
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
message: str,
|
||||
response_model: MODEL_TYPE,
|
||||
response_model: Any,
|
||||
status_code: str = "HTTP_200_OK",
|
||||
result: AlchemyResponse = None,
|
||||
completed: bool = True,
|
||||
cls_object: Any = None,
|
||||
):
|
||||
cls.status_code = getattr(status, status_code, "HTTP_200_OK")
|
||||
cls.message = message
|
||||
|
|
@ -75,10 +91,16 @@ class SingleAlchemyResponse:
|
|||
cls.response_model = response_model
|
||||
|
||||
if not isinstance(cls.result, AlchemyResponse):
|
||||
raise Exception("Invalid response type 4 single alchemy response")
|
||||
HTTPExceptionEvyos(
|
||||
lang=cls_object.lang,
|
||||
error_code="HTTP_400_BAD_REQUEST",
|
||||
)
|
||||
|
||||
if not cls.result.first:
|
||||
raise Exception("Invalid data type 4 single alchemy response")
|
||||
HTTPExceptionEvyos(
|
||||
lang=cls_object.lang,
|
||||
error_code="HTTP_400_BAD_REQUEST",
|
||||
)
|
||||
|
||||
pagination = Pagination()
|
||||
pagination.change(page=1)
|
||||
|
|
@ -103,7 +125,7 @@ class AlchemyJsonResponse:
|
|||
result: AlchemyResponse
|
||||
completed: bool
|
||||
filter_attributes: Any = None
|
||||
response_model: MODEL_TYPE = None
|
||||
response_model: Any = None
|
||||
cls_object: Any = None
|
||||
|
||||
def __new__(
|
||||
|
|
@ -112,7 +134,7 @@ class AlchemyJsonResponse:
|
|||
status_code: str = "HTTP_200_OK",
|
||||
result: Union[BaseModelRegular, BaseModel, PydanticBaseModel] = None,
|
||||
completed: bool = True,
|
||||
response_model: MODEL_TYPE = None,
|
||||
response_model: Any = None,
|
||||
cls_object: Any = None,
|
||||
filter_attributes: Any = None,
|
||||
):
|
||||
|
|
@ -126,11 +148,15 @@ class AlchemyJsonResponse:
|
|||
pagination = Pagination()
|
||||
|
||||
if cls.result.first:
|
||||
raise Exception("Invalid data type 4 alchemy response")
|
||||
HTTPExceptionEvyos(
|
||||
lang=cls_object.lang,
|
||||
error_code="HTTP_400_BAD_REQUEST",
|
||||
)
|
||||
|
||||
if filter_attributes:
|
||||
pagination.change(
|
||||
page=filter_attributes.page, size=filter_attributes.size,
|
||||
page=filter_attributes.page,
|
||||
size=filter_attributes.size,
|
||||
order_field=filter_attributes.order_field,
|
||||
order_type=filter_attributes.order_type,
|
||||
)
|
||||
|
|
@ -158,8 +184,8 @@ class ListJsonResponse:
|
|||
message: str
|
||||
completed: bool
|
||||
filter_attributes: Any
|
||||
response_model: MODEL_TYPE = None,
|
||||
cls_object: Any = None,
|
||||
response_model: Any = (None,)
|
||||
cls_object: Any = (None,)
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
|
|
@ -167,7 +193,7 @@ class ListJsonResponse:
|
|||
status_code: str = "HTTP_200_OK",
|
||||
result: Union[BaseModelRegular, BaseModel, PydanticBaseModel] = None,
|
||||
completed: bool = True,
|
||||
response_model: MODEL_TYPE = None,
|
||||
response_model: Any = None,
|
||||
cls_object: Any = None,
|
||||
filter_attributes: Any = None,
|
||||
):
|
||||
|
|
@ -176,15 +202,20 @@ class ListJsonResponse:
|
|||
cls.result = result
|
||||
cls.completed = completed
|
||||
cls.filter_attributes = filter_attributes
|
||||
cls.response_model: MODEL_TYPE = response_model
|
||||
cls.response_model: Any = response_model
|
||||
|
||||
if not isinstance(cls.result, list):
|
||||
raise Exception("Invalid data type 4 list json response")
|
||||
HTTPExceptionEvyos(
|
||||
lang=cls_object.lang,
|
||||
error_code="HTTP_400_BAD_REQUEST",
|
||||
)
|
||||
pagination = Pagination()
|
||||
pagination.change(page=1)
|
||||
data = list(cls.result)
|
||||
if cls.response_model:
|
||||
data = [cls.response_model(**data_object).dump() for data_object in cls.result]
|
||||
data = [
|
||||
cls.response_model(**data_object).dump() for data_object in cls.result
|
||||
]
|
||||
pagination.feed(data)
|
||||
return JSONResponse(
|
||||
status_code=cls.status_code,
|
||||
|
|
@ -196,14 +227,15 @@ class ListJsonResponse:
|
|||
),
|
||||
)
|
||||
|
||||
|
||||
class DictJsonResponse:
|
||||
status_code = "HTTP_200_OK"
|
||||
result: dict
|
||||
message: str
|
||||
completed: bool
|
||||
filter_attributes: Any
|
||||
response_model: MODEL_TYPE = None,
|
||||
cls_object: Any = None,
|
||||
response_model: Any = (None,)
|
||||
cls_object: Any = (None,)
|
||||
|
||||
def __new__(
|
||||
cls,
|
||||
|
|
@ -211,7 +243,7 @@ class DictJsonResponse:
|
|||
status_code: str = "HTTP_200_OK",
|
||||
result: Union[BaseModelRegular, BaseModel, PydanticBaseModel] = None,
|
||||
completed: bool = True,
|
||||
response_model: MODEL_TYPE = None,
|
||||
response_model: Any = None,
|
||||
cls_object: Any = None,
|
||||
filter_attributes: Any = None,
|
||||
):
|
||||
|
|
@ -220,10 +252,13 @@ class DictJsonResponse:
|
|||
cls.result = result
|
||||
cls.completed = completed
|
||||
cls.filter_attributes = filter_attributes
|
||||
cls.response_model: MODEL_TYPE = response_model
|
||||
cls.response_model: Any = response_model
|
||||
|
||||
if not isinstance(cls.result, dict):
|
||||
raise Exception("Invalid data type 4 dict json response")
|
||||
HTTPExceptionEvyos(
|
||||
lang=cls_object.lang,
|
||||
error_code="HTTP_400_BAD_REQUEST",
|
||||
)
|
||||
|
||||
pagination = Pagination()
|
||||
pagination.change(page=1)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
services:
|
||||
|
||||
wag_management_auth_service:
|
||||
container_name: wag_management_auth_service
|
||||
# restart: on-failure
|
||||
|
|
@ -13,41 +14,39 @@ services:
|
|||
- auth_venv:/service_app/.venv
|
||||
- auth_logs:/service_app/logs
|
||||
|
||||
wag_management_validation_service:
|
||||
container_name: wag_management_validation_service
|
||||
# restart: on-failure
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ApiServices/ValidationService/Dockerfile
|
||||
ports:
|
||||
- "1113:41575"
|
||||
environment:
|
||||
- PYTHONPATH=/service_app
|
||||
volumes:
|
||||
- validation_venv:/service_app/.venv
|
||||
- validation_logs:/service_app/logs
|
||||
|
||||
# wag_management_init_service:
|
||||
# container_name: wag_management_init_service
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: service_app_init/Dockerfile
|
||||
|
||||
# wag_management_event_service:
|
||||
# container_name: wag_management_event_service
|
||||
# # restart: on-failure
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ApiServices/EventService/Dockerfile
|
||||
# ports:
|
||||
# - "1112:41575"
|
||||
# environment:
|
||||
# - PYTHONPATH=/service_app
|
||||
# volumes:
|
||||
# - event_venv:/service_app/.venv
|
||||
# - event_logs:/service_app/logs
|
||||
|
||||
# wag_management_validation_service:
|
||||
# container_name: wag_management_validation_service
|
||||
# # restart: on-failure
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ApiServices/ValidationService/Dockerfile
|
||||
# ports:
|
||||
# - "1113:41575"
|
||||
# environment:
|
||||
# - PYTHONPATH=/service_app
|
||||
# volumes:
|
||||
# - validation_venv:/service_app/.venv
|
||||
# - validation_logs:/service_app/logs
|
||||
|
||||
|
||||
# wag_management_event_service:
|
||||
# container_name: wag_management_event_service
|
||||
# # restart: on-failure
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: ApiServices/EventService/Dockerfile
|
||||
# ports:
|
||||
# - "1112:41575"
|
||||
# environment:
|
||||
# - PYTHONPATH=/service_app
|
||||
# volumes:
|
||||
# - event_venv:/service_app/.venv
|
||||
# - event_logs:/service_app/logs
|
||||
|
||||
volumes:
|
||||
auth_venv:
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class MethodToEvent(ABC, ActionsSchemaFactory):
|
|||
event_category: str = ""
|
||||
|
||||
__event_keys__: Dict[str, str] = {}
|
||||
__event_validation__: Dict[str, Any] = {}
|
||||
__event_validation__: list[Any, list[Any]] = []
|
||||
|
||||
@classmethod
|
||||
def call_event_method(cls, method_uu_id: str, *args: Any, **kwargs: Any) -> Any:
|
||||
|
|
@ -106,7 +106,6 @@ class MethodToEvent(ABC, ActionsSchemaFactory):
|
|||
function_name = cls.__event_keys__.get(method_uu_id)
|
||||
if not function_name:
|
||||
raise AttributeError(f"No method found for UUID: {method_uu_id}")
|
||||
|
||||
return getattr(cls, function_name)(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -128,3 +127,22 @@ class MethodToEvent(ABC, ActionsSchemaFactory):
|
|||
status_code=status.HTTP_406_NOT_ACCEPTABLE,
|
||||
detail=f"No {user_type} can reach this event. A notification has been sent to admin.",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def retrieve_language_parameters(cls, language: str, function_code: str):
|
||||
|
||||
event_response_model = dict(cls.__event_validation__).get(function_code)[0]
|
||||
event_language_models = list(
|
||||
dict(cls.__event_validation__).get(function_code)[1]
|
||||
)
|
||||
language_models, language_response = {}, {}
|
||||
for event_language_model in event_language_models:
|
||||
language_models.update({**event_language_model.get(language, "tr")})
|
||||
from api_validations.validations_response.building_responses import (
|
||||
ListBuildingResponse,
|
||||
)
|
||||
|
||||
for model_field in event_response_model.model_fields:
|
||||
if model_field in language_models:
|
||||
language_response[model_field] = language_models[model_field]
|
||||
return language_response
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ from api_validations.validations_request import (
|
|||
PatchRecord,
|
||||
ListOptions,
|
||||
)
|
||||
from api_validations.validations_response import BuildResponse
|
||||
from ApiServices.api_handlers import AlchemyJsonResponse
|
||||
from api_events.events.abstract_class import MethodToEvent, ActionsSchema
|
||||
from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject
|
||||
from api_validations.validations_response.building_responses import ListBuildingResponse
|
||||
|
||||
|
||||
class BuildListEventMethods(MethodToEvent):
|
||||
|
|
@ -34,7 +34,10 @@ class BuildListEventMethods(MethodToEvent):
|
|||
"68b3b5ed-b74c-4a27-820f-3959214e94e9": "build_list",
|
||||
}
|
||||
__event_validation__ = {
|
||||
"68b3b5ed-b74c-4a27-820f-3959214e94e9": BuildResponse,
|
||||
"68b3b5ed-b74c-4a27-820f-3959214e94e9": (
|
||||
ListBuildingResponse,
|
||||
[Build.__language_model__],
|
||||
)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
|
@ -59,7 +62,7 @@ class BuildListEventMethods(MethodToEvent):
|
|||
result=records,
|
||||
cls_object=Build,
|
||||
filter_attributes=list_options,
|
||||
response_model=BuildResponse,
|
||||
response_model=ListBuildingResponse,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from api_events.tasks2events.common_tasks.default_user import AuthDefaultEventBlock
|
||||
# from api_events.tasks2events.common_tasks.default_user import AuthDefaultEventBlock
|
||||
from api_events.tasks2events.employee_tasks.super_user import SuperUserEventBlock
|
||||
|
||||
from api_events.tasks2events.occupant_tasks.build_manager import BuildManager
|
||||
|
|
@ -29,7 +29,7 @@ from api_events.tasks2events.occupant_tasks.project_responsiable import (
|
|||
|
||||
|
||||
__all__ = [
|
||||
"AuthDefaultEventBlock",
|
||||
# "AuthDefaultEventBlock",
|
||||
"SuperUserEventBlock",
|
||||
"BuildManager",
|
||||
"BuildOwner",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
class HTTPExceptionError:
|
||||
|
||||
def __init__(self, lang):
|
||||
|
|
|
|||
|
|
@ -1,17 +1,6 @@
|
|||
from typing import Any
|
||||
|
||||
|
||||
# class HTTPExceptionInstance:
|
||||
|
||||
# def __init__(self, statuses, exceptions, exceptions_dict, errors_dict, response_model, error_language_dict):
|
||||
# self.EXCEPTIONS = exceptions # from fastapi.exceptions import HTTPException
|
||||
# self.STATUSES = statuses # from fastapi import status
|
||||
# self.EXCEPTION_DICTS: dict = exceptions_dict
|
||||
# self.ERRORS_DICT: dict = errors_dict
|
||||
# self.ERRORS_LANG: dict = error_language_dict
|
||||
# self.RESPONSE_MODEL: Any = response_model
|
||||
|
||||
|
||||
class HTTPExceptionEvyos(Exception):
|
||||
|
||||
def __init__(self, error_code: str, lang: str):
|
||||
|
|
@ -21,26 +10,33 @@ class HTTPExceptionEvyos(Exception):
|
|||
|
||||
class HTTPExceptionEvyosHandler:
|
||||
|
||||
def __init__(self, statuses, exceptions, exceptions_dict, errors_dict, response_model, error_language_dict):
|
||||
self.EXCEPTIONS = exceptions # from fastapi.exceptions import HTTPException
|
||||
self.STATUSES = statuses # from fastapi import status
|
||||
self.EXCEPTION_DICTS: dict = exceptions_dict
|
||||
self.ERRORS_DICT: dict = errors_dict
|
||||
self.ERRORS_LANG: dict = error_language_dict
|
||||
self.RESPONSE_MODEL: Any = response_model
|
||||
def __init__(
|
||||
self,
|
||||
**kwargs,
|
||||
):
|
||||
self.EXCEPTIONS = kwargs.get(
|
||||
"exceptions"
|
||||
) # from fastapi.exceptions import HTTPException
|
||||
self.STATUSES = kwargs.get("statuses") # from fastapi import status
|
||||
self.EXCEPTION_DICTS: dict = kwargs.get("exceptions_dict")
|
||||
self.ERRORS_DICT: dict = kwargs.get("errors_dict")
|
||||
self.ERRORS_LANG: dict = kwargs.get("error_language_dict")
|
||||
self.RESPONSE_MODEL: Any = kwargs.get("response_model")
|
||||
|
||||
def retrieve_error_status_code(self, exc: HTTPExceptionEvyos):
|
||||
grab_status = self.ERRORS_DICT.get(str(exc.error_code).upper(), "")
|
||||
grab_status_code = self.EXCEPTION_DICTS.get(str(grab_status).upper(), "500")
|
||||
return getattr(self.STATUSES, str(grab_status_code), getattr(self.STATUSES, "HTTP_500_INTERNAL_SERVER_ERROR"))
|
||||
return getattr(
|
||||
self.STATUSES,
|
||||
str(grab_status_code),
|
||||
getattr(self.STATUSES, "HTTP_500_INTERNAL_SERVER_ERROR"),
|
||||
)
|
||||
|
||||
def retrieve_error_message(self, exc: HTTPExceptionEvyos):
|
||||
message_by_lang = self.ERRORS_LANG.get(str(exc.lang).lower(), {})
|
||||
message_by_code = message_by_lang.get(str(exc.error_code).upper(), "Unknown error")
|
||||
return message_by_code
|
||||
return message_by_lang.get(str(exc.error_code).upper(), "Unknown error")
|
||||
|
||||
def handle_exception(self, request, exc: HTTPExceptionEvyos):
|
||||
headers = getattr(request, "headers", {})
|
||||
status_code = self.retrieve_error_status_code(exc)
|
||||
error_message = self.retrieve_error_message(exc)
|
||||
return self.RESPONSE_MODEL(
|
||||
|
|
@ -48,6 +44,7 @@ class HTTPExceptionEvyosHandler:
|
|||
content={"message": error_message, "lang": exc.lang},
|
||||
)
|
||||
|
||||
|
||||
class HTTPExceptionAnyHandler:
|
||||
|
||||
def __init__(self, response_model):
|
||||
|
|
@ -56,5 +53,5 @@ class HTTPExceptionAnyHandler:
|
|||
def any_exception_handler(self, request, exc: Exception):
|
||||
return self.RESPONSE_MODEL(
|
||||
status_code=200,
|
||||
content={"message": str(exc), "lang": None, "status_code": 417},
|
||||
content={"message": str(exc), "lang": "en"},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ERRORS_LANG = {
|
|||
},
|
||||
"en": {
|
||||
**BASE_ERROR_LANGUAGE["en"],
|
||||
}
|
||||
},
|
||||
}
|
||||
ERRORS_DICT = {
|
||||
**BASE_ERRORS,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
BASE_ERRORS = {
|
||||
"NOT_CREATED": 405,
|
||||
"NOT_DELETED": 405,
|
||||
|
|
@ -46,6 +45,7 @@ BASE_ERROR_LANGUAGE = {
|
|||
}
|
||||
|
||||
from fastapi import status
|
||||
|
||||
EXCEPTION_DICTS = {
|
||||
"100": "HTTP_100_CONTINUE",
|
||||
"101": "HTTP_101_SWITCHING_PROTOCOLS",
|
||||
|
|
@ -100,5 +100,4 @@ EXCEPTION_DICTS = {
|
|||
"451": "HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS",
|
||||
"500": "HTTP_500_INTERNAL_SERVER_ERROR",
|
||||
"502": "HTTP_502_BAD_GATEWAY",
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,35 @@
|
|||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from typing import Optional, List, Generic
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
from decimal import Decimal
|
||||
from .base_responses import BaseResponse, CrudCollection
|
||||
|
||||
from api_validations.validations_response.base_responses import (
|
||||
BaseResponse,
|
||||
CrudCollection,
|
||||
)
|
||||
from api_validations.validations_request import PydanticBaseModel
|
||||
|
||||
|
||||
class ListBuildingResponse(PydanticBaseModel):
|
||||
|
||||
gov_address_code: str
|
||||
build_name: str
|
||||
build_types_uu_id: Optional[str] = None
|
||||
build_no: Optional[str] = None
|
||||
max_floor: Optional[int] = None
|
||||
underground_floor: Optional[int] = None
|
||||
address_uu_id: Optional[str] = None
|
||||
build_date: Optional[str] = None
|
||||
decision_period_date: Optional[str] = None
|
||||
tax_no: Optional[str] = None
|
||||
lift_count: Optional[int] = None
|
||||
heating_system: Optional[bool] = None
|
||||
cooling_system: Optional[bool] = None
|
||||
hot_water_system: Optional[bool] = None
|
||||
block_service_man_count: Optional[int] = None
|
||||
security_service_man_count: Optional[int] = None
|
||||
garage_count: Optional[int] = None
|
||||
site_uu_id: Optional[str] = None
|
||||
|
||||
|
||||
class BuildAreaListResponse(BaseResponse):
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ from sqlalchemy import (
|
|||
from sqlalchemy.orm import mapped_column, Mapped
|
||||
|
||||
from databases.language_models.company.employee import (
|
||||
StaffLanguageModel,
|
||||
EmployeesLanguageModel,
|
||||
EmployeeHistoryLanguageModel,
|
||||
EmployeesSalariesLanguageModel,
|
||||
StaffLanguageModel,
|
||||
EmployeesLanguageModel,
|
||||
EmployeeHistoryLanguageModel,
|
||||
EmployeesSalariesLanguageModel,
|
||||
)
|
||||
from databases.sql_models.core_mixin import CrudCollection
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ COPY api_services ./service_app_init/api_services
|
|||
|
||||
COPY ApiServices ./service_app_init/ApiServices
|
||||
COPY ApiServices/EventService/routers ./service_app_init/routers
|
||||
#COPY ../service_app/application ./service_app_init/application
|
||||
COPY ApiServices/EventService/application ./service_app_init/application
|
||||
|
||||
WORKDIR /service_app_init
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,9 @@ def add_events_all_services_and_occupant_types():
|
|||
import api_events.tasks2events as tasks2events
|
||||
|
||||
for event_block in tasks2events.__all__:
|
||||
print("event_block", event_block)
|
||||
event_block_class = getattr(tasks2events, event_block)
|
||||
print("event_block_class", event_block_class)
|
||||
service_selected = Services.filter_one(
|
||||
Services.service_code == getattr(event_block_class, "service_code", None),
|
||||
system=True,
|
||||
|
|
|
|||
|
|
@ -157,6 +157,6 @@ def create_application_defaults_func(create_address=False):
|
|||
|
||||
if __name__ == "__main__":
|
||||
print("Service App Initial Default Runner is running")
|
||||
do_alembic()
|
||||
# create_application_defaults_func(create_address=True)
|
||||
# do_alembic()
|
||||
create_application_defaults_func(create_address=False)
|
||||
print("Service App Initial Default Runner is completed")
|
||||
|
|
|
|||
Loading…
Reference in New Issue