auth service up running

This commit is contained in:
berkay 2025-01-10 14:17:22 +03:00
parent 03accfed1b
commit 79aa3a1bc5
41 changed files with 480 additions and 340 deletions

View File

@ -22,7 +22,7 @@ def save_access_token_to_redis(
Employees, Employees,
Staff, Staff,
Addresses, Addresses,
OccupantTypes OccupantTypes,
) )
if not found_user: if not found_user:
@ -32,7 +32,9 @@ def save_access_token_to_redis(
) )
# Check user is already logged in or has a previous session # Check user is already logged in or has a previous session
already_tokens = AccessObjectActions.get_object_via_user_uu_id(user_id=found_user.uu_id) already_tokens = AccessObjectActions.get_object_via_user_uu_id(
user_id=found_user.uu_id
)
for key, token_user in already_tokens.items(): for key, token_user in already_tokens.items():
if token_user.get("domain", "") == domain: if token_user.get("domain", "") == domain:
redis_cli.delete(key) redis_cli.delete(key)

View File

@ -12,7 +12,7 @@ class AccessObjectActions:
cls, cls,
access_token, access_token,
model_object: typing.Union[OccupantTokenObject, EmployeeTokenObject], model_object: typing.Union[OccupantTokenObject, EmployeeTokenObject],
expiry_minutes: int = Auth.TOKEN_EXPIRE_MINUTES_30.total_seconds() // 60 expiry_minutes: int = Auth.TOKEN_EXPIRE_MINUTES_30.total_seconds() // 60,
) -> bool: ) -> bool:
"""Save access token object to Redis with expiry """Save access token object to Redis with expiry
Args: Args:
@ -28,17 +28,14 @@ class AccessObjectActions:
RedisActions.save_object_to_redis( RedisActions.save_object_to_redis(
access_token=access_token, access_token=access_token,
model_object=model_object, model_object=model_object,
expiry_minutes=expiry_minutes expiry_minutes=expiry_minutes,
) )
return True return True
except Exception as e: except Exception as e:
print("Save Object to Redis Error: ", e) print("Save Object to Redis Error: ", e)
raise HTTPException( raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=dict( detail=dict(message="Failed to save token to Redis", error=str(e)),
message="Failed to save token to Redis",
error=str(e)
),
) )
@classmethod @classmethod
@ -64,23 +61,21 @@ class AccessObjectActions:
if not hasattr(request, "headers"): if not hasattr(request, "headers"):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail=dict( detail=dict(message="Headers not found in request"),
message="Headers not found in request"
)
) )
access_token = request.headers.get(Auth.ACCESS_TOKEN_TAG) access_token = request.headers.get(Auth.ACCESS_TOKEN_TAG)
if not access_token: if not access_token:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail=dict( detail=dict(message="Unauthorized user, please login"),
message="Unauthorized user, please login"
),
) )
return access_token return access_token
@classmethod @classmethod
def get_token_object(cls, request) -> typing.Union[OccupantTokenObject, EmployeeTokenObject]: def get_token_object(
cls, request
) -> typing.Union[OccupantTokenObject, EmployeeTokenObject]:
"""Get and validate token object from request """Get and validate token object from request
Args: Args:
request: The request object request: The request object
@ -93,18 +88,16 @@ class AccessObjectActions:
return RedisActions.get_object_via_access_key(request) return RedisActions.get_object_via_access_key(request)
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED, detail=dict(message=str(e))
detail=dict(
message=str(e)
) )
)
@classmethod @classmethod
def get_object_via_access_key( def get_object_via_access_key(
cls, request, cls,
request,
) -> typing.Union[EmployeeTokenObject, OccupantTokenObject, None]: ) -> typing.Union[EmployeeTokenObject, OccupantTokenObject, None]:
from api_configs import Auth from api_configs import Auth
access_object = RedisActions.get_with_regex( access_object = RedisActions.get_with_regex(
value_regex=str(request.headers.get(Auth.ACCESS_TOKEN_TAG) + ":*") value_regex=str(request.headers.get(Auth.ACCESS_TOKEN_TAG) + ":*")
).data ).data
@ -120,7 +113,7 @@ class AccessObjectActions:
status_code=401, status_code=401,
detail=dict( detail=dict(
message="User type is not found in the token object. Please reach to your administrator." message="User type is not found in the token object. Please reach to your administrator."
) ),
) )

View File

@ -6,13 +6,15 @@ from typing import TypeVar, Union, Dict, Any, Optional, Type
from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject
TokenType = TypeVar('TokenType', bound=Union[EmployeeTokenObject, OccupantTokenObject]) TokenType = TypeVar("TokenType", bound=Union[EmployeeTokenObject, OccupantTokenObject])
class ActionsSchema(ABC): class ActionsSchema(ABC):
"""Base class for defining API action schemas. """Base class for defining API action schemas.
This class handles endpoint registration and validation in the database. This class handles endpoint registration and validation in the database.
""" """
def __init__(self, endpoint: str): def __init__(self, endpoint: str):
"""Initialize with an API endpoint path. """Initialize with an API endpoint path.
@ -38,7 +40,7 @@ class ActionsSchema(ABC):
"endpoint_desc": "Temporary endpoint", "endpoint_desc": "Temporary endpoint",
"endpoint_code": "dummy_code", "endpoint_code": "dummy_code",
"id": 1, "id": 1,
"uu_id": "dummy_uuid" "uu_id": "dummy_uuid",
} }
@ -47,6 +49,7 @@ class ActionsSchemaFactory:
This class validates and initializes action schemas for API endpoints. This class validates and initializes action schemas for API endpoints.
""" """
def __init__(self, action: ActionsSchema): def __init__(self, action: ActionsSchema):
"""Initialize with an action schema. """Initialize with an action schema.
@ -67,7 +70,7 @@ class ActionsSchemaFactory:
print(f"ActionsSchemaFactory Error: {e}") print(f"ActionsSchemaFactory Error: {e}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to initialize action schema" detail="Failed to initialize action schema",
) from e ) from e
@ -76,6 +79,7 @@ class MethodToEvent(ABC, ActionsSchemaFactory):
This class handles method registration and validation for API events. This class handles method registration and validation for API events.
""" """
action_key: Optional[str] = None action_key: Optional[str] = None
event_type: Optional[str] = None event_type: Optional[str] = None
event_description: str = "" event_description: str = ""
@ -106,11 +110,7 @@ class MethodToEvent(ABC, ActionsSchemaFactory):
return getattr(cls, function_name)(*args, **kwargs) return getattr(cls, function_name)(*args, **kwargs)
@classmethod @classmethod
def ban_token_objects( def ban_token_objects(cls, token: TokenType, ban_list: Type[TokenType]) -> None:
cls,
token: TokenType,
ban_list: Type[TokenType]
) -> None:
"""Check if a token type is banned from accessing an event. """Check if a token type is banned from accessing an event.
Args: Args:
@ -121,8 +121,10 @@ class MethodToEvent(ABC, ActionsSchemaFactory):
HTTPException: If token type matches banned type HTTPException: If token type matches banned type
""" """
if isinstance(token, ban_list): if isinstance(token, ban_list):
user_type = "employee" if isinstance(token, EmployeeTokenObject) else "occupant" user_type = (
"employee" if isinstance(token, EmployeeTokenObject) else "occupant"
)
raise HTTPException( raise HTTPException(
status_code=status.HTTP_406_NOT_ACCEPTABLE, status_code=status.HTTP_406_NOT_ACCEPTABLE,
detail=f"No {user_type} can reach this event. A notification has been sent to admin." detail=f"No {user_type} can reach this event. A notification has been sent to admin.",
) )

View File

@ -54,7 +54,7 @@ class AccountRecordsListEventMethods(MethodToEvent):
result=records, result=records,
cls_object=AccountRecords, cls_object=AccountRecords,
filter_attributes=list_options, filter_attributes=list_options,
response_model=AccountRecordResponse response_model=AccountRecordResponse,
) )
@classmethod @classmethod
@ -224,7 +224,9 @@ class AccountRecordsCreateEventMethods(MethodToEvent):
) )
account_record = AccountRecords.find_or_create(**data.excluded_dump()) account_record = AccountRecords.find_or_create(**data.excluded_dump())
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, message="Account record created successfully", result=account_record completed=True,
message="Account record created successfully",
result=account_record,
) )
elif isinstance(token_dict, EmployeeTokenObject): elif isinstance(token_dict, EmployeeTokenObject):
# Build.pre_query = Build.select_action( # Build.pre_query = Build.select_action(
@ -266,7 +268,9 @@ class AccountRecordsCreateEventMethods(MethodToEvent):
account_record = AccountRecords.insert_one(data_dict).data account_record = AccountRecords.insert_one(data_dict).data
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, message="Account record created successfully", result=account_record completed=True,
message="Account record created successfully",
result=account_record,
) )
@ -297,7 +301,9 @@ class AccountRecordsUpdateEventMethods(MethodToEvent):
account_record = AccountRecords.update_one(build_uu_id, data).data account_record = AccountRecords.update_one(build_uu_id, data).data
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, message="Account record updated successfully", result=account_record completed=True,
message="Account record updated successfully",
result=account_record,
) )
@ -323,7 +329,9 @@ class AccountRecordsPatchEventMethods(MethodToEvent):
): ):
account_record = AccountRecords.patch_one(build_uu_id, data).data account_record = AccountRecords.patch_one(build_uu_id, data).data
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, message="Account record patched successfully", result=account_record completed=True,
message="Account record patched successfully",
result=account_record,
) )

View File

