services updated

This commit is contained in:
2025-03-25 10:43:25 +03:00
parent 92942af6f3
commit 79028493a9
298 changed files with 23435 additions and 5 deletions

View File

@@ -0,0 +1,39 @@
from fastapi import APIRouter
import uuid
from Events.Engine.abstract_class import CategoryCluster, MethodToEvent
class CreateRouterFromCluster:
def __init__(self, **kwargs):
self.prefix = kwargs.get("prefix")
self.tags = kwargs.get("tags")
self.include_in_schema = bool(kwargs.get("include_in_schema", True))
self.router = APIRouter(
prefix=self.prefix, tags=self.tags, include_in_schema=self.include_in_schema
)
class CreateEndpointFromCluster:
def __init__(self, **kwargs):
self.router: CategoryCluster = kwargs.get("router")
self.method_endpoint: MethodToEvent = kwargs.get("method_endpoint")
self.attach_router()
def attach_router(self):
method = getattr(self.router, self.method_endpoint.METHOD.lower())
# Create a unique operation ID based on the endpoint path, method, and a unique identifier
kwargs = {
"path": self.method_endpoint.URL,
"summary": self.method_endpoint.SUMMARY,
"description": self.method_endpoint.DESCRIPTION,
}
if (
hasattr(self.method_endpoint, "RESPONSE_MODEL")
and self.method_endpoint.RESPONSE_MODEL is not None
):
kwargs["response_model"] = self.method_endpoint.RESPONSE_MODEL
method(**kwargs)(self.method_endpoint.endpoint_callable)

View File

@@ -0,0 +1,4 @@
from Services.Redis import RedisActions, AccessToken
from Services.Redis.Models.cluster import RedisList
redis_list = RedisList(redis_key="test")

View File

@@ -0,0 +1,95 @@
from ApiLayers.ApiValidations.Request.authentication import Login
from ApiLayers.ApiLibrary.token.password_module import PasswordModule
from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error
from ApiLayers.ErrorHandlers import HTTPExceptionApi
class UserLoginModule:
def __init__(self, request: "Request"):
self.request = request
self.user = None
self.access_object = None
self.access_token = None
self.refresh_token = None
@property
def as_dict(self) -> dict:
return {
"user": self.user,
"access_object": self.access_object,
"access_token": self.access_token,
"refresh_token": self.refresh_token,
}
@staticmethod
def check_user_exists(access_key: str):
from ApiLayers.Schemas import Users
"""
Check if the user exists in the database.
"""
db_session = Users.new_session() # Check if user exists.
if "@" in access_key:
found_user: Users = Users.filter_one(
Users.email == access_key.lower(), db=db_session
).data
else:
found_user: Users = Users.filter_one(
Users.phone_number == access_key.replace(" ", ""), db=db_session
).data
if not found_user:
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang="en",
loc=get_line_number_for_error(),
sys_msg="check_user_exists: User not found",
)
return found_user
def login_user_via_credentials(self, access_data: "Login") -> None:
from ApiLayers.ApiServices.Token.token_handler import TokenService
from ApiLayers.Schemas import Users
"""
Login the user via the credentials.
"""
# Get the actual data from the BaseRequestModel if needed
found_user: Users = self.check_user_exists(access_key=access_data.access_key)
if len(found_user.hash_password) < 5:
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang=found_user.lang,
loc=get_line_number_for_error(),
sys_msg="login_user_via_credentials: Invalid password create a password to user first",
)
# Check if the password is correct
if PasswordModule.check_password(
domain=access_data.domain,
id_=found_user.uu_id,
password=access_data.password,
password_hashed=found_user.hash_password,
):
# Set the access token to the redis
token_response = TokenService.set_access_token_to_redis(
request=self.request,
user=found_user,
domain=access_data.domain,
remember=access_data.remember_me,
)
# Set the user and token information to the instance
self.user = found_user.get_dict()
self.access_token = token_response.get("access_token")
self.refresh_token = token_response.get("refresh_token")
self.access_object = {
"user_type": token_response.get("user_type", None),
"selection_list": token_response.get("selection_list", {}),
}
return None
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang="tr",
loc=get_line_number_for_error(),
sys_msg="login_user_via_credentials: raised an unknown error",
)

View File

