base context for wrappers updated

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

View File

@ -19,7 +19,7 @@ class ApiStatic:
class Auth:
ACCESS_EMAIL_EXT = "evyos.com.tr"
ACCESS_TOKEN_TAG = "evyos-session-key"
REFRESHER_TOKEN_TAG = "eys_token_refresher"
REFRESHER_TOKEN_TAG = "eys-session-refresher"
SECRET_KEY_72 = (
"t3sUAmjTGeTgDc6dAUrB41u2SNg0ZHzj4HTjem95y3fRH1nZXOHIBj163kib6iLybT0gLaxq"
)

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
)

View File

@ -8,12 +8,14 @@ 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
from Schemas.identity.identity import UsersTokens, People
from Services.Redis import RedisActions, AccessToken
from ApiValidations.Custom.token_objects import (
EmployeeTokenObject,
OccupantTokenObject,
UserType,
CompanyToken,
OccupantToken,
)
from Schemas import (
Users,
@ -37,6 +39,7 @@ if TYPE_CHECKING:
T = TypeVar("T", EmployeeTokenObject, OccupantTokenObject)
class TokenService:
"""Service class for handling authentication tokens and user sessions."""
@ -128,22 +131,57 @@ class TokenService:
timezone=user.local_timezone or "GMT+0",
lang=user.lang or "tr",
).model_dump()
cls.set_object_to_redis(user, model_value)
return {
"user_type": UserType.occupant.name,
"available_occupants": occupants_selection_dict,
}
if access_token := cls.set_object_to_redis(user, model_value):
return {
"access_token": access_token,
"user_type": UserType.occupant.name,
"available_occupants": occupants_selection_dict,
}
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Creating Token failed...",
)
@classmethod
def set_object_to_redis(cls, user, model: Dict):
accessObject = AccessToken(
access_object = AccessToken(
userUUID=user.uu_id,
accessToken=cls._create_access_token(),
)
return RedisActions.set_json(
list_keys=accessObject.to_list(),
value=json.dumps(model),
expires=Auth.TOKEN_EXPIRE_MINUTES_30.seconds,
redis_action = RedisActions.set_json(
list_keys=access_object.to_list(),
value=model,
expires={"seconds": int(Auth.TOKEN_EXPIRE_MINUTES_30.seconds)},
)
if redis_action.status:
return access_object.accessToken
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Saving Token failed...",
)
@classmethod
def update_object_to_redis(cls, access_token: str, user_uu_id: str, model: Dict):
access_object = AccessToken(
userUUID=user_uu_id,
accessToken=access_token,
)
redis_action = RedisActions.set_json(
list_keys=access_object.to_list(),
value=model,
expires={"seconds": int(Auth.TOKEN_EXPIRE_MINUTES_30.seconds)},
)
if redis_action.status:
return access_object.accessToken
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Saving Token failed...",
)
@classmethod
@ -194,15 +232,17 @@ class TokenService:
"company_address": company_address,
}
)
person = People.filter_one(
People.id == user.person_id, db=db_session
).data
model_value = EmployeeTokenObject(
domain=domain,
user_type=UserType.employee.value,
user_uu_id=str(user.uu_id),
credentials=user.credentials(),
user_id=user.id,
person_id=user.person_id,
person_uu_id=str(user.person.uu_id),
person_id=person.id,
person_uu_id=str(person.uu_id),
request=dict(request.headers),
companies_uu_id_list=companies_uu_id_list,
companies_id_list=companies_id_list,
@ -211,8 +251,9 @@ class TokenService:
timezone=user.local_timezone or "GMT+0",
lang=user.lang or "tr",
).model_dump()
if cls.set_object_to_redis(user, model_value):
if access_token := cls.set_object_to_redis(user, model_value):
return {
"access_token": access_token,
"user_type": UserType.employee.name,
"companies_list": companies_list,
}
@ -228,7 +269,8 @@ class TokenService:
"""Remove all tokens for a user with specific domain."""
redis_rows = cls._get_user_tokens(user)
for redis_row in redis_rows.all:
if redis_row.get("domain") == domain:
print('redis_row', redis_row.data)
if redis_row.data.get("domain") == domain:
RedisActions.delete_key(redis_row.key)
@classmethod
@ -291,6 +333,36 @@ class TokenService:
"user": user.get_dict(),
}
@classmethod
def update_token_at_redis(
cls, request: "Request", add_payload: Union[CompanyToken, OccupantToken]
) -> Dict[str, Any]:
"""Update token at Redis."""
access_token = cls.get_access_token_from_request(request=request)
token_object = cls.get_object_via_access_key(access_token=access_token)
if isinstance(token_object, EmployeeTokenObject) and isinstance(add_payload, CompanyToken):
token_object.selected_company = add_payload
cls.update_object_to_redis(
access_token=access_token,
user_uu_id=token_object.user_uu_id,
model=token_object.model_dump()
)
return token_object.selected_company.model_dump()
elif isinstance(token_object, OccupantTokenObject) and isinstance(add_payload, OccupantToken):
token_object.selected_occupant = add_payload
cls.update_object_to_redis(
access_token=access_token,
user_uu_id=token_object.user_uu_id,
model=token_object.model_dump()
)
return token_object.selected_occupant.model_dump()
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Token not found",
)
@classmethod
def raise_error_if_request_has_no_token(cls, request: "Request") -> None:
"""Validate request has required token headers."""
@ -330,7 +402,6 @@ class TokenService:
redis_object["selected_company"] = None
if not redis_object.get("selected_occupant"):
redis_object["selected_occupant"] = None
if redis_object.get("user_type") == UserType.employee.value:
return EmployeeTokenObject(**redis_object)
elif redis_object.get("user_type") == UserType.occupant.value:
@ -348,17 +419,17 @@ class TokenService:
"""Get token object using access key."""
access_token_obj = AccessToken(accessToken=access_token)
redis_response = RedisActions.get_json(list_keys=access_token_obj.to_list())
if redis_object := redis_response.first.data:
access_token_obj.userUUID = redis_object.get("user_uu_id")
return cls._process_redis_object(redis_object)
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Invalid access token",
)
if not redis_response.status:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Access token token is not found or unable to retrieve",
)
if redis_object := redis_response.first:
redis_object_dict = redis_object.data
access_token_obj.userUUID = redis_object_dict.get("user_uu_id")
return cls._process_redis_object(redis_object_dict)
@classmethod
def get_object_via_user_uu_id(cls, user_id: str) -> T:

View File

@ -91,7 +91,15 @@ class OccupantTokenObject(ApplicationToken):
available_occupants: dict = None
selected_occupant: Optional[OccupantToken] = None # Selected Occupant Type
available_event: Optional[Any] = None
@property
def is_employee(self) -> bool:
return False
@property
def is_occupant(self) -> bool:
return True
class EmployeeTokenObject(ApplicationToken):
@ -104,4 +112,12 @@ class EmployeeTokenObject(ApplicationToken):
duty_uu_id_list: List[str] # List of duty objects
selected_company: Optional[CompanyToken] = None # Selected Company Object
available_event: Optional[Any] = None
@property
def is_employee(self) -> bool:
return True
@property
def is_occupant(self) -> bool:
return False

View File

@ -1,3 +1,5 @@
from ApiValidations.Request import BaseModelRegular
from typing import Optional
@ -55,15 +57,11 @@ class OccupantSelectionValidation:
class OccupantSelection(BaseModel, OccupantSelectionValidation):
occupant_uu_id: str = Field(..., example="123e4567-e89b-12d3-a456-426614174000")
build_part_uu_id: str = Field(..., example="987fcdeb-51a2-43e7-9876-543210987654")
build_living_space_uu_id: str = Field(..., example="987fcdeb-51a2-43e7-9876-543210987654")
model_config = ConfigDict(
json_schema_extra={
"example": {
"occupant_uu_id": "123e4567-e89b-12d3-a456-426614174000",
"build_part_uu_id": "987fcdeb-51a2-43e7-9876-543210987654",
}
"example": {"build_living_space_uu_id": "987fcdeb-51a2-43e7-9876-543210987654"}
}
)
@ -107,10 +105,10 @@ class Login(BaseModelRegular, LoginValidation):
model_config = ConfigDict(
json_schema_extra={
"example": {
"domain": "example.com",
"access_key": "user@example.com",
"password": "password123",
"remember_me": True,
"domain": "evyos.com.tr",
"access_key": "karatay.berkay.sup@evyos.com.tr",
"password": "string",
"remember_me": False
}
}
)