@ -138,7 +138,9 @@ class AddressCreateEventMethods(MethodToEvent):
address.update(is_confirmed=True) address.update(is_confirmed=True)
address.save() address.save()
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, message="Address created successfully", result=address.get_dict() completed=True,
message="Address created successfully",
result=address.get_dict(),
) )
@ -148,6 +150,7 @@ class AddressSearchEventMethods(MethodToEvent):
This class handles address search functionality including text search This class handles address search functionality including text search
and filtering. and filtering.
""" """
event_type = "SEARCH" event_type = "SEARCH"
event_description = "Search for addresses using text and filters" event_description = "Search for addresses using text and filters"
event_category = "Address" event_category = "Address"
@ -161,10 +164,7 @@ class AddressSearchEventMethods(MethodToEvent):
@classmethod @classmethod
def _build_order_clause( def _build_order_clause(
cls, cls, filter_list: Dict[str, Any], schemas: List[str], filter_table: Any
filter_list: Dict[str, Any],
schemas: List[str],
filter_table: Any
) -> Any: ) -> Any:
"""Build the ORDER BY clause for the query. """Build the ORDER BY clause for the query.
@ -187,7 +187,11 @@ class AddressSearchEventMethods(MethodToEvent):
# Build order clause # Build order clause
field = getattr(filter_table, filter_list.get("order_field")) field = getattr(filter_table, filter_list.get("order_field"))
return field.desc() if str(filter_list.get("order_type"))[0] == "d" else field.asc() return (
field.desc()
if str(filter_list.get("order_type"))[0] == "d"
else field.asc()
)
@classmethod @classmethod
def _format_record(cls, record: Any, schemas: List[str]) -> Dict[str, str]: def _format_record(cls, record: Any, schemas: List[str]) -> Dict[str, str]:
@ -236,7 +240,7 @@ class AddressSearchEventMethods(MethodToEvent):
if not search_result: if not search_result:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail="No addresses found matching search criteria" detail="No addresses found matching search criteria",
) )
query = search_result.get("query") query = search_result.get("query")
@ -270,9 +274,7 @@ class AddressSearchEventMethods(MethodToEvent):
print(f"Address search completed in {duration:.3f}s") print(f"Address search completed in {duration:.3f}s")
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True, message="Address search results", result=results
message="Address search results",
result=results
) )
except HTTPException as e: except HTTPException as e:
@ -283,7 +285,7 @@ class AddressSearchEventMethods(MethodToEvent):
print(f"Address search error: {str(e)}") print(f"Address search error: {str(e)}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to search addresses" detail="Failed to search addresses",
) from e ) from e
@ -321,7 +323,9 @@ class AddressUpdateEventMethods(MethodToEvent):
updated_address = address.update(**data_dict) updated_address = address.update(**data_dict)
updated_address.save() updated_address.save()
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, message="Address updated successfully", result=updated_address.get_dict() completed=True,
message="Address updated successfully",
result=updated_address.get_dict(),
) )
elif isinstance(token_dict, OccupantTokenObject): elif isinstance(token_dict, OccupantTokenObject):
raise HTTPException( raise HTTPException(
@ -368,7 +372,9 @@ class AddressPatchEventMethods(MethodToEvent):
patched_address = address.patch(**data_dict) patched_address = address.patch(**data_dict)
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, message="Address patched successfully", result=patched_address.get_dict() completed=True,
message="Address patched successfully",
result=patched_address.get_dict(),
) )
@ -416,7 +422,9 @@ class AddressPostCodeCreateEventMethods(MethodToEvent):
relation_table.update(is_confirmed=True) relation_table.update(is_confirmed=True)
relation_table.save() relation_table.save()
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, message="Post code created successfully", result=post_code.get_dict() completed=True,
message="Post code created successfully",
result=post_code.get_dict(),
) )
@ -457,7 +465,9 @@ class AddressPostCodeUpdateEventMethods(MethodToEvent):
updated_post_code = post_code.update(**data_dict) updated_post_code = post_code.update(**data_dict)
updated_post_code.save() updated_post_code.save()
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, message="Post code updated successfully", result=updated_post_code.get_dict() completed=True,
message="Post code updated successfully",
result=updated_post_code.get_dict(),
) )
elif isinstance(token_dict, OccupantTokenObject): elif isinstance(token_dict, OccupantTokenObject):
raise HTTPException( raise HTTPException(

View File

@ -13,10 +13,21 @@ from api_configs import Auth, ApiStatic
from api_events.events.abstract_class import MethodToEvent, ActionsSchema from api_events.events.abstract_class import MethodToEvent, ActionsSchema
from databases import ( from databases import (
Companies, Staff, Duties, Departments, Employees, Companies,
BuildLivingSpace, BuildParts, Build, Duty, Event2Occupant, Staff,
Event2Employee, Users, UsersTokens, OccupantTypes, Duties,
RelationshipEmployee2Build Departments,
Employees,
BuildLivingSpace,
BuildParts,
Build,
Duty,
Event2Occupant,
Event2Employee,
Users,
UsersTokens,
OccupantTypes,
RelationshipEmployee2Build,
) )
from api_services import ( from api_services import (
@ -28,17 +39,23 @@ from api_services import (
) )
from api_validations.validations_request import ( from api_validations.validations_request import (
Login, Logout, ChangePassword, Remember, Login,
Forgot, CreatePassword, OccupantSelection, Logout,
ChangePassword,
Remember,
Forgot,
CreatePassword,
OccupantSelection,
EmployeeSelection, EmployeeSelection,
) )
from api_validations.validations_response import ( from api_validations.validations_response import (
AuthenticationLoginResponse, AuthenticationLoginResponse,
AuthenticationRefreshResponse, AuthenticationRefreshResponse,
AuthenticationUserInfoResponse AuthenticationUserInfoResponse,
) )
class AuthenticationLoginEventMethods(MethodToEvent): class AuthenticationLoginEventMethods(MethodToEvent):
event_type = "LOGIN" event_type = "LOGIN"
event_description = "Login via domain and access key : [email] | [phone]" event_description = "Login via domain and access key : [email] | [phone]"
@ -59,14 +76,17 @@ class AuthenticationLoginEventMethods(MethodToEvent):
if not found_user: if not found_user:
user_logger.log_login_attempt( user_logger.log_login_attempt(
request, None, data.domain, data.access_key, request,
success=False, error="Invalid credentials" None,
data.domain,
data.access_key,
success=False,
error="Invalid credentials",
) )
return ResponseHandler.unauthorized("Invalid credentials") return ResponseHandler.unauthorized("Invalid credentials")
user_logger.log_login_attempt( user_logger.log_login_attempt(
request, found_user.id, data.domain, data.access_key, request, found_user.id, data.domain, data.access_key, success=True
success=True
) )
response_data = { response_data = {
@ -78,17 +98,13 @@ class AuthenticationLoginEventMethods(MethodToEvent):
return ResponseHandler.success( return ResponseHandler.success(
message="User logged in successfully", message="User logged in successfully",
data=response_data, data=response_data,
response_model=AuthenticationLoginResponse response_model=AuthenticationLoginResponse,
) )
except Exception as e: except Exception as e:
user_logger.log_login_attempt( user_logger.log_login_attempt(
request, None, data.domain, data.access_key, request, None, data.domain, data.access_key, success=False, error=str(e)
success=False, error=str(e)
)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=str(e)
) )
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e))
class AuthenticationSelectEventMethods(MethodToEvent): class AuthenticationSelectEventMethods(MethodToEvent):
@ -105,14 +121,13 @@ class AuthenticationSelectEventMethods(MethodToEvent):
@classmethod @classmethod
def _handle_employee_selection( def _handle_employee_selection(
cls, cls, data: EmployeeSelection, token_dict: EmployeeTokenObject, request: Request
data: EmployeeSelection,
token_dict: EmployeeTokenObject,
request: Request
): ):
"""Handle employee company selection""" """Handle employee company selection"""
if data.company_uu_id not in token_dict.companies_uu_id_list: if data.company_uu_id not in token_dict.companies_uu_id_list:
return ResponseHandler.unauthorized("Company not found in user's company list") return ResponseHandler.unauthorized(
"Company not found in user's company list"
)
selected_company = Companies.filter_one( selected_company = Companies.filter_one(
Companies.uu_id == data.company_uu_id Companies.uu_id == data.company_uu_id
@ -122,29 +137,27 @@ class AuthenticationSelectEventMethods(MethodToEvent):
# Get department IDs for the company # Get department IDs for the company
department_ids = [ department_ids = [
dept.id for dept in Departments.filter_all( dept.id
for dept in Departments.filter_all(
Departments.company_id == selected_company.id Departments.company_id == selected_company.id
).data ).data
] ]
# Get duties IDs for the company # Get duties IDs for the company
duties_ids = [ duties_ids = [
duty.id for duty in Duties.filter_all( duty.id
Duties.company_id == selected_company.id for duty in Duties.filter_all(Duties.company_id == selected_company.id).data
).data
] ]
# Get staff IDs # Get staff IDs
staff_ids = [ staff_ids = [
staff.id for staff in Staff.filter_all( staff.id for staff in Staff.filter_all(Staff.duties_id.in_(duties_ids)).data
Staff.duties_id.in_(duties_ids)
).data
] ]
# Get employee # Get employee
employee = Employees.filter_one( employee = Employees.filter_one(
Employees.people_id == token_dict.person_id, Employees.people_id == token_dict.person_id,
Employees.staff_id.in_(staff_ids) Employees.staff_id.in_(staff_ids),
).data ).data
if not employee: if not employee:
@ -158,16 +171,14 @@ class AuthenticationSelectEventMethods(MethodToEvent):
# Get staff and duties # Get staff and duties
staff = Staff.filter_one(Staff.id == employee.staff_id).data staff = Staff.filter_one(Staff.id == employee.staff_id).data
duties = Duties.filter_one(Duties.id == staff.duties_id).data duties = Duties.filter_one(Duties.id == staff.duties_id).data
department = Departments.filter_one( department = Departments.filter_one(Departments.id == duties.department_id).data
Departments.id == duties.department_id
).data
# Get bulk duty # Get bulk duty
bulk_id = Duty.filter_by_one(system=True, duty_code="BULK").data bulk_id = Duty.filter_by_one(system=True, duty_code="BULK").data
bulk_duty_id = Duties.filter_by_one( bulk_duty_id = Duties.filter_by_one(
company_id=selected_company.id, company_id=selected_company.id,
duties_id=bulk_id.id, duties_id=bulk_id.id,
**Duties.valid_record_dict **Duties.valid_record_dict,
).data ).data
# Create company token # Create company token
@ -183,7 +194,7 @@ class AuthenticationSelectEventMethods(MethodToEvent):
staff_uu_id=staff.uu_id.__str__(), staff_uu_id=staff.uu_id.__str__(),
employee_id=employee.id, employee_id=employee.id,
employee_uu_id=employee.uu_id.__str__(), employee_uu_id=employee.uu_id.__str__(),
reachable_event_list_id=reachable_event_list_id reachable_event_list_id=reachable_event_list_id,
) )
# Update Redis # Update Redis
@ -192,24 +203,19 @@ class AuthenticationSelectEventMethods(MethodToEvent):
@classmethod @classmethod
def _handle_occupant_selection( def _handle_occupant_selection(
cls, cls, data: OccupantSelection, token_dict: OccupantTokenObject, request: Request
data: OccupantSelection,
token_dict: OccupantTokenObject,
request: Request
): ):
"""Handle occupant type selection""" """Handle occupant type selection"""
# Get occupant type # Get occupant type
occupant_type = OccupantTypes.filter_by_one( occupant_type = OccupantTypes.filter_by_one(
system=True, system=True, uu_id=data.occupant_uu_id
uu_id=data.occupant_uu_id
).data ).data
if not occupant_type: if not occupant_type:
return ResponseHandler.not_found("Occupant Type not found") return ResponseHandler.not_found("Occupant Type not found")
# Get build part # Get build part
build_part = BuildParts.filter_by_one( build_part = BuildParts.filter_by_one(
system=True, system=True, uu_id=data.build_part_uu_id
uu_id=data.build_part_uu_id
).data ).data
if not build_part: if not build_part:
return ResponseHandler.not_found("Build Part not found") return ResponseHandler.not_found("Build Part not found")
@ -230,7 +236,7 @@ class AuthenticationSelectEventMethods(MethodToEvent):
selected_occupant_type = BuildLivingSpace.filter_one( selected_occupant_type = BuildLivingSpace.filter_one(
BuildLivingSpace.occupant_type == occupant_type.id, BuildLivingSpace.occupant_type == occupant_type.id,
BuildLivingSpace.person_id == token_dict.person_id, BuildLivingSpace.person_id == token_dict.person_id,
BuildLivingSpace.build_parts_id == build_part.id BuildLivingSpace.build_parts_id == build_part.id,
).data ).data
if not selected_occupant_type: if not selected_occupant_type:
return ResponseHandler.not_found("Selected occupant type not found") return ResponseHandler.not_found("Selected occupant type not found")
@ -255,7 +261,7 @@ class AuthenticationSelectEventMethods(MethodToEvent):
responsible_employee_uuid=responsible_employee.uu_id.__str__(), responsible_employee_uuid=responsible_employee.uu_id.__str__(),
responsible_company_id=company_related.id, responsible_company_id=company_related.id,
responsible_company_uuid=company_related.uu_id.__str__(), responsible_company_uuid=company_related.uu_id.__str__(),
reachable_event_list_id=reachable_event_list_id reachable_event_list_id=reachable_event_list_id,
) )
# Update Redis # Update Redis
@ -276,13 +282,11 @@ class AuthenticationSelectEventMethods(MethodToEvent):
elif isinstance(token_dict, OccupantTokenObject): elif isinstance(token_dict, OccupantTokenObject):
return cls._handle_occupant_selection(data, token_dict, request) return cls._handle_occupant_selection(data, token_dict, request)
return ResponseHandler.error( return ResponseHandler.error(
"Invalid token type", "Invalid token type", status_code=status.HTTP_400_BAD_REQUEST
status_code=status.HTTP_400_BAD_REQUEST
) )
except Exception as e: except Exception as e:
return ResponseHandler.error( return ResponseHandler.error(
str(e), str(e), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
) )
@ -331,9 +335,7 @@ class AuthenticationRefreshEventMethods(MethodToEvent):
return ResponseHandler.unauthorized() return ResponseHandler.unauthorized()
# Get user and token info # Get user and token info
found_user = Users.filter_one( found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data
Users.uu_id == token_dict.user_uu_id
).data
if not found_user: if not found_user:
return ResponseHandler.not_found("User not found") return ResponseHandler.not_found("User not found")
@ -349,12 +351,12 @@ class AuthenticationRefreshEventMethods(MethodToEvent):
response_data = { response_data = {
"access_token": access_token, "access_token": access_token,
"refresh_token": getattr(user_token, "token", None), "refresh_token": getattr(user_token, "token", None),
"user": found_user.get_dict() "user": found_user.get_dict(),
} }
return ResponseHandler.success( return ResponseHandler.success(
"User info refreshed successfully", "User info refreshed successfully",
data=response_data, data=response_data,
response_model=AuthenticationRefreshResponse response_model=AuthenticationRefreshResponse,
) )
except Exception as e: except Exception as e:
return ResponseHandler.error(str(e)) return ResponseHandler.error(str(e))
@ -381,7 +383,9 @@ class AuthenticationChangePasswordEventMethods(MethodToEvent):
): ):
try: try:
if not isinstance(token_dict, EmployeeTokenObject): if not isinstance(token_dict, EmployeeTokenObject):
return ResponseHandler.unauthorized("Only employees can change password") return ResponseHandler.unauthorized(
"Only employees can change password"
)
found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data
if not found_user: if not found_user:
@ -389,22 +393,27 @@ class AuthenticationChangePasswordEventMethods(MethodToEvent):
if not found_user.check_password(data.old_password): if not found_user.check_password(data.old_password):
user_logger.log_password_change( user_logger.log_password_change(
request, found_user.id, "change", request,
success=False, error="Invalid old password" found_user.id,
"change",
success=False,
error="Invalid old password",
) )
return ResponseHandler.unauthorized("Old password is incorrect") return ResponseHandler.unauthorized("Old password is incorrect")
found_user.set_password(data.new_password) found_user.set_password(data.new_password)
user_logger.log_password_change( user_logger.log_password_change(
request, found_user.id, "change", request, found_user.id, "change", success=True
success=True
) )
return ResponseHandler.success("Password changed successfully") return ResponseHandler.success("Password changed successfully")
except Exception as e: except Exception as e:
user_logger.log_password_change( user_logger.log_password_change(
request, found_user.id if found_user else None, request,
"change", success=False, error=str(e) found_user.id if found_user else None,
"change",
success=False,
error=str(e),
) )
return ResponseHandler.error(str(e)) return ResponseHandler.error(str(e))
@ -471,7 +480,9 @@ class AuthenticationDisconnectUserEventMethods(MethodToEvent):
found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data found_user = Users.filter_one(Users.uu_id == token_dict.user_uu_id).data
if not found_user: if not found_user:
return ResponseHandler.not_found("User not found") return ResponseHandler.not_found("User not found")
if already_tokens := RedisActions.get_object_via_user_uu_id(user_id=str(found_user.uu_id)): if already_tokens := RedisActions.get_object_via_user_uu_id(
user_id=str(found_user.uu_id)
):
for key, token_user in already_tokens.items(): for key, token_user in already_tokens.items():
RedisActions.delete_key(key) RedisActions.delete_key(key)
selected_user = Users.filter_one( selected_user = Users.filter_one(
@ -566,7 +577,7 @@ class AuthenticationRefreshTokenEventMethods(MethodToEvent):
return ResponseHandler.success( return ResponseHandler.success(
"User is logged in successfully via refresher token", "User is logged in successfully via refresher token",
data=response_data, data=response_data,
response_model=AuthenticationRefreshResponse response_model=AuthenticationRefreshResponse,
) )
return ResponseHandler.not_found("Invalid data") return ResponseHandler.not_found("Invalid data")
@ -697,7 +708,7 @@ class AuthenticationDownloadAvatarEventMethods(MethodToEvent):
return ResponseHandler.success( return ResponseHandler.success(
"Avatar and profile is shared via user credentials", "Avatar and profile is shared via user credentials",
data=user_info, data=user_info,
response_model=AuthenticationUserInfoResponse response_model=AuthenticationUserInfoResponse,
) )
return ResponseHandler.not_found("Invalid data") return ResponseHandler.not_found("Invalid data")

View File

@ -59,7 +59,7 @@ class BuildListEventMethods(MethodToEvent):
result=records, result=records,
cls_object=Build, cls_object=Build,
filter_attributes=list_options, filter_attributes=list_options,
response_model=BuildResponse response_model=BuildResponse,
) )
@ -213,7 +213,7 @@ class BuildUpdateEventMethods(MethodToEvent):
completed=False, completed=False,
message="Building not found", message="Building not found",
result={}, result={},
status_code="HTTP_404_NOT_FOUND" status_code="HTTP_404_NOT_FOUND",
) )
build.update(**data.excluded_dump()) build.update(**data.excluded_dump())
@ -256,7 +256,7 @@ class BuildPatchEventMethods(MethodToEvent):
completed=False, completed=False,
message="Building not found", message="Building not found",
result={}, result={},
status_code="HTTP_404_NOT_FOUND" status_code="HTTP_404_NOT_FOUND",
) )
build.update(**data.excluded_dump()) build.update(**data.excluded_dump())

