diff --git a/ApiServices/AuthService/endpoints/auth/route.py b/ApiServices/AuthService/endpoints/auth/route.py index 067aa54..6e6a28f 100644 --- a/ApiServices/AuthService/endpoints/auth/route.py +++ b/ApiServices/AuthService/endpoints/auth/route.py @@ -342,7 +342,6 @@ def authentication_token_refresh_post( status_code=status.HTTP_406_NOT_ACCEPTABLE, headers=headers, ) - return JSONResponse( content={}, status_code=status.HTTP_202_ACCEPTED, @@ -374,7 +373,6 @@ def authentication_password_verify_otp( "tz": tz or "GMT+3", "token": token, } - print("Token&OTP : ", data.otp, data.token) if not domain or not language: return JSONResponse( content={"error": "EYS_0003"}, @@ -419,8 +417,17 @@ def authentication_page_valid( status_code=status.HTTP_406_NOT_ACCEPTABLE, headers=headers, ) + result = AuthHandlers.PageHandlers.retrieve_valid_page_via_token( + page_url=data.page_url, access_token=token + ) + if not result: + return JSONResponse( + content={"error": "EYS_0004"}, + status_code=status.HTTP_406_NOT_ACCEPTABLE, + headers=headers, + ) return JSONResponse( - content={}, + content={"application": result}, status_code=status.HTTP_202_ACCEPTED, headers=headers, ) diff --git a/ApiServices/AuthService/events/auth/auth.py b/ApiServices/AuthService/events/auth/auth.py index bb312de..e7fda20 100644 --- a/ApiServices/AuthService/events/auth/auth.py +++ b/ApiServices/AuthService/events/auth/auth.py @@ -29,14 +29,16 @@ from Schemas import ( ) from Modules.Token.password_module import PasswordModule from Schemas.building.build import RelationshipEmployee2Build -from Schemas.event.event import Event2Occupant +from Schemas.event.event import Event2Occupant, Application2Employee from Controllers.Redis.database import RedisActions from Controllers.Mongo.database import mongo_handler + TokenDictType = Union[EmployeeTokenObject, OccupantTokenObject] class RedisHandlers: + AUTH_TOKEN: str = "AUTH_TOKEN" @classmethod @@ -79,9 +81,7 @@ class RedisHandlers: return generated_access_token @classmethod - def update_token_at_redis( - cls, token: str, add_payload: Union[CompanyToken, OccupantToken] - ): + def update_token_at_redis(cls, token: str, add_payload: Union[CompanyToken, OccupantToken]): if already_token_data := RedisActions.get_json( list_keys=[RedisHandlers.AUTH_TOKEN, token, "*"] ).first: @@ -124,9 +124,7 @@ class UserHandlers: return found_user @staticmethod - def check_password_valid( - domain: str, id_: str, password: str, password_hashed: str - ) -> bool: + def check_password_valid(domain: str, id_: str, password: str, password_hashed: str) -> bool: """ Check if the password is valid. """ @@ -144,6 +142,10 @@ class UserHandlers: return True raise ValueError("EYS_0004") + @staticmethod + def update_password(): + return + class LoginHandler: @@ -156,9 +158,7 @@ class LoginHandler: return str(email).split("@")[1] == api_config.ACCESS_EMAIL_EXT @classmethod - def do_employee_login( - cls, request: Any, data: Any, extra_dict: Optional[Dict[str, Any]] = None - ): + def do_employee_login(cls, request: Any, data: Any, extra_dict: Optional[Dict[str, Any]] = None): """ Handle employee login. """ @@ -268,9 +268,7 @@ class LoginHandler: raise ValueError("Something went wrong") @classmethod - def do_employee_occupant( - cls, request: Any, data: Any, extra_dict: Optional[Dict[str, Any]] = None - ): + def do_employee_occupant(cls, request: Any, data: Any, extra_dict: Optional[Dict[str, Any]] = None): """ Handle occupant login. """ @@ -421,9 +419,7 @@ class LoginHandler: return request.headers.get(api_config.ACCESS_TOKEN_TAG) @classmethod - def handle_employee_selection( - cls, access_token: str, data: Any, token_dict: TokenDictType - ): + def handle_employee_selection(cls, access_token: str, data: Any, token_dict: TokenDictType): with Users.new_session() as db: if data.company_uu_id not in token_dict.companies_uu_id_list: ValueError("EYS_0011") @@ -479,6 +475,10 @@ class LoginHandler: db=db, ).data + reachable_app_codes = Application2Employee.get_application_codes( + employee_id=employee.id, db=db + ) + # Create company token company_token = CompanyToken( company_uu_id=selected_company.uu_id.__str__(), @@ -493,6 +493,7 @@ class LoginHandler: employee_id=employee.id, employee_uu_id=employee.uu_id.__str__(), reachable_event_codes=reachable_event_codes, + reachable_app_codes=reachable_app_codes ) redis_handler = RedisHandlers() redis_result = redis_handler.update_token_at_redis( @@ -503,9 +504,7 @@ class LoginHandler: } @classmethod - def handle_occupant_selection( - cls, access_token: str, data: Any, token_dict: TokenDictType - ): + def handle_occupant_selection(cls, access_token: str, data: Any, token_dict: TokenDictType): """Handle occupant type selection""" with BuildLivingSpace.new_session() as db: # Get selected occupant type @@ -707,6 +706,22 @@ class PasswordHandler: return found_user +class PageHandlers: + + @classmethod + def retrieve_valid_page_via_token(cls, access_token: str, page_url: str): + if result := RedisHandlers.get_object_from_redis(access_token=access_token): + if result.is_employee: + if application := result.selected_company.reachable_app_codes.get(page_url, None): + return application + elif result.is_occupant: + if application := result.selected_company.reachable_app_codes.get(page_url, None): + return application + raise ValueError("EYS_0013") + + class AuthHandlers: + LoginHandler: LoginHandler = LoginHandler() PasswordHandler: PasswordHandler = PasswordHandler() + PageHandlers: PageHandlers = PageHandlers() diff --git a/ApiServices/AuthService/validations/custom/token.py b/ApiServices/AuthService/validations/custom/token.py index 278e792..25b21a8 100644 --- a/ApiServices/AuthService/validations/custom/token.py +++ b/ApiServices/AuthService/validations/custom/token.py @@ -59,8 +59,8 @@ class OccupantToken(BaseModel): responsible_employee_id: Optional[int] = None responsible_employee_uuid: Optional[str] = None - reachable_event_codes: Optional[list[str]] = None # ID list of reachable modules - reachable_app_codes: Optional[list[str]] = None # ID list of reachable modules + reachable_event_codes: Optional[dict[str, str]] = None # ID list of reachable modules + reachable_app_codes: Optional[dict[str, str]] = None # ID list of reachable modules class CompanyToken(BaseModel): @@ -83,8 +83,8 @@ class CompanyToken(BaseModel): bulk_duties_id: int - reachable_event_codes: Optional[list[str]] = None # ID list of reachable modules - reachable_app_codes: Optional[list[str]] = None # ID list of reachable modules + reachable_event_codes: Optional[dict[str, str]] = None # ID list of reachable modules + reachable_app_codes: Optional[dict[str, str]] = None # ID list of reachable modules class OccupantTokenObject(ApplicationToken): diff --git a/ApiServices/AuthService/validations/request/authentication/login_post.py b/ApiServices/AuthService/validations/request/authentication/login_post.py index fb74b9c..595834b 100644 --- a/ApiServices/AuthService/validations/request/authentication/login_post.py +++ b/ApiServices/AuthService/validations/request/authentication/login_post.py @@ -15,7 +15,7 @@ class RequestVerifyOTP(BaseModel): class RequestApplication(BaseModel): - page: str # /building/create + page_url: str # /building/create class RequestSelectEmployee(BaseModel): diff --git a/ApiServices/CompanyService/init_applications.py b/ApiServices/CompanyService/init_applications.py new file mode 100644 index 0000000..b64dcba --- /dev/null +++ b/ApiServices/CompanyService/init_applications.py @@ -0,0 +1,36 @@ +from Schemas import ( + Applications, + Application2Employee, + Application2Occupant, +) + + +""" + + name=Building Create + site_url=/buildings/create + application_type=CreateFrom + application_code=APP003 + + name=Building Create + site_url=/buildings/create + application_type=CreateFrom + application_code=APP004 + + name=Building Create + site_url=/buildings/create + application_type=CreateFrom + application_code=APP005 + + name=Building Update + site_url=/buildings/update + application_type=/building/update + application_code=APP002 + + name=Building List + site_url=/buildings + application_type=ListFrom + application_code=APP001 + +""" + diff --git a/ApiServices/DealerService/Dockerfile b/ApiServices/DealerService/Dockerfile new file mode 100644 index 0000000..feaafeb --- /dev/null +++ b/ApiServices/DealerService/Dockerfile @@ -0,0 +1,28 @@ +FROM python:3.12-slim + +WORKDIR / + +# Install system dependencies and Poetry +RUN apt-get update && apt-get install -y --no-install-recommends gcc \ + && rm -rf /var/lib/apt/lists/* && pip install --no-cache-dir poetry + +# Copy Poetry configuration +COPY /pyproject.toml ./pyproject.toml + +# Configure Poetry and install dependencies with optimizations +RUN poetry config virtualenvs.create false \ + && poetry install --no-interaction --no-ansi --no-root --only main \ + && pip cache purge && rm -rf ~/.cache/pypoetry + +# Copy application code +COPY /ApiServices/DealerService /ApiServices/DealerService +COPY /ApiServices/DealerService / +COPY /Controllers /Controllers +COPY /Modules /Modules +COPY /Schemas /Schemas + +# Set Python path to include app directory +ENV PYTHONPATH=/ PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 + +# Run the application using the configured uvicorn server +CMD ["poetry", "run", "python", "ApiServices/DealerService/app.py"] diff --git a/ApiServices/DealerService/app.py b/ApiServices/DealerService/app.py new file mode 100644 index 0000000..349a050 --- /dev/null +++ b/ApiServices/DealerService/app.py @@ -0,0 +1,24 @@ +from Controllers.Postgres.database import get_db +from Schemas import ( + Users, + Employees, +) +from init_service_to_events import init_service_to_event_matches_for_super_user +from init_applications import init_applications_for_super_user + + +if __name__ == "__main__": + """ + Create Applications to serve on Next.js | Create Events to add to Service + Set Events to service | Set Service to employee + """ + with get_db() as db_session: + if super_man := Users.filter_one( + Users.email == "karatay.berkay.sup@evyos.com.tr", db=db_session + ).data: + super_employee = Employees.filter_one( + Employees.people_id == super_man.person_id, db=db_session + ).data + + init_service_to_event_matches_for_super_user(super_user=super_employee, db_session=db_session) + init_applications_for_super_user(super_user=super_employee, db_session=db_session) diff --git a/ApiServices/DealerService/init_applications.py b/ApiServices/DealerService/init_applications.py new file mode 100644 index 0000000..1051877 --- /dev/null +++ b/ApiServices/DealerService/init_applications.py @@ -0,0 +1,40 @@ +from Schemas import ( + Applications, + Application2Employee, + Employees +) + +def init_applications_for_super_user(super_user: Employees, db_session=None) -> None: + list_of_created_apps = [ + dict( + name="Dashboard1", + application_code = "app000001", + site_url = "/dashboard", + application_type = "info", + description = "Dashboard Page" + ), + dict( + name="Dashboard2", + application_code = "app000002", + site_url = "/buildings/list", + application_type = "CreateFrom", + description = "Dashboard Page" + ), + ] + + for list_of_created_app in list_of_created_apps: + dashboard_page = Applications.find_or_create( + **list_of_created_app, db=db_session, + ) + print('dashboard_page', dashboard_page) + if dashboard_page.meta_data.created: + dashboard_page.save(db=db_session) + Application2Employee.find_or_create( + employee_id=super_user.id, + employee_uu_id=str(super_user.uu_id), + site_url=dashboard_page.site_url, + application_code=dashboard_page.application_code, + application_id=dashboard_page.id, + application_uu_id=str(dashboard_page.uu_id), + db=db_session, + ) diff --git a/ApiServices/DealerService/init_service_to_events.py b/ApiServices/DealerService/init_service_to_events.py new file mode 100644 index 0000000..9087b73 --- /dev/null +++ b/ApiServices/DealerService/init_service_to_events.py @@ -0,0 +1,47 @@ +from Schemas import ( + Users, + Services, + Service2Events, + Applications, + Application2Employee, + Application2Occupant, + Employees, + Event2Employee, +) + + +list_of_event_codes = [] + + +def init_service_to_event_matches_for_super_user(super_user, db_session=None) -> None: + service_match = Services.filter_one( + Services.service_name == "Super User", db=db_session, + ).data + for list_of_event_code in list_of_event_codes: + created_service = Service2Events.find_or_create( + service_id=service_match.id, + service_uu_id=str(service_match.uu_id), + event_id=list_of_event_code.id, + event_uu_id=str(list_of_event_code.uu_id), + is_confirmed=True, + active=True, + db=db_session, + ) + if created_service.meta_data.created: + created_service.save(db=db_session) + print( + f"UUID: {created_service.uu_id} event is saved to {service_match.uu_id}" + ) + employee_added_service = Event2Employee.find_or_create( + event_service_id=created_service.id, + event_service_uu_id=str(created_service.uu_id), + employee_id=super_user.id, + employee_uu_id=str(super_user.uu_id), + is_confirmed=True, + db=db_session + ) + if employee_added_service.meta_data.created: + employee_added_service.save(db=db_session) + print( + f"UUID: {employee_added_service.uu_id} event is saved to {super_user.uu_id}" + ) diff --git a/ApiServices/TemplateService/endpoints/test_template/route.py b/ApiServices/TemplateService/endpoints/test_template/route.py index dddf4d5..3f889e0 100644 --- a/ApiServices/TemplateService/endpoints/test_template/route.py +++ b/ApiServices/TemplateService/endpoints/test_template/route.py @@ -1,9 +1,11 @@ import uuid + from fastapi import APIRouter, Request, Response, Header from ApiServices.TemplateService.config import api_config from ApiServices.TemplateService.events.template.event import template_event_cluster + test_template_route = APIRouter(prefix="/test", tags=["Test"]) diff --git a/Schemas/__init__.py b/Schemas/__init__.py index 7a80c61..3efeccb 100644 --- a/Schemas/__init__.py +++ b/Schemas/__init__.py @@ -80,6 +80,9 @@ from Schemas.event.event import ( Event2Employee, Event2OccupantExtra, Event2EmployeeExtra, + Applications, + Application2Employee, + Application2Occupant, ) from Schemas.identity.identity import ( UsersTokens, @@ -175,6 +178,9 @@ __all__ = [ "Event2Employee", "Event2OccupantExtra", "Event2EmployeeExtra", + "Applications", + "Application2Employee", + "Application2Occupant", "Addresses", "AddressCity", "AddressStreet", diff --git a/Schemas/event/event.py b/Schemas/event/event.py index b5280b7..ec11bab 100644 --- a/Schemas/event/event.py +++ b/Schemas/event/event.py @@ -11,6 +11,25 @@ from sqlalchemy.orm import mapped_column, Mapped from Controllers.Postgres.mixin import CrudCollection +class Applications(CrudCollection): + """ + Applications class based on declarative_base and BaseMixin via session + """ + + __tablename__ = "applications" + __exclude__fields__ = [] + + name: Mapped[str] = mapped_column( + String, nullable=False, comment="Application Name" + ) + site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL") + application_code: Mapped[str] = mapped_column( + String, nullable=False, comment="Application Code" + ) + application_type: Mapped[str] = mapped_column(String, comment="Application Type") + description: Mapped[str] = mapped_column(String, comment="Application Description") + + class Events(CrudCollection): """ Events class based on declarative_base and BaseMixin via session @@ -82,6 +101,42 @@ class Modules(CrudCollection): __table_args__ = ({"comment": "Modules Information"},) +class ModulePrice(CrudCollection): + """ + ModulePrice class based on declarative_base and BaseMixin via session + """ + + __tablename__ = "module_price" + __exclude__fields__ = [] + + campaign_code: Mapped[str] = mapped_column( + String, nullable=False, comment="Campaign Code" + ) + module_id: Mapped[int] = mapped_column(ForeignKey("modules.id"), nullable=False) + module_uu_id: Mapped[str] = mapped_column( + String, nullable=False, comment="Module UUID" + ) + service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False) + service_uu_id: Mapped[str] = mapped_column( + String, nullable=False, comment="Service UUID" + ) + event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False) + event_uu_id: Mapped[str] = mapped_column( + String, nullable=False, comment="Event UUID" + ) + is_counted_percentage: Mapped[float] = mapped_column( + Numeric(6, 2), server_default="0.00" + ) # %22 + discounted_price: Mapped[float] = mapped_column( + Numeric(20, 2), server_default="0.00" + ) # Normal: 78.00 TL + calculated_price: Mapped[float] = mapped_column( + Numeric(20, 2), server_default="0.00" + ) # sana düz 75.00 TL yapar + + __table_args__ = ({"comment": "ModulePrice Information"},) + + class Services(CrudCollection): """ Services class based on declarative_base and BaseMixin via session @@ -143,6 +198,10 @@ class Service2Events(CrudCollection): __table_args__ = ({"comment": "Service2Events Information"},) +# class Service2Application(CrudCollection): +# pass + + class Event2OccupantExtra(CrudCollection): __tablename__ = "event2occupant_extra" @@ -229,7 +288,7 @@ class Event2Employee(CrudCollection): ) @classmethod - def get_event_codes(cls, employee_id: int, db) -> list: + def get_event_codes(cls, employee_id: int, db) -> dict[str : list[str]]: employee_events = cls.filter_all( cls.employee_id == employee_id, db=db, @@ -253,43 +312,13 @@ class Event2Employee(CrudCollection): db=db, ).data active_events.extend(events_extra) - return [event.function_code for event in active_events] - - # @classmethod - # def get_event_endpoints(cls, employee_id: int) -> list: - # from Schemas import EndpointRestriction - # - # db = cls.new_session() - # employee_events = cls.filter_all( - # cls.employee_id == employee_id, - # db=db, - # ).data - # active_event_ids = Service2Events.filter_all( - # Service2Events.service_id.in_( - # [event.event_service_id for event in employee_events] - # ), - # db=db, - # system=True, - # ).data - # 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: - # events_extra = Events.filter_all( - # Events.id.in_([event.event_id for event in extra_events]), - # db=db, - # ).data - # active_events.extend(events_extra) - # endpoint_restrictions = EndpointRestriction.filter_all( - # EndpointRestriction.id.in_([event.endpoint_id for event in active_events]), - # db=db, - # ).data - # return [event.endpoint_name for event in endpoint_restrictions] - # + events_dict = {} + for event in active_events: + if event.endpoint_code in events_dict: + events_dict[event.endpoint_code].append(event.function_code) + else: + events_dict[event.endpoint_code] = [event.function_code] + return events_dict class Event2Occupant(CrudCollection): @@ -326,7 +355,7 @@ class Event2Occupant(CrudCollection): ) @classmethod - def get_event_codes(cls, build_living_space_id, db) -> list: + def get_event_codes(cls, build_living_space_id: int, db) -> dict[str : list[str]]: occupant_events = cls.filter_all( cls.build_living_space_id == build_living_space_id, db=db, @@ -350,75 +379,106 @@ class Event2Occupant(CrudCollection): db=db, ).data active_events.extend(events_extra) - return [event.function_code for event in active_events] - - # @classmethod - # def get_event_endpoints(cls, build_living_space_id) -> list: - # from Schemas import EndpointRestriction - # - # db = cls.new_session() - # occupant_events = cls.filter_all( - # cls.build_living_space_id == build_living_space_id, - # db=db, - # ).data - # 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 = 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, - # db=db, - # ).data: - # events_extra = Events.filter_all( - # Events.id.in_([event.event_id for event in extra_events]), - # db=db, - # ).data - # active_events.extend(events_extra) - # endpoint_restrictions = EndpointRestriction.filter_all( - # EndpointRestriction.id.in_([event.endpoint_id for event in active_events]), - # db=db, - # ).data - # return [event.endpoint_name for event in endpoint_restrictions] + events_dict = {} + for event in active_events: + if event.endpoint_code in events_dict: + events_dict[event.endpoint_code].append(event.function_code) + else: + events_dict[event.endpoint_code] = [event.function_code] + return events_dict -class ModulePrice(CrudCollection): +class Application2Employee(CrudCollection): """ - ModulePrice class based on declarative_base and BaseMixin via session + Application2Employee class based on declarative_base and BaseMixin via session """ - __tablename__ = "module_price" + __tablename__ = "application2employee" __exclude__fields__ = [] - campaign_code: Mapped[str] = mapped_column( - String, nullable=False, comment="Campaign Code" + employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False) + employee_uu_id: Mapped[str] = mapped_column( + String, nullable=False, comment="Employee UUID" ) - module_id: Mapped[int] = mapped_column(ForeignKey("modules.id"), nullable=False) - module_uu_id: Mapped[str] = mapped_column( - String, nullable=False, comment="Module UUID" - ) - service_id: Mapped[int] = mapped_column(ForeignKey("services.id"), nullable=False) - service_uu_id: Mapped[str] = mapped_column( - String, nullable=False, comment="Service UUID" - ) - event_id: Mapped[int] = mapped_column(ForeignKey("events.id"), nullable=False) - event_uu_id: Mapped[str] = mapped_column( - String, nullable=False, comment="Event UUID" - ) - is_counted_percentage: Mapped[float] = mapped_column( - Numeric(6, 2), server_default="0.00" - ) # %22 - discounted_price: Mapped[float] = mapped_column( - Numeric(20, 2), server_default="0.00" - ) # Normal: 78.00 TL - calculated_price: Mapped[float] = mapped_column( - Numeric(20, 2), server_default="0.00" - ) # sana düz 75.00 TL yapar - __table_args__ = ({"comment": "ModulePrice Information"},) + application_id: Mapped[int] = mapped_column(ForeignKey("applications.id")) + application_uu_id: Mapped[str] = mapped_column( + String, nullable=False, comment="Application UUID" + ) + + site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL") + application_code: Mapped[str] = mapped_column( + String, nullable=False, comment="Application Code" + ) + + @classmethod + def get_application_codes(cls, employee_id: int, db) -> dict[str , str]: + print('employee_id', employee_id) + employee_applications = cls.filter_all( + Application2Employee.employee_id == employee_id, db=db, + ).data + applications_dict = {} + print('employee_applications', employee_applications) + for employee_application in employee_applications: + if employee_application.site_url not in applications_dict: + applications_dict[str(employee_application.site_url)] = str(employee_application.application_code) + return applications_dict + + __table_args__ = ( + Index( + "application_to_employee", + employee_id, + site_url, + application_id, + unique=True, + ), + {"comment": "Application2Employee Information"}, + ) + + +class Application2Occupant(CrudCollection): + """ + Application2Occupant class based on declarative_base and BaseMixin via session + """ + + __tablename__ = "application2occupant" + __exclude__fields__ = [] + + build_living_space_id: Mapped[int] = mapped_column( + ForeignKey("build_living_space.id"), nullable=False + ) + build_living_space_uu_id: Mapped[str] = mapped_column( + String, nullable=False, comment="Build Living Space UUID" + ) + + application_id: Mapped[int] = mapped_column(ForeignKey("applications.id")) + application_uu_id: Mapped[str] = mapped_column( + String, nullable=False, comment="Application UUID" + ) + + site_url: Mapped[str] = mapped_column(String, nullable=False, comment="Site URL") + application_code: Mapped[str] = mapped_column( + String, nullable=False, comment="Application Code" + ) + + @classmethod + def get_application_codes(cls, build_living_space_id: int, db) -> dict[str , str]: + occupant_applications = cls.filter_all( + cls.build_living_space_id == build_living_space_id, db=db, + ).data + applications_dict = {} + for occupant_application in occupant_applications: + if occupant_application.site_url not in applications_dict: + applications_dict[str(occupant_application.site_url)] = str(occupant_application.application_code) + return applications_dict + + __table_args__ = ( + Index( + "application_to_occupant", + build_living_space_id, + site_url, + application_id, + unique=True, + ), + {"comment": "Application2Occupant Information"}, + ) diff --git a/WebServices/client-frontend/package-lock.json b/WebServices/client-frontend/package-lock.json index 2111f47..e46688c 100644 --- a/WebServices/client-frontend/package-lock.json +++ b/WebServices/client-frontend/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@hookform/resolvers": "^5.0.1", "flatpickr": "^4.6.13", + "lucide-react": "^0.487.0", "next": "15.2.4", "next-crypto": "^1.0.8", "react": "^19.0.0", @@ -1152,6 +1153,14 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lucide-react": { + "version": "0.487.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.487.0.tgz", + "integrity": "sha512-aKqhOQ+YmFnwq8dWgGjOuLc8V1R9/c/yOd+zDY4+ohsR2Jo05lSGc3WsstYPIzcTpeosN7LoCkLReUUITvaIvw==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", diff --git a/WebServices/client-frontend/package.json b/WebServices/client-frontend/package.json index dc07c72..9c948c7 100644 --- a/WebServices/client-frontend/package.json +++ b/WebServices/client-frontend/package.json @@ -11,6 +11,7 @@ "dependencies": { "@hookform/resolvers": "^5.0.1", "flatpickr": "^4.6.13", + "lucide-react": "^0.487.0", "next": "15.2.4", "next-crypto": "^1.0.8", "react": "^19.0.0", diff --git a/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/leftMenu.tsx b/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/leftMenu.tsx new file mode 100644 index 0000000..7dfc94f --- /dev/null +++ b/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/leftMenu.tsx @@ -0,0 +1,104 @@ +"use server"; +import React from "react"; +import { Home, User, Settings, Mail, Calendar } from "lucide-react"; +import { transformMenu, LanguageTranslation } from "@/components/menu/runner"; +import Link from "next/link"; + +async function LeftMenu({ + searchParams, + pageUuidList, + lang, +}: { + pageUuidList: string[]; + lang: keyof LanguageTranslation; + searchParams: { [key: string]: string | string[] | undefined }; +}) { + const transformedMenu = transformMenu(pageUuidList); + + // Get the menuContext from searchParams without setting a default value + const menuContext = searchParams?.menu; + + // Only parse the indices if menuContext exists + let firstLayerIndex = -1; + let secondLayerIndex = -1; + + if (menuContext) { + const indices = menuContext.toString().split("*").map(Number); + firstLayerIndex = indices[0] || 0; + secondLayerIndex = indices[1] || 0; + } + + const pageSelected = searchParams?.page; + + return ( +
+ You do not have permission to access this page. +
+Please contact the administrator.
+{pageDescription}
+