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 ( +
+ +
+ ); +} + +export default LeftMenu; diff --git a/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/main.tsx b/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/main.tsx new file mode 100644 index 0000000..2d44e16 --- /dev/null +++ b/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/main.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import retrievePage from "@/components/NavigatePages"; + +function MainPage({ + pageSelected, + lang, +}: { + pageSelected: string | undefined; + lang: string; +}) { + const ComponentPage = retrievePage({ + pageId: pageSelected ?? "", + }); + + if (!ComponentPage) { + return ( +
+

No Page Selected

+
+ ); + } + return ( + <> + {/* Main Content */} +
+ +
+ + ); +} + +export default MainPage; diff --git a/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/page.tsx b/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/page.tsx index 64c2af0..ebd353e 100644 --- a/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/page.tsx +++ b/WebServices/client-frontend/src/app/(AuthLayout)/dashboard/page.tsx @@ -2,13 +2,56 @@ import React from "react"; import { checkAccessTokenIsValid } from "@/apicalls/cookies/token"; import { redirect } from "next/navigation"; -async function DashboardPage() { - const token_is_valid = await checkAccessTokenIsValid(); +import LeftMenu from "./leftMenu"; +import MainPage from "./main"; +export default async function DashboardLayout({ + searchParams, +}: { + searchParams: { [key: string]: string | undefined }; +}) { + const token_is_valid = await checkAccessTokenIsValid(); if (!token_is_valid) { redirect("/auth/login"); } - return
You have arrived to Dashboard Page
; -} + const pageUuidList = [ + "6015129b-f665-479c-a440-04fb82ea6114", + "14a98ae7-c64e-403d-9b53-32e7ea867ab4", + "e368137d-d548-4ed4-90da-337bcc5d1559", + "d3d97973-41c6-4bad-881b-6bf77d837fa5", + ]; // Mock data of pageUUID list [] + const lang = "tr"; // Assuming you have a way to determine the current language + const queryParams = await searchParams; + const pageSelected = queryParams?.page || undefined; -export default DashboardPage; + return ( +
+ {/* Sidebar */} + + + {/* Main Content Area */} + +
+ {/* Sticky Header */} +
+

Dashboard

+
+ +
+
+
+ +
+
+ ); +} diff --git a/WebServices/client-frontend/src/components/NavigatePages/index.tsx b/WebServices/client-frontend/src/components/NavigatePages/index.tsx new file mode 100644 index 0000000..fac3e5a --- /dev/null +++ b/WebServices/client-frontend/src/components/NavigatePages/index.tsx @@ -0,0 +1,41 @@ +import { PageProps } from "./interFaces"; +import Page0001 from "./page0001"; + +const PageIndexs = { + "6015129b-f665-479c-a440-04fb82ea6114": Page0001, +}; + +function UnAuthorizedPage({ lang }: PageProps) { + return ( + <> +
+
+

Unauthorized Access

+
+
+

+ You do not have permission to access this page. +

+

Please contact the administrator.

+
+ +
+ + ); +} + +export function retrievePage({ + pageId, +}: { + pageId: string; +}): React.ComponentType { + const PageComponent = PageIndexs[pageId as keyof typeof PageIndexs]; + if (!PageComponent) { + return UnAuthorizedPage; + } + return PageComponent; +} + +export default retrievePage; diff --git a/WebServices/client-frontend/src/components/NavigatePages/interFaces.tsx b/WebServices/client-frontend/src/components/NavigatePages/interFaces.tsx new file mode 100644 index 0000000..8157413 --- /dev/null +++ b/WebServices/client-frontend/src/components/NavigatePages/interFaces.tsx @@ -0,0 +1,3 @@ +export interface PageProps { + lang: string; +} diff --git a/WebServices/client-frontend/src/components/NavigatePages/page0001.tsx b/WebServices/client-frontend/src/components/NavigatePages/page0001.tsx new file mode 100644 index 0000000..3fbe072 --- /dev/null +++ b/WebServices/client-frontend/src/components/NavigatePages/page0001.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { PageProps } from "./interFaces"; + +const pageContext = { + tr: { + pageTitle: "Sayfa 0001", + pageDescription: "Bu, Sayfa 0001'in içeriğidir.", + }, + en: { + pageTitle: "Page 0001", + pageDescription: "This is the content of Page 0001.", + }, +}; + +function Page0001({ lang }: PageProps) { + const { pageTitle, pageDescription } = + pageContext[lang as keyof typeof pageContext]; + + return ( + <> +
+
+

{pageTitle}

+
+
+

{pageDescription}

+
+ +
+ + ); +} + +export default Page0001; diff --git a/WebServices/client-frontend/src/components/menu/runner.tsx b/WebServices/client-frontend/src/components/menu/runner.tsx new file mode 100644 index 0000000..43033e2 --- /dev/null +++ b/WebServices/client-frontend/src/components/menu/runner.tsx @@ -0,0 +1,125 @@ +/** + * Filters the menu structure based on intersections with provided UUIDs + * @param {string[]} uuids - Array of UUIDs to check for intersection + * @param {Array} menu - The original menu structure + * @returns {Array} - Filtered menu structure with only matching items + */ +import Menu from "@/menu/store"; // Assuming you have a menu structure imported + +// Define TypeScript interfaces for menu structure +interface LanguageTranslation { + tr: string; + en: string; +} + +interface MenuThirdLevel { + name: string; + lg: LanguageTranslation; + appList: string[]; +} + +interface MenuSecondLevel { + name: string; + lg: LanguageTranslation; + subList: MenuThirdLevel[]; +} + +interface MenuFirstLevel { + name: string; + lg: LanguageTranslation; + subList: MenuSecondLevel[]; +} + +// Define interfaces for the filtered menu structure +interface FilteredMenuThirdLevel { + name: string; + lg: LanguageTranslation; + appUUID: string; +} + +interface FilteredMenuSecondLevel { + name: string; + lg: LanguageTranslation; + subList: FilteredMenuThirdLevel[]; +} + +interface FilteredMenuFirstLevel { + name: string; + lg: LanguageTranslation; + subList: FilteredMenuSecondLevel[]; +} + +export type { LanguageTranslation }; + +function transformMenu(uuids: string[]) { + // Helper function to check if arrays have at least one common element + const hasIntersection = (array1: string[], array2: string[]): boolean => { + return array1.some((item) => array2.includes(item)); + }; + + // Process the menu structure + const filteredMenu: FilteredMenuFirstLevel[] = Menu.reduce( + (acc: FilteredMenuFirstLevel[], firstLevel: MenuFirstLevel) => { + // Create a new first level item with empty subList + const newFirstLevel: FilteredMenuFirstLevel = { + name: firstLevel.name, + lg: { ...firstLevel.lg }, + subList: [], + }; + + // Process second level items + firstLevel.subList.forEach((secondLevel: MenuSecondLevel) => { + // Create a new second level item with empty subList + const newSecondLevel: FilteredMenuSecondLevel = { + name: secondLevel.name, + lg: { ...secondLevel.lg }, + subList: [], + }; + + // Process third level items + secondLevel.subList.forEach((thirdLevel: MenuThirdLevel) => { + // Check if the third level's appList has an intersection with our UUIDs + if ( + thirdLevel.appList && + hasIntersection(thirdLevel.appList, uuids) + ) { + // Find the first matching UUID + const matchedUUID = thirdLevel.appList.find((uuid) => + uuids.includes(uuid) + ); + + // Only proceed if we found a matching UUID (should always be true due to hasIntersection) + if (matchedUUID) { + // Create a modified third level item with the matched UUID + const newThirdLevel: FilteredMenuThirdLevel = { + name: thirdLevel.name, + lg: { ...thirdLevel.lg }, + appUUID: matchedUUID, + }; + + // Add the modified third level to the second level's subList + newSecondLevel.subList.push(newThirdLevel); + } + } + }); + + // Only add the second level to the first level if it has any matching third level items + if (newSecondLevel.subList.length > 0) { + newFirstLevel.subList.push(newSecondLevel); + } + }); + + // Only add the first level to the result if it has any matching second level items + if (newFirstLevel.subList.length > 0) { + acc.push(newFirstLevel); + } + + return acc; + }, + [] + ); + + return filteredMenu; +} + +export { transformMenu }; diff --git a/WebServices/client-frontend/src/components/menu/store.tsx b/WebServices/client-frontend/src/components/menu/store.tsx new file mode 100644 index 0000000..c42f952 --- /dev/null +++ b/WebServices/client-frontend/src/components/menu/store.tsx @@ -0,0 +1,180 @@ +const Individual = { + name: "Individual", + lg: { + tr: "Birey", + en: "Individual", + }, + appList: [ + "0362071d-90d9-48db-8fa0-3528aaf450bd", + "6015129b-f665-479c-a440-04fb82ea6114", + ], +}; + +const User = { + name: "User", + lg: { + tr: "Kullanıcı", + en: "User", + }, + appList: ["14a98ae7-c64e-403d-9b53-32e7ea867ab4"], +}; + +const Build = { + name: "Build", + lg: { + tr: "Apartman", + en: "Build", + }, + appList: ["e368137d-d548-4ed4-90da-337bcc5d1559"], +}; + +const BuildParts = { + name: "BuildParts", + lg: { + tr: "Daire", + en: "BuildParts", + }, + appList: [], +}; + +const BuildArea = { + name: "BuildArea", + lg: { + tr: "Daire", + en: "BuildArea", + }, + appList: [], +}; + +const ManagementAccounting = { + name: "ManagementAccounting", + lg: { + tr: "Yönetim Cari Hareketler", + en: "ManagementAccounting", + }, + appList: [], +}; + +const ManagementBudget = { + name: "ManagementBudget", + lg: { + tr: "Yönetim Bütçe İşlemleri", + en: "Management Budget", + }, + appList: [], +}; + +const BuildPartsAccounting = { + name: "BuildPartsAccounting", + lg: { + tr: "Daire Cari Hareketler", + en: "Build Parts Accounting", + }, + appList: [], +}; + +const AnnualMeeting = { + name: "AnnualMeeting", + lg: { + tr: "Yıllık Olağan Toplantı Tanımlama ve Davet", + en: "Annual Meetings and Invitations", + }, + appList: ["d3d97973-41c6-4bad-881b-6bf77d837fa5"], +}; + +const AnnualMeetingClose = { + name: "AnnualMeetingClose", + lg: { + tr: "Yıllık Olağan Toplantı kapatma ve Cari Yaratma", + en: "Annual Meeting Close and Accountings", + }, + appList: [], +}; + +const EmergencyMeeting = { + name: "EmergencyMeeting", + lg: { + tr: "Acil Toplantı Tanımlama ve Davet", + en: "Emergency Meeting and Invitations", + }, + appList: [], +}; + +const EmergencyMeetingClose = { + name: "EmergencyMeetingClose", + lg: { + tr: "Acil Olağan Toplantı kapatma ve Cari Yaratma", + en: "Emergency Meeting Close and Accountings", + }, + appList: [], +}; + +const MeetingParticipations = { + name: "MeetingParticipations", + lg: { + tr: "Toplantı Katılım İşlemleri", + en: "Meeting Participations", + }, + appList: ["SomeUUID"], +}; + +const Menu = [ + { + name: "Definitions", + lg: { + tr: "Tanımlar", + en: "Definitions", + }, + subList: [ + { + name: "People", + lg: { + tr: "Kişiler", + en: "People", + }, + subList: [Individual, User], + }, + { + name: "Building", + lg: { + tr: "Binalar", + en: "Building", + }, + subList: [Build, BuildParts, BuildArea], + }, + ], + }, + { + name: "Building Management", + lg: { + tr: "Bina Yönetimi", + en: "Building Management", + }, + subList: [ + { + name: "Management Accounting", + lg: { + tr: "Cari işlemler", + en: "Management Accounting", + }, + subList: [ManagementAccounting, ManagementBudget, BuildPartsAccounting], + }, + { + name: "Meetings", + lg: { + tr: "Toplantılar", + en: "Meetings", + }, + subList: [ + AnnualMeeting, + AnnualMeetingClose, + EmergencyMeeting, + EmergencyMeetingClose, + MeetingParticipations, + ], + }, + ], + }, +]; + +export default Menu; diff --git a/docker-compose.yml b/docker-compose.yml index e5c0313..367a591 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,19 +49,19 @@ services: ports: - "11222:6379" - client_frontend: - container_name: client_frontend - build: - context: . - dockerfile: WebServices/client-frontend/Dockerfile - networks: - - wag-services - ports: - - "3000:3000" - # volumes: - # - client-frontend:/WebServices/client-frontend - environment: - - NODE_ENV=development +# client_frontend: +# container_name: client_frontend +# build: +# context: . +# dockerfile: WebServices/client-frontend/Dockerfile +# networks: +# - wag-services +# ports: +# - "3000:3000" +# # volumes: +# # - client-frontend:/WebServices/client-frontend +# environment: +# - NODE_ENV=development # management_frontend: # container_name: management_frontend @@ -77,47 +77,61 @@ services: # environment: # - NODE_ENV=development - # initializer_service: - # container_name: initializer_service - # build: - # context: . - # dockerfile: ApiServices/InitialService/Dockerfile - # networks: - # - wag-services - # env_file: - # - api_env.env - # depends_on: - # - postgres-service - # - mongo_service - # - redis_service +# initializer_service: +# container_name: initializer_service +# build: +# context: . +# dockerfile: ApiServices/InitialService/Dockerfile +# networks: +# - wag-services +# env_file: +# - api_env.env +# depends_on: +# - postgres-service +# - mongo_service +# - redis_service - # template_service: - # container_name: template_service - # build: - # context: . - # dockerfile: ApiServices/TemplateService/Dockerfile - # networks: - # - wag-services - # env_file: - # - api_env.env - # environment: - # - API_PATH=app:app - # - API_HOST=0.0.0.0 - # - API_PORT=8000 - # - API_LOG_LEVEL=info - # - API_RELOAD=1 - # - API_ACCESS_TOKEN_TAG=1 - # - API_APP_NAME=evyos-template-api-gateway - # - API_TITLE=WAG API Template Api Gateway - # - API_FORGOT_LINK=https://template_service/forgot-password - # - API_DESCRIPTION=This api is serves as web template api gateway only to evyos web services. - # - API_APP_URL=https://template_service - # ports: - # - "8000:8000" - # depends_on: - # - postgres-service - # - mongo_service - # - redis_service + dealer_service: + container_name: dealer_service + build: + context: . + dockerfile: ApiServices/DealerService/Dockerfile + networks: + - wag-services + env_file: + - api_env.env + depends_on: + - postgres-service + - mongo_service + - redis_service + +# template_service: +# container_name: template_service +# build: +# context: . +# dockerfile: ApiServices/TemplateService/Dockerfile +# networks: +# - wag-services +# env_file: +# - api_env.env +# environment: +# - API_PATH=app:app +# - API_HOST=0.0.0.0 +# - API_PORT=8000 +# - API_LOG_LEVEL=info +# - API_RELOAD=1 +# - API_ACCESS_TOKEN_TAG=1 +# - API_APP_NAME=evyos-template-api-gateway +# - API_TITLE=WAG API Template Api Gateway +# - API_FORGOT_LINK=https://template_service/forgot-password +# - API_DESCRIPTION=This api is serves as web template api gateway only to evyos web services. +# - API_APP_URL=https://template_service +# ports: +# - "8000:8000" +# depends_on: +# - postgres-service +# - mongo_service +# - redis_service auth_service: container_name: auth_service