View File

@ -10,77 +10,9 @@ This module initializes and configures the FastAPI application with:
"""
import uvicorn
from fastapi import FastAPI
from create_routes import get_all_routers
from prometheus_fastapi_instrumentator import Instrumentator
from app_handler import setup_middleware, get_uvicorn_config
from create_file import setup_security_schema, configure_route_security
from open_api_creator import OpenAPISchemaCreator, create_openapi_schema
def create_app() -> FastAPI:
"""Create and configure the FastAPI application."""
app = FastAPI(
responses={
422: {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"detail": {
"type": "array",
"items": {
"type": "object",
"properties": {
"loc": {
"type": "array",
"items": {"type": "string"},
},
"msg": {"type": "string"},
"type": {"type": "string"},
},
},
}
},
}
}
},
}
}
)
# Get all routers and protected routes from the new configuration
routers, protected_routes = get_all_routers()
# Include all routers
for router in routers:
app.include_router(router)
# Configure OpenAPI schema with security
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
# Create OpenAPI schema using our custom creator
openapi_schema = create_openapi_schema(app)
# Add security scheme
openapi_schema.update(setup_security_schema())
# Configure security for protected routes
for path, methods in protected_routes.items():
for method in methods:
configure_route_security(
path, method, openapi_schema, list(protected_routes.keys())
)
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
return app
from create_file import create_app
app = create_app() # Initialize FastAPI application

View File

@ -14,6 +14,7 @@ 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
@ -29,11 +30,11 @@ def setup_security_schema() -> Dict[str, Any]:
return {
"components": {
"securitySchemes": {
"Bearer": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Enter the token",
"Bearer Auth": {
"type": "apiKey",
"in": "header",
"name": Auth.ACCESS_TOKEN_TAG,
"description": "Enter: **'Bearer <JWT>'**, where JWT is the access token",
}
}
}
@ -65,12 +66,15 @@ def create_app() -> FastAPI:
Returns:
FastAPI: Configured FastAPI application instance
"""
# Initialize FastAPI app
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:
@ -84,31 +88,5 @@ def create_app() -> FastAPI:
for router in routers:
app.include_router(router)
# Configure OpenAPI schema with security
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="WAG Management API",
version="4.0.0",
description="WAG Management API Service",
routes=app.routes,
)
# Add security scheme
security_schema = setup_security_schema()
openapi_schema.update(security_schema)
# Configure security for protected routes
for path, methods in protected_routes.items():
for method in methods:
configure_route_security(
path, method, openapi_schema, list(protected_routes.keys())
)
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
app.openapi = lambda app=app: create_openapi_schema(app)
return app

View File