@@ -0,0 +1,457 @@
"""Token service for handling authentication tokens and user sessions."""
from typing import List, Union, TypeVar, Dict, Any, TYPE_CHECKING
import arrow
from ApiLayers.AllConfigs.Token.config import Auth
from ApiLayers.ApiLibrary.common.line_number import get_line_number_for_error
from ApiLayers.ApiLibrary.date_time_actions.date_functions import DateTimeLocal
from ApiLayers.ApiLibrary.token.password_module import PasswordModule
from ApiLayers.ErrorHandlers import HTTPExceptionApi
from ApiLayers.ApiValidations.Custom.token_objects import (
EmployeeTokenObject,
OccupantTokenObject,
UserType,
CompanyToken,
OccupantToken,
)
from ApiLayers.Schemas import (
Users,
BuildLivingSpace,
BuildParts,
Employees,
Addresses,
Companies,
Staff,
Duty,
Duties,
Departments,
OccupantTypes,
)
from Services.Redis.Models.response import RedisResponse
from Services.Redis import RedisActions, AccessToken
if TYPE_CHECKING:
from fastapi import Request
T = TypeVar("T", EmployeeTokenObject, OccupantTokenObject)
class TokenService:
"""Service class for handling authentication tokens and user sessions."""
@classmethod
def _create_access_token(cls, access: bool = True) -> str:
"""Generate a new access token."""
if not access:
return PasswordModule.generate_token(Auth.REFRESHER_TOKEN_LENGTH)
return PasswordModule.generate_token(Auth.ACCESS_TOKEN_LENGTH)
@classmethod
def _get_user_tokens(cls, user: Users) -> RedisResponse:
"""Get all tokens for a user from Redis."""
return RedisActions.get_json(list_keys=[f"*:{str(user.uu_id)}"])
@classmethod
def do_employee_login(
cls, request: "Request", user: Users, domain: str
) -> Dict[str, Any]:
"""Handle employee login process and return login information."""
from ApiLayers.Schemas.identity.identity import UsersTokens, People
db_session = Employees.new_session()
list_employee = Employees.filter_all(
Employees.people_id == user.person_id, db=db_session
).data
companies_uu_id_list: List[str] = []
companies_id_list: List[int] = []
companies_list: List[Dict[str, Any]] = []
duty_uu_id_list: List[str] = []
duty_id_list: List[int] = []
for employee in list_employee:
staff = Staff.filter_one(Staff.id == employee.staff_id, db=db_session).data
if duties := Duties.filter_one(
Duties.id == staff.duties_id, db=db_session
).data:
if duty_found := Duty.filter_by_one(
id=duties.duties_id, db=db_session
).data:
duty_uu_id_list.append(str(duty_found.uu_id))
duty_id_list.append(duty_found.id)
department = Departments.filter_one(
Departments.id == duties.department_id, db=db_session
).data
if company := Companies.filter_one(
Companies.id == department.company_id, db=db_session
).data:
companies_uu_id_list.append(str(company.uu_id))
companies_id_list.append(company.id)
company_address = Addresses.filter_by_one(
id=company.official_address_id, db=db_session
).data
companies_list.append(
{
"uu_id": str(company.uu_id),
"public_name": company.public_name,
"company_type": company.company_type,
"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=person.id,
person_uu_id=str(person.uu_id),
full_name=person.full_name,
request=dict(request.headers),
companies_uu_id_list=companies_uu_id_list,
companies_id_list=companies_id_list,
duty_uu_id_list=duty_uu_id_list,
duty_id_list=duty_id_list,
timezone=user.local_timezone or "GMT+0",
lang="tr",
).model_dump()
if access_token := cls.set_object_to_redis(user, model_value):
return {
"access_token": access_token,
"user_type": UserType.employee.name,
"selection_list": companies_list,
}
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Creating Token failed...",
)
@classmethod
def do_occupant_login(
cls, request: "Request", user: Users, domain: str
) -> Dict[str, Any]:
"""Handle occupant login process and return login information."""
db_session = BuildLivingSpace.new_session()
living_spaces: list[BuildLivingSpace] = BuildLivingSpace.filter_all(
BuildLivingSpace.person_id == user.person_id, db=db_session
).data
if not living_spaces:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="User does not have any living space",
)
occupants_selection_dict: Dict[str, Any] = {}
for living_space in living_spaces:
build_parts_selection = BuildParts.filter_all(
BuildParts.id == living_space.build_parts_id,
db=db_session,
).data
if not build_parts_selection:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="User does not have any living space",
)
build_part = build_parts_selection.get(1)
build = build_part.buildings
occupant_type = OccupantTypes.filter_by_one(
id=living_space.occupant_type,
db=db_session,
system=True,
).data
occupant_data = {
"part_uu_id": str(build_part.uu_id),
"part_name": build_part.part_name,
"part_level": build_part.part_level,
"uu_id": str(occupant_type.uu_id),
"description": occupant_type.occupant_description,
"code": occupant_type.occupant_code,
}
build_key = str(build.uu_id)
if build_key not in occupants_selection_dict:
occupants_selection_dict[build_key] = {
"build_uu_id": build_key,
"build_name": build.build_name,
"build_no": build.build_no,
"occupants": [occupant_data],
}
else:
occupants_selection_dict[build_key]["occupants"].append(occupant_data)
person = user.person
model_value = OccupantTokenObject(
domain=domain,
user_type=UserType.occupant.value,
user_uu_id=str(user.uu_id),
credentials=user.credentials(),
user_id=user.id,
person_id=person.id,
person_uu_id=str(person.uu_id),
full_name=person.full_name,
request=dict(request.headers),
available_occupants=occupants_selection_dict,
timezone=user.local_timezone or "GMT+0",
lang="tr",
).model_dump()
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):
access_object = AccessToken(
userUUID=user.uu_id,
accessToken=cls._create_access_token(),
)
cls.remove_token_with_domain(user=user, domain=model.get("domain"))
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
def remove_token_with_domain(cls, user: Users, domain: str) -> None:
"""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.row.get("domain") == domain:
RedisActions.delete([redis_row.key])
@classmethod
def remove_all_token(cls, user: Users) -> None:
"""Remove all tokens for a user."""
redis_rows = cls._get_user_tokens(user)
RedisActions.delete([redis_row.key for redis_row in redis_rows.all])
@classmethod
def set_access_token_to_redis(
cls,
request: "Request",
user: Users,
domain: str,
remember: bool,
) -> Dict[str, Any]:
"""Set access token to redis and handle user session."""
from ApiLayers.AllConfigs.Token.config import Auth
from ApiLayers.Schemas.identity.identity import UsersTokens, People
cls.remove_token_with_domain(user=user, domain=domain)
# Users.client_arrow = DateTimeLocal(is_client=True, timezone=user.local_timezone)
login_dict, db_session = {}, UsersTokens.new_session()
if user.is_occupant: # Handle login based on user type
login_dict = cls.do_occupant_login(
request=request, user=user, domain=domain
)
elif user.is_employee:
login_dict = cls.do_employee_login(
request=request, user=user, domain=domain
)
# Handle remember me functionality
user.remember_me = bool(remember)
if remember:
users_token_created = cls._create_access_token(access=False)
login_dict["refresh_token"] = users_token_created
users_token = UsersTokens.find_or_create(
db=db_session,
user_id=user.id,
token_type="RememberMe",
domain=domain,
)
if users_token.meta_data.created:
users_token.token = users_token_created
users_token.save(db=db_session)
else:
if arrow.now() > arrow.get(
str(users_token.expires_at)
): # Check if token is expired
users_token.token = users_token_created
users_token.expires_at = str(
arrow.now().datetime + Auth.TOKEN_EXPIRE_DAY_1
)
users_token.save(db=db_session)
else:
login_dict["refresh_token"] = users_token.token
else:
already_refresher = UsersTokens.filter_all(
UsersTokens.user_id == user.id,
UsersTokens.token_type == "RememberMe",
UsersTokens.domain == domain,
db=db_session,
)
if already_refresher.count:
already_refresher.query.delete(synchronize_session=False)
user.save(db=db_session)
return {**login_dict, "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."""
if not hasattr(request, "headers"):
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Request has no headers",
)
if not request.headers.get(Auth.ACCESS_TOKEN_TAG):
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Request has no access token presented",
)
@classmethod
def access_token_is_valid(cls, request: "Request") -> bool:
"""Check if access token in request is valid."""
access_token = cls.get_access_token_from_request(request=request)
return RedisActions.get_json(
list_keys=AccessToken(accessToken=access_token).to_list()
).status
@classmethod
def get_access_token_from_request(cls, request: "Request") -> str:
"""Extract access token from request headers."""
cls.raise_error_if_request_has_no_token(request=request)
return request.headers.get(Auth.ACCESS_TOKEN_TAG)
@classmethod
def _process_redis_object(cls, redis_object: Dict[str, Any]) -> T:
"""Process Redis object and return appropriate token object."""
if not redis_object.get("selected_company"):
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:
return OccupantTokenObject(**redis_object)
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Unknown user type",
)
@classmethod
def get_object_via_access_key(cls, access_token: str) -> T:
"""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 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:
access_token_obj.userUUID = redis_object.get("user_uu_id")
return cls._process_redis_object(redis_object)
@classmethod
def get_object_via_user_uu_id(cls, user_id: str) -> T:
"""Get token object using user UUID."""
access_token = AccessToken(userUUID=user_id)
redis_response = RedisActions.get_json(list_keys=access_token.to_list())
if redis_object := redis_response.first.row:
access_token.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",
)

View File

@@ -0,0 +1,5 @@
from ApiLayers.ApiServices.Token.token_handler import TokenService
__all__ = [
"TokenService",
]