View File

@ -56,7 +56,7 @@ class BuildAreaListEventMethods(MethodToEvent):
result=records, result=records,
cls_object=BuildArea, cls_object=BuildArea,
filter_attributes=list_options, filter_attributes=list_options,
response_model=BuildResponse response_model=BuildResponse,
) )
@ -112,9 +112,7 @@ class BuildAreaCreateEventMethods(MethodToEvent):
data_dict["build_uu_id"] = str(selected_build.uu_id) data_dict["build_uu_id"] = str(selected_build.uu_id)
area = BuildArea.insert_one(data_dict).data area = BuildArea.insert_one(data_dict).data
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True, message="Building area created successfully", result=area
message="Building area created successfully",
result=area
) )
@ -137,9 +135,7 @@ class BuildAreaUpdateEventMethods(MethodToEvent):
): ):
area = BuildArea.update_one(build_uu_id, data).data area = BuildArea.update_one(build_uu_id, data).data
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True, message="Building area updated successfully", result=area
message="Building area updated successfully",
result=area
) )
@ -162,9 +158,7 @@ class BuildAreaPatchEventMethods(MethodToEvent):
): ):
area = BuildArea.patch_one(build_uu_id, data).data area = BuildArea.patch_one(build_uu_id, data).data
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True, message="Building area patched successfully", result=area
message="Building area patched successfully",
result=area
) )

View File

@ -14,6 +14,7 @@ from databases import (
BuildParts, BuildParts,
) )
class BuildingBuildPartsListEventMethods(MethodToEvent): class BuildingBuildPartsListEventMethods(MethodToEvent):
event_type = "SELECT" event_type = "SELECT"

View File

@ -64,7 +64,7 @@ class BuildSitesListEventMethods(MethodToEvent):
result=records, result=records,
cls_object=BuildSites, cls_object=BuildSites,
filter_attributes=list_options, filter_attributes=list_options,
response_model=BuildSitesResponse response_model=BuildSitesResponse,
) )
@ -110,9 +110,7 @@ class BuildSitesCreateEventMethods(MethodToEvent):
data_dict = data.excluded_dump() data_dict = data.excluded_dump()
site = BuildSites.insert_one(data_dict).data site = BuildSites.insert_one(data_dict).data
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True, message="Building site created successfully", result=site
message="Building site created successfully",
result=site
) )
@ -135,9 +133,7 @@ class BuildSitesUpdateEventMethods(MethodToEvent):
): ):
site = BuildSites.update_one(build_uu_id, data).data site = BuildSites.update_one(build_uu_id, data).data
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True, message="Building site updated successfully", result=site
message="Building site updated successfully",
result=site
) )
@ -160,9 +156,7 @@ class BuildSitesPatchEventMethods(MethodToEvent):
): ):
site = BuildSites.patch_one(build_uu_id, data).data site = BuildSites.patch_one(build_uu_id, data).data
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True, message="Building site patched successfully", result=site
message="Building site patched successfully",
result=site
) )

View File

@ -16,9 +16,7 @@ class BuildTypesListEventMethods(MethodToEvent):
__event_keys__ = { __event_keys__ = {
"5344d03c-fc47-43ec-8c44-6c2acd7e5d9f": "build_types_list", "5344d03c-fc47-43ec-8c44-6c2acd7e5d9f": "build_types_list",
} }
__event_validation__ = { __event_validation__ = {"5344d03c-fc47-43ec-8c44-6c2acd7e5d9f": BuildTypesResponse}
"5344d03c-fc47-43ec-8c44-6c2acd7e5d9f": BuildTypesResponse
}
@classmethod @classmethod
def build_types_list( def build_types_list(
@ -37,7 +35,7 @@ class BuildTypesListEventMethods(MethodToEvent):
result=results, result=results,
cls_object=BuildTypes, cls_object=BuildTypes,
filter_attributes=list_options, filter_attributes=list_options,
response_model=BuildTypesListResponse response_model=BuildTypesListResponse,
) )
elif isinstance(token_dict, OccupantTokenObject): elif isinstance(token_dict, OccupantTokenObject):
return AlchemyJsonResponse( return AlchemyJsonResponse(
@ -46,7 +44,7 @@ class BuildTypesListEventMethods(MethodToEvent):
result=None, result=None,
cls_object=BuildTypes, cls_object=BuildTypes,
filter_attributes=list_options, filter_attributes=list_options,
response_model=BuildTypesResponse response_model=BuildTypesResponse,
) )

View File

@ -73,7 +73,7 @@ class BuildingLivingSpacesListEventMethods(MethodToEvent):
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True,
message="Living spaces listed successfully", message="Living spaces listed successfully",
result=records result=records,
) )
elif isinstance(token_dict, EmployeeTokenObject): elif isinstance(token_dict, EmployeeTokenObject):
build_id_list_query = Build.select_action( build_id_list_query = Build.select_action(
@ -217,7 +217,7 @@ class BuildingLivingSpacesCreateEventMethods(MethodToEvent):
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True,
message="Living space created successfully", message="Living space created successfully",
result=created_living_space result=created_living_space,
) )
@ -307,7 +307,7 @@ class BuildingLivingSpacesUpdateEventMethods(MethodToEvent):
return AlchemyJsonResponse( return AlchemyJsonResponse(
completed=True, completed=True,
message="Living space updated successfully", message="Living space updated successfully",
result=living_space result=living_space,
) )