@ -24,8 +24,6 @@ class EndpointFactoryConfig:
summary: str
description: str
endpoint_function: Callable[P, R] # Now accepts any parameters and return type
response_model: Optional[type] = None
request_model: Optional[type] = None
is_auth_required: bool = True
is_event_required: bool = False
extra_options: Dict[str, Any] = None
@ -56,7 +54,7 @@ class EnhancedEndpointFactory:
endpoint_function = config.endpoint_function
if config.is_auth_required:
endpoint_function = MiddlewareModule.auth_required(endpoint_function)
# 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:
@ -66,7 +64,6 @@ class EnhancedEndpointFactory:
# Register the endpoint with FastAPI router
getattr(self.router, config.method.lower())(
endpoint_path,
response_model=config.response_model,
summary=config.summary,
description=config.description,
**config.extra_options,

View File

@ -15,6 +15,7 @@ from ApiLibrary.common.line_number import get_line_number_for_error
from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi
from .base_context import BaseContext
from ApiServices.Token.token_handler import OccupantTokenObject, EmployeeTokenObject
import inspect
class AuthContext(BaseContext):
@ -42,7 +43,12 @@ class AuthContext(BaseContext):
@property
def user_id(self) -> str:
"""Get the user's UUID from token context."""
return self.token_context.user_uu_id if self.token_context else ""
return self.token_context.user_uu_id if self.token_context else None
def as_dict(self):
if not isinstance(self.token_context, dict):
return self.token_context.model_dump()
return self.token_context
def __repr__(self) -> str:
user_type = "Employee" if self.is_employee else "Occupant"
@ -57,7 +63,7 @@ class MiddlewareModule:
@staticmethod
def get_user_from_request(
request: Request,
) -> AuthContext:
) -> dict:
"""
Get authenticated token context from request.
@ -74,7 +80,6 @@ class MiddlewareModule:
# Get token and validate - will raise HTTPExceptionApi if invalid
redis_token = TokenService.get_access_token_from_request(request=request)
# Get token context - will validate token and raise appropriate errors
token_context = TokenService.get_object_via_access_key(access_token=redis_token)
if not token_context:
@ -85,7 +90,7 @@ class MiddlewareModule:
sys_msg="TokenService: Token Context couldnt retrieved from redis",
)
return AuthContext(token_context=token_context)
return token_context
@classmethod
def auth_required(cls, func: Callable) -> Callable:
@ -116,14 +121,13 @@ class MiddlewareModule:
"""
@wraps(func)
def wrapper(request: Request, *args, **kwargs):
async def wrapper(request: Request, *args, **kwargs):
# Get and validate token context from request
auth_context = cls.get_user_from_request(request)
# Attach auth context to function
func.auth = auth_context
# Create auth context and Attach auth context to both wrapper and original function
func.auth = cls.get_user_from_request(request) # This ensures the context is available in both places
# Call the original endpoint function
if inspect.iscoroutinefunction(func):
return await func(request, *args, **kwargs)
return func(request, *args, **kwargs)
return wrapper
@ -147,7 +151,6 @@ class RequestTimingMiddleware(BaseHTTPMiddleware):
Response: Processed response with timing headers
"""
start_time = perf_counter()
# Process the request
response = await call_next(request)

View File

@ -6,6 +6,7 @@ from functools import wraps
from typing import Callable, Dict, Any
from .auth_middleware import MiddlewareModule
from .base_context import BaseContext
import inspect
class TokenEventHandler(BaseContext):
@ -51,26 +52,14 @@ class TokenEventMiddleware:
authenticated_func = MiddlewareModule.auth_required(func)
@wraps(authenticated_func)
def wrapper(*args, **kwargs) -> Dict[str, Any]:
async def wrapper(*args, **kwargs) -> Dict[str, Any]:
# Create handler with context
handler = TokenEventHandler(
func=authenticated_func,
url_of_endpoint=authenticated_func.url_of_endpoint,
)
# Update event-specific context
handler.update_context(
function_code="7192c2aa-5352-4e36-98b3-dafb7d036a3d" # Keep function_code as URL
)
# Copy auth context from authenticated function
if hasattr(authenticated_func, "auth"):
handler.token_context = authenticated_func.auth.token_context
# Make handler available to the function
authenticated_func.handler = handler
function_code = "7192c2aa-5352-4e36-98b3-dafb7d036a3d" # Keep function_code as URL
# Make handler available to all functions in the chain
func.func_code = {"function_code": function_code}
# Call the authenticated function
if inspect.iscoroutinefunction(authenticated_func):
return await authenticated_func(*args, **kwargs)
return authenticated_func(*args, **kwargs)
return wrapper

View File

@ -13,6 +13,8 @@ from typing import Any, Dict, List, Optional, Set
from fastapi import FastAPI, APIRouter
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
@ -62,17 +64,11 @@ class OpenAPISchemaCreator:
"""
return {
"Bearer Auth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Enter the token with the `Bearer: ` prefix",
},
"API Key": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "Optional API key for service authentication",
},
"name": Auth.ACCESS_TOKEN_TAG,
"description": "Enter: **'Bearer <JWT>'**, where JWT is the access token",
}
}
def _create_common_responses(self) -> Dict[str, Any]:
@ -198,7 +194,6 @@ class OpenAPISchemaCreator:
if path in self.protected_routes and method in self.protected_routes[path]:
schema["paths"][path][method]["security"] = [
{"Bearer Auth": []},
{"API Key": []},
]
schema["paths"][path][method]["responses"].update(
self._create_common_responses()
@ -219,7 +214,7 @@ class OpenAPISchemaCreator:
openapi_schema = get_openapi(
title=Config.TITLE,
description=Config.DESCRIPTION,
version="1.0.0",
version="1.1.1",
routes=self.app.routes,
tags=self.tags_metadata,
)
@ -229,17 +224,15 @@ class OpenAPISchemaCreator:
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
# # Add custom documentation extensions
openapi_schema["x-documentation"] = {
"postman_collection": "/docs/postman",
"swagger_ui": "/docs",

View File

@ -251,18 +251,22 @@ class Event2Employee(CrudCollection):
@classmethod
def get_event_id_by_employee_id(cls, employee_id) -> list:
db = cls.new_session()
occupant_events = cls.filter_all(
cls.employee_id == employee_id,
db=db,
).data
active_events = Service2Events.filter_all(
Service2Events.service_id.in_(
[event.event_service_id for event in occupant_events]
),
db=db,
system=True,
).data
active_events_id = [event.event_id for event in active_events]
if extra_events := Event2EmployeeExtra.filter_all(
Event2EmployeeExtra.employee_id == employee_id
Event2EmployeeExtra.employee_id == employee_id,
db=db,
).data:
active_events_id.extend([event.event_id for event in extra_events])
return active_events_id

View File

@ -77,12 +77,7 @@ class RedisActions:
time=expiry_time,
value=redis_row.value,
)
redis_row.expires_at = (
arrow.now()
.shift(seconds=expiry_time)
.format(MainConfig.DATETIME_FORMAT)
)
redis_row.expires_at = str(arrow.now().shift(seconds=expiry_time).format(MainConfig.DATETIME_FORMAT))
else:
redis_cli.set(name=redis_row.redis_key, value=redis_row.value)
@ -115,7 +110,6 @@ class RedisActions:
list_of_rows = []
regex = RedisRow.regex(list_keys=list_keys)
json_get = redis_cli.scan_iter(match=regex)
for row in list(json_get):
redis_row = RedisRow()
redis_row.set_key(key=row)
@ -123,11 +117,16 @@ class RedisActions:
redis_value = redis_cli.get(redis_row.redis_key)
redis_row.feed(redis_value)
list_of_rows.append(redis_row)
if list_of_rows:
return RedisResponse(
status=True,
message="Value is get successfully.",
data=list_of_rows,
)
return RedisResponse(
status=True,
message="Value is get successfully.",
data=list_of_rows,
status=False,
message="Value is not get successfully.",
data=list_of_rows
)
except Exception as e:
return RedisResponse(
@ -135,3 +134,4 @@ class RedisActions:
message="Value is not get successfully.",
error=str(e),
)

View File

@ -94,7 +94,7 @@ class RedisRow:
# Filter and convert valid keys
valid_keys = []
for key in list_keys:
if key is None:
if key is None or str(key) == "None":
continue
if isinstance(key, bytes):
key = key.decode()
@ -108,7 +108,8 @@ class RedisRow:
# Add wildcard if first key was None
if list_keys[0] is None:
pattern = f"*{cls.delimiter}{pattern}"
if '*' not in pattern:
pattern = f"{pattern}:*"
return pattern
@classmethod

View File

@ -47,4 +47,7 @@ class RedisResponse:
@property
def first(self) -> Union[RedisRow, None]:
return self.data[0] if self.data else None
if self.data:
return self.data[0]
self.status = False
return

View File

@ -1,23 +1,23 @@
from typing import Optional
from typing import Optional, Literal
from uuid import UUID
from pydantic import BaseModel, validator
from pydantic import BaseModel, field_validator
class AccessToken(BaseModel):
accessToken: Optional[str] = None
userUUID: Optional[str] = None
userUUID: Optional[str | UUID] = None
@validator("userUUID", pre=True)
@field_validator("userUUID", mode="after")
def validate_uuid(cls, v):
"""Convert UUID to string during validation."""
if isinstance(v, UUID):
return str(v)
return v
if v is None:
return None
return str(v)
def to_list(self):
"""Convert to list for Redis storage."""
return [self.accessToken, self.userUUID]
return [self.accessToken, str(self.userUUID) if self.userUUID else None]
@property
def count(self):