base context for wrappers updated

This commit is contained in:
2025-01-17 20:00:53 +03:00
parent 61229cb761
commit 628f6bd483
21 changed files with 404 additions and 300 deletions

View File

@@ -8,10 +8,22 @@ from typing import TYPE_CHECKING, Union, Dict, Any
from ApiEvents.abstract_class import MethodToEvent
from ApiEvents.base_request_model import DictRequestModel, SuccessResponse
from ApiLibrary.common.line_number import get_line_number_for_error
from ApiLibrary.date_time_actions.date_functions import DateTimeLocal
from ApiServices.Login.user_login_handler import UserLoginModule
from ApiServices.Token.token_handler import AccessToken, TokenService
from ApiValidations.Request.authentication import EmployeeSelectionValidation, Login, OccupantSelectionValidation
from ApiServices.Token.token_handler import TokenService
from ApiValidations.Custom.token_objects import CompanyToken
from ApiValidations.Request.authentication import (
Login,
EmployeeSelectionValidation,
OccupantSelectionValidation, OccupantSelection, EmployeeSelection,
)
from ErrorHandlers import HTTPExceptionApi
from Schemas.company.company import Companies
from Schemas.company.department import Departments, Duties, Duty
from Schemas.company.employee import Staff, Employees
from Schemas.event.event import Event2Employee
from Schemas.identity.identity import Users
from Services.Redis.Actions.actions import RedisActions
from .models import (
LoginData,
LoginRequestModel,
@@ -27,7 +39,10 @@ from .models import (
if TYPE_CHECKING:
from fastapi import Request
from ApiServices.Token.token_handler import OccupantTokenObject, EmployeeTokenObject
from ApiServices.Token.token_handler import (
OccupantTokenObject,
EmployeeTokenObject
)
# Type aliases for common types
TokenDictType = Union["EmployeeTokenObject", "OccupantTokenObject"]
@@ -69,7 +84,7 @@ class AuthenticationLoginEventMethods(MethodToEvent):
# Return response with token and headers
return {
"token": token,
**token,
"headers": dict(request.headers),
}
@@ -89,18 +104,113 @@ class AuthenticationSelectEventMethods(MethodToEvent):
@classmethod
def _handle_employee_selection(
cls,
data: SelectionDataEmployee,
data: EmployeeSelection,
token_dict: TokenDictType,
request: "Request",
):
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error()
Users.set_user_define_properties(token=token_dict)
db_session = Users.new_session()
if data.company_uu_id not in token_dict.companies_uu_id_list:
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang=token_dict.lang,
loc=get_line_number_for_error(),
sys_msg="Company not found in token"
)
selected_company = Companies.filter_one(
Companies.uu_id == data.company_uu_id,
db=db_session,
).first
if not selected_company:
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang=token_dict.lang,
loc=get_line_number_for_error(),
sys_msg="Company not found in token"
)
# Get department IDs for the company
department_ids = [
dept.id
for dept in Departments.filter_all(
Departments.company_id == selected_company.id,
db=db_session,
).data
]
# Get duties IDs for the company
duties_ids = [
duty.id
for duty in Duties.filter_all(Duties.company_id == selected_company.id, db=db_session).data
]
# Get staff IDs
staff_ids = [
staff.id for staff in Staff.filter_all(Staff.duties_id.in_(duties_ids), db=db_session).data
]
# Get employee
employee = Employees.filter_one(
Employees.people_id == token_dict.person_id,
Employees.staff_id.in_(staff_ids),
db=db_session,
).first
if not employee:
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang=token_dict.lang,
loc=get_line_number_for_error(),
sys_msg="Employee not found in token"
)
# Get reachable events
reachable_event_list_id = Event2Employee.get_event_id_by_employee_id(
employee_id=employee.id
)
# Get staff and duties
staff = Staff.filter_one(Staff.id == employee.staff_id, db=db_session).data
duties = Duties.filter_one(Duties.id == staff.duties_id, db=db_session).data
department = Departments.filter_one(Departments.id == duties.department_id, db=db_session).data
# Get bulk duty
bulk_id = Duty.filter_by_one(system=True, duty_code="BULK", db=db_session).data
bulk_duty_id = Duties.filter_by_one(
company_id=selected_company.id,
duties_id=bulk_id.id,
**Duties.valid_record_dict,
db=db_session,
).data
# Create company token
company_token = CompanyToken(
company_uu_id=selected_company.uu_id.__str__(),
company_id=selected_company.id,
department_id=department.id,
department_uu_id=department.uu_id.__str__(),
duty_id=duties.id,
duty_uu_id=duties.uu_id.__str__(),
bulk_duties_id=bulk_duty_id.id,
staff_id=staff.id,
staff_uu_id=staff.uu_id.__str__(),
employee_id=employee.id,
employee_uu_id=employee.uu_id.__str__(),
reachable_event_list_id=reachable_event_list_id,
)
try: # Update Redis
update_token = TokenService.update_token_at_redis(request=request, add_payload=company_token)
return update_token
except Exception as e:
raise HTTPExceptionApi(
error_code="", lang="en", loc=get_line_number_for_error(), sys_msg=f"{e}"
)
@classmethod
def _handle_occupant_selection(
cls,
data: SelectionDataOccupant,
data: OccupantSelection,
token_dict: TokenDictType,
request: "Request",
):
@@ -116,25 +226,22 @@ class AuthenticationSelectEventMethods(MethodToEvent):
async def authentication_select_company_or_occupant_type(
cls,
request: "Request",
data: Union[EmployeeSelectionValidation, OccupantSelectionValidation],
data: Union[EmployeeSelection, OccupantSelection],
token_dict: TokenDictType,
):
"""Handle selection of company or occupant type"""
try:
print(
dict(
data=data,
token_dict=token_dict.model_dump(),
request=dict(request.headers)
)
)
except Exception as e:
raise HTTPExceptionApi(
error_code="HTTP_500_INTERNAL_SERVER_ERROR",
lang="en",
loc=get_line_number_for_error(),
sys_msg=str(e),
)
if token_dict.is_employee:
return cls._handle_employee_selection(data, token_dict, request)
elif token_dict.is_occupant:
return cls._handle_occupant_selection(data, token_dict, request)
# except Exception as e:
# raise HTTPExceptionApi(
# error_code="HTTP_500_INTERNAL_SERVER_ERROR",
# lang="en",
# loc=get_line_number_for_error(),
# sys_msg=str(e),
# )
class AuthenticationCheckTokenEventMethods(MethodToEvent):