View File

@ -108,7 +108,7 @@ class CompanyUpdateEventMethods(MethodToEvent):
completed=False, completed=False,
message="Company not found", message="Company not found",
result={}, result={},
status_code="HTTP_404_NOT_FOUND" status_code="HTTP_404_NOT_FOUND",
) )
company.save() company.save()
return AlchemyJsonResponse( return AlchemyJsonResponse(
@ -142,7 +142,7 @@ class CompanyPatchEventMethods(MethodToEvent):
completed=False, completed=False,
message="Company not found", message="Company not found",
result={}, result={},
status_code="HTTP_404_NOT_FOUND" status_code="HTTP_404_NOT_FOUND",
) )
company.save() company.save()
return AlchemyJsonResponse( return AlchemyJsonResponse(

View File

@ -4,17 +4,16 @@ from fastapi import status
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from api_validations.validations_response import PeopleListResponse from api_validations.validations_response import PeopleListResponse
from api_validations.validations_request import InsertPerson, UpdateUsers
from api_events.events.abstract_class import MethodToEvent, ActionsSchema
from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject
from ApiServices.api_handlers import AlchemyJsonResponse
from databases import ( from databases import (
People, People,
Users, Users,
Companies, Companies,
) )
from api_validations.validations_request import InsertPerson, UpdateUsers
from api_events.events.abstract_class import MethodToEvent, ActionsSchema
from api_objects.auth.token_objects import EmployeeTokenObject, OccupantTokenObject
from ApiServices.api_handlers import AlchemyJsonResponse
class PeopleListEventMethods(MethodToEvent): class PeopleListEventMethods(MethodToEvent):

View File

@ -8,7 +8,9 @@ class DateTimeLocal:
def __init__(self, timezone: str = None, is_client: bool = True): def __init__(self, timezone: str = None, is_client: bool = True):
if timezone and timezone not in Config.SUPPORTED_TIMEZONES: if timezone and timezone not in Config.SUPPORTED_TIMEZONES:
raise ValueError(f"Unsupported timezone: {timezone}. Must be one of {Config.SUPPORTED_TIMEZONES}") raise ValueError(
f"Unsupported timezone: {timezone}. Must be one of {Config.SUPPORTED_TIMEZONES}"
)
self.timezone = Config.SYSTEM_TIMEZONE self.timezone = Config.SYSTEM_TIMEZONE
if is_client: if is_client:
@ -84,11 +86,11 @@ class DateTimeLocal:
components = [str(base_key)] components = [str(base_key)]
components.extend(str(arg) for arg in args) components.extend(str(arg) for arg in args)
components.append(f"tz_{self.timezone}") components.append(f"tz_{self.timezone}")
return ':'.join(components) return ":".join(components)
def format_for_db(self, date): def format_for_db(self, date):
"""Format date for database storage""" """Format date for database storage"""
return self.get(date).format('YYYY-MM-DD HH:mm:ss.SSSZZ') return self.get(date).format("YYYY-MM-DD HH:mm:ss.SSSZZ")
def parse_from_db(self, date_str): def parse_from_db(self, date_str):
"""Parse date from database format""" """Parse date from database format"""
@ -99,16 +101,17 @@ class DateTimeLocal:
def get_day_boundaries(self, date=None): def get_day_boundaries(self, date=None):
"""Get start and end of day in current timezone""" """Get start and end of day in current timezone"""
dt = self.get(date) if date else self.now() dt = self.get(date) if date else self.now()
start = dt.floor('day') start = dt.floor("day")
end = dt.ceil('day') end = dt.ceil("day")
return start, end return start, end
def get_month_boundaries(self, date=None): def get_month_boundaries(self, date=None):
"""Get start and end of month in current timezone""" """Get start and end of month in current timezone"""
dt = self.get(date) if date else self.now() dt = self.get(date) if date else self.now()
start = dt.floor('month') start = dt.floor("month")
end = dt.ceil('month') end = dt.ceil("month")
return start, end return start, end
client_arrow = DateTimeLocal(is_client=True) client_arrow = DateTimeLocal(is_client=True)
system_arrow = DateTimeLocal(is_client=False) system_arrow = DateTimeLocal(is_client=False)

View File

@ -4,6 +4,7 @@ from typing import Optional, Dict, Any
from fastapi.requests import Request from fastapi.requests import Request
from api_library.date_time_actions.date_functions import system_arrow from api_library.date_time_actions.date_functions import system_arrow
class UserActivityLogger: class UserActivityLogger:
def __init__(self): def __init__(self):
self.logger = logging.getLogger("user_activity") self.logger = logging.getLogger("user_activity")
@ -15,7 +16,7 @@ class UserActivityLogger:
os.makedirs(os.path.dirname(log_path), exist_ok=True) os.makedirs(os.path.dirname(log_path), exist_ok=True)
handler = logging.FileHandler(log_path) handler = logging.FileHandler(log_path)
formatter = logging.Formatter( formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s' "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
) )
handler.setFormatter(formatter) handler.setFormatter(formatter)
self.logger.addHandler(handler) self.logger.addHandler(handler)
@ -24,9 +25,10 @@ class UserActivityLogger:
"""Extract common metadata from request""" """Extract common metadata from request"""
return { return {
"agent": request.headers.get("User-Agent"), "agent": request.headers.get("User-Agent"),
"ip": getattr(request, "remote_addr", None) or request.headers.get("X-Forwarded-For"), "ip": getattr(request, "remote_addr", None)
or request.headers.get("X-Forwarded-For"),
"platform": request.headers.get("Origin"), "platform": request.headers.get("Origin"),
"timestamp": str(system_arrow.now()) "timestamp": str(system_arrow.now()),
} }
def log_login_attempt( def log_login_attempt(
@ -36,7 +38,7 @@ class UserActivityLogger:
domain: str, domain: str,
access_key: str, access_key: str,
success: bool, success: bool,
error: Optional[str] = None error: Optional[str] = None,
): ):
"""Log login attempts""" """Log login attempts"""
metadata = self._get_request_metadata(request) metadata = self._get_request_metadata(request)
@ -47,7 +49,7 @@ class UserActivityLogger:
"access_key": access_key, "access_key": access_key,
"success": success, "success": success,
"error": error, "error": error,
**metadata **metadata,
} }
if success: if success:
@ -61,7 +63,7 @@ class UserActivityLogger:
user_id: int, user_id: int,
change_type: str, change_type: str,
success: bool, success: bool,
error: Optional[str] = None error: Optional[str] = None,
): ):
"""Log password changes""" """Log password changes"""
metadata = self._get_request_metadata(request) metadata = self._get_request_metadata(request)
@ -71,7 +73,7 @@ class UserActivityLogger:
"change_type": change_type, "change_type": change_type,
"success": success, "success": success,
"error": error, "error": error,
**metadata **metadata,
} }
if success: if success:
@ -86,7 +88,7 @@ class UserActivityLogger:
activity_type: str, activity_type: str,
domain: Optional[str] = None, domain: Optional[str] = None,
success: bool = True, success: bool = True,
error: Optional[str] = None error: Optional[str] = None,
): ):
"""Log session activities (logout, disconnect, etc)""" """Log session activities (logout, disconnect, etc)"""
metadata = self._get_request_metadata(request) metadata = self._get_request_metadata(request)
@ -97,7 +99,7 @@ class UserActivityLogger:
"domain": domain, "domain": domain,
"success": success, "success": success,
"error": error, "error": error,
**metadata **metadata,
} }
if success: if success:
@ -105,5 +107,6 @@ class UserActivityLogger:
else: else:
self.logger.warning(f"{activity_type} failed", extra=log_data) self.logger.warning(f"{activity_type} failed", extra=log_data)
# Global logger instance # Global logger instance
user_logger = UserActivityLogger() user_logger = UserActivityLogger()

View File

