validations updated

This commit is contained in:
berkay 2025-01-19 21:06:00 +03:00
parent d6785ed36f
commit 8e34497c80
49 changed files with 2642 additions and 142 deletions

View File

@ -11,7 +11,7 @@ from ApiLibrary.common.line_number import get_line_number_for_error
from ApiLibrary.date_time_actions.date_functions import DateTimeLocal
from ApiServices.Login.user_login_handler import UserLoginModule
from ApiServices.Token.token_handler import TokenService
from ApiValidations.Custom.token_objects import CompanyToken
from ApiValidations.Custom.token_objects import CompanyToken, OccupantToken
from ApiValidations.Request.authentication import (
Login,
EmployeeSelectionValidation,
@ -20,11 +20,16 @@ from ApiValidations.Request.authentication import (
EmployeeSelection,
)
from ErrorHandlers import HTTPExceptionApi
from Schemas.building.build import (
BuildLivingSpace,
BuildParts,
RelationshipEmployee2Build,
)
from Schemas.company.company import Companies
from Schemas.company.department import Departments, Duties, Duty
from Schemas.company.employee import Staff, Employees
from Schemas.event.event import Event2Employee
from Schemas.identity.identity import Users
from Schemas.event.event import Event2Employee, Event2Occupant
from Schemas.identity.identity import OccupantTypes, Users
from Services.Redis.Actions.actions import RedisActions
from .models import (
LoginData,
@ -170,9 +175,7 @@ class AuthenticationSelectEventMethods(MethodToEvent):
)
# Get reachable events
reachable_event_list_id = Event2Employee.get_event_id_by_employee_id(
employee_id=employee.id
)
reachable_event_codes = Event2Employee.get_event_codes(employee_id=employee.id)
# Get staff and duties
staff = Staff.filter_one(Staff.id == employee.staff_id, db=db_session).data
@ -189,7 +192,6 @@ class AuthenticationSelectEventMethods(MethodToEvent):
**Duties.valid_record_dict,
db=db_session,
).data
# Create company token
company_token = CompanyToken(
company_uu_id=selected_company.uu_id.__str__(),
@ -203,7 +205,7 @@ class AuthenticationSelectEventMethods(MethodToEvent):
staff_uu_id=staff.uu_id.__str__(),
employee_id=employee.id,
employee_uu_id=employee.uu_id.__str__(),
reachable_event_list_id=reachable_event_list_id,
reachable_event_codes=reachable_event_codes,
)
try: # Update Redis
update_token = TokenService.update_token_at_redis(
@ -226,11 +228,80 @@ class AuthenticationSelectEventMethods(MethodToEvent):
request: "Request",
):
"""Handle occupant type selection"""
db = BuildLivingSpace.new_session()
# Get selected occupant type
selected_build_living_space = BuildLivingSpace.filter_one(
BuildLivingSpace.uu_id == data.build_living_space_uu_id,
db=db,
).data
if not selected_build_living_space:
raise HTTPExceptionApi(
error_code="HTTP_400_BAD_REQUEST",
lang=token_dict.lang,
loc=get_line_number_for_error(),
sys_msg="Occupant selection not implemented",
sys_msg="Selected occupant type not found",
)
# Get reachable events
reachable_event_codes = Event2Occupant.get_event_codes(
build_living_space_id=selected_build_living_space.id
)
occupant_type = OccupantTypes.filter_one(
OccupantTypes.id == selected_build_living_space.occupant_type_id,
db=db,
system=True,
).data
build_part = BuildParts.filter_one(
BuildParts.id == selected_build_living_space.build_parts_id,
db=db,
).data
build = BuildParts.filter_one(
BuildParts.id == build_part.build_id,
db=db,
).data
responsible_employee = Employees.filter_one(
Employees.id == build_part.responsible_employee_id,
db=db,
).data
related_company = RelationshipEmployee2Build.filter_one(
RelationshipEmployee2Build.member_id == build.id,
db=db,
).data
# Get company
company_related = Companies.filter_one(
Companies.id == related_company.company_id,
db=db,
).data
# Create occupant token
occupant_token = OccupantToken(
living_space_id=selected_build_living_space.id,
living_space_uu_id=selected_build_living_space.uu_id.__str__(),
occupant_type_id=occupant_type.id,
occupant_type_uu_id=occupant_type.uu_id.__str__(),
occupant_type=occupant_type.occupant_type,
build_id=build.id,
build_uuid=build.uu_id.__str__(),
build_part_id=build_part.id,
build_part_uuid=build_part.uu_id.__str__(),
responsible_employee_id=responsible_employee.id,
responsible_employee_uuid=responsible_employee.uu_id.__str__(),
responsible_company_id=company_related.id,
responsible_company_uuid=company_related.uu_id.__str__(),
reachable_event_codes=reachable_event_codes,
)
try: # Update Redis
update_token = TokenService.update_token_at_redis(
request=request, add_payload=occupant_token
)
return update_token
except Exception as e:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg=f"{e}",
)
@classmethod
@ -246,14 +317,6 @@ class AuthenticationSelectEventMethods(MethodToEvent):
elif token_dict.is_occupant:
return cls._handle_occupant_selection(data, token_dict, request)
# except Exception as e:
# raise HTTPExceptionApi(
# error_code="HTTP_500_INTERNAL_SERVER_ERROR",
# lang="en",
# loc=get_line_number_for_error(),
# sys_msg=str(e),
# )
class AuthenticationCheckTokenEventMethods(MethodToEvent):
event_type = "LOGIN"

View File

@ -3,8 +3,8 @@ Authentication endpoint configurations.
"""
from typing import TYPE_CHECKING, Dict, Any, Union, Annotated
from fastapi import HTTPException, status, Body
from ApiServices.Token.token_handler import TokenService
from ApiValidations.Request import (
Logout,
Login,
@ -49,7 +49,7 @@ from ApiEvents.abstract_class import (
)
if TYPE_CHECKING:
from fastapi import Request
from fastapi import Request, HTTPException, status, Body
from ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTokenObject
@ -59,22 +59,18 @@ from ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTok
@endpoint_wrapper("/authentication/select")
async def authentication_select_company_or_occupant_type(
request: "Request",
data: EndpointBaseRequestModel,
data: Union[EmployeeSelection, OccupantSelection],
) -> Dict[str, Any]:
"""
Select company or occupant type.
"""
auth_dict = authentication_select_company_or_occupant_type.auth
if data.data.get("company_uu_id"):
data = EmployeeSelection(**data.data)
elif data.data.get("build_living_space_uu_id"):
data = OccupantSelection(**data.data)
if await AuthenticationSelectEventMethods.authentication_select_company_or_occupant_type(
request=request, data=data, token_dict=auth_dict
):
if isinstance(data, EmployeeSelection):
if data.is_employee:
return {"selected_company": data.company_uu_id}
elif isinstance(data, OccupantSelection):
elif data.is_occupant:
return {"selected_occupant": data.build_living_space_uu_id}
@ -99,9 +95,17 @@ async def authentication_check_token_is_valid(
"""
Check if a token is valid.
"""
try:
access_token = TokenService.get_access_token_from_request(request=request)
if TokenService.get_object_via_access_key(access_token=access_token):
return {
"headers": dict(request.headers),
"data": data.model_dump(),
"status": True,
"message": "Access Token is valid",
}
except HTTPException:
return {
"status": False,
"message": "Access Token is NOT valid",
}

View File

@ -6,15 +6,13 @@ to be used by the dynamic route creation system.
"""
from typing import Dict, List, Any
from .auth.endpoints import AUTH_CONFIG
from .events.auth.endpoints import AUTH_CONFIG
# Registry of all route configurations
ROUTE_CONFIGS = [
AUTH_CONFIG,
]
ROUTE_CONFIGS = [AUTH_CONFIG]
def get_route_configs() -> List[Dict[str, Any]]:
"""Get all registered route configurations."""
return [AUTH_CONFIG]
return ROUTE_CONFIGS

View File

