""" request models. """ from typing import TYPE_CHECKING, Dict, Any, Literal, Optional, TypedDict, Union from pydantic import BaseModel, Field, model_validator, RootModel, ConfigDict from ApiEvents.base_request_model import BaseRequestModel, DictRequestModel from ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTokenObject from ApiValidations.Request.base_validations import ListOptions from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi from Schemas.identity.identity import ( AddressPostcode, Addresses, RelationshipEmployee2PostCode, ) if TYPE_CHECKING: from fastapi import Request class AddressListEventMethods(MethodToEvent): event_type = "SELECT" event_description = "List Address records" event_category = "Address" __event_keys__ = { "9c251d7d-da70-4d63-a72c-e69c26270442": "address_list_super_user", "52afe375-dd95-4f4b-aaa2-4ec61bc6de52": "address_list_employee", } __event_validation__ = { "9c251d7d-da70-4d63-a72c-e69c26270442": ListAddressResponse, "52afe375-dd95-4f4b-aaa2-4ec61bc6de52": ListAddressResponse, } @classmethod def address_list_super_user( cls, list_options: ListOptions, token_dict: Union[EmployeeTokenObject, OccupantTokenObject], ): db = RelationshipEmployee2PostCode.new_session() post_code_list = RelationshipEmployee2PostCode.filter_all( RelationshipEmployee2PostCode.company_id == token_dict.selected_company.company_id, db=db, ).data post_code_id_list = [post_code.member_id for post_code in post_code_list] if not post_code_id_list: raise HTTPExceptionApi( status_code=404, detail="User has no post code registered. User can not list addresses.", ) get_street_ids = [ street_id[0] for street_id in AddressPostcode.select_only( AddressPostcode.id.in_(post_code_id_list), select_args=[AddressPostcode.street_id], order_by=AddressPostcode.street_id.desc(), ).data ] if not get_street_ids: raise HTTPExceptionApi( status_code=404, detail="User has no street registered. User can not list addresses.", ) Addresses.pre_query = Addresses.filter_all( Addresses.street_id.in_(get_street_ids), ).query Addresses.filter_attr = list_options records = Addresses.filter_all().data return # return AlchemyJsonResponse( # completed=True, message="List Address records", result=records # ) @classmethod def address_list_employee( cls, list_options: ListOptions, token_dict: Union[EmployeeTokenObject, OccupantTokenObject], ): Addresses.filter_attr = list_options Addresses.pre_query = Addresses.filter_all( Addresses.street_id.in_(get_street_ids), ) records = Addresses.filter_all().data return # return AlchemyJsonResponse( # completed=True, message="List Address records", result=records # ) class AddressCreateEventMethods(MethodToEvent): event_type = "CREATE" event_description = "" event_category = "" __event_keys__ = { "ffdc445f-da10-4ce4-9531-d2bdb9a198ae": "create_address", } __event_validation__ = { "ffdc445f-da10-4ce4-9531-d2bdb9a198ae": InsertAddress, } @classmethod def create_address( cls, data: InsertAddress, token_dict: Union[EmployeeTokenObject, OccupantTokenObject], ): post_code = AddressPostcode.filter_one( AddressPostcode.uu_id == data.post_code_uu_id, ).data if not post_code: raise HTTPExceptionApi( status_code=404, detail="Post code not found. User can not create address without post code.", ) data_dict = data.excluded_dump() data_dict["street_id"] = post_code.street_id data_dict["street_uu_id"] = str(post_code.street_uu_id) del data_dict["post_code_uu_id"] address = Addresses.find_or_create(**data_dict) address.save() address.update(is_confirmed=True) address.save() return AlchemyJsonResponse( completed=True, message="Address created successfully", result=address.get_dict(), ) class AddressSearchEventMethods(MethodToEvent): """Event methods for searching addresses. This class handles address search functionality including text search and filtering. """ event_type = "SEARCH" event_description = "Search for addresses using text and filters" event_category = "Address" __event_keys__ = { "e0ac1269-e9a7-4806-9962-219ac224b0d0": "search_address", } __event_validation__ = { "e0ac1269-e9a7-4806-9962-219ac224b0d0": SearchAddress, } @classmethod def _build_order_clause( cls, filter_list: Dict[str, Any], schemas: List[str], filter_table: Any ) -> Any: """Build the ORDER BY clause for the query. Args: filter_list: Dictionary of filter options schemas: List of available schema fields filter_table: SQLAlchemy table to query Returns: SQLAlchemy order_by clause """ # Default to ordering by UUID if field not in schema if filter_list.get("order_field") not in schemas: filter_list["order_field"] = "uu_id" else: # Extract table and field from order field table_name, field_name = str(filter_list.get("order_field")).split(".") filter_table = getattr(databases.sql_models, table_name) filter_list["order_field"] = field_name # Build order clause field = getattr(filter_table, filter_list.get("order_field")) return ( field.desc() if str(filter_list.get("order_type"))[0] == "d" else field.asc() ) @classmethod def _format_record(cls, record: Any, schemas: List[str]) -> Dict[str, str]: """Format a database record into a dictionary. Args: record: Database record to format schemas: List of schema fields Returns: Formatted record dictionary """ result = {} for index, schema in enumerate(schemas): value = str(record[index]) # Special handling for UUID fields if "uu_id" in value: value = str(value) result[schema] = value return result @classmethod def search_address( cls, data: SearchAddress, token_dict: Union[EmployeeTokenObject, OccupantTokenObject], ) -> JSONResponse: """Search for addresses using text search and filters. Args: data: Search parameters including text and filters token_dict: Authentication token Returns: JSON response with search results Raises: HTTPExceptionApi: If search fails """ try: # Start performance measurement start_time = perf_counter() # Get initial query search_result = AddressStreet.search_address_text(search_text=data.search) if not search_result: raise HTTPExceptionApi( status_code=status.HTTP_404_NOT_FOUND, detail="No addresses found matching search criteria", ) query = search_result.get("query") schemas = search_result.get("schema") # Apply filters filter_list = data.list_options.dump() filter_table = AddressStreet # Build and apply order clause order = cls._build_order_clause(filter_list, schemas, filter_table) # Apply pagination page_size = int(filter_list.get("size")) offset = (int(filter_list.get("page")) - 1) * page_size # Execute query query = ( query.order_by(order) .limit(page_size) .offset(offset) .populate_existing() ) records = list(query.all()) # Format results results = [cls._format_record(record, schemas) for record in records] # Log performance duration = perf_counter() - start_time print(f"Address search completed in {duration:.3f}s") return AlchemyJsonResponse( completed=True, message="Address search results", result=results ) except HTTPExceptionApi as e: # Re-raise HTTP exceptions raise e except Exception as e: # Log and wrap other errors print(f"Address search error: {str(e)}") raise HTTPExceptionApi( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to search addresses", ) from e class AddressUpdateEventMethods(MethodToEvent): event_type = "UPDATE" event_description = "" event_category = "" __event_keys__ = { "1f9c3a9c-e5bd-4dcd-9b9a-3742d7e03a27": "update_address", } __event_validation__ = { "1f9c3a9c-e5bd-4dcd-9b9a-3742d7e03a27": UpdateAddress, } @classmethod def update_address( cls, address_uu_id: str, data: UpdateAddress, token_dict: Union[EmployeeTokenObject, OccupantTokenObject], ): if isinstance(token_dict, EmployeeTokenObject): address = Addresses.filter_one( Addresses.uu_id == address_uu_id, ).data if not address: raise HTTPExceptionApi( status_code=404, detail=f"Address not found. User can not update with given address uuid : {address_uu_id}", ) data_dict = data.excluded_dump() updated_address = address.update(**data_dict) updated_address.save() return AlchemyJsonResponse( completed=True, message="Address updated successfully", result=updated_address.get_dict(), ) elif isinstance(token_dict, OccupantTokenObject): raise HTTPExceptionApi( status_code=403, detail="Occupant can not update address.", )