@ -2,9 +2,12 @@ from typing import Any, Optional
from fastapi import status from fastapi import status
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
class ResponseHandler: class ResponseHandler:
@staticmethod @staticmethod
def success(message: str, data: Optional[Any] = None, status_code: int = status.HTTP_200_OK) -> JSONResponse: def success(
message: str, data: Optional[Any] = None, status_code: int = status.HTTP_200_OK
) -> JSONResponse:
"""Create a success response""" """Create a success response"""
return JSONResponse( return JSONResponse(
content={ content={
@ -16,7 +19,11 @@ class ResponseHandler:
) )
@staticmethod @staticmethod
def error(message: str, data: Optional[Any] = None, status_code: int = status.HTTP_400_BAD_REQUEST) -> JSONResponse: def error(
message: str,
data: Optional[Any] = None,
status_code: int = status.HTTP_400_BAD_REQUEST,
) -> JSONResponse:
"""Create an error response""" """Create an error response"""
return JSONResponse( return JSONResponse(
content={ content={

View File

@ -70,7 +70,9 @@ class RedisActions:
already_tokens = redis_cli.scan_iter(match=str(value_regex)) already_tokens = redis_cli.scan_iter(match=str(value_regex))
already_tokens_list = {} already_tokens_list = {}
for already_token in already_tokens: for already_token in already_tokens:
already_tokens_list[already_token.decode()] = json.loads(redis_cli.get(already_token)) already_tokens_list[already_token.decode()] = json.loads(
redis_cli.get(already_token)
)
return RedisResponse( return RedisResponse(
status=True, status=True,
message="Values are listed successfully.", message="Values are listed successfully.",
@ -187,21 +189,22 @@ class RedisActions:
try: try:
search_name = str(name) if isinstance(name, str) else name.decode() search_name = str(name) if isinstance(name, str) else name.decode()
expiry_time = system_arrow.get_expiry_time(**expiry_kwargs) expiry_time = system_arrow.get_expiry_time(**expiry_kwargs)
seconds_until_expiry = int(expiry_time.timestamp() - system_arrow.now().timestamp()) seconds_until_expiry = int(
expiry_time.timestamp() - system_arrow.now().timestamp()
)
redis_cli.setex( redis_cli.setex(
name=search_name, name=search_name,
time=seconds_until_expiry, time=seconds_until_expiry,
value=json.dumps({ value=json.dumps(
'value': value, {"value": value, "expires_at": expiry_time.timestamp()}
'expires_at': expiry_time.timestamp() ),
})
) )
return RedisResponse( return RedisResponse(
status=True, status=True,
message="Value is set successfully with expiry.", message="Value is set successfully with expiry.",
data={'value': value, 'expires_at': expiry_time.timestamp()}, data={"value": value, "expires_at": expiry_time.timestamp()},
) )
except Exception as e: except Exception as e:
return RedisResponse( return RedisResponse(
@ -224,7 +227,7 @@ class RedisActions:
) )
data = json.loads(result) data = json.loads(result)
if system_arrow.is_expired(data.get('expires_at')): if system_arrow.is_expired(data.get("expires_at")):
redis_cli.delete(search_name) redis_cli.delete(search_name)
return RedisResponse( return RedisResponse(
status=False, status=False,
@ -234,7 +237,7 @@ class RedisActions:
return RedisResponse( return RedisResponse(
status=True, status=True,
message="Value retrieved successfully.", message="Value retrieved successfully.",
data=data['value'], data=data["value"],
) )
except Exception as e: except Exception as e:
return RedisResponse( return RedisResponse(
@ -272,23 +275,21 @@ class RedisActions:
try: try:
key = f"{access_token}:{model_object.user_uu_id}" key = f"{access_token}:{model_object.user_uu_id}"
expiry_time = system_arrow.get_expiry_time(minutes=expiry_minutes) expiry_time = system_arrow.get_expiry_time(minutes=expiry_minutes)
seconds_until_expiry = max(1, int(expiry_time.timestamp() - system_arrow.now().timestamp())) seconds_until_expiry = max(
1, int(expiry_time.timestamp() - system_arrow.now().timestamp())
)
# Add expiry time to the model data # Add expiry time to the model data
model_data = json.loads(model_object.model_dump_json()) model_data = json.loads(model_object.model_dump_json())
model_data['expires_at'] = expiry_time.timestamp() model_data["expires_at"] = expiry_time.timestamp()
if redis_cli.setex( if redis_cli.setex(
name=key, name=key, time=seconds_until_expiry, value=json.dumps(model_data)
time=seconds_until_expiry,
value=json.dumps(model_data)
): ):
return access_token return access_token
except Exception as e: except Exception as e:
raise Exception( raise Exception(f"Failed to save object to Redis. Error: {str(e)}")
f"Failed to save object to Redis. Error: {str(e)}"
)
raise Exception("Failed to save token to Redis") raise Exception("Failed to save token to Redis")
@ -328,7 +329,7 @@ class RedisActions:
raise Exception("Token expired. Please login again") raise Exception("Token expired. Please login again")
# Get the token data # Get the token data
token_data = json.loads(redis_cli.get(token_key) or '{}') token_data = json.loads(redis_cli.get(token_key) or "{}")
# Return appropriate token object based on user type # Return appropriate token object based on user type
if token_data.get("user_type") == 1: # Employee if token_data.get("user_type") == 1: # Employee
@ -359,10 +360,10 @@ class RedisActions:
tokens_dict = {} tokens_dict = {}
for token_key in matching_tokens: for token_key in matching_tokens:
token_data = json.loads(redis_cli.get(token_key) or '{}') token_data = json.loads(redis_cli.get(token_key) or "{}")
# Skip expired tokens and clean them up # Skip expired tokens and clean them up
if system_arrow.is_expired(token_data.get('expires_at')): if system_arrow.is_expired(token_data.get("expires_at")):
redis_cli.delete(token_key) redis_cli.delete(token_key)
continue continue
@ -373,7 +374,11 @@ class RedisActions:
class RedisResponse: class RedisResponse:
def __init__( def __init__(
self, status: bool, message: str, data: typing.Union[dict | list] = None, error: str = None self,
status: bool,
message: str,
data: typing.Union[dict | list] = None,
error: str = None,
): ):
self.status = status self.status = status
self.message = message self.message = message

View File

@ -8,16 +8,18 @@ from api_configs import Auth
from databases import Users, UsersTokens from databases import Users, UsersTokens
from api_library.date_time_actions.date_functions import system_arrow from api_library.date_time_actions.date_functions import system_arrow
class TokenService: class TokenService:
@staticmethod @staticmethod
def validate_token(request: Request) -> Union[OccupantTokenObject, EmployeeTokenObject]: def validate_token(
request: Request,
) -> Union[OccupantTokenObject, EmployeeTokenObject]:
"""Validate and return token object from request""" """Validate and return token object from request"""
try: try:
return RedisActions.get_object_via_access_key(request) return RedisActions.get_object_via_access_key(request)
except Exception as e: except Exception as e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED, detail={"message": str(e)}
detail={"message": str(e)}
) )
@staticmethod @staticmethod
@ -26,12 +28,12 @@ class TokenService:
return RedisActions.get_object_via_user_uu_id(user_id) return RedisActions.get_object_via_user_uu_id(user_id)
@staticmethod @staticmethod
def validate_refresh_token(domain: str, refresh_token: str) -> Optional[UsersTokens]: def validate_refresh_token(
domain: str, refresh_token: str
) -> Optional[UsersTokens]:
"""Validate refresh token and return token object""" """Validate refresh token and return token object"""
return UsersTokens.filter_by_one( return UsersTokens.filter_by_one(
token=refresh_token, token=refresh_token, domain=domain, **UsersTokens.valid_record_dict
domain=domain,
**UsersTokens.valid_record_dict
).data ).data
@staticmethod @staticmethod
@ -39,10 +41,9 @@ class TokenService:
"""Update user metadata from request""" """Update user metadata from request"""
user.last_agent = request.headers.get("User-Agent") user.last_agent = request.headers.get("User-Agent")
user.last_platform = request.headers.get("Origin") user.last_platform = request.headers.get("Origin")
user.last_remote_addr = ( user.last_remote_addr = getattr(
getattr(request, "remote_addr", None) or request, "remote_addr", None
request.headers.get("X-Forwarded-For") ) or request.headers.get("X-Forwarded-For")
)
user.last_seen = str(system_arrow.now()) user.last_seen = str(system_arrow.now())
user.save() user.save()

View File

@ -19,19 +19,19 @@ from .building_responses import (
BuildCompaniesProvidingResponse, BuildCompaniesProvidingResponse,
BuildCompaniesProvidingCollection, BuildCompaniesProvidingCollection,
BuildManagementResponse, BuildManagementResponse,
BuildManagementCollection BuildManagementCollection,
) )
from .account_responses import ( from .account_responses import (
AccountRecordResponse, AccountRecordResponse,
AccountRecordCollection, AccountRecordCollection,
AccountRecordExchangeResponse, AccountRecordExchangeResponse,
AccountRecordExchangeCollection AccountRecordExchangeCollection,
) )
from .address_responses import ListAddressResponse, AddressPostCodeResponse from .address_responses import ListAddressResponse, AddressPostCodeResponse
from .auth_responses import ( from .auth_responses import (
AuthenticationLoginResponse, AuthenticationLoginResponse,
AuthenticationRefreshResponse, AuthenticationRefreshResponse,
AuthenticationUserInfoResponse AuthenticationUserInfoResponse,
) )
from .base_responses import BaseResponse, CrudCollection from .base_responses import BaseResponse, CrudCollection
from .budget_responses import ( from .budget_responses import (
@ -42,13 +42,13 @@ from .budget_responses import (
DecisionBookBudgetMasterResponse, DecisionBookBudgetMasterResponse,
DecisionBookBudgetMasterCollection, DecisionBookBudgetMasterCollection,
DecisionBookBudgetsResponse, DecisionBookBudgetsResponse,
DecisionBookBudgetsCollection DecisionBookBudgetsCollection,
) )
from .company_responses import ( from .company_responses import (
CompanyListResponse, CompanyListResponse,
CompanyDepartmentListResponse, CompanyDepartmentListResponse,
CompanyDutyListResponse, CompanyDutyListResponse,
CompanyEmployeeListResponse CompanyEmployeeListResponse,
) )
from .decision_book_responses import ( from .decision_book_responses import (
BuildDecisionBookResponse, BuildDecisionBookResponse,
@ -66,7 +66,7 @@ from .decision_book_responses import (
BuildDecisionBookPaymentsResponse, BuildDecisionBookPaymentsResponse,
BuildDecisionBookPaymentsCollection, BuildDecisionBookPaymentsCollection,
BuildDecisionBookLegalResponse, BuildDecisionBookLegalResponse,
BuildDecisionBookLegalCollection BuildDecisionBookLegalCollection,
) )
from .living_space_responses import LivingSpaceListResponse from .living_space_responses import LivingSpaceListResponse
from .parts_responses import BuildPartsListResponse from .parts_responses import BuildPartsListResponse
@ -115,5 +115,5 @@ __all__ = [
"BuildDecisionBookLegalCollection", "BuildDecisionBookLegalCollection",
"LivingSpaceListResponse", "LivingSpaceListResponse",
"BuildPartsListResponse", "BuildPartsListResponse",
"PeopleListResponse" "PeopleListResponse",
] ]

View File

@ -8,6 +8,7 @@ from .base_responses import BaseResponse, CrudCollection
class AccountBooksResponse(BaseResponse): class AccountBooksResponse(BaseResponse):
"""Response model for account books""" """Response model for account books"""
country: str country: str
branch_type: int branch_type: int
company_id: int company_id: int
@ -18,11 +19,13 @@ class AccountBooksResponse(BaseResponse):
class AccountBooksCollection(CrudCollection[AccountBooksResponse]): class AccountBooksCollection(CrudCollection[AccountBooksResponse]):
"""Collection of account books""" """Collection of account books"""
pass pass
class AccountCodesResponse(BaseResponse): class AccountCodesResponse(BaseResponse):
"""Response model for account codes""" """Response model for account codes"""
account_code: str account_code: str
comment_line: str comment_line: str
is_receive_or_debit: bool is_receive_or_debit: bool
@ -42,11 +45,13 @@ class AccountCodesResponse(BaseResponse):
class AccountCodesCollection(CrudCollection[AccountCodesResponse]): class AccountCodesCollection(CrudCollection[AccountCodesResponse]):
"""Collection of account codes""" """Collection of account codes"""
pass pass
class AccountCodeParserResponse(BaseResponse): class AccountCodeParserResponse(BaseResponse):
"""Response model for account code parser""" """Response model for account code parser"""
account_code_1: str account_code_1: str
account_code_2: str account_code_2: str
account_code_3: str account_code_3: str
@ -59,11 +64,13 @@ class AccountCodeParserResponse(BaseResponse):
class AccountCodeParserCollection(CrudCollection[AccountCodeParserResponse]): class AccountCodeParserCollection(CrudCollection[AccountCodeParserResponse]):
"""Collection of account code parsers""" """Collection of account code parsers"""
pass pass
class AccountMasterResponse(BaseResponse): class AccountMasterResponse(BaseResponse):
"""Response model for account master""" """Response model for account master"""
doc_date: datetime doc_date: datetime
plug_type: str plug_type: str
plug_number: int plug_number: int
@ -106,11 +113,13 @@ class AccountMasterResponse(BaseResponse):
class AccountMasterCollection(CrudCollection[AccountMasterResponse]): class AccountMasterCollection(CrudCollection[AccountMasterResponse]):
"""Collection of account masters""" """Collection of account masters"""
pass pass
class AccountDetailResponse(BaseResponse): class AccountDetailResponse(BaseResponse):
"""Response model for account detail""" """Response model for account detail"""
doc_date: datetime doc_date: datetime
line_no: int line_no: int
receive_debit: str receive_debit: str
@ -160,6 +169,7 @@ class AccountDetailResponse(BaseResponse):
class AccountDetailCollection(CrudCollection[AccountDetailResponse]): class AccountDetailCollection(CrudCollection[AccountDetailResponse]):
"""Collection of account details""" """Collection of account details"""
pass pass
@ -206,6 +216,7 @@ class AccountRecordResponse(BaseResponse):
build_parts_uu_id (Optional[str]): Related building part ID build_parts_uu_id (Optional[str]): Related building part ID
build_decision_book_uu_id (Optional[str]): Related decision book ID build_decision_book_uu_id (Optional[str]): Related decision book ID
""" """
iban: str iban: str
bank_date: datetime bank_date: datetime
currency_value: Decimal currency_value: Decimal
@ -249,6 +260,7 @@ class AccountRecordCollection(CrudCollection[AccountRecordResponse]):
This model represents a paginated list of account records with This model represents a paginated list of account records with
sorting and pagination information. sorting and pagination information.
""" """
pass pass
@ -266,6 +278,7 @@ class AccountRecordExchangeResponse(BaseResponse):
exchange_value (Decimal): Converted amount exchange_value (Decimal): Converted amount
exchange_date (datetime): When the exchange was calculated exchange_date (datetime): When the exchange was calculated
""" """
account_record_id: int account_record_id: int
account_record_uu_id: str account_record_uu_id: str
exchange_rate: Decimal exchange_rate: Decimal
@ -280,11 +293,13 @@ class AccountRecordExchangeCollection(CrudCollection[AccountRecordExchangeRespon
This model represents a paginated list of currency exchange records This model represents a paginated list of currency exchange records
with sorting and pagination information. with sorting and pagination information.
""" """
pass pass
class AccountRecordsListResponse(BaseModel): class AccountRecordsListResponse(BaseModel):
"""Response model for account records list endpoint""" """Response model for account records list endpoint"""
uu_id: UUID uu_id: UUID
account_name: str account_name: str
account_code: str account_code: str

View File

@ -6,6 +6,7 @@ from uuid import UUID
class AuthenticationLoginResponse(BaseModel): class AuthenticationLoginResponse(BaseModel):
"""Response model for authentication login endpoint""" """Response model for authentication login endpoint"""
token: str token: str
refresh_token: str refresh_token: str
token_type: str token_type: str
@ -15,6 +16,7 @@ class AuthenticationLoginResponse(BaseModel):
class AuthenticationRefreshResponse(BaseModel): class AuthenticationRefreshResponse(BaseModel):
"""Response model for authentication refresh endpoint""" """Response model for authentication refresh endpoint"""
token: str token: str
refresh_token: str refresh_token: str
token_type: str token_type: str
@ -23,6 +25,7 @@ class AuthenticationRefreshResponse(BaseModel):
class AuthenticationUserInfoResponse(BaseModel): class AuthenticationUserInfoResponse(BaseModel):
"""Response model for authentication user info endpoint""" """Response model for authentication user info endpoint"""
user_id: int user_id: int
username: str username: str
email: str email: str

View File

@ -3,7 +3,8 @@ from typing import Optional, TypeVar, Generic, List
from datetime import datetime from datetime import datetime
from uuid import UUID from uuid import UUID
T = TypeVar('T') T = TypeVar("T")
class BaseResponse(BaseModel): class BaseResponse(BaseModel):
"""Base response model that all response models inherit from. """Base response model that all response models inherit from.
@ -27,6 +28,7 @@ class BaseResponse(BaseModel):
is_notification_send (Optional[bool]): Whether notifications have been sent for this record is_notification_send (Optional[bool]): Whether notifications have been sent for this record
is_email_send (Optional[bool]): Whether emails have been sent for this record is_email_send (Optional[bool]): Whether emails have been sent for this record
""" """
uu_id: str uu_id: str
created_at: datetime created_at: datetime
updated_at: Optional[datetime] updated_at: Optional[datetime]
@ -47,6 +49,7 @@ class BaseResponse(BaseModel):
Attributes: Attributes:
from_attributes (bool): Enables ORM mode for SQLAlchemy integration from_attributes (bool): Enables ORM mode for SQLAlchemy integration
""" """
from_attributes = True from_attributes = True
@ -84,6 +87,7 @@ class CrudCollection(BaseModel, Generic[T]):
) )
``` ```
""" """
page: int = 1 page: int = 1
size: int = 10 size: int = 10
total: int = 0 total: int = 0
@ -97,4 +101,5 @@ class CrudCollection(BaseModel, Generic[T]):
Attributes: Attributes:
from_attributes (bool): Enables ORM mode for SQLAlchemy integration from_attributes (bool): Enables ORM mode for SQLAlchemy integration
""" """
from_attributes = True from_attributes = True

View File

@ -8,6 +8,7 @@ from .base_responses import BaseResponse, CrudCollection
class DecisionBookBudgetBooksResponse(BaseResponse): class DecisionBookBudgetBooksResponse(BaseResponse):
"""Response model for decision book budget books""" """Response model for decision book budget books"""
country: str country: str
branch_type: int = 0 branch_type: int = 0
company_id: int company_id: int
@ -18,13 +19,17 @@ class DecisionBookBudgetBooksResponse(BaseResponse):
build_decision_book_uu_id: Optional[str] build_decision_book_uu_id: Optional[str]
class DecisionBookBudgetBooksCollection(CrudCollection[DecisionBookBudgetBooksResponse]): class DecisionBookBudgetBooksCollection(
CrudCollection[DecisionBookBudgetBooksResponse]
):
"""Collection of decision book budget books""" """Collection of decision book budget books"""
pass pass
class DecisionBookBudgetCodesResponse(BaseResponse): class DecisionBookBudgetCodesResponse(BaseResponse):
"""Response model for decision book budget codes""" """Response model for decision book budget codes"""
budget_code: str budget_code: str
comment_line: str comment_line: str
budget_type: str budget_type: str
@ -37,13 +42,17 @@ class DecisionBookBudgetCodesResponse(BaseResponse):
customer_uu_id: str customer_uu_id: str
class DecisionBookBudgetCodesCollection(CrudCollection[DecisionBookBudgetCodesResponse]): class DecisionBookBudgetCodesCollection(
CrudCollection[DecisionBookBudgetCodesResponse]
):
"""Collection of decision book budget codes""" """Collection of decision book budget codes"""
pass pass
class DecisionBookBudgetMasterResponse(BaseResponse): class DecisionBookBudgetMasterResponse(BaseResponse):
"""Response model for decision book budget master""" """Response model for decision book budget master"""
budget_type: str budget_type: str
currency: str = "TRY" currency: str = "TRY"
total_budget: Decimal total_budget: Decimal
@ -55,13 +64,17 @@ class DecisionBookBudgetMasterResponse(BaseResponse):
department_uu_id: Optional[str] department_uu_id: Optional[str]
class DecisionBookBudgetMasterCollection(CrudCollection[DecisionBookBudgetMasterResponse]): class DecisionBookBudgetMasterCollection(
CrudCollection[DecisionBookBudgetMasterResponse]
):
"""Collection of decision book budget masters""" """Collection of decision book budget masters"""
pass pass
class DecisionBookBudgetsResponse(BaseResponse): class DecisionBookBudgetsResponse(BaseResponse):
"""Response model for decision book budgets""" """Response model for decision book budgets"""
process_date: datetime process_date: datetime
budget_codes_id: int budget_codes_id: int
total_budget: Decimal total_budget: Decimal
@ -73,4 +86,5 @@ class DecisionBookBudgetsResponse(BaseResponse):
class DecisionBookBudgetsCollection(CrudCollection[DecisionBookBudgetsResponse]): class DecisionBookBudgetsCollection(CrudCollection[DecisionBookBudgetsResponse]):
"""Collection of decision book budgets""" """Collection of decision book budgets"""
pass pass

View File

@ -8,6 +8,7 @@ from .base_responses import BaseResponse, CrudCollection
class BuildAreaListResponse(BaseResponse): class BuildAreaListResponse(BaseResponse):
"""Response model for building area list endpoint""" """Response model for building area list endpoint"""
uu_id: UUID uu_id: UUID
build_id: int build_id: int
build_uu_id: str build_uu_id: str
@ -20,11 +21,13 @@ class BuildAreaListResponse(BaseResponse):
class BuildAreaListCollection(CrudCollection[BuildAreaListResponse]): class BuildAreaListCollection(CrudCollection[BuildAreaListResponse]):
"""Collection of building area list""" """Collection of building area list"""
pass pass
class BuildSitesListResponse(BaseResponse): class BuildSitesListResponse(BaseResponse):
"""Response model for building sites list endpoint""" """Response model for building sites list endpoint"""
uu_id: UUID uu_id: UUID
address_id: int address_id: int
site_name: str site_name: str
@ -36,11 +39,13 @@ class BuildSitesListResponse(BaseResponse):
class BuildSitesListCollection(CrudCollection[BuildSitesListResponse]): class BuildSitesListCollection(CrudCollection[BuildSitesListResponse]):
"""Collection of building sites list""" """Collection of building sites list"""
pass pass
class BuildTypesListResponse(BaseResponse): class BuildTypesListResponse(BaseResponse):
"""Response model for building types list endpoint""" """Response model for building types list endpoint"""
uu_id: UUID uu_id: UUID
type_name: str type_name: str
type_value: str type_value: str
@ -51,11 +56,13 @@ class BuildTypesListResponse(BaseResponse):
class BuildTypesListCollection(CrudCollection[BuildTypesListResponse]): class BuildTypesListCollection(CrudCollection[BuildTypesListResponse]):
"""Collection of building types list""" """Collection of building types list"""
pass pass
class BuildTypesResponse(BaseResponse): class BuildTypesResponse(BaseResponse):
"""Response model for building types""" """Response model for building types"""
function_code: str function_code: str
type_code: str type_code: str
lang: str = "TR" lang: str = "TR"
@ -63,11 +70,13 @@ class BuildTypesResponse(BaseResponse):
class BuildTypesCollection(CrudCollection[BuildTypesResponse]): class BuildTypesCollection(CrudCollection[BuildTypesResponse]):
"""Collection of building types""" """Collection of building types"""
pass pass
class Part2EmployeeResponse(BaseResponse): class Part2EmployeeResponse(BaseResponse):
"""Response model for part to employee mapping""" """Response model for part to employee mapping"""
build_id: int build_id: int
part_id: int part_id: int
employee_id: int employee_id: int
@ -75,11 +84,13 @@ class Part2EmployeeResponse(BaseResponse):
class Part2EmployeeCollection(CrudCollection[Part2EmployeeResponse]): class Part2EmployeeCollection(CrudCollection[Part2EmployeeResponse]):
"""Collection of part to employee mappings""" """Collection of part to employee mappings"""
pass pass
class RelationshipEmployee2BuildResponse(BaseResponse): class RelationshipEmployee2BuildResponse(BaseResponse):
"""Response model for employee to build relationship""" """Response model for employee to build relationship"""
company_id: int company_id: int
employee_id: int employee_id: int
member_id: int member_id: int
@ -87,13 +98,17 @@ class RelationshipEmployee2BuildResponse(BaseResponse):
show_only: bool = False show_only: bool = False
class RelationshipEmployee2BuildCollection(CrudCollection[RelationshipEmployee2BuildResponse]): class RelationshipEmployee2BuildCollection(
CrudCollection[RelationshipEmployee2BuildResponse]
):
"""Collection of employee to build relationships""" """Collection of employee to build relationships"""
pass pass
class BuildResponse(BaseResponse): class BuildResponse(BaseResponse):
"""Response model for buildings""" """Response model for buildings"""
gov_address_code: str = "" gov_address_code: str = ""
build_name: str build_name: str
build_no: str build_no: str
@ -120,11 +135,13 @@ class BuildResponse(BaseResponse):
class BuildCollection(CrudCollection[BuildResponse]): class BuildCollection(CrudCollection[BuildResponse]):
"""Collection of buildings""" """Collection of buildings"""
pass pass
class BuildPartsResponse(BaseResponse): class BuildPartsResponse(BaseResponse):
"""Response model for building parts""" """Response model for building parts"""
address_gov_code: str address_gov_code: str
part_no: int = 0 part_no: int = 0
part_level: int = 0 part_level: int = 0
@ -144,11 +161,13 @@ class BuildPartsResponse(BaseResponse):
class BuildPartsCollection(CrudCollection[BuildPartsResponse]): class BuildPartsCollection(CrudCollection[BuildPartsResponse]):
"""Collection of building parts""" """Collection of building parts"""
pass pass
class BuildLivingSpaceResponse(BaseResponse): class BuildLivingSpaceResponse(BaseResponse):
"""Response model for building living space""" """Response model for building living space"""
fix_value: Decimal = Decimal("0") fix_value: Decimal = Decimal("0")
fix_percent: Decimal = Decimal("0") fix_percent: Decimal = Decimal("0")
agreement_no: str = "" agreement_no: str = ""
@ -164,11 +183,13 @@ class BuildLivingSpaceResponse(BaseResponse):
class BuildLivingSpaceCollection(CrudCollection[BuildLivingSpaceResponse]): class BuildLivingSpaceCollection(CrudCollection[BuildLivingSpaceResponse]):
"""Collection of building living spaces""" """Collection of building living spaces"""
pass pass
class BuildManagementResponse(BaseResponse): class BuildManagementResponse(BaseResponse):
"""Response model for building management""" """Response model for building management"""
discounted_percentage: Decimal = Decimal("0.00") discounted_percentage: Decimal = Decimal("0.00")
discounted_price: Decimal = Decimal("0.00") discounted_price: Decimal = Decimal("0.00")
calculated_price: Decimal = Decimal("0.00") calculated_price: Decimal = Decimal("0.00")
@ -182,11 +203,13 @@ class BuildManagementResponse(BaseResponse):
class BuildManagementCollection(CrudCollection[BuildManagementResponse]): class BuildManagementCollection(CrudCollection[BuildManagementResponse]):
"""Collection of building management records""" """Collection of building management records"""
pass pass
class BuildAreaResponse(BaseResponse): class BuildAreaResponse(BaseResponse):
"""Response model for building area""" """Response model for building area"""
area_name: str = "" area_name: str = ""
area_code: str = "" area_code: str = ""
area_type: str = "GREEN" area_type: str = "GREEN"
@ -203,11 +226,13 @@ class BuildAreaResponse(BaseResponse):
class BuildAreaCollection(CrudCollection[BuildAreaResponse]): class BuildAreaCollection(CrudCollection[BuildAreaResponse]):
"""Collection of building areas""" """Collection of building areas"""
pass pass
class BuildSitesResponse(BaseResponse): class BuildSitesResponse(BaseResponse):
"""Response model for building sites""" """Response model for building sites"""
site_name: str site_name: str
site_no: str site_no: str
address_id: int address_id: int
@ -216,11 +241,13 @@ class BuildSitesResponse(BaseResponse):
class BuildSitesCollection(CrudCollection[BuildSitesResponse]): class BuildSitesCollection(CrudCollection[BuildSitesResponse]):
"""Collection of building sites""" """Collection of building sites"""
pass pass
class BuildCompaniesProvidingResponse(BaseResponse): class BuildCompaniesProvidingResponse(BaseResponse):
"""Response model for building companies providing services""" """Response model for building companies providing services"""
build_id: int build_id: int
build_uu_id: Optional[str] build_uu_id: Optional[str]
company_id: int company_id: int
@ -230,13 +257,17 @@ class BuildCompaniesProvidingResponse(BaseResponse):
contract_id: Optional[int] contract_id: Optional[int]
class BuildCompaniesProvidingCollection(CrudCollection[BuildCompaniesProvidingResponse]): class BuildCompaniesProvidingCollection(
CrudCollection[BuildCompaniesProvidingResponse]
):
"""Collection of building companies providing services""" """Collection of building companies providing services"""
pass pass
class BuildPersonProvidingResponse(BaseResponse): class BuildPersonProvidingResponse(BaseResponse):
"""Response model for building person providing services""" """Response model for building person providing services"""
build_id: int build_id: int
build_uu_id: Optional[str] build_uu_id: Optional[str]
people_id: int people_id: int
@ -248,4 +279,5 @@ class BuildPersonProvidingResponse(BaseResponse):
class BuildPersonProvidingCollection(CrudCollection[BuildPersonProvidingResponse]): class BuildPersonProvidingCollection(CrudCollection[BuildPersonProvidingResponse]):
"""Collection of building person providing services""" """Collection of building person providing services"""
pass pass

View File

@ -6,6 +6,7 @@ from uuid import UUID
class CompanyListResponse(BaseModel): class CompanyListResponse(BaseModel):
"""Response model for company list endpoint""" """Response model for company list endpoint"""
uu_id: UUID uu_id: UUID
company_name: str company_name: str
company_code: str company_code: str
@ -19,6 +20,7 @@ class CompanyListResponse(BaseModel):
class CompanyDepartmentListResponse(BaseModel): class CompanyDepartmentListResponse(BaseModel):
"""Response model for company department list endpoint""" """Response model for company department list endpoint"""
uu_id: UUID uu_id: UUID
department_name: str department_name: str
department_code: str department_code: str
@ -31,6 +33,7 @@ class CompanyDepartmentListResponse(BaseModel):
class CompanyDutyListResponse(BaseModel): class CompanyDutyListResponse(BaseModel):
"""Response model for company duty list endpoint""" """Response model for company duty list endpoint"""
uu_id: UUID uu_id: UUID
duty_name: str duty_name: str
duty_code: str duty_code: str
@ -43,6 +46,7 @@ class CompanyDutyListResponse(BaseModel):
class CompanyEmployeeListResponse(BaseModel): class CompanyEmployeeListResponse(BaseModel):
"""Response model for company employee list endpoint""" """Response model for company employee list endpoint"""
uu_id: UUID uu_id: UUID
employee_id: int employee_id: int
employee_uu_id: str employee_uu_id: str

View File

@ -8,6 +8,7 @@ from .base_responses import BaseResponse, CrudCollection
class BuildDecisionBookResponse(BaseResponse): class BuildDecisionBookResponse(BaseResponse):
"""Response model for building decision book""" """Response model for building decision book"""
decision_book_pdf_path: Optional[str] = "" decision_book_pdf_path: Optional[str] = ""
resp_company_fix_wage: float = 0 resp_company_fix_wage: float = 0
contact_agreement_path: Optional[str] = "" contact_agreement_path: Optional[str] = ""
@ -18,11 +19,13 @@ class BuildDecisionBookResponse(BaseResponse):
class BuildDecisionBookCollection(CrudCollection[BuildDecisionBookResponse]): class BuildDecisionBookCollection(CrudCollection[BuildDecisionBookResponse]):
"""Collection of building decision books""" """Collection of building decision books"""
pass pass
class BuildDecisionBookInvitationsResponse(BaseResponse): class BuildDecisionBookInvitationsResponse(BaseResponse):
"""Response model for building decision book invitations""" """Response model for building decision book invitations"""
build_id: int build_id: int
build_uu_id: Optional[str] build_uu_id: Optional[str]
decision_book_id: int decision_book_id: int
@ -36,13 +39,17 @@ class BuildDecisionBookInvitationsResponse(BaseResponse):
planned_date_expires: datetime planned_date_expires: datetime
class BuildDecisionBookInvitationsCollection(CrudCollection[BuildDecisionBookInvitationsResponse]): class BuildDecisionBookInvitationsCollection(
CrudCollection[BuildDecisionBookInvitationsResponse]
):
"""Collection of building decision book invitations""" """Collection of building decision book invitations"""
pass pass
class BuildDecisionBookPersonResponse(BaseResponse): class BuildDecisionBookPersonResponse(BaseResponse):
"""Response model for building decision book person""" """Response model for building decision book person"""
dues_percent_discount: int = 0 dues_percent_discount: int = 0
dues_fix_discount: Decimal = Decimal("0") dues_fix_discount: Decimal = Decimal("0")
dues_discount_approval_date: datetime dues_discount_approval_date: datetime
@ -61,13 +68,17 @@ class BuildDecisionBookPersonResponse(BaseResponse):
person_id: int person_id: int
class BuildDecisionBookPersonCollection(CrudCollection[BuildDecisionBookPersonResponse]): class BuildDecisionBookPersonCollection(
CrudCollection[BuildDecisionBookPersonResponse]
):
"""Collection of building decision book persons""" """Collection of building decision book persons"""
pass pass
class BuildDecisionBookPersonOccupantsResponse(BaseResponse): class BuildDecisionBookPersonOccupantsResponse(BaseResponse):
"""Response model for building decision book person occupants""" """Response model for building decision book person occupants"""
build_decision_book_person_id: int build_decision_book_person_id: int
build_decision_book_person_uu_id: Optional[str] build_decision_book_person_uu_id: Optional[str]
invite_id: Optional[int] invite_id: Optional[int]
@ -76,13 +87,17 @@ class BuildDecisionBookPersonOccupantsResponse(BaseResponse):
occupant_type_uu_id: Optional[str] occupant_type_uu_id: Optional[str]
class BuildDecisionBookPersonOccupantsCollection(CrudCollection[BuildDecisionBookPersonOccupantsResponse]): class BuildDecisionBookPersonOccupantsCollection(
CrudCollection[BuildDecisionBookPersonOccupantsResponse]
):
"""Collection of building decision book person occupants""" """Collection of building decision book person occupants"""
pass pass
class BuildDecisionBookItemsResponse(BaseResponse): class BuildDecisionBookItemsResponse(BaseResponse):
"""Response model for building decision book items""" """Response model for building decision book items"""
item_order: int item_order: int
item_comment: str item_comment: str
item_objection: Optional[str] item_objection: Optional[str]
@ -97,11 +112,13 @@ class BuildDecisionBookItemsResponse(BaseResponse):
class BuildDecisionBookItemsCollection(CrudCollection[BuildDecisionBookItemsResponse]): class BuildDecisionBookItemsCollection(CrudCollection[BuildDecisionBookItemsResponse]):
"""Collection of building decision book items""" """Collection of building decision book items"""
pass pass
class BuildDecisionBookItemsUnapprovedResponse(BaseResponse): class BuildDecisionBookItemsUnapprovedResponse(BaseResponse):
"""Response model for building decision book items unapproved""" """Response model for building decision book items unapproved"""
item_objection: str item_objection: str
item_order: int item_order: int
decision_book_item_id: int decision_book_item_id: int
@ -112,13 +129,17 @@ class BuildDecisionBookItemsUnapprovedResponse(BaseResponse):
build_decision_book_item_uu_id: Optional[str] build_decision_book_item_uu_id: Optional[str]
class BuildDecisionBookItemsUnapprovedCollection(CrudCollection[BuildDecisionBookItemsUnapprovedResponse]): class BuildDecisionBookItemsUnapprovedCollection(
CrudCollection[BuildDecisionBookItemsUnapprovedResponse]
):
"""Collection of building decision book items unapproved""" """Collection of building decision book items unapproved"""
pass pass
class BuildDecisionBookPaymentsResponse(BaseResponse): class BuildDecisionBookPaymentsResponse(BaseResponse):
"""Response model for building decision book payments""" """Response model for building decision book payments"""
payment_plan_time_periods: str payment_plan_time_periods: str
process_date: datetime process_date: datetime
payment_amount: Decimal payment_amount: Decimal
@ -138,13 +159,17 @@ class BuildDecisionBookPaymentsResponse(BaseResponse):
account_records_uu_id: Optional[str] account_records_uu_id: Optional[str]
class BuildDecisionBookPaymentsCollection(CrudCollection[BuildDecisionBookPaymentsResponse]): class BuildDecisionBookPaymentsCollection(
CrudCollection[BuildDecisionBookPaymentsResponse]
):
"""Collection of building decision book payments""" """Collection of building decision book payments"""
pass pass
class BuildDecisionBookLegalResponse(BaseResponse): class BuildDecisionBookLegalResponse(BaseResponse):
"""Response model for building decision book legal""" """Response model for building decision book legal"""
period_start_date: datetime period_start_date: datetime
lawsuits_decision_number: str lawsuits_decision_number: str
lawsuits_decision_date: datetime lawsuits_decision_date: datetime
@ -175,4 +200,5 @@ class BuildDecisionBookLegalResponse(BaseResponse):
class BuildDecisionBookLegalCollection(CrudCollection[BuildDecisionBookLegalResponse]): class BuildDecisionBookLegalCollection(CrudCollection[BuildDecisionBookLegalResponse]):
"""Collection of building decision book legal records""" """Collection of building decision book legal records"""
pass pass

View File

@ -70,7 +70,7 @@ RelationshipDutyPeopleLanguageModel = dict(
"member_id": "Member ID", "member_id": "Member ID",
"relationship_type": "Relationship Type", "relationship_type": "Relationship Type",
"show_only": "Show Only", "show_only": "Show Only",
} },
) )
PeopleLanguageModel = dict( PeopleLanguageModel = dict(
@ -105,7 +105,7 @@ PeopleLanguageModel = dict(
"birth_place": "Birth Place", "birth_place": "Birth Place",
"birth_date": "Birth Date", "birth_date": "Birth Date",
"tax_no": "Tax No", "tax_no": "Tax No",
} },
) )
RelationshipEmployee2PostCodeLanguageModel = dict( RelationshipEmployee2PostCodeLanguageModel = dict(
@ -124,7 +124,7 @@ RelationshipEmployee2PostCodeLanguageModel = dict(
"member_id": "Member ID", "member_id": "Member ID",
"relationship_type": "Relationship Type", "relationship_type": "Relationship Type",
"show_only": "Show Only", "show_only": "Show Only",
} },
) )
AddressPostcodeLanguageModel = dict( AddressPostcodeLanguageModel = dict(
@ -168,7 +168,7 @@ AddressesLanguageModel = dict(
"longitude": "Longitude", "longitude": "Longitude",
"street_id": "Street ID", "street_id": "Street ID",
"street_uu_id": "Street UUID", "street_uu_id": "Street UUID",
} },
) )
AddressGeographicLocationsLanguageModel = dict( AddressGeographicLocationsLanguageModel = dict(
@ -195,7 +195,7 @@ AddressGeographicLocationsLanguageModel = dict(
"geo_description": "Description", "geo_description": "Description",
"geo_area_size": "Area", "geo_area_size": "Area",
"geo_population": "Population", "geo_population": "Population",
} },
) )
AddressCountryLanguageModel = dict( AddressCountryLanguageModel = dict(

View File

@ -16,5 +16,5 @@ EndpointRestrictionLanguageModel = dict(
"endpoint_method": "API Method", "endpoint_method": "API Method",
"endpoint_desc": "API Description", "endpoint_desc": "API Description",
"endpoint_code": "API Code", "endpoint_code": "API Code",
} },
) )

View File

@ -14,7 +14,7 @@ def validate_timestamp(doc):
if not doc: if not doc:
return doc return doc
timestamp_fields = ['modified_at', 'created_at', 'accessed_at', 'timestamp'] timestamp_fields = ["modified_at", "created_at", "accessed_at", "timestamp"]
for field in timestamp_fields: for field in timestamp_fields:
if field in doc and not isinstance(doc[field], (int, float)): if field in doc and not isinstance(doc[field], (int, float)):
# Convert to proper timestamp if it's not already # Convert to proper timestamp if it's not already

View File

@ -41,6 +41,7 @@ from databases.language_models.building.build import (
BuildPersonProvidingLanguageModel, BuildPersonProvidingLanguageModel,
) )
class BuildTypes(CrudCollection): class BuildTypes(CrudCollection):
""" """
BuildTypes class based on declarative_base and BaseMixin via session BuildTypes class based on declarative_base and BaseMixin via session
@ -177,12 +178,8 @@ class Build(CrudCollection, SelectActionWithEmployee):
heating_system: Mapped[bool] = mapped_column(Boolean, server_default="True") heating_system: Mapped[bool] = mapped_column(Boolean, server_default="True")
cooling_system: Mapped[bool] = mapped_column(Boolean, server_default="False") cooling_system: Mapped[bool] = mapped_column(Boolean, server_default="False")
hot_water_system: Mapped[bool] = mapped_column(Boolean, server_default="False") hot_water_system: Mapped[bool] = mapped_column(Boolean, server_default="False")
block_service_man_count: Mapped[int] = mapped_column( block_service_man_count: Mapped[int] = mapped_column(Integer, server_default="0")
Integer, server_default="0" security_service_man_count: Mapped[int] = mapped_column(Integer, server_default="0")
)
security_service_man_count: Mapped[int] = mapped_column(
Integer, server_default="0"
)
garage_count: Mapped[int] = mapped_column( garage_count: Mapped[int] = mapped_column(
Integer, server_default="0", comment="Garage Count" Integer, server_default="0", comment="Garage Count"
) )

View File

@ -2,7 +2,16 @@ from fastapi.exceptions import HTTPException
from databases.sql_models.core_mixin import CrudCollection from databases.sql_models.core_mixin import CrudCollection
from sqlalchemy import String, Integer, Boolean, ForeignKey, Index, Identity, TIMESTAMP, func from sqlalchemy import (
String,
Integer,
Boolean,
ForeignKey,
Index,
Identity,
TIMESTAMP,
func,
)
from sqlalchemy.orm import mapped_column, relationship, Mapped from sqlalchemy.orm import mapped_column, relationship, Mapped
from api_configs import RelationAccess from api_configs import RelationAccess

View File

@ -14,7 +14,9 @@ engine_config = {
"pool_pre_ping": True, "pool_pre_ping": True,
} }
engine = create_engine(**engine_config, ) engine = create_engine(
**engine_config,
)
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False) SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False)
session = scoped_session(sessionmaker(bind=engine)) session = scoped_session(sessionmaker(bind=engine))