View File

@@ -5,7 +5,16 @@ Authentication endpoint configurations.
from typing import TYPE_CHECKING, Dict, Any, Union, Annotated
from fastapi import HTTPException, status, Body
from ApiValidations.Request.authentication import Login
from ApiValidations.Request import (
Logout,
Login,
Remember,
Forgot,
CreatePassword,
ChangePassword,
OccupantSelection,
EmployeeSelection,
)
from .auth import (
AuthenticationChangePasswordEventMethods,
@@ -32,7 +41,7 @@ from .models import (
SelectionDataOccupant,
RememberRequestModel,
)
from ApiEvents.base_request_model import DictRequestModel
from ApiEvents.base_request_model import DictRequestModel, EndpointBaseRequestModel
from ApiEvents.abstract_class import RouteFactoryConfig, EndpointFactoryConfig, endpoint_wrapper
if TYPE_CHECKING:
@@ -41,30 +50,27 @@ from ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTok
# Type aliases for common types
TokenDictType = Union[EmployeeTokenObject, OccupantTokenObject]
@endpoint_wrapper("/authentication/select")
async def authentication_select_company_or_occupant_type(
request: "Request",
data: Union[SelectionDataEmployee, SelectionDataOccupant],
token_dict: TokenDictType = None
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Handle selection of company or occupant type.
Args:
request: The FastAPI request object
data: Selection request data
Returns:
Dict containing the response data
Select company or occupant type.
"""
return {
"headers": dict(request.headers),
"data": data,
"token": token_dict
}
auth_dict = authentication_select_company_or_occupant_type.auth
if data.data.get("company_uu_id"):
data = EmployeeSelection(**data.data)
elif data.data.get("build_living_space_uu_id"):
data = OccupantSelection(**data.data)
if r := await AuthenticationSelectEventMethods.authentication_select_company_or_occupant_type(
request=request, data=data, token_dict=auth_dict
):
if isinstance(data, EmployeeSelection):
return {"selected_company": data.company_uu_id}
elif isinstance(data, OccupantSelection):
return {"selected_occupant": data.build_living_space_uu_id}
@endpoint_wrapper("/authentication/login")
@@ -75,7 +81,7 @@ async def authentication_login_with_domain_and_creds(
"""
Authenticate user with domain and credentials.
"""
return AuthenticationLoginEventMethods.authentication_login_with_domain_and_creds(
return await AuthenticationLoginEventMethods.authentication_login_with_domain_and_creds(
request=request, data=data
)
@@ -83,13 +89,14 @@ async def authentication_login_with_domain_and_creds(
@endpoint_wrapper("/authentication/check")
async def authentication_check_token_is_valid(
request: "Request",
data: DictRequestModel,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Check if a token is valid.
"""
return {
"status": "OK",
"headers": dict(request.headers),
"data": data.model_dump(),
}
@@ -97,8 +104,7 @@ async def authentication_check_token_is_valid(
@endpoint_wrapper("/authentication/refresh")
async def authentication_refresh_user_info(
request: "Request",
data: DictRequestModel,
token_dict: TokenDictType = None,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Refresh user information.
@@ -111,8 +117,7 @@ async def authentication_refresh_user_info(
@endpoint_wrapper("/authentication/change-password")
async def authentication_change_password(
request: "Request",
data: ChangePasswordRequestModel,
token_dict: TokenDictType = None,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Change user password.
@@ -125,7 +130,7 @@ async def authentication_change_password(
@endpoint_wrapper("/authentication/create-password")
async def authentication_create_password(
request: "Request",
data: CreatePasswordRequestModel,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Create new password.
@@ -137,7 +142,7 @@ async def authentication_create_password(
@endpoint_wrapper("/authentication/forgot-password")
async def authentication_forgot_password(
request: "Request",
data: ForgotRequestModel,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Handle forgot password request.
@@ -149,7 +154,7 @@ async def authentication_forgot_password(
@endpoint_wrapper("/authentication/reset-password")
async def authentication_reset_password(
request: "Request",
data: ForgotRequestModel,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Reset password.
@@ -161,8 +166,7 @@ async def authentication_reset_password(
@endpoint_wrapper("/authentication/disconnect")
async def authentication_disconnect_user(
request: "Request",
data: LogoutRequestModel,
token_dict: TokenDictType = None,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Disconnect user.
@@ -175,8 +179,7 @@ async def authentication_disconnect_user(
@endpoint_wrapper("/authentication/logout")
async def authentication_logout_user(
request: "Request",
data: LogoutRequestModel,
token_dict: TokenDictType = None,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Logout user.
@@ -189,8 +192,7 @@ async def authentication_logout_user(
@endpoint_wrapper("/authentication/remember")
async def authentication_refresher_token(
request: "Request",
data: RememberRequestModel,
token_dict: TokenDictType = None,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Refresh remember token.
@@ -203,8 +205,7 @@ async def authentication_refresher_token(
@endpoint_wrapper("/authentication/avatar")
async def authentication_download_avatar(
request: "Request",
data: DictRequestModel,
token_dict: TokenDictType = None,
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Download user avatar.

View File

@@ -127,12 +127,12 @@ class CreatePasswordRequestModel(BaseRequestModel[CreatePasswordData]):
)
class SelectionDataOccupant(TypedDict):
class SelectionDataOccupant(BaseModel):
"""Type for selection data."""
build_living_space_uu_id: Optional[str]
class SelectionDataEmployee(TypedDict):
class SelectionDataEmployee(BaseModel):
"""Type for selection data."""
company_uu_id: Optional[str]

View File

@@ -4,18 +4,16 @@ Account records endpoint configurations.
"""
from ApiEvents.abstract_class import RouteFactoryConfig, EndpointFactoryConfig, endpoint_wrapper
from ApiEvents.base_request_model import DictRequestModel
from ApiEvents.base_request_model import EndpointBaseRequestModel
from Services.PostgresDb.Models.alchemy_response import DictJsonResponse
from fastapi import Request, Path, Body
from .models import ListOptionsRequestModel
@endpoint_wrapper("/account/records/address/list")
async def address_list(request: "Request", data: ListOptionsRequestModel):
async def address_list(request: "Request", data: EndpointBaseRequestModel):
"""Handle address list endpoint."""
auth_dict = address_list.auth
return {
"data": data,
"request": str(request.headers),
@@ -25,7 +23,7 @@ async def address_list(request: "Request", data: ListOptionsRequestModel):
@endpoint_wrapper("/account/records/address/create")
async def address_create(request: "Request", data: DictRequestModel):
async def address_create(request: "Request", data: EndpointBaseRequestModel):
"""Handle address creation endpoint."""
return {
"data": data,
@@ -36,16 +34,18 @@ async def address_create(request: "Request", data: DictRequestModel):
@endpoint_wrapper("/account/records/address/search")
async def address_search(request: "Request", data: DictRequestModel):
async def address_search(request: "Request", data: EndpointBaseRequestModel):
"""Handle address search endpoint."""
return {"data": data}
auth_dict = address_search.auth
code_dict = getattr(address_search, 'func_code', {"function_code": None})
return {"auth_dict": auth_dict, "code_dict": code_dict, "data": data}
@endpoint_wrapper("/account/records/address/{address_uu_id}")
async def address_update(
request: Request,
address_uu_id: str = Path(..., description="UUID of the address to update"),
request_data: DictRequestModel = Body(..., description="Request body"),
request_data: EndpointBaseRequestModel = Body(..., description="Request body"),
):
"""
Handle address update endpoint.
@@ -58,6 +58,7 @@ async def address_update(
Returns:
DictJsonResponse: Response containing updated address info
"""
auth_dict = address_update.auth
return DictJsonResponse(
data={
"address_uu_id": address_uu_id,

View File

@@ -49,15 +49,15 @@ def endpoint_wrapper(url_of_endpoint: Optional[str] = None):
# If result is a coroutine, await it
if inspect.iscoroutine(result):
result = await result
# Add function_code to the result
print('result', result)
# Add endpoint to the result
if isinstance(result, dict):
result["function_code"] = url_of_endpoint
result["endpoint"] = url_of_endpoint
return result
elif isinstance(result, BaseModel):
# Convert Pydantic model to dict and add function_code
# Convert Pydantic model to dict and add endpoint
result_dict = result.model_dump()
result_dict["function_code"] = url_of_endpoint
result_dict["endpoint"] = url_of_endpoint
return result_dict
return result
@@ -107,24 +107,23 @@ class EndpointFactoryConfig:
- If only event required -> wrap with EventMiddleware
- If only auth required -> wrap with MiddlewareModule.auth_required
"""
# Wrap the endpoint function to store url_of_endpoint
self.endpoint_function = endpoint_wrapper(self.url_of_endpoint)(
self.endpoint_function
)
if self.is_auth_required and self.is_event_required:
# First apply auth/event middleware
if self.is_event_required:
from middleware import TokenEventMiddleware
self.endpoint_function = TokenEventMiddleware.event_required(
self.endpoint_function
)
elif self.is_auth_required:
from middleware import MiddlewareModule
self.endpoint_function = MiddlewareModule.auth_required(
self.endpoint_function
)
# Then wrap with endpoint_wrapper to store url_of_endpoint
self.endpoint_function = endpoint_wrapper(self.url_of_endpoint)(
self.endpoint_function
)
class RouteFactoryConfig:
"""Configuration class for API route factories.

View File

@@ -12,10 +12,21 @@ from pydantic import BaseModel, Field, ConfigDict, RootModel
T = TypeVar("T")
class EndpointBaseRequestModel(BaseModel):
data: dict = Field(..., description="Data to be sent with the request")
class Config:
json_schema_extra = {
"data": {
"key": "value",
}
}
class BaseRequestModel(RootModel[T], Generic[T]):
"""Base model for all API requests."""
model_config = ConfigDict(
json_schema_extra={"example": {}} # Will be populated by subclasses
json_schema_extra={"example": {"base": "example"}} # Will be populated by subclasses
)