updated app reachable codes

This commit is contained in:
berkay 2025-04-09 11:51:28 +03:00
parent 7eadadbd1d
commit 7c2150a8b0
23 changed files with 1040 additions and 187 deletions

View File

@ -342,7 +342,6 @@ def authentication_token_refresh_post(
status_code=status.HTTP_406_NOT_ACCEPTABLE, status_code=status.HTTP_406_NOT_ACCEPTABLE,
headers=headers, headers=headers,
) )
return JSONResponse( return JSONResponse(
content={}, content={},
status_code=status.HTTP_202_ACCEPTED, status_code=status.HTTP_202_ACCEPTED,
@ -374,7 +373,6 @@ def authentication_password_verify_otp(
"tz": tz or "GMT+3", "tz": tz or "GMT+3",
"token": token, "token": token,
} }
print("Token&OTP : ", data.otp, data.token)
if not domain or not language: if not domain or not language:
return JSONResponse( return JSONResponse(
content={"error": "EYS_0003"}, content={"error": "EYS_0003"},
@ -419,8 +417,17 @@ def authentication_page_valid(
status_code=status.HTTP_406_NOT_ACCEPTABLE, status_code=status.HTTP_406_NOT_ACCEPTABLE,
headers=headers, headers=headers,
) )
result = AuthHandlers.PageHandlers.retrieve_valid_page_via_token(
page_url=data.page_url, access_token=token
)
if not result:
return JSONResponse( return JSONResponse(
content={}, content={"error": "EYS_0004"},
status_code=status.HTTP_406_NOT_ACCEPTABLE,
headers=headers,
)
return JSONResponse(
content={"application": result},
status_code=status.HTTP_202_ACCEPTED, status_code=status.HTTP_202_ACCEPTED,
headers=headers, headers=headers,
) )

View File

@ -29,14 +29,16 @@ from Schemas import (
) )
from Modules.Token.password_module import PasswordModule from Modules.Token.password_module import PasswordModule
from Schemas.building.build import RelationshipEmployee2Build 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.Redis.database import RedisActions
from Controllers.Mongo.database import mongo_handler from Controllers.Mongo.database import mongo_handler
TokenDictType = Union[EmployeeTokenObject, OccupantTokenObject] TokenDictType = Union[EmployeeTokenObject, OccupantTokenObject]
class RedisHandlers: class RedisHandlers:
AUTH_TOKEN: str = "AUTH_TOKEN" AUTH_TOKEN: str = "AUTH_TOKEN"
@classmethod @classmethod
@ -79,9 +81,7 @@ class RedisHandlers:
return generated_access_token return generated_access_token
@classmethod @classmethod
def update_token_at_redis( def update_token_at_redis(cls, token: str, add_payload: Union[CompanyToken, OccupantToken]):
cls, token: str, add_payload: Union[CompanyToken, OccupantToken]
):
if already_token_data := RedisActions.get_json( if already_token_data := RedisActions.get_json(
list_keys=[RedisHandlers.AUTH_TOKEN, token, "*"] list_keys=[RedisHandlers.AUTH_TOKEN, token, "*"]
).first: ).first:
@ -124,9 +124,7 @@ class UserHandlers:
return found_user return found_user
@staticmethod @staticmethod
def check_password_valid( def check_password_valid(domain: str, id_: str, password: str, password_hashed: str) -> bool:
domain: str, id_: str, password: str, password_hashed: str
) -> bool:
""" """
Check if the password is valid. Check if the password is valid.
""" """
@ -144,6 +142,10 @@ class UserHandlers:
return True return True
raise ValueError("EYS_0004") raise ValueError("EYS_0004")
@staticmethod
def update_password():
return
class LoginHandler: class LoginHandler:
@ -156,9 +158,7 @@ class LoginHandler:
return str(email).split("@")[1] == api_config.ACCESS_EMAIL_EXT return str(email).split("@")[1] == api_config.ACCESS_EMAIL_EXT
@classmethod @classmethod
def do_employee_login( def do_employee_login(cls, request: Any, data: Any, extra_dict: Optional[Dict[str, Any]] = None):
cls, request: Any, data: Any, extra_dict: Optional[Dict[str, Any]] = None
):
""" """
Handle employee login. Handle employee login.
""" """
@ -268,9 +268,7 @@ class LoginHandler:
raise ValueError("Something went wrong") raise ValueError("Something went wrong")
@classmethod @classmethod
def do_employee_occupant( def do_employee_occupant(cls, request: Any, data: Any, extra_dict: Optional[Dict[str, Any]] = None):
cls, request: Any, data: Any, extra_dict: Optional[Dict[str, Any]] = None
):
""" """
Handle occupant login. Handle occupant login.
""" """
@ -421,9 +419,7 @@ class LoginHandler:
return request.headers.get(api_config.ACCESS_TOKEN_TAG) return request.headers.get(api_config.ACCESS_TOKEN_TAG)
@classmethod @classmethod
def handle_employee_selection( def handle_employee_selection(cls, access_token: str, data: Any, token_dict: TokenDictType):
cls, access_token: str, data: Any, token_dict: TokenDictType
):
with Users.new_session() as db: with Users.new_session() as db:
if data.company_uu_id not in token_dict.companies_uu_id_list: if data.company_uu_id not in token_dict.companies_uu_id_list:
ValueError("EYS_0011") ValueError("EYS_0011")
@ -479,6 +475,10 @@ class LoginHandler:
db=db, db=db,
).data ).data
reachable_app_codes = Application2Employee.get_application_codes(
employee_id=employee.id, db=db
)
# Create company token # Create company token
company_token = CompanyToken( company_token = CompanyToken(
company_uu_id=selected_company.uu_id.__str__(), company_uu_id=selected_company.uu_id.__str__(),
@ -493,6 +493,7 @@ class LoginHandler:
employee_id=employee.id, employee_id=employee.id,
employee_uu_id=employee.uu_id.__str__(), employee_uu_id=employee.uu_id.__str__(),
reachable_event_codes=reachable_event_codes, reachable_event_codes=reachable_event_codes,
reachable_app_codes=reachable_app_codes
) )
redis_handler = RedisHandlers() redis_handler = RedisHandlers()
redis_result = redis_handler.update_token_at_redis( redis_result = redis_handler.update_token_at_redis(
@ -503,9 +504,7 @@ class LoginHandler:
} }
@classmethod @classmethod
def handle_occupant_selection( def handle_occupant_selection(cls, access_token: str, data: Any, token_dict: TokenDictType):
cls, access_token: str, data: Any, token_dict: TokenDictType
):
"""Handle occupant type selection""" """Handle occupant type selection"""
with BuildLivingSpace.new_session() as db: with BuildLivingSpace.new_session() as db:
# Get selected occupant type # Get selected occupant type
@ -707,6 +706,22 @@ class PasswordHandler:
return found_user 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: class AuthHandlers:
LoginHandler: LoginHandler = LoginHandler() LoginHandler: LoginHandler = LoginHandler()
PasswordHandler: PasswordHandler = PasswordHandler() PasswordHandler: PasswordHandler = PasswordHandler()
PageHandlers: PageHandlers = PageHandlers()