View File

@ -52,9 +52,9 @@ class FilterAttributes:
meta_data = getattr(cls, "meta_data", {}) meta_data = getattr(cls, "meta_data", {})
meta_data_created = meta_data.get("created", False) meta_data_created = meta_data.get("created", False)
if meta_data_created: if meta_data_created:
print('meta_data_created commit', meta_data_created) print("meta_data_created commit", meta_data_created)
cls.__session__.commit() cls.__session__.commit()
print('meta_data_created rollback', meta_data_created) print("meta_data_created rollback", meta_data_created)
cls.__session__.rollback() cls.__session__.rollback()
# cls.raise_http_exception( # cls.raise_http_exception(
# status_code="HTTP_304_NOT_MODIFIED", # status_code="HTTP_304_NOT_MODIFIED",
@ -82,6 +82,7 @@ class FilterAttributes:
data={}, data={},
message=str(e.__context__).split("\n")[0], message=str(e.__context__).split("\n")[0],
) )
@classmethod @classmethod
def rollback(cls): def rollback(cls):
"""Rollback the current session.""" """Rollback the current session."""

View File

@ -16,6 +16,7 @@ def do_alembic():
generate_alembic_with_session(text=text) generate_alembic_with_session(text=text)
# #
# def create_one_address(): # def create_one_address():
# from databases import ( # from databases import (