@ -2,7 +2,7 @@
Account records service implementation.
"""
from typing import Dict, Any, Union
from typing import Union
from pydantic import Field
from ApiEvents.abstract_class import MethodToEvent, endpoint_wrapper
@ -26,7 +26,6 @@ from Schemas import (
)
from Services.PostgresDb.Models.alchemy_response import (
AlchemyJsonResponse,
DictJsonResponse,
)
from ApiValidations.Response import AccountRecordResponse
from .models import (
@ -36,7 +35,7 @@ from .models import (
)
class AccountRecordsListEventMethods(MethodToEvent):
class AccountListEventMethod(MethodToEvent):
event_type = "SELECT"
event_description = ""
@ -47,8 +46,8 @@ class AccountRecordsListEventMethods(MethodToEvent):
"208e6273-17ef-44f0-814a-8098f816b63a": "account_records_list_flt_res",
}
__event_validation__ = {
"7192c2aa-5352-4e36-98b3-dafb7d036a3d": AccountRecordResponse,
"208e6273-17ef-44f0-814a-8098f816b63a": AccountRecordResponse,
"7192c2aa-5352-4e36-98b3-dafb7d036a3d": (AccountRecordResponse, [AccountRecords.__language_model__]),
"208e6273-17ef-44f0-814a-8098f816b63a": (AccountRecordResponse, [AccountRecords.__language_model__]),
}
@classmethod
@ -217,7 +216,7 @@ class AccountRecordsListEventMethods(MethodToEvent):
)
class AccountRecordsCreateEventMethods(MethodToEvent):
class AccountCreateEventMethod(MethodToEvent):
event_type = "CREATE"
event_description = ""
@ -227,7 +226,7 @@ class AccountRecordsCreateEventMethods(MethodToEvent):
"31f4f32f-0cd4-4995-8a6a-f9f56335848a": "account_records_create",
}
__event_validation__ = {
"31f4f32f-0cd4-4995-8a6a-f9f56335848a": InsertAccountRecord,
"31f4f32f-0cd4-4995-8a6a-f9f56335848a": (InsertAccountRecord, [AccountRecords.__language_model__]),
}
@classmethod
@ -305,7 +304,7 @@ class AccountRecordsCreateEventMethods(MethodToEvent):
)
class AccountRecordsUpdateEventMethods(MethodToEvent):
class AccountUpdateEventMethod(MethodToEvent):
event_type = "UPDATE"
event_description = ""
@ -315,11 +314,11 @@ class AccountRecordsUpdateEventMethods(MethodToEvent):
"ec98ef2c-bcd0-432d-a8f4-1822a56c33b2": "account_records_update",
}
__event_validation__ = {
"ec98ef2c-bcd0-432d-a8f4-1822a56c33b2": UpdateAccountRecord,
"ec98ef2c-bcd0-432d-a8f4-1822a56c33b2": (UpdateAccountRecord, [AccountRecords.__language_model__]),
}
@classmethod
def build_area_update(
def account_records_update(
cls,
build_uu_id: str,
data: UpdateAccountRecordRequestModel,

View File

@ -14,19 +14,15 @@ from Services.PostgresDb.Models.alchemy_response import DictJsonResponse
from fastapi import Request, Path, Body
@endpoint_wrapper("/account/records/address/list")
@endpoint_wrapper("/account/records/list")
async def address_list(request: "Request", data: EndpointBaseRequestModel):
"""Handle address list endpoint."""
auth_dict = address_list.auth
return {
"data": data,
"request": str(request.headers),
"request_url": str(request.url),
"request_base_url": str(request.base_url),
}
code_dict = getattr(address_list, "func_code", {"function_code": None})
return {"auth_dict": auth_dict, "code_dict": code_dict, "data": data}
@endpoint_wrapper("/account/records/address/create")
@endpoint_wrapper("/account/records/create")
async def address_create(request: "Request", data: EndpointBaseRequestModel):
"""Handle address creation endpoint."""
return {
@ -37,7 +33,7 @@ async def address_create(request: "Request", data: EndpointBaseRequestModel):
}
@endpoint_wrapper("/account/records/address/search")
@endpoint_wrapper("/account/records/search")
async def address_search(request: "Request", data: EndpointBaseRequestModel):
"""Handle address search endpoint."""
auth_dict = address_search.auth
@ -45,7 +41,7 @@ async def address_search(request: "Request", data: EndpointBaseRequestModel):
return {"auth_dict": auth_dict, "code_dict": code_dict, "data": data}
@endpoint_wrapper("/account/records/address/{address_uu_id}")
@endpoint_wrapper("/account/records/{address_uu_id}")
async def address_update(
request: Request,
address_uu_id: str = Path(..., description="UUID of the address to update"),

View File

@ -0,0 +1,336 @@
"""
request models.
"""
from typing import TYPE_CHECKING, Dict, Any, List, Optional, TypedDict, Union
from pydantic import BaseModel, Field, model_validator, RootModel, ConfigDict
from ApiEvents.abstract_class import MethodToEvent
from ApiEvents.base_request_model import BaseRequestModel, DictRequestModel
from ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTokenObject
from ApiValidations.Request.address import SearchAddress, UpdateAddress
from ApiValidations.Request.base_validations import ListOptions
from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi
from Schemas.identity.identity import (
AddressPostcode,
AddressStreet,
Addresses,
RelationshipEmployee2PostCode,
)
from ApiValidations.Request import (
InsertAddress,
)
from ApiValidations.Response import (
ListAddressResponse,
)
if TYPE_CHECKING:
from fastapi import Request
class AddressListEventMethod(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, [Addresses.__language_model__]),
"52afe375-dd95-4f4b-aaa2-4ec61bc6de52": (ListAddressResponse, [Addresses.__language_model__]),
}
@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(),
db=db,
).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),
db=db,
).query
Addresses.filter_attr = list_options
records = Addresses.filter_all(db=db).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 AddressCreateEventMethod(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, [Addresses.__language_model__]),
}
@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 AddressSearchEventMethod(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, [Addresses.__language_model__]),
}
@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],
) -> Any:
"""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 AddressUpdateEventMethod(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, [Addresses.__language_model__]),
}
@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.",
)

View File

@ -6,7 +6,7 @@ to be used by the dynamic route creation system.
"""
from typing import Dict, List, Any
from .account.endpoints import ACCOUNT_RECORDS_CONFIG
from .events.account.endpoints import ACCOUNT_RECORDS_CONFIG
# Registry of all route configurations
@ -17,4 +17,4 @@ ROUTE_CONFIGS = [
def get_route_configs() -> List[Dict[str, Any]]:
"""Get all registered route configurations."""
return [ACCOUNT_RECORDS_CONFIG]
return ROUTE_CONFIGS

View File

View File

@ -0,0 +1,52 @@
from typing import TYPE_CHECKING, Dict, Any, Union
from ApiEvents.base_request_model import DictRequestModel, EndpointBaseRequestModel
from ApiEvents.abstract_class import (
RouteFactoryConfig,
EndpointFactoryConfig,
endpoint_wrapper,
)
if TYPE_CHECKING:
from fastapi import Request, HTTPException, status, Body
from ApiValidations.Custom.token_objects import EmployeeTokenObject, OccupantTokenObject
# Type aliases for common types
prefix = ""
@endpoint_wrapper(f"{prefix}")
async def authentication_select_company_or_occupant_type(
request: "Request",
data: EndpointBaseRequestModel,
) -> Dict[str, Any]:
"""
Select company or occupant type.
"""
auth_dict = authentication_select_company_or_occupant_type.auth
return {}
_CONFIG = RouteFactoryConfig(
name="",
prefix=prefix,
tags=[""],
include_in_schema=True,
endpoints=[
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/",
url_of_endpoint="/",
endpoint="/",
method="POST",
summary="",
description="",
is_auth_required=True, # Needs token_dict
is_event_required=False,
endpoint_function=lambda: "",
),
],
).as_dict()

View File

@ -0,0 +1,19 @@
"""
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

View File

@ -0,0 +1,325 @@
"""
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.",
)

View File

@ -1,5 +1,6 @@
"""Event Service API initialization"""
"""Validation Service API initialization"""
from .route_configs import get_route_configs
__all__ = ["get_route_configs"]

View File

@ -0,0 +1,116 @@
from typing import Dict, Any
from .models import ValidationsPydantic
from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi
from .validation import (
ValidationsBoth,
ValidationsHeaders,
ValidationsValidations,
)
from ApiEvents.abstract_class import RouteFactoryConfig, EndpointFactoryConfig
from ApiEvents.base_request_model import EndpointBaseRequestModel
from ApiLibrary.common.line_number import get_line_number_for_error
from Services.PostgresDb.Models.alchemy_response import DictJsonResponse
from fastapi import Request, Path, Body
from middleware.token_event_middleware import TokenEventMiddleware
prefix = "/validation"
@TokenEventMiddleware.validation_required
async def validations_validations_select(request: Request, data: EndpointBaseRequestModel) -> Dict[str, Any]:
"""
Select validations.
"""
wrapped_context = getattr(validations_validations_select, "__wrapped__", None)
auth_context = getattr(wrapped_context, "auth", None)
validation_code = getattr(validations_validations_select, "validation_code", {"validation_code": None})
if not validation_code:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Validation code not found",
)
validations_pydantic = ValidationsPydantic(
class_model=validation_code.get("class", None),
reachable_event_code=validation_code.get("reachable_event_code", None),
lang=getattr(auth_context, "lang", None),
)
validations_both = ValidationsBoth.retrieve_both_validations_and_headers(validations_pydantic)
return {"status": "OK", "validation_code": validation_code, **validations_both }
@TokenEventMiddleware.validation_required
async def validations_headers_select(request: Request, data: EndpointBaseRequestModel) -> Dict[str, Any]:
"""
Select headers.
"""
ValidationsHeaders.retrieve_headers()
return {
"status": "OK",
}
@TokenEventMiddleware.validation_required
async def validations_validations_and_headers_select(request: Request, data: EndpointBaseRequestModel) -> Dict[str, Any]:
"""
Select validations and headers.
"""
ValidationsBoth.retrieve_both_validations_and_headers()
return {
"status": "OK",
}
VALIDATION_CONFIG_MAIN =RouteFactoryConfig(
name="validations",
prefix=prefix,
tags=["Validation"],
include_in_schema=True,
endpoints=[
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/select",
url_of_endpoint=f"{prefix}/validations/select",
endpoint="/select",
method="POST",
summary="Select company or occupant type",
description="Select company or occupant type",
is_auth_required=False, # Needs token_dict
is_event_required=False,
endpoint_function=validations_validations_select,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/headers/select",
url_of_endpoint=f"{prefix}/headers/select",
endpoint="/headers/select",
method="POST",
summary="Select company or occupant type",
description="Select company or occupant type",
is_auth_required=False, # Needs token_dict
is_event_required=False,
endpoint_function=validations_headers_select,
),
EndpointFactoryConfig(
url_prefix=prefix,
url_endpoint="/both/select",
url_of_endpoint=f"{prefix}/validationsAndHeaders/select",
endpoint="/both/select",
method="POST",
summary="Select company or occupant type",
description="Select company or occupant type",
is_auth_required=False, # Needs token_dict
is_event_required=False,
endpoint_function=validations_validations_and_headers_select,
),
],
)
VALIDATION_CONFIG = VALIDATION_CONFIG_MAIN.as_dict()
VALIDATION_ENDPOINTS = [endpoint.url_of_endpoint for endpoint in VALIDATION_CONFIG_MAIN.endpoints]

View File

@ -0,0 +1,29 @@
"""
Validation records request and response models.
"""
from typing import TYPE_CHECKING, Dict, Any
from pydantic import BaseModel, Field, RootModel
from ApiEvents.base_request_model import BaseRequestModel
if TYPE_CHECKING:
from ApiValidations.Request import (
ListOptions,
)
class ValidationsPydantic(BaseModel):
class_model: str
reachable_event_code: str
lang: str
class InsertValidationRecordRequestModel(BaseRequestModel):
pass
class UpdateValidationRecordRequestModel(BaseRequestModel):
pass
class ListOptionsValidationRecordRequestModel(BaseRequestModel):
pass

View File

@ -0,0 +1,97 @@
"""
Validation request models.
"""
from typing import TYPE_CHECKING, Dict, Any, Literal, Optional, TypedDict, Union
from pydantic import BaseModel, Field, model_validator, RootModel, ConfigDict
from ApiLibrary.common.line_number import get_line_number_for_error
from ApiValidations.Custom.validation_response import ValidationModel, ValidationParser
from ApiEvents.abstract_class import MethodToEvent
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 .models import ValidationsPydantic
if TYPE_CHECKING:
from fastapi import Request
class AllModelsImport:
@classmethod
def import_all_models(cls):
from ApiEvents.events.account.account_records import (
AccountListEventMethod,
AccountUpdateEventMethod,
AccountCreateEventMethod,
)
from ApiEvents.events.address.address import (
AddressListEventMethod,
AddressUpdateEventMethod,
AddressCreateEventMethod,
AddressSearchEventMethod,
)
return dict(
AccountListEventMethod=AccountListEventMethod,
AccountUpdateEventMethod=AccountUpdateEventMethod,
AccountCreateEventMethod=AccountCreateEventMethod,
AddressListEventMethod=AddressListEventMethod,
AddressUpdateEventMethod=AddressUpdateEventMethod,
AddressCreateEventMethod=AddressCreateEventMethod,
AddressSearchEventMethod=AddressSearchEventMethod,
)
class ValidationsBoth(MethodToEvent):
@classmethod
def retrieve_both_validations_and_headers(
cls, event: ValidationsPydantic
) -> Dict[str, Any]:
EVENT_MODELS = AllModelsImport.import_all_models()
return_single_model = EVENT_MODELS.get(event.class_model, None)
print("return_single_model", return_single_model, type(return_single_model))
# event_class_validation = getattr(return_single_model, "__event_validation__", None)
if not return_single_model:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Validation code not found",
)
response_model = return_single_model.retrieve_event_response_model(event.reachable_event_code)
language_model_all = return_single_model.retrieve_language_parameters(function_code=event.reachable_event_code, language=event.lang)
language_model = language_model_all.get("language_model", None)
language_models = language_model_all.get("language_models", None)
validation = ValidationModel(response_model, language_model, language_models)
return {
"headers": validation.headers,
"validation": validation.validation,
"language_models": language_model_all,
}
class ValidationsValidations(MethodToEvent):
@classmethod
def retrieve_validations(
cls, event: ValidationsPydantic
) -> Dict[str, Any]:
return {}
class ValidationsHeaders(MethodToEvent):
@classmethod
def retrieve_headers(
cls, event: ValidationsPydantic
) -> Dict[str, Any]:
return {}

View File

@ -0,0 +1,33 @@
from typing import Callable
class ValidationActions:
def __init__(self, function_code: str, func: Callable = None):
self.function_code = function_code
self.func = func
@classmethod
def retrieve_validation(cls):
"""
Retrieve validation [] by validation via [Response Model of Table]
"""
return
@classmethod
def retrieve_headers(cls):
"""
Retrieve headers for validations [] by event function code [Response Model of Table]
"""
return
@classmethod
def retrieve_validations_and_headers(cls):
"""
Retrieve validations and headers [] via event function code [Response Model of Table][]
"""
return
# Singleton class
validation_action = ValidationActions(function_code="")

View File

@ -5,13 +5,13 @@ This module collects and registers all route configurations from different modul
to be used by the dynamic route creation system.
"""
from typing import Dict, List, Any
from typing import Dict, List, Any, TypeVar
from .events.validation.endpoints import VALIDATION_CONFIG
# Registry of all route configurations
ROUTE_CONFIGS = []
ROUTE_CONFIGS = [VALIDATION_CONFIG]
def get_route_configs() -> List[Dict[str, Any]]:
"""Get all registered route configurations."""
return []
return ROUTE_CONFIGS

View File

@ -24,6 +24,10 @@ from fastapi import Request, Depends, APIRouter
from functools import wraps
import inspect
from ApiLibrary.common.line_number import get_line_number_for_error
from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi
from Schemas.rules.rules import EndpointRestriction
ResponseModel = TypeVar("ResponseModel", bound=BaseModel)
@ -239,14 +243,91 @@ class MethodToEvent:
event_description: ClassVar[str] = ""
event_category: ClassVar[str] = ""
__event_keys__: ClassVar[Dict[str, str]] = {}
__event_validation__: ClassVar[Dict[str, Tuple[Type[ResponseModel], List[Any]]]] = (
{}
)
__event_validation__: Dict[str, Tuple[Type, Union[List, tuple]]] = {}
@classmethod
def retrieve_language_parameters(
cls, language: str, function_code: str
) -> Dict[str, str]:
def retrieve_event_response_model(cls, function_code: str) -> Tuple:
"""Retrieve event validation for a specific function.
Args:
function_code: Function identifier
Returns:
Tuple containing response model and language models
"""
event_validation_list = cls.__event_validation__.get(function_code, None)
if not event_validation_list:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Function not found",
)
return event_validation_list[0]
@classmethod
def retrieve_event_languages(cls, function_code: str) -> Union[List, tuple]:
"""Retrieve event description for a specific function.
Args:
function_code: Function identifier
Returns:
Event description
"""
event_keys_list = cls.__event_validation__.get(function_code, None)
print('event_keys_list', event_keys_list)
if not event_keys_list:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Function not found",
)
function_language_models: Union[List, tuple] = event_keys_list[1]
if not function_language_models:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Function not found",
)
return function_language_models
@staticmethod
def merge_models(language_model: List) -> Tuple:
merged_models = {"tr": {}, "en": {}}
for model in language_model:
for lang in dict(model).keys():
if lang not in merged_models:
merged_models[lang] = model[lang]
else:
merged_models[lang].update(model[lang])
return merged_models
@classmethod
def retrieve_event_function(cls, function_code: str) -> Dict[str, str]:
"""Retrieve event parameters for a specific function.
Args:
function_code: Function identifier
Returns:
Dictionary of event parameters
"""
function_event = cls.__event_keys__[function_code]
function_itself = getattr(cls, function_event, None)
if not function_itself:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Function not found",
)
return function_itself
@classmethod
def retrieve_language_parameters(cls, function_code: str, language: str = "tr") -> Dict[str, str]:
"""Retrieve language-specific parameters for an event.
Args:
@ -256,20 +337,24 @@ class MethodToEvent:
Returns:
Dictionary of language-specific field mappings
"""
validation_dict = dict(cls.__event_validation__)
if function_code not in validation_dict:
return {}
event_response_model, event_language_models = validation_dict[function_code]
# Collect language-specific field mappings
language_models = {}
for model in event_language_models:
language_models.update(model.get(language, model.get("tr", {})))
event_language_models = cls.retrieve_event_languages(function_code)
event_response_model = cls.retrieve_event_response_model(function_code)
event_response_model_merged = cls.merge_models(event_language_models)
event_response_model_merged_lang = event_response_model_merged[language]
# Map response model fields to language-specific values
return {
field: language_models[field]
print('event_response_model', dict(
event_response_model=event_response_model,
event_response_model_merged_lang=event_response_model_merged_lang,
event_response_model_merged=event_response_model_merged,
language=language,
function_code=function_code,
))
only_language_dict = {
field: event_response_model_merged_lang[field]
for field in event_response_model.model_fields
if field in language_models
if field in event_response_model_merged_lang
}
return {
"language_model": only_language_dict,
"language_models": event_response_model_merged,
}

View File

@ -37,7 +37,6 @@ class UserLoginModule:
async def login_user_via_credentials(self, access_data: "Login") -> Dict[str, Any]:
"""Login user via credentials."""
# Get the actual data from the BaseRequestModel if needed
print("access_data", access_data)
if hasattr(access_data, "data"):
access_data = access_data.data

View File

@ -1,6 +1,5 @@
"""Token service for handling authentication tokens and user sessions."""
import json
from typing import List, Union, TypeVar, Dict, Any, Optional, TYPE_CHECKING
from AllConfigs.Token.config import Auth
@ -75,7 +74,6 @@ class TokenService:
)
occupants_selection_dict: Dict[str, Any] = {}
for living_space in living_spaces:
build_parts_selection = BuildParts.filter_all(
BuildParts.id == living_space.build_parts_id,
@ -221,7 +219,6 @@ class TokenService:
company_address = Addresses.filter_by_one(
id=company.official_address_id, db=db_session
).data
companies_list.append(
{
"uu_id": str(company.uu_id),
@ -287,14 +284,13 @@ class TokenService:
Users.client_arrow = DateTimeLocal(is_client=True, timezone=user.local_timezone)
db_session = UsersTokens.new_session()
# Handle login based on user type
login_dict = (
cls.do_occupant_login(request=request, user=user, domain=domain)
if user.is_occupant
else (
cls.do_employee_login(request=request, user=user, domain=domain)
if user.is_employee
else {}
if user.is_occupant:
login_dict = cls.do_occupant_login(
request=request, user=user, domain=domain
)
elif user.is_employee:
login_dict = cls.do_employee_login(
request=request, user=user, domain=domain
)
# Handle remember me functionality

View File

@ -58,7 +58,7 @@ class OccupantToken(BaseModel):
responsible_employee_id: Optional[int] = None
responsible_employee_uuid: Optional[str] = None
reachable_event_list_id: Optional[list] = None # ID list of reachable modules
reachable_event_codes: Optional[list[str]] = None # ID list of reachable modules
# reachable_event_list_uu_id: Optional[list] = None # UUID list of reachable modules
@ -81,7 +81,7 @@ class CompanyToken(BaseModel): # Required Company Object for an employee
bulk_duties_id: int
reachable_event_list_id: Optional[list] = None # ID list of reachable modules
reachable_event_codes: Optional[list[str]] = None # ID list of reachable modules
# reachable_event_list_uu_id: Optional[list] = None # UUID list of reachable modules

View File

@ -0,0 +1,68 @@
import json
from typing import Any, ClassVar, TypeVar, Dict, Tuple, List
from pydantic import BaseModel
from ErrorHandlers import HTTPExceptionApi
from ApiLibrary.common.line_number import get_line_number_for_error
from ApiValidations.Request.base_validations import CrudRecords, PydanticBaseModel
class ValidationParser:
def __init__(self, active_validation: BaseModel):
self.core_validation = active_validation
self.annotations = active_validation.model_json_schema()
self.annotations = json.loads(json.dumps(self.annotations))
self.schema = {}
self.parse()
def parse(self):
from ApiValidations.Request.base_validations import CrudRecords, PydanticBaseModel
properties = dict(self.annotations.get("properties")).items()
total_class_annotations = {
**self.core_validation.__annotations__,
**PydanticBaseModel.__annotations__,
**CrudRecords.__annotations__,
}
for key, value in properties:
default, required, possible_types = dict(value).get("default", None), True, []
if dict(value).get("anyOf", None):
for _ in dict(value).get("anyOf") or []:
type_opt = json.loads(json.dumps(_))
if not type_opt.get("type") == "null":
possible_types.append(type_opt.get("type"))
field_type = possible_types[0]
required = False
else:
field_type = dict(value).get("type", "string")
attribute_of_class = total_class_annotations.get(key, None)
aoc = str(attribute_of_class) if attribute_of_class else None
if attribute_of_class:
if aoc in ("<class 'str'>", "typing.Optional[str]"):
field_type, required = "string", aoc == "<class 'str'>"
elif aoc in ("<class 'int'>", "typing.Optional[int]"):
field_type, required = "integer", aoc == "<class 'int'>"
elif aoc in ("<class 'bool'>", "typing.Optional[bool]"):
field_type, required = "boolean", aoc == "<class 'bool'>"
elif aoc in ("<class 'float'>", "typing.Optional[float]"):
field_type, required = "float", aoc == "<class 'float'>"
elif aoc in ("<class 'datetime.datetime'>", "typing.Optional[datetime.datetime]"):
field_type, required = "datetime", aoc == "<class 'datetime.datetime'>"
self.schema[key] = {
"type": field_type, "required": required, "default": default
}
class ValidationModel:
def __init__(self, response_model: BaseModel, language_model, language_models):
self.response_model = response_model
self.validation = None
self.headers = language_model
self.language_models = language_models
self.get_validation()
def get_validation(self) -> Tuple:
self.headers = self.language_models
self.validation = ValidationParser(self.response_model).schema

View File

@ -61,12 +61,21 @@ class OccupantSelection(BaseModel, OccupantSelectionValidation):
model_config = ConfigDict(
json_schema_extra={
"example": {
"build_living_space_uu_id": "987fcdeb-51a2-43e7-9876-543210987654"
}
"example": [
{"company_uu_id": "abcdef12-3456-7890-abcd-ef1234567890"},
{"build_living_space_uu_id": "987fcdeb-51a2-43e7-9876-543210987654"},
],
}
)
@property
def is_employee(self):
return False
@property
def is_occupant(self):
return True
class EmployeeSelectionValidation:
tr = {"company_uu_id": "Şirket UU ID"}
@ -78,10 +87,21 @@ class EmployeeSelection(BaseModel, EmployeeSelectionValidation):
model_config = ConfigDict(
json_schema_extra={
"example": {"company_uu_id": "abcdef12-3456-7890-abcd-ef1234567890"}
"example": [
{"company_uu_id": "abcdef12-3456-7890-abcd-ef1234567890"},
{"build_living_space_uu_id": "987fcdeb-51a2-43e7-9876-543210987654"},
],
}
)
@property
def is_employee(self):
return True
@property
def is_occupant(self):
return False
class LoginValidation:
tr = {

View File

@ -1,3 +1,16 @@
from .account_responses import AccountRecordResponse
from .address_responses import ListAddressResponse
from .auth_responses import (
AuthenticationLoginResponse,
AuthenticationRefreshResponse,
AuthenticationUserInfoResponse
)
__all__ = ["AccountRecordResponse"]
__all__ = [
"AccountRecordResponse",
"ListAddressResponse",
"AuthenticationLoginResponse",
"AuthenticationRefreshResponse",
"AuthenticationUserInfoResponse",
]

View File

@ -0,0 +1,20 @@
from typing import Optional
from pydantic import BaseModel
class ListAddressResponse(BaseModel):
build_number: Optional[str] = None
door_number: Optional[str] = None
floor_number: Optional[str] = None
comment_address: Optional[str] = None
letter_address: Optional[str] = None
short_letter_address: Optional[str] = None
latitude: Optional[float] = None
longitude: Optional[float] = None
street_uu_id: Optional[str] = None
class AddressPostCodeResponse:
street_id: Optional[int] = None
street_uu_id: Optional[str] = None
postcode: Optional[str] = None

View File

@ -0,0 +1,36 @@
from pydantic import BaseModel
from typing import Optional, List, Dict, Any
from datetime import datetime
from uuid import UUID
class AuthenticationLoginResponse(BaseModel):
"""Response model for authentication login endpoint"""
token: str
refresh_token: str
token_type: str
expires_in: int
user_info: Dict[str, Any]
class AuthenticationRefreshResponse(BaseModel):
"""Response model for authentication refresh endpoint"""
token: str
refresh_token: str
token_type: str
expires_in: int
class AuthenticationUserInfoResponse(BaseModel):
"""Response model for authentication user info endpoint"""
user_id: int
username: str
email: str
first_name: str
last_name: str
is_active: bool
created_at: datetime
updated_at: Optional[datetime]

View File

@ -0,0 +1,105 @@
from pydantic import BaseModel
from typing import Optional, TypeVar, Generic, List
from datetime import datetime
from uuid import UUID
T = TypeVar("T")
class BaseResponse(BaseModel):
"""Base response model that all response models inherit from.
This model provides common fields that are present in all database records,
including tracking information (created/updated timestamps), user actions
(created by, updated by, confirmed by), and record status (active, deleted).
Attributes:
uu_id (str): Unique identifier for the record, typically a UUID
created_at (datetime): Timestamp when the record was created
updated_at (Optional[datetime]): Timestamp when the record was last updated
created_by (Optional[str]): Username or identifier of the user who created the record
updated_by (Optional[str]): Username or identifier of the user who last updated the record
confirmed_by (Optional[str]): Username or identifier of the user who confirmed the record
is_confirmed (Optional[bool]): Whether the record has been confirmed/approved
active (Optional[bool]): Whether the record is currently active
deleted (Optional[bool]): Whether the record has been marked as deleted
expiry_starts (Optional[datetime]): When the record becomes valid/active
expiry_ends (Optional[datetime]): When the record expires/becomes inactive
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
"""
uu_id: str
created_at: datetime
updated_at: Optional[datetime]
created_by: Optional[str]
updated_by: Optional[str]
confirmed_by: Optional[str]
is_confirmed: Optional[bool] = None
active: Optional[bool] = True
deleted: Optional[bool] = False
expiry_starts: Optional[datetime]
expiry_ends: Optional[datetime]
is_notification_send: Optional[bool] = False
is_email_send: Optional[bool] = False
class Config:
"""Pydantic configuration for the base response model.
Attributes:
from_attributes (bool): Enables ORM mode for SQLAlchemy integration
"""
from_attributes = True
class CrudCollection(BaseModel, Generic[T]):
"""Base collection model for paginated responses.
This model is used to return collections of items with pagination information.
It is generic over the type of items in the collection, allowing it to be
used with any response model.
Type Parameters:
T: The type of items in the collection
Attributes:
page (int): Current page number, 1-based indexing
size (int): Number of items per page
total (int): Total number of items across all pages
order_field (str): Field used for sorting the collection
order_type (str): Sort direction ('asc' or 'desc')
items (List[T]): List of items in the current page
Example:
```python
class UserResponse(BaseResponse):
name: str
email: str
users = CrudCollection[UserResponse](
page=1,
size=10,
total=100,
order_field="name",
order_type="asc",
items=[...]
)
```
"""
page: int = 1
size: int = 10
total: int = 0
order_field: str = "id"
order_type: str = "asc"
items: List[T] = []
class Config:
"""Pydantic configuration for the collection model.
Attributes:
from_attributes (bool): Enables ORM mode for SQLAlchemy integration
"""
from_attributes = True

View File

@ -0,0 +1,90 @@
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
from uuid import UUID
from decimal import Decimal
from .base_responses import BaseResponse, CrudCollection
class DecisionBookBudgetBooksResponse(BaseResponse):
"""Response model for decision book budget books"""
country: str
branch_type: int = 0
company_id: int
company_uu_id: str
branch_id: Optional[int]
branch_uu_id: Optional[str]
build_decision_book_id: int
build_decision_book_uu_id: Optional[str]
class DecisionBookBudgetBooksCollection(
CrudCollection[DecisionBookBudgetBooksResponse]
):
"""Collection of decision book budget books"""
pass
class DecisionBookBudgetCodesResponse(BaseResponse):
"""Response model for decision book budget codes"""
budget_code: str
comment_line: str
budget_type: str
budget_code_seperator: str = "."
system_id: int = 0
locked: bool = False
company_id: Optional[int]
company_uu_id: str
customer_id: Optional[int]
customer_uu_id: str
class DecisionBookBudgetCodesCollection(
CrudCollection[DecisionBookBudgetCodesResponse]
):
"""Collection of decision book budget codes"""
pass
class DecisionBookBudgetMasterResponse(BaseResponse):
"""Response model for decision book budget master"""
budget_type: str
currency: str = "TRY"
total_budget: Decimal
tracking_period_id: Optional[int]
tracking_period_uu_id: Optional[str]
budget_books_id: int
budget_books_uu_id: Optional[str]
department_id: int
department_uu_id: Optional[str]
class DecisionBookBudgetMasterCollection(
CrudCollection[DecisionBookBudgetMasterResponse]
):
"""Collection of decision book budget masters"""
pass
class DecisionBookBudgetsResponse(BaseResponse):
"""Response model for decision book budgets"""
process_date: datetime
budget_codes_id: int
total_budget: Decimal
used_budget: Decimal = Decimal("0")
remaining_budget: Decimal = Decimal("0")
decision_book_budget_master_id: int
decision_book_budget_master_uu_id: Optional[str]
class DecisionBookBudgetsCollection(CrudCollection[DecisionBookBudgetsResponse]):
"""Collection of decision book budgets"""
pass

View File

@ -0,0 +1,309 @@
from typing import Optional, List, Generic
from datetime import datetime
from uuid import UUID
from decimal import Decimal
from api_validations.validations_response.base_responses import (
BaseResponse,
CrudCollection,
)
from api_validations.validations_request import PydanticBaseModel
class ListBuildingResponse(PydanticBaseModel):
gov_address_code: str
build_name: str
build_types_uu_id: Optional[str] = None
build_no: Optional[str] = None
max_floor: Optional[int] = None
underground_floor: Optional[int] = None
address_uu_id: Optional[str] = None
build_date: Optional[str] = None
decision_period_date: Optional[str] = None
tax_no: Optional[str] = None
lift_count: Optional[int] = None
heating_system: Optional[bool] = None
cooling_system: Optional[bool] = None
hot_water_system: Optional[bool] = None
block_service_man_count: Optional[int] = None
security_service_man_count: Optional[int] = None
garage_count: Optional[int] = None
site_uu_id: Optional[str] = None
class BuildAreaListResponse(BaseResponse):
"""Response model for building area list endpoint"""
uu_id: UUID
build_id: int
build_uu_id: str
area_name: str
area_value: float
created_at: datetime
updated_at: Optional[datetime]
deleted: bool = False
class BuildAreaListCollection(CrudCollection[BuildAreaListResponse]):
"""Collection of building area list"""
pass
class BuildSitesListResponse(BaseResponse):
"""Response model for building sites list endpoint"""
uu_id: UUID
address_id: int
site_name: str
site_value: float
created_at: datetime
updated_at: Optional[datetime]
deleted: bool = False
class BuildSitesListCollection(CrudCollection[BuildSitesListResponse]):
"""Collection of building sites list"""
pass
class BuildTypesListResponse(BaseResponse):
"""Response model for building types list endpoint"""
uu_id: UUID
type_name: str
type_value: str
created_at: datetime
updated_at: Optional[datetime]
deleted: bool = False
class BuildTypesListCollection(CrudCollection[BuildTypesListResponse]):
"""Collection of building types list"""
pass
class BuildTypesResponse(BaseResponse):
"""Response model for building types"""
function_code: str
type_code: str
lang: str = "TR"
class BuildTypesCollection(CrudCollection[BuildTypesResponse]):
"""Collection of building types"""
pass
class Part2EmployeeResponse(BaseResponse):
"""Response model for part to employee mapping"""
build_id: int
part_id: int
employee_id: int
class Part2EmployeeCollection(CrudCollection[Part2EmployeeResponse]):
"""Collection of part to employee mappings"""
pass
class RelationshipEmployee2BuildResponse(BaseResponse):
"""Response model for employee to build relationship"""
company_id: int
employee_id: int
member_id: int
relationship_type: Optional[str] = "Employee"
show_only: bool = False
class RelationshipEmployee2BuildCollection(
CrudCollection[RelationshipEmployee2BuildResponse]
):
"""Collection of employee to build relationships"""
pass
class BuildResponse(BaseResponse):
"""Response model for buildings"""
gov_address_code: str = ""
build_name: str
build_no: str
max_floor: int = 1
underground_floor: int = 0
build_date: datetime
decision_period_date: datetime
tax_no: str = ""
lift_count: int = 0
heating_system: bool = True
cooling_system: bool = False
hot_water_system: bool = False
block_service_man_count: int = 0
security_service_man_count: int = 0
garage_count: int = 0
management_room_id: Optional[int]
site_id: Optional[int]
site_uu_id: Optional[str]
address_id: int
address_uu_id: str
build_types_id: int
build_types_uu_id: Optional[str]
class BuildCollection(CrudCollection[BuildResponse]):
"""Collection of buildings"""
pass
class BuildPartsResponse(BaseResponse):
"""Response model for building parts"""
address_gov_code: str
part_no: int = 0
part_level: int = 0
part_code: str
part_gross_size: int = 0
part_net_size: int = 0
default_accessory: str = "0"
human_livable: bool = True
due_part_key: str
build_id: int
build_uu_id: str
part_direction_id: Optional[int]
part_direction_uu_id: Optional[str]
part_type_id: int
part_type_uu_id: str
class BuildPartsCollection(CrudCollection[BuildPartsResponse]):
"""Collection of building parts"""
pass
class BuildLivingSpaceResponse(BaseResponse):
"""Response model for building living space"""
fix_value: Decimal = Decimal("0")
fix_percent: Decimal = Decimal("0")
agreement_no: str = ""
marketing_process: bool = False
marketing_layer: int = 0
build_parts_id: int
build_parts_uu_id: str
person_id: int
person_uu_id: str
occupant_type: int
occupant_type_uu_id: str
class BuildLivingSpaceCollection(CrudCollection[BuildLivingSpaceResponse]):
"""Collection of building living spaces"""
pass
class BuildManagementResponse(BaseResponse):
"""Response model for building management"""
discounted_percentage: Decimal = Decimal("0.00")
discounted_price: Decimal = Decimal("0.00")
calculated_price: Decimal = Decimal("0.00")
occupant_type: int
occupant_type_uu_id: str
build_id: int
build_uu_id: str
build_parts_id: int
build_parts_uu_id: str
class BuildManagementCollection(CrudCollection[BuildManagementResponse]):
"""Collection of building management records"""
pass
class BuildAreaResponse(BaseResponse):
"""Response model for building area"""
area_name: str = ""
area_code: str = ""
area_type: str = "GREEN"
area_direction: str = "NN"
area_gross_size: Decimal = Decimal("0")
area_net_size: Decimal = Decimal("0")
width: int = 0
size: int = 0
build_id: int
build_uu_id: str
part_type_id: Optional[int]
part_type_uu_id: Optional[str]
class BuildAreaCollection(CrudCollection[BuildAreaResponse]):
"""Collection of building areas"""
pass
class BuildSitesResponse(BaseResponse):
"""Response model for building sites"""
site_name: str
site_no: str
address_id: int
address_uu_id: Optional[str]
class BuildSitesCollection(CrudCollection[BuildSitesResponse]):
"""Collection of building sites"""
pass
class BuildCompaniesProvidingResponse(BaseResponse):
"""Response model for building companies providing services"""
build_id: int
build_uu_id: Optional[str]
company_id: int
company_uu_id: Optional[str]
provide_id: Optional[int]
provide_uu_id: Optional[str]
contract_id: Optional[int]
class BuildCompaniesProvidingCollection(
CrudCollection[BuildCompaniesProvidingResponse]
):
"""Collection of building companies providing services"""
pass
class BuildPersonProvidingResponse(BaseResponse):
"""Response model for building person providing services"""
build_id: int
build_uu_id: Optional[str]
people_id: int
people_uu_id: Optional[str]
provide_id: Optional[int]
provide_uu_id: Optional[str]
contract_id: Optional[int]
class BuildPersonProvidingCollection(CrudCollection[BuildPersonProvidingResponse]):
"""Collection of building person providing services"""
pass

View File

@ -0,0 +1,59 @@
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
from uuid import UUID
class CompanyListResponse(BaseModel):
"""Response model for company list endpoint"""
uu_id: UUID
company_name: str
company_code: str
company_email: str
company_phone: str
company_address: str
created_at: datetime
updated_at: Optional[datetime]
deleted: bool = False
class CompanyDepartmentListResponse(BaseModel):
"""Response model for company department list endpoint"""
uu_id: UUID
department_name: str
department_code: str
company_id: int
company_uu_id: str
created_at: datetime
updated_at: Optional[datetime]
deleted: bool = False
class CompanyDutyListResponse(BaseModel):
"""Response model for company duty list endpoint"""
uu_id: UUID
duty_name: str
duty_code: str
department_id: int
department_uu_id: str
created_at: datetime
updated_at: Optional[datetime]
deleted: bool = False
class CompanyEmployeeListResponse(BaseModel):
"""Response model for company employee list endpoint"""
uu_id: UUID
employee_id: int
employee_uu_id: str
company_id: int
company_uu_id: str
duty_id: int
duty_uu_id: str
created_at: datetime
updated_at: Optional[datetime]
deleted: bool = False

View File

@ -0,0 +1,204 @@
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
from uuid import UUID
from decimal import Decimal
from .base_responses import BaseResponse, CrudCollection
class BuildDecisionBookResponse(BaseResponse):
"""Response model for building decision book"""
decision_book_pdf_path: Optional[str] = ""
resp_company_fix_wage: float = 0
contact_agreement_path: Optional[str] = ""
contact_agreement_date: Optional[datetime]
meeting_date: Optional[str]
decision_type: Optional[str]
class BuildDecisionBookCollection(CrudCollection[BuildDecisionBookResponse]):
"""Collection of building decision books"""
pass
class BuildDecisionBookInvitationsResponse(BaseResponse):
"""Response model for building decision book invitations"""
build_id: int
build_uu_id: Optional[str]
decision_book_id: int
decision_book_uu_id: Optional[str]
invitation_type: str
invitation_attempt: int = 1
living_part_count: int = 1
living_part_percentage: Decimal = Decimal("0.51")
message: Optional[str]
planned_date: datetime
planned_date_expires: datetime
class BuildDecisionBookInvitationsCollection(
CrudCollection[BuildDecisionBookInvitationsResponse]
):
"""Collection of building decision book invitations"""
pass
class BuildDecisionBookPersonResponse(BaseResponse):
"""Response model for building decision book person"""
dues_percent_discount: int = 0
dues_fix_discount: Decimal = Decimal("0")
dues_discount_approval_date: datetime
send_date: datetime
is_attending: bool = False
confirmed_date: Optional[datetime]
token: str = ""
vicarious_person_id: Optional[int]
vicarious_person_uu_id: Optional[str]
invite_id: int
invite_uu_id: str
build_decision_book_id: int
build_decision_book_uu_id: str
build_living_space_id: int
build_living_space_uu_id: Optional[str]
person_id: int
class BuildDecisionBookPersonCollection(
CrudCollection[BuildDecisionBookPersonResponse]
):
"""Collection of building decision book persons"""
pass
class BuildDecisionBookPersonOccupantsResponse(BaseResponse):
"""Response model for building decision book person occupants"""
build_decision_book_person_id: int
build_decision_book_person_uu_id: Optional[str]
invite_id: Optional[int]
invite_uu_id: Optional[str]
occupant_type_id: int
occupant_type_uu_id: Optional[str]
class BuildDecisionBookPersonOccupantsCollection(
CrudCollection[BuildDecisionBookPersonOccupantsResponse]
):
"""Collection of building decision book person occupants"""
pass
class BuildDecisionBookItemsResponse(BaseResponse):
"""Response model for building decision book items"""
item_order: int
item_comment: str
item_objection: Optional[str]
info_is_completed: bool = False
is_payment_created: bool = False
info_type_id: Optional[int]
info_type_uu_id: Optional[str]
build_decision_book_id: int
build_decision_book_uu_id: Optional[str]
item_short_comment: Optional[str]
class BuildDecisionBookItemsCollection(CrudCollection[BuildDecisionBookItemsResponse]):
"""Collection of building decision book items"""
pass
class BuildDecisionBookItemsUnapprovedResponse(BaseResponse):
"""Response model for building decision book items unapproved"""
item_objection: str
item_order: int
decision_book_item_id: int
decision_book_item_uu_id: Optional[str]
person_id: int
person_uu_id: Optional[str]
build_decision_book_item: int
build_decision_book_item_uu_id: Optional[str]
class BuildDecisionBookItemsUnapprovedCollection(
CrudCollection[BuildDecisionBookItemsUnapprovedResponse]
):
"""Collection of building decision book items unapproved"""
pass
class BuildDecisionBookPaymentsResponse(BaseResponse):
"""Response model for building decision book payments"""
payment_plan_time_periods: str
process_date: datetime
payment_amount: Decimal
currency: str = "TRY"
payment_types_id: Optional[int]
payment_types_uu_id: Optional[str]
period_time: str
process_date_y: int
process_date_m: int
build_decision_book_item_id: int
build_decision_book_item_uu_id: str
build_parts_id: int
build_parts_uu_id: str
decision_book_project_id: Optional[int]
decision_book_project_uu_id: Optional[str]
account_records_id: Optional[int]
account_records_uu_id: Optional[str]
class BuildDecisionBookPaymentsCollection(
CrudCollection[BuildDecisionBookPaymentsResponse]
):
"""Collection of building decision book payments"""
pass
class BuildDecisionBookLegalResponse(BaseResponse):
"""Response model for building decision book legal"""
period_start_date: datetime
lawsuits_decision_number: str
lawsuits_decision_date: datetime
period_stop_date: datetime
decision_book_pdf_path: Optional[str] = ""
resp_company_total_wage: Optional[Decimal] = Decimal("0")
contact_agreement_path: Optional[str] = ""
contact_agreement_date: Optional[datetime]
meeting_date: str
lawsuits_type: str = "C"
lawsuits_name: str
lawsuits_note: str
lawyer_cost: Decimal
mediator_lawyer_cost: Decimal
other_cost: Decimal
legal_cost: Decimal
approved_cost: Decimal
total_price: Decimal
build_db_item_id: int
build_db_item_uu_id: Optional[str]
resp_attorney_id: int
resp_attorney_uu_id: Optional[str]
resp_attorney_company_id: int
resp_attorney_company_uu_id: Optional[str]
mediator_lawyer_person_id: int
mediator_lawyer_person_uu_id: Optional[str]
class BuildDecisionBookLegalCollection(CrudCollection[BuildDecisionBookLegalResponse]):
"""Collection of building decision book legal records"""
pass

View File

@ -0,0 +1,52 @@
from typing import Optional
from api_validations.core_validations import BaseModelRegular
from api_validations.validations_request import (
CrudRecordValidation,
CrudRecords,
)
class LivingSpaceListValidation:
tr = {
**CrudRecordValidation.tr,
"fix_value": "Sabit Değer",
"fix_percent": "Sabit Yüzde",
"agreement_no": "Anlaşma No",
"marketing_process": "Pazarlama Süreci",
"marketing_layer": "Pazarlama Katmanı",
"build_parts_id": "Bölüm ID",
"build_parts_uu_id": "Bölüm UUID",
"person_id": "Sorumlu Kişi ID",
"person_uu_id": "Sorumlu Kişi UUID",
"occupant_type": "Kiracı Tipi",
"occupant_type_uu_id": "Kiracı Tipi UUID",
}
en = {
**CrudRecordValidation.en,
"fix_value": "Fixed Value",
"fix_percent": "Fixed Percent",
"agreement_no": "Agreement No",
"marketing_process": "Marketing Process",
"marketing_layer": "Marketing Layer",
"build_parts_id": "Part ID",
"build_parts_uu_id": "Part UUID",
"person_id": "Responsible Person ID",
"person_uu_id": "Responsible Person UUID",
"occupant_type": "Occupant Type",
"occupant_type_uu_id": "Occupant Type UUID",
}
class LivingSpaceListResponse(BaseModelRegular, CrudRecords, LivingSpaceListValidation):
fix_value: Optional[float] = None
fix_percent: Optional[float] = None
agreement_no: Optional[str] = None
marketing_process: Optional[str] = None
marketing_layer: Optional[str] = None
build_parts_id: Optional[int] = None
build_parts_uu_id: Optional[str] = None
person_id: Optional[int] = None
person_uu_id: Optional[str] = None
occupant_type: Optional[str] = None
occupant_type_uu_id: Optional[str] = None

View File

@ -0,0 +1,54 @@
from typing import Optional
from api_validations.core_validations import BaseModelRegular
from api_validations.validations_request import (
CrudRecordValidation,
CrudRecords,
)
class BuildPartsListValidation:
tr = {
**CrudRecordValidation.tr,
"address_gov_code": "Adres Kapı Kodu",
"part_no": "Bölüm No",
"part_level": "Bölüm Seviyesi",
"part_code": "Bölüm Kodu",
"part_gross": "Bölüm Brüt",
"part_net": "Bölüm Net",
"default_accessory": "Varsayılan Aksesuar",
"human_livable": "İnsan Yaşamı",
"due_part_key": "Sabit Ödeme Grubu",
"build_uu_id": "Bina UUID",
"part_direction_uu_id": "Bölüm Yönü UUID",
"part_type_uu_id": "Bölüm Tipi UUID",
}
en = {
**CrudRecordValidation.en,
"address_gov_code": "Address Government Code",
"part_no": "Part Number",
"part_level": "Part Level",
"part_code": "Part Code",
"part_gross": "Part Gross",
"part_net": "Part Net",
"default_accessory": "Default Accessory",
"human_livable": "Human Livable",
"due_part_key": "Constant Payment Group",
"build_uu_id": "Building UUID",
"part_direction_uu_id": "Part Direction UUID",
"part_type_uu_id": "Part Type UUID",
}
class BuildPartsListResponse(BaseModelRegular, CrudRecords, BuildPartsListValidation):
address_gov_code: Optional[str] = None
part_no: Optional[int] = None
part_level: Optional[int] = None
part_code: Optional[str] = None
part_gross: Optional[int] = None
part_net: Optional[int] = None
default_accessory: Optional[str] = None
human_livable: Optional[bool] = None
due_part_key: Optional[str] = None
build_uu_id: Optional[str] = None
part_direction_uu_id: Optional[str] = None
part_type_uu_id: Optional[str] = None

View File

@ -0,0 +1,57 @@
from typing import Optional
from api_validations.core_validations import BaseModelRegular
from api_validations.validations_request import (
CrudRecordValidation,
CrudRecords,
)
class PeopleListValidation:
tr = {
**CrudRecordValidation.tr,
"firstname": "Ad",
"surname": "Soyad",
"middle_name": "Orta İsim",
"sex_code": "Cinsiyet Kodu",
"person_ref": "Kişi Referansı",
"person_tag": "Kişi Etiketi",
"father_name": "Baba Adı",
"mother_name": "Anne Adı",
"country_code": "Ülke Kodu",
"national_identity_id": "Kimlik Numarası",
"birth_place": "Doğum Yeri",
"birth_date": "Doğum Tarihi",
"tax_no": "Vergi Numarası",
}
en = {
**CrudRecordValidation.en,
"firstname": "First Name",
"surname": "Surname",
"middle_name": "Middle Name",
"sex_code": "Sex Code",
"person_ref": "Person Reference",
"person_tag": "Person Tag",
"father_name": "Father's Name",
"mother_name": "Mother's Name",
"country_code": "Country Code",
"national_identity_id": "National Identity ID",
"birth_place": "Birth Place",
"birth_date": "Birth Date",
"tax_no": "Tax Number",
}
class PeopleListResponse(BaseModelRegular, CrudRecords, PeopleListValidation):
firstname: Optional[str] = None
surname: Optional[str] = None
middle_name: Optional[str] = None
sex_code: Optional[str] = None
person_ref: Optional[str] = None
person_tag: Optional[str] = None
father_name: Optional[str] = None
mother_name: Optional[str] = None
country_code: Optional[str] = None
national_identity_id: Optional[str] = None
birth_place: Optional[str] = None
birth_date: Optional[str] = None
tax_no: Optional[str] = None

View File

@ -14,6 +14,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
from ApiLibrary.common.line_number import get_line_number_for_error
from ErrorHandlers.ErrorHandlers.api_exc_handler import HTTPExceptionApi
from AllConfigs.Token.config import Auth
import inspect
@ -86,9 +87,8 @@ class MiddlewareModule:
async def wrapper(request: Request, *args, **kwargs):
# Get and validate token context from request
# Create auth context and Attach auth context to both wrapper and original function
func.auth = cls.get_user_from_request(
request
) # This ensures the context is available in both places
func.auth = cls.get_user_from_request(request)
wrapper.auth = func.auth
# Call the original endpoint function
if inspect.iscoroutinefunction(func):
return await func(request, *args, **kwargs)
@ -141,10 +141,22 @@ class LoggerTimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next: Callable) -> Response:
# Log the request
print(f"Handling request: {request.method} {request.url}")
import arrow
headers = dict(request.headers)
response = await call_next(request)
# Log the response
print(
f"Completed request: {request.method} {request.url} with status {response.status_code}"
"Loggers :",
{
"url": request.url,
"method": request.method,
"access_token": headers.get(Auth.ACCESS_TOKEN_TAG, ""),
"referer": headers.get("referer", ""),
"origin": headers.get("origin", ""),
"user-agent": headers.get("user-agent", ""),
"datetime": arrow.now().format("YYYY-MM-DD HH:mm:ss ZZ"),
"status_code": response.status_code,
},
)
return response

View File

@ -2,11 +2,196 @@
Token event middleware for handling authentication and event tracking.
"""
from functools import wraps
from typing import Callable, Dict, Any
from .auth_middleware import MiddlewareModule
import inspect
from functools import wraps
from typing import Callable, Dict, Any, Optional, Union
from fastapi import Request
from pydantic import BaseModel
from ApiLibrary.common.line_number import get_line_number_for_error
from ApiServices.Token.token_handler import TokenService
from ErrorHandlers.Exceptions.api_exc import HTTPExceptionApi
from Schemas.rules.rules import EndpointRestriction
from .auth_middleware import MiddlewareModule
from Schemas import Events
class EventFunctions:
def __init__(self, endpoint: str, request: Request):
self.endpoint = endpoint
self.request = request
def match_endpoint_with_accesiable_event(self) -> Optional[Dict[str, Any]]:
"""
Match an endpoint with accessible events.
Args:
endpoint: The endpoint to match
Returns:
Dict containing the endpoint registration data
None if endpoint is not found in database
"""
access_token = TokenService.get_access_token_from_request(self.request)
token_context = TokenService.get_object_via_access_key(
access_token=access_token
)
if token_context.is_employee:
reachable_event_codes: list[str] = (
token_context.selected_company.reachable_event_codes
)
elif token_context.is_occupant:
reachable_event_codes: list[str] = (
token_context.selected_occupant.reachable_event_codes
)
else:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Token not found",
)
if not access_token:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Token not found",
)
db = EndpointRestriction.new_session()
restriction = EndpointRestriction.filter_one(
EndpointRestriction.endpoint_name == self.endpoint,
db=db,
).data
if not restriction:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Function code not found",
)
event_related = Events.filter_all(
Events.endpoint_id == restriction.id,
db=db,
).data
if not event_related:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="No event is registered for this user.",
)
an_event = event_related[0]
event_related_codes: list[str] = [
event.function_code for event in event_related
]
intersected_code: set = set(reachable_event_codes).intersection(
set(event_related_codes)
)
if not len(list(intersected_code)) == 1:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="No event is registered for this user.",
)
return {
"endpoint_url": self.endpoint,
"reachable_event_code": list(intersected_code)[0],
"class": an_event.function_class,
}
def retrieve_function_dict(self) -> Optional[Dict[str, Any]]:
"""
Retrieve function dictionary for a given endpoint.
Args:
endpoint: The endpoint to retrieve the function dictionary for
Returns:
Dictionary containing the function dictionary
None if endpoint is not found
"""
access_token = TokenService.get_access_token_from_request(self.request)
token_context = TokenService.get_object_via_access_key(
access_token=access_token
)
if token_context.is_employee:
reachable_event_codes: list[str] = (
token_context.selected_company.reachable_event_codes
)
elif token_context.is_occupant:
reachable_event_codes: list[str] = (
token_context.selected_occupant.reachable_event_codes
)
else:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Token not found",
)
if not access_token:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Token not found",
)
db = EndpointRestriction.new_session()
restriction = EndpointRestriction.filter_one(
EndpointRestriction.endpoint_name == self.endpoint,
db=db,
).data
if not restriction:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Function code not found",
)
event_related = Events.filter_all(
Events.endpoint_id == restriction.id,
db=db,
).data
if not event_related:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="No event is registered for this user.",
)
an_event = event_related[0]
event_related_codes: list[str] = [
event.function_code for event in event_related
]
intersected_code: set = set(reachable_event_codes).intersection(
set(event_related_codes)
)
if not len(list(intersected_code)) == 1:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="No event is registered for this user.",
)
return {
"endpoint_url": self.endpoint,
"reachable_event_code": list(intersected_code)[0],
"class": an_event.function_class,
}
class TokenEventMiddleware:
"""
@ -37,17 +222,71 @@ class TokenEventMiddleware:
authenticated_func = MiddlewareModule.auth_required(func)
@wraps(authenticated_func)
async def wrapper(*args, **kwargs) -> Dict[str, Any]:
# Create handler with context
function_code = (
"7192c2aa-5352-4e36-98b3-dafb7d036a3d" # Keep function_code as URL
async def wrapper(request: Request, *args, **kwargs) -> Dict[str, Any]:
# Get function code from the function's metadata
endpoint_url = getattr(authenticated_func, "url_of_endpoint", {})
if not endpoint_url:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Function code not found",
)
# Make handler available to all functions in the chain
func.func_code = {"function_code": function_code}
func.func_code = EventFunctions(endpoint_url, request).match_endpoint_with_accesiable_event()
# Call the authenticated function
if inspect.iscoroutinefunction(authenticated_func):
return await authenticated_func(*args, **kwargs)
return authenticated_func(*args, **kwargs)
return await authenticated_func(request, *args, **kwargs)
return authenticated_func(request, *args, **kwargs)
return wrapper
@staticmethod
def validation_required(
func: Callable[..., Dict[str, Any]]
) -> Callable[..., Dict[str, Any]]:
"""
Decorator for endpoints with token and event requirements.
This decorator:
1. First validates authentication using MiddlewareModule.auth_required
2. Then adds event tracking context
Args:
func: The function to be decorated
Returns:
Callable: The wrapped function with both auth and event handling
"""
# First apply authentication
authenticated_func = MiddlewareModule.auth_required(func)
@wraps(authenticated_func)
async def wrapper(
request: Request, *args: Any, **kwargs: Any
) -> Union[Dict[str, Any], BaseModel]:
# Handle both async and sync functions
endpoint_asked = getattr(kwargs.get("data", None), "data", None).get("endpoint", None)
if not endpoint_asked:
raise HTTPExceptionApi(
error_code="",
lang="en",
loc=get_line_number_for_error(),
sys_msg="Endpoint not found",
)
wrapper.validation_code = EventFunctions(endpoint_asked, request).retrieve_function_dict()
if inspect.iscoroutinefunction(authenticated_func):
result = await authenticated_func(request, *args, **kwargs)
else:
result = authenticated_func(request, *args, **kwargs)
function_auth = getattr(authenticated_func, "auth", None)
wrapper.auth = function_auth
func.auth = function_auth
authenticated_func.auth = function_auth
# If result is a coroutine, await it
if inspect.iscoroutine(result):
result = await result
return result
return wrapper

View File

@ -33,7 +33,7 @@ class OpenAPISchemaCreator:
"""
self.app = app
_, self.protected_routes = get_all_routers()
self.tags_metadata = self._create_tags_metadata()
# self.tags_metadata = self._create_tags_metadata()
@staticmethod
def _create_tags_metadata() -> List[Dict[str, str]]:
@ -232,7 +232,6 @@ class OpenAPISchemaCreator:
description=Config.DESCRIPTION,
version="1.1.1",
routes=self.app.routes,
tags=self.tags_metadata,
)
# Add security schemes

View File

@ -35,9 +35,6 @@ COPY ApiEvents/AuthServiceApi /app/ApiEvents
COPY ApiEvents/abstract_class.py /app/ApiEvents/abstract_class.py
COPY ApiEvents/base_request_model.py /app/ApiEvents/base_request_model.py
# Create empty __init__.py files to make directories into Python packages
RUN touch /app/ApiEvents/__init__.py
# Set Python path to include app directory
ENV PYTHONPATH=/app \
PYTHONUNBUFFERED=1 \

View File

@ -35,8 +35,6 @@ COPY ApiEvents/EventServiceApi /app/ApiEvents
COPY ApiEvents/abstract_class.py /app/ApiEvents/abstract_class.py
COPY ApiEvents/base_request_model.py /app/ApiEvents/base_request_model.py
# Create empty __init__.py files to make directories into Python packages
RUN touch /app/ApiEvents/__init__.py
# Set Python path to include app directory
ENV PYTHONPATH=/app \

View File

@ -31,7 +31,9 @@ COPY Services /app/Services
COPY ApiServices /app/ApiServices
# Copy Events structure with consistent naming
COPY ApiEvents/EventServiceApi /app/ApiEvents
COPY ApiEvents/ValidationServiceApi /app/ApiEvents
ADD ApiEvents/AuthServiceApi/events /app/ApiEvents/events
ADD ApiEvents/EventServiceApi/events /app/ApiEvents/events
COPY ApiEvents/abstract_class.py /app/ApiEvents/abstract_class.py
COPY ApiEvents/base_request_model.py /app/ApiEvents/base_request_model.py

View File

@ -250,26 +250,33 @@ class Event2Employee(CrudCollection):
)
@classmethod
def get_event_id_by_employee_id(cls, employee_id) -> list:
def get_event_codes(cls, employee_id: int) -> list:
db = cls.new_session()
occupant_events = cls.filter_all(
employee_events = cls.filter_all(
cls.employee_id == employee_id,
db=db,
).data
active_events = Service2Events.filter_all(
active_event_ids = Service2Events.filter_all(
Service2Events.service_id.in_(
[event.event_service_id for event in occupant_events]
[event.event_service_id for event in employee_events]
),
db=db,
system=True,
).data
active_events_id = [event.event_id for event in active_events]
active_events = Events.filter_all(
Events.id.in_([event.event_id for event in active_event_ids]),
db=db,
).data
if extra_events := Event2EmployeeExtra.filter_all(
Event2EmployeeExtra.employee_id == employee_id,
db=db,
).data:
active_events_id.extend([event.event_id for event in extra_events])
return active_events_id
events_extra = Events.filter_all(
Events.id.in_([event.event_id for event in extra_events]),
db=db,
).data
active_events.extend(events_extra)
return [event.function_code for event in active_events]
class Event2Occupant(CrudCollection):
@ -307,22 +314,33 @@ class Event2Occupant(CrudCollection):
)
@classmethod
def get_event_id_by_build_living_space_id(cls, build_living_space_id) -> list:
def get_event_codes(cls, build_living_space_id) -> list:
db = cls.new_session()
occupant_events = cls.filter_all(
cls.build_living_space_id == build_living_space_id,
db=db,
).data
active_events = Service2Events.filter_all(
active_event_ids = Service2Events.filter_all(
Service2Events.service_id.in_(
[event.event_service_id for event in occupant_events]
),
db=db,
system=True,
).data
active_events_id = [event.event_id for event in active_events]
active_events = Events.filter_all(
Events.id.in_([event.event_id for event in active_event_ids]),
db=db,
).data
if extra_events := Event2OccupantExtra.filter_all(
Event2OccupantExtra.build_living_space_id == build_living_space_id
Event2OccupantExtra.build_living_space_id == build_living_space_id,
db=db,
).data:
active_events_id.extend([event.event_id for event in extra_events])
return active_events_id
events_extra = Events.filter_all(
Events.id.in_([event.event_id for event in extra_events]),
db=db,
).data
active_events.extend(events_extra)
return [event.function_code for event in active_events]
class ModulePrice(CrudCollection):