View File

@ -59,8 +59,8 @@ class OccupantToken(BaseModel):
responsible_employee_id: Optional[int] = None responsible_employee_id: Optional[int] = None
responsible_employee_uuid: Optional[str] = None responsible_employee_uuid: Optional[str] = None
reachable_event_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[list[str]] = None # ID list of reachable modules reachable_app_codes: Optional[dict[str, str]] = None # ID list of reachable modules
class CompanyToken(BaseModel): class CompanyToken(BaseModel):
@ -83,8 +83,8 @@ class CompanyToken(BaseModel):
bulk_duties_id: int bulk_duties_id: int
reachable_event_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[list[str]] = None # ID list of reachable modules reachable_app_codes: Optional[dict[str, str]] = None # ID list of reachable modules
class OccupantTokenObject(ApplicationToken): class OccupantTokenObject(ApplicationToken):

View File

@ -15,7 +15,7 @@ class RequestVerifyOTP(BaseModel):
class RequestApplication(BaseModel): class RequestApplication(BaseModel):
page: str # /building/create page_url: str # /building/create
class RequestSelectEmployee(BaseModel): class RequestSelectEmployee(BaseModel):

View File

@ -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
"""

View File

@ -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"]

View File

@ -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)

View File

@ -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,
)

View File

@ -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}"
)

View File

@ -1,9 +1,11 @@
import uuid import uuid
from fastapi import APIRouter, Request, Response, Header from fastapi import APIRouter, Request, Response, Header
from ApiServices.TemplateService.config import api_config from ApiServices.TemplateService.config import api_config
from ApiServices.TemplateService.events.template.event import template_event_cluster from ApiServices.TemplateService.events.template.event import template_event_cluster
test_template_route = APIRouter(prefix="/test", tags=["Test"]) test_template_route = APIRouter(prefix="/test", tags=["Test"])

View File

@ -80,6 +80,9 @@ from Schemas.event.event import (
Event2Employee, Event2Employee,
Event2OccupantExtra, Event2OccupantExtra,
Event2EmployeeExtra, Event2EmployeeExtra,
Applications,
Application2Employee,
Application2Occupant,
) )
from Schemas.identity.identity import ( from Schemas.identity.identity import (
UsersTokens, UsersTokens,
@ -175,6 +178,9 @@ __all__ = [
"Event2Employee", "Event2Employee",
"Event2OccupantExtra", "Event2OccupantExtra",
"Event2EmployeeExtra", "Event2EmployeeExtra",
"Applications",
"Application2Employee",
"Application2Occupant",
"Addresses", "Addresses",
"AddressCity", "AddressCity",
"AddressStreet", "AddressStreet",

View File

@ -11,6 +11,25 @@ from sqlalchemy.orm import mapped_column, Mapped
from Controllers.Postgres.mixin import CrudCollection 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): class Events(CrudCollection):
""" """
Events class based on declarative_base and BaseMixin via session Events class based on declarative_base and BaseMixin via session
@ -82,6 +101,42 @@ class Modules(CrudCollection):
__table_args__ = ({"comment": "Modules Information"},) __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): class Services(CrudCollection):
""" """
Services class based on declarative_base and BaseMixin via session Services class based on declarative_base and BaseMixin via session
@ -143,6 +198,10 @@ class Service2Events(CrudCollection):
__table_args__ = ({"comment": "Service2Events Information"},) __table_args__ = ({"comment": "Service2Events Information"},)
# class Service2Application(CrudCollection):
# pass
class Event2OccupantExtra(CrudCollection): class Event2OccupantExtra(CrudCollection):
__tablename__ = "event2occupant_extra" __tablename__ = "event2occupant_extra"
@ -229,7 +288,7 @@ class Event2Employee(CrudCollection):
) )
@classmethod @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( employee_events = cls.filter_all(
cls.employee_id == employee_id, cls.employee_id == employee_id,
db=db, db=db,
@ -253,43 +312,13 @@ class Event2Employee(CrudCollection):
db=db, db=db,
).data ).data
active_events.extend(events_extra) active_events.extend(events_extra)
return [event.function_code for event in active_events] events_dict = {}
for event in active_events:
# @classmethod if event.endpoint_code in events_dict:
# def get_event_endpoints(cls, employee_id: int) -> list: events_dict[event.endpoint_code].append(event.function_code)
# from Schemas import EndpointRestriction else:
# events_dict[event.endpoint_code] = [event.function_code]
# db = cls.new_session() return events_dict
# 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]
#
class Event2Occupant(CrudCollection): class Event2Occupant(CrudCollection):
@ -326,7 +355,7 @@ class Event2Occupant(CrudCollection):
) )
@classmethod @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( occupant_events = cls.filter_all(
cls.build_living_space_id == build_living_space_id, cls.build_living_space_id == build_living_space_id,
db=db, db=db,
@ -350,75 +379,106 @@ class Event2Occupant(CrudCollection):
db=db, db=db,
).data ).data
active_events.extend(events_extra) active_events.extend(events_extra)
return [event.function_code for event in active_events] events_dict = {}
for event in active_events:
# @classmethod if event.endpoint_code in events_dict:
# def get_event_endpoints(cls, build_living_space_id) -> list: events_dict[event.endpoint_code].append(event.function_code)
# from Schemas import EndpointRestriction else:
# events_dict[event.endpoint_code] = [event.function_code]
# db = cls.new_session() return events_dict
# 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]
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__ = [] __exclude__fields__ = []
campaign_code: Mapped[str] = mapped_column( employee_id: Mapped[int] = mapped_column(ForeignKey("employees.id"), nullable=False)
String, nullable=False, comment="Campaign Code" 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"},
)

View File

@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.0.1", "@hookform/resolvers": "^5.0.1",
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"lucide-react": "^0.487.0",
"next": "15.2.4", "next": "15.2.4",
"next-crypto": "^1.0.8", "next-crypto": "^1.0.8",
"react": "^19.0.0", "react": "^19.0.0",
@ -1152,6 +1153,14 @@
"url": "https://opencollective.com/parcel" "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": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",

View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.0.1", "@hookform/resolvers": "^5.0.1",
"flatpickr": "^4.6.13", "flatpickr": "^4.6.13",
"lucide-react": "^0.487.0",
"next": "15.2.4", "next": "15.2.4",
"next-crypto": "^1.0.8", "next-crypto": "^1.0.8",
"react": "^19.0.0", "react": "^19.0.0",

View File

@ -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 (
<div>
<nav className="flex flex-col space-y-2">
<div className="text-xl font-bold mb-6 text-center">Dashboard</div>
{transformedMenu.map((item, firstIndex) => (
<div key={item.name} className="mb-4">
<Link
href={`/dashboard?${
pageSelected ? `page=${pageSelected}` : ""
}&menu=${firstIndex}*0`}
className={`text-xl font-semibold pl-5 my-2 py-2 block ${
firstIndex === firstLayerIndex
? "text-emerald-600"
: "text-emerald-400"
} hover:text-emerald-600`}
>
{item.lg[lang]}
</Link>
{/* Only render the second layer if menuContext exists and this first layer item is selected */}
{menuContext && firstIndex === firstLayerIndex && (
<ul className="space-y-2">
{item.subList.map((subItem, secondIndex) => (
<div key={subItem.name}>
<Link
href={`/dashboard?${
pageSelected ? `page=${pageSelected}` : ""
}&menu=${firstIndex}*${secondIndex}`}
className={`ml-5 my-4 pl-4 text-xl font-semibold block ${
secondIndex === secondLayerIndex
? "text-emerald-700"
: "text-emerald-500"
} hover:text-emerald-700`}
>
{subItem.lg[lang]}
</Link>
{/* Only render the third layer if this second layer item is selected */}
{firstIndex === firstLayerIndex &&
secondIndex === secondLayerIndex && (
<div className="ml-5">
{subItem.subList.map((subSubItem) => (
<Link
key={subSubItem.appUUID}
href={`/dashboard?page=${subSubItem.appUUID}&menu=${firstIndex}*${secondIndex}`}
className={`flex flex-row text-xl py-4 my-4 w-full space-x-2 p-2 rounded ${
pageSelected === subSubItem.appUUID
? " bg-gray-100 cursor-not-allowed"
: "hover:bg-gray-200"
}`}
>
<span className="text-gray-400">
<Home />
</span>
<span className="ml-5 text-gray-700">
{subSubItem.lg[lang]}
</span>
</Link>
))}
</div>
)}
</div>
))}
</ul>
)}
</div>
))}
</nav>
</div>
);
}
export default LeftMenu;

View File

@ -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 (
<div className="flex flex-col w-full">
<h2 className="text-2xl font-semibold p-4">No Page Selected</h2>
</div>
);
}
return (
<>
{/* Main Content */}
<main className="flex-grow p-6 bg-gray-50 overflow-y-auto">
<ComponentPage lang={lang} />
</main>
</>
);
}
export default MainPage;

View File

@ -2,13 +2,56 @@ import React from "react";
import { checkAccessTokenIsValid } from "@/apicalls/cookies/token"; import { checkAccessTokenIsValid } from "@/apicalls/cookies/token";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
async function DashboardPage() { import LeftMenu from "./leftMenu";
const token_is_valid = await checkAccessTokenIsValid(); 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) { if (!token_is_valid) {
redirect("/auth/login"); redirect("/auth/login");
} }
return <div>You have arrived to Dashboard Page</div>; 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 (
<div className="flex h-screen overflow-hidden">
{/* Sidebar */}
<aside className="w-1/4 border-r p-4 overflow-y-auto">
<LeftMenu
pageUuidList={pageUuidList}
lang={lang}
searchParams={queryParams}
/>
</aside>
{/* Main Content Area */}
<div className="flex flex-col w-3/4">
{/* Sticky Header */}
<header className="sticky top-0 bg-white shadow-md z-10 p-4 flex justify-between items-center">
<h1 className="text-2xl font-semibold">Dashboard</h1>
<div className="flex items-center space-x-4">
<input
type="text"
placeholder="Search..."
className="border px-3 py-2 rounded-lg"
/>
<div className="w-10 h-10 bg-gray-300 rounded-full"></div>
</div>
</header>
<MainPage pageSelected={pageSelected} lang={lang} />
</div>
</div>
);
}

View File

@ -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 (
<>
<div className="flex flex-col h-screen">
<header className="bg-gray-800 text-white p-4 text-center">
<h1 className="text-2xl font-bold">Unauthorized Access</h1>
</header>
<main className="flex-grow p-4 bg-gray-100">
<p className="text-gray-700">
You do not have permission to access this page.
</p>
<p className="text-gray-700">Please contact the administrator.</p>
</main>
<footer className="bg-gray-800 text-white p-4 text-center">
<p>&copy; 2023 My Application</p>
</footer>
</div>
</>
);
}
export function retrievePage({
pageId,
}: {
pageId: string;
}): React.ComponentType<PageProps> {
const PageComponent = PageIndexs[pageId as keyof typeof PageIndexs];
if (!PageComponent) {
return UnAuthorizedPage;
}
return PageComponent;
}
export default retrievePage;

View File

@ -0,0 +1,3 @@
export interface PageProps {
lang: string;
}

View File

@ -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 (
<>
<div className="flex flex-col h-screen">
<header className="bg-gray-800 text-white p-4 text-center">
<h1 className="text-2xl font-bold">{pageTitle}</h1>
</header>
<main className="flex-grow p-4 bg-gray-100">
<p className="text-gray-700">{pageDescription}</p>
</main>
<footer className="bg-gray-800 text-white p-4 text-center">
<p>&copy; 2023 My Application</p>
</footer>
</div>
</>
);
}
export default Page0001;

View File

@ -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 };

View File

@ -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;

View File

@ -49,19 +49,19 @@ services:
ports: ports:
- "11222:6379" - "11222:6379"
client_frontend: # client_frontend:
container_name: client_frontend # container_name: client_frontend
build: # build:
context: . # context: .
dockerfile: WebServices/client-frontend/Dockerfile # dockerfile: WebServices/client-frontend/Dockerfile
networks: # networks:
- wag-services # - wag-services
ports: # ports:
- "3000:3000" # - "3000:3000"
# volumes: # # volumes:
# - client-frontend:/WebServices/client-frontend # # - client-frontend:/WebServices/client-frontend
environment: # environment:
- NODE_ENV=development # - NODE_ENV=development
# management_frontend: # management_frontend:
# container_name: management_frontend # container_name: management_frontend
@ -77,47 +77,61 @@ services:
# environment: # environment:
# - NODE_ENV=development # - NODE_ENV=development
# initializer_service: # initializer_service:
# container_name: initializer_service # container_name: initializer_service
# build: # build:
# context: . # context: .
# dockerfile: ApiServices/InitialService/Dockerfile # dockerfile: ApiServices/InitialService/Dockerfile
# networks: # networks:
# - wag-services # - wag-services
# env_file: # env_file:
# - api_env.env # - api_env.env
# depends_on: # depends_on:
# - postgres-service # - postgres-service
# - mongo_service # - mongo_service
# - redis_service # - redis_service
# template_service: dealer_service:
# container_name: template_service container_name: dealer_service
# build: build:
# context: . context: .
# dockerfile: ApiServices/TemplateService/Dockerfile dockerfile: ApiServices/DealerService/Dockerfile
# networks: networks:
# - wag-services - wag-services
# env_file: env_file:
# - api_env.env - api_env.env
# environment: depends_on:
# - API_PATH=app:app - postgres-service
# - API_HOST=0.0.0.0 - mongo_service
# - API_PORT=8000 - redis_service
# - API_LOG_LEVEL=info
# - API_RELOAD=1 # template_service:
# - API_ACCESS_TOKEN_TAG=1 # container_name: template_service
# - API_APP_NAME=evyos-template-api-gateway # build:
# - API_TITLE=WAG API Template Api Gateway # context: .
# - API_FORGOT_LINK=https://template_service/forgot-password # dockerfile: ApiServices/TemplateService/Dockerfile
# - API_DESCRIPTION=This api is serves as web template api gateway only to evyos web services. # networks:
# - API_APP_URL=https://template_service # - wag-services
# ports: # env_file:
# - "8000:8000" # - api_env.env
# depends_on: # environment:
# - postgres-service # - API_PATH=app:app
# - mongo_service # - API_HOST=0.0.0.0
# - redis_service # - 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: auth_service:
container_name: auth_service container_name: auth_service