View File

@ -36,10 +36,9 @@ CrudCollection.http_exception = HTTPException
new_person_find_or_create = People.find_or_create(**person_dict) new_person_find_or_create = People.find_or_create(**person_dict)
new_person_find_or_create.save_via_metadata() new_person_find_or_create.save_via_metadata()
print('meta_data', new_person_find_or_create.meta_data) print("meta_data", new_person_find_or_create.meta_data)
print('new_person_find_or_create', new_person_find_or_create) print("new_person_find_or_create", new_person_find_or_create)
quit() quit()
new_user_find_or_create = Users.find_or_create(**user_dict) new_user_find_or_create = Users.find_or_create(**user_dict)
new_user_find_or_abort = Users.find_or_abort(**user_dict) new_user_find_or_abort = Users.find_or_abort(**user_dict)

View File

@ -9,7 +9,7 @@ def create_test_occupant_token(
person_id: int = 1, person_id: int = 1,
person_uu_id: str = "test-person", person_uu_id: str = "test-person",
credentials: Optional[Dict[str, Any]] = None, credentials: Optional[Dict[str, Any]] = None,
available_occupants: Optional[Dict[str, Any]] = None available_occupants: Optional[Dict[str, Any]] = None,
) -> OccupantTokenObject: ) -> OccupantTokenObject:
"""Create a test occupant token object""" """Create a test occupant token object"""
return OccupantTokenObject( return OccupantTokenObject(
@ -32,7 +32,7 @@ def create_test_employee_token(
person_id: int = 1, person_id: int = 1,
person_uu_id: str = "test-person", person_uu_id: str = "test-person",
credentials: Optional[Dict[str, Any]] = None, credentials: Optional[Dict[str, Any]] = None,
selected_company: Optional[Dict[str, Any]] = None selected_company: Optional[Dict[str, Any]] = None,
) -> EmployeeTokenObject: ) -> EmployeeTokenObject:
"""Create a test employee token object""" """Create a test employee token object"""
return EmployeeTokenObject( return EmployeeTokenObject(
@ -50,5 +50,6 @@ def create_test_employee_token(
class MockRequest: class MockRequest:
"""Mock request object for testing""" """Mock request object for testing"""
def __init__(self, headers: Optional[Dict[str, str]] = None): def __init__(self, headers: Optional[Dict[str, str]] = None):
self.headers = headers or {} self.headers = headers or {}

View File

@ -35,9 +35,7 @@ def test_save_object_to_redis_success():
# Test save # Test save
result = AccessObjectActions.save_object_to_redis( result = AccessObjectActions.save_object_to_redis(
access_token=access_token, access_token=access_token, model_object=model_object, expiry_minutes=1
model_object=model_object,
expiry_minutes=1
) )
assert result is True assert result is True
@ -69,7 +67,7 @@ def test_save_object_to_redis_expiry():
AccessObjectActions.save_object_to_redis( AccessObjectActions.save_object_to_redis(
access_token=access_token, access_token=access_token,
model_object=model_object, model_object=model_object,
expiry_minutes=2/60 # 2 seconds expiry_minutes=2 / 60, # 2 seconds
) )
# Verify token exists # Verify token exists
@ -86,7 +84,9 @@ def test_save_object_to_redis_expiry():
RedisActions.get_object_via_access_key( RedisActions.get_object_via_access_key(
MockRequest(headers={Auth.ACCESS_TOKEN_TAG: access_token}) MockRequest(headers={Auth.ACCESS_TOKEN_TAG: access_token})
) )
assert any(msg in str(exc_info.value) for msg in ["Token expired", "Invalid credentials"]) assert any(
msg in str(exc_info.value) for msg in ["Token expired", "Invalid credentials"]
)
def test_get_object_via_user_uu_id(): def test_get_object_via_user_uu_id():