From 81e78d8d5550f1c0d0d6a66198abbcbdf9a1df22 Mon Sep 17 00:00:00 2001 From: PaulKalho Date: Tue, 2 Dec 2025 20:07:37 -0600 Subject: [PATCH 1/4] feat: check project resource access --- .../controllers/project_controller.py | 50 ++++--------------- .../controllers/workflow_controller.py | 7 +-- .../workflow_service/models/__init__.py | 8 +++ .../workflow_service/schemas/project.py | 1 - .../workflow_service/views/project.py | 41 +++++++-------- .../workflow_service/views/workflow.py | 12 ++--- core/utils/security/resources.py | 27 ++++++++++ frontend/next-env.d.ts | 1 + 8 files changed, 70 insertions(+), 77 deletions(-) create mode 100644 core/services/workflow_service/models/__init__.py create mode 100644 core/utils/security/resources.py diff --git a/core/services/workflow_service/controllers/project_controller.py b/core/services/workflow_service/controllers/project_controller.py index dd550754..9e20d990 100644 --- a/core/services/workflow_service/controllers/project_controller.py +++ b/core/services/workflow_service/controllers/project_controller.py @@ -76,31 +76,15 @@ def create_project_from_template( raise e -def read_project(project_uuid: UUID) -> Project: - logging.debug(f"Reading project with UUID: {project_uuid}") - db: Session = next(get_database()) - - project = db.query(Project).filter_by(uuid=project_uuid).one_or_none() - - if not project: - logging.error(f"Project {project_uuid} not found") - raise HTTPException(status_code=404, detail="Project not found") - - return project - - -def rename_project(project_uuid: UUID, new_name: str, db: Session) -> Project: - logging.debug(f"Renaming project {project_uuid} to {new_name}.") - - project = db.query(Project).filter_by(uuid=project_uuid).one_or_none() - - if not project: - logging.error(f"Project {project_uuid} not found.") - raise HTTPException(status_code=404, detail="Project not found") +def rename_project(db: Session, project: Project, new_name: str) -> Project: + logging.debug(f"Renaming project {project.uuid} to {new_name}.") project.name = new_name - logging.info(f"Project {project_uuid} renamed successfully to {new_name}") + db.commit() + db.refresh(project) + + logging.debug(f"Project {project.uuid} renamed successfully to {new_name}") return project @@ -154,20 +138,13 @@ def delete_user(project_uuid: UUID, user_uuid: UUID) -> None: logging.info(f"User {user_uuid} removed from project {project_uuid}") -def delete_project(project_uuid: UUID) -> None: - logging.debug(f"Deleting project with UUID: {project_uuid}") - db: Session = next(get_database()) - - project = db.query(Project).filter_by(uuid=project_uuid).one_or_none() - - if not project: - logging.error(f"Project {project_uuid} not found") - raise HTTPException(status_code=404, detail="Project not found") +def delete_project(db: Session, project: Project) -> None: + logging.debug(f"Deleting project with UUID: {project.uuid}") db.delete(project) db.commit() - logging.info(f"Project {project_uuid} deleted successfully") + logging.debug("Project deleted successfully") def read_all_projects() -> list[Project]: @@ -188,13 +165,4 @@ def read_projects_by_user_uuid(user_uuid: UUID) -> list[Project]: .all() ) - if not projects: - logging.error(f"No projects found for user {user_uuid}") - raise HTTPException( - status_code=404, - detail="No projects found for user", - ) - - logging.info(f"Retrieved {len(projects)} projects for user {user_uuid}") - return projects diff --git a/core/services/workflow_service/controllers/workflow_controller.py b/core/services/workflow_service/controllers/workflow_controller.py index bd2ca05f..b084578d 100644 --- a/core/services/workflow_service/controllers/workflow_controller.py +++ b/core/services/workflow_service/controllers/workflow_controller.py @@ -29,9 +29,6 @@ compute_block_controller, template_controller, ) -from services.workflow_service.controllers.project_controller import ( - read_project, -) from services.workflow_service.models.block import ( Block, block_dependencies, @@ -45,6 +42,7 @@ BlockStatus, ConfigType, ) +from services.workflow_service.models import Project from services.workflow_service.schemas.workflow import ( WorfklowValidationError, WorkflowEnvsWithBlockInfo, @@ -478,10 +476,9 @@ def wait_for_dag_registration( return False -def translate_project_to_dag(project_uuid: UUID) -> str: +def translate_project_to_dag(project: Project, project_uuid: UUID) -> str: """Parses a project and its blocks into a DAG, validates it, and saves it.""" - project = read_project(project_uuid) graph = create_graph(project) templates = init_templates() dag_id = _project_id_to_dag_id(project_uuid) diff --git a/core/services/workflow_service/models/__init__.py b/core/services/workflow_service/models/__init__.py new file mode 100644 index 00000000..c5eb9688 --- /dev/null +++ b/core/services/workflow_service/models/__init__.py @@ -0,0 +1,8 @@ +from .block import Block +from .entrypoint import Entrypoint +from .input_output import InputOutput +from .project import Project + +__all__ = [ + "Block", "Entrypoint", "InputOutput", "Project" +] diff --git a/core/services/workflow_service/schemas/project.py b/core/services/workflow_service/schemas/project.py index ba172ab2..84716024 100644 --- a/core/services/workflow_service/schemas/project.py +++ b/core/services/workflow_service/schemas/project.py @@ -48,7 +48,6 @@ class ReadAllResponse(BaseModel): class RenameProjectRequest(BaseModel): - project_uuid: UUID new_name: str diff --git a/core/services/workflow_service/views/project.py b/core/services/workflow_service/views/project.py index 0ef2beda..b2e202e3 100644 --- a/core/services/workflow_service/views/project.py +++ b/core/services/workflow_service/views/project.py @@ -1,5 +1,4 @@ -from fastapi import APIRouter, Depends, HTTPException -from uuid import UUID +from fastapi import APIRouter, Depends from utils.errors.error import handle_error import logging from sqlalchemy.orm import Session @@ -20,6 +19,7 @@ ) from utils.database.session_injector import get_database from utils.security.token import User, get_user +from utils.security.resources import get_project router = APIRouter(prefix="/project", tags=["project"]) @@ -83,41 +83,36 @@ async def read_projects_by_user( raise handle_error(e) -@router.get("/{project_id}", response_model=Project) +@router.get("/{project_uuid}", response_model=Project) async def read_project( - project_id: UUID | None = None, + project: Project = Depends(get_project) ): - try: - if project_id is None: - raise HTTPException(status=422, detail="Project ID is required") - - project = project_controller.read_project(project_id) - return project - except Exception as e: - logging.error(f"Error reading project: {e}") - raise handle_error(e) + return project -@router.put("/", response_model=Project) +@router.put("/{project_uuid}", response_model=Project) async def rename_project( data: RenameProjectRequest, + project: Project = Depends(get_project), db: Session = Depends(get_database) ): try: - with db.begin(): - updated_project = project_controller.rename_project( - data.project_uuid, data.new_name, db - ) + updated_project = project_controller.rename_project( + db, project, data.new_name) return updated_project except Exception as e: raise handle_error(e) -@router.delete("/{project_id}", status_code=200) -async def delete_project(project_id: UUID, _: User = Depends(get_user)): +@router.delete("/{project_uuid}", status_code=200) +async def delete_project( + project: Project = Depends(get_project), + db: Session = Depends(get_database) +): try: - project_controller.delete_project(project_id) - workflow_controller.delete_dag_from_airflow(project_id) + project_controller.delete_project(db, project) + workflow_controller.delete_dag_from_airflow(project.uuid) except Exception as e: - logging.exception(f"Error deleting project with id {project_id}: {e}") + logging.exception(f"Error deleting project with id { + project.uuid}: {e}") raise handle_error(e) diff --git a/core/services/workflow_service/views/workflow.py b/core/services/workflow_service/views/workflow.py index abad7a39..e1c9b974 100644 --- a/core/services/workflow_service/views/workflow.py +++ b/core/services/workflow_service/views/workflow.py @@ -16,6 +16,7 @@ from services.workflow_service.controllers import ( project_controller as project_controller, ) +from services.workflow_service.models import Project from services.workflow_service.controllers import workflow_controller from services.workflow_service.schemas.workflow import ( GetWorkflowConfigurationResponse, @@ -27,6 +28,7 @@ from utils.database.session_injector import get_database from utils.errors.error import handle_error from utils.security.token import User, get_user, get_user_from_token +from utils.security.resources import get_project router = APIRouter(prefix="/workflow", tags=["workflow"]) @@ -145,15 +147,11 @@ def update_workflow_configurations( @router.post("/{project_id}", status_code=200) def translate_project_to_dag( - project_id: UUID | None = None, - _: User = Depends(get_user), + project: Project = Depends(get_project) ): - if not project_id: - raise HTTPException(status_code=422, detail="Project ID missing") - try: - workflow_controller.validate_workflow(project_id) - dag_id = workflow_controller.translate_project_to_dag(project_id) + workflow_controller.validate_workflow(project.uuid) + dag_id = workflow_controller.translate_project_to_dag(project.uuid) # Make sure airflow has enough time to create the dag internally if not workflow_controller.wait_for_dag_registration(dag_id): logging.error(f"DAG {dag_id} was not registered in time.") diff --git a/core/utils/security/resources.py b/core/utils/security/resources.py new file mode 100644 index 00000000..9f9a5367 --- /dev/null +++ b/core/utils/security/resources.py @@ -0,0 +1,27 @@ +from fastapi import HTTPException, Depends, Path +from sqlalchemy.orm import Session +from uuid import UUID + +from utils.database.session_injector import get_database +from utils.security.token import User, get_user + +from services.workflow_service.models import ( + Project, +) + + +def get_project( + project_uuid: UUID = Path(...), + user: User = Depends(get_user), + db: Session = Depends(get_database) +): + project = db.query(Project).filter_by(uuid=project_uuid).one_or_none() + if not project: + raise HTTPException(404, f"Project {project_uuid} not found") + + print(project.users) + # authorize access + if user.uuid not in (project.users or []): + raise HTTPException(403, "Access to resource denied") + + return project diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts index 1b3be084..830fb594 100644 --- a/frontend/next-env.d.ts +++ b/frontend/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. From e783c05fe28d93b2b3ce35a81fea421fbb3db2d7 Mon Sep 17 00:00:00 2001 From: PaulKalho Date: Mon, 22 Dec 2025 12:20:32 -0600 Subject: [PATCH 2/4] fix: use project_uuid param --- core/services/workflow_service/views/workflow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/services/workflow_service/views/workflow.py b/core/services/workflow_service/views/workflow.py index e1c9b974..65da821e 100644 --- a/core/services/workflow_service/views/workflow.py +++ b/core/services/workflow_service/views/workflow.py @@ -145,13 +145,14 @@ def update_workflow_configurations( raise handle_error(e) -@router.post("/{project_id}", status_code=200) +@router.post("/{project_uuid}", status_code=200) def translate_project_to_dag( project: Project = Depends(get_project) ): try: workflow_controller.validate_workflow(project.uuid) - dag_id = workflow_controller.translate_project_to_dag(project.uuid) + dag_id = workflow_controller.translate_project_to_dag( + project, project.uuid) # Make sure airflow has enough time to create the dag internally if not workflow_controller.wait_for_dag_registration(dag_id): logging.error(f"DAG {dag_id} was not registered in time.") From 47f7a848b511d09277e24e3def6007b677d47e12 Mon Sep 17 00:00:00 2001 From: PaulKalho Date: Mon, 22 Dec 2025 15:07:05 -0600 Subject: [PATCH 3/4] feat: update workflow view to secure resources --- .../controllers/project_controller.py | 10 ++--- .../controllers/workflow_controller.py | 4 +- .../workflow_service/views/project.py | 13 ++++-- .../workflow_service/views/workflow.py | 42 +++++++------------ 4 files changed, 27 insertions(+), 42 deletions(-) diff --git a/core/services/workflow_service/controllers/project_controller.py b/core/services/workflow_service/controllers/project_controller.py index 9e20d990..e8e8f8ae 100644 --- a/core/services/workflow_service/controllers/project_controller.py +++ b/core/services/workflow_service/controllers/project_controller.py @@ -33,6 +33,7 @@ def create_project(db: Session, name: str, current_user_uuid: UUID) -> UUID: def create_project_from_template( + db: Session, name: str, template_identifier: str, current_user_uuid: UUID @@ -41,8 +42,6 @@ def create_project_from_template( This method will handle the creation of project, blocks and edges as defined in the template.yaml """ - db: Session = next(get_database()) - template: WorkflowTemplate =\ template_controller.get_workflow_template_by_identifier( template_identifier @@ -147,17 +146,14 @@ def delete_project(db: Session, project: Project) -> None: logging.debug("Project deleted successfully") -def read_all_projects() -> list[Project]: - db: Session = next(get_database()) - +def read_all_projects(db: Session) -> list[Project]: projects = db.query(Project).all() return projects -def read_projects_by_user_uuid(user_uuid: UUID) -> list[Project]: +def read_projects_by_user_uuid(db: Session, user_uuid: UUID) -> list[Project]: logging.debug(f"Fetching projects for user UUID: {user_uuid}") - db: Session = next(get_database()) projects = ( db.query(Project) diff --git a/core/services/workflow_service/controllers/workflow_controller.py b/core/services/workflow_service/controllers/workflow_controller.py index b084578d..10a45708 100644 --- a/core/services/workflow_service/controllers/workflow_controller.py +++ b/core/services/workflow_service/controllers/workflow_controller.py @@ -166,7 +166,7 @@ def _get_unconfigured_ios(ios: list[InputOutput]) -> list[InputOutput]: return result -def get_workflow_configurations(project_id: UUID) -> tuple[ +def get_workflow_configurations(db: Session, project_id: UUID) -> tuple[ list[WorkflowEnvsWithBlockInfo], list[InputOutput], # Workflow Inputs list[InputOutput], # Intermediates @@ -224,8 +224,6 @@ def get_workflow_configurations(project_id: UUID) -> tuple[ - List of InputOutput for workflow outputs - Dictionary mapping entrypoint UUIDs to Block instances """ - db: Session = next(get_database()) - # 1. Load blocks blocks = compute_block_controller.get_compute_blocks_by_project(project_id) block_by_entry_id = {b.selected_entrypoint_uuid: b for b in blocks} diff --git a/core/services/workflow_service/views/project.py b/core/services/workflow_service/views/project.py index b2e202e3..e83f4334 100644 --- a/core/services/workflow_service/views/project.py +++ b/core/services/workflow_service/views/project.py @@ -47,10 +47,12 @@ async def create_project( ) async def create_project_from_template( data: CreateProjectFromTemplateRequest, - user: User = Depends(get_user) + user: User = Depends(get_user), + db: Session = Depends(get_database) ): try: id = project_controller.create_project_from_template( + db, data.name, data.template_identifier, user.uuid, @@ -62,9 +64,11 @@ async def create_project_from_template( @router.get("/read_all", response_model=ReadAllResponse) -async def read_all_projects(): +async def read_all_projects( + db: Session = Depends(get_database) +): try: - projects = project_controller.read_all_projects() + projects = project_controller.read_all_projects(db) return ReadAllResponse(projects=projects) except Exception as e: logging.error(f"Error reading all projects: {e}") @@ -74,9 +78,10 @@ async def read_all_projects(): @router.get("/read_by_user", response_model=ReadByUserResponse) async def read_projects_by_user( user: User = Depends(get_user), + db: Session = Depends(get_database) ): try: - projects = project_controller.read_projects_by_user_uuid(user.uuid) + projects = project_controller.read_projects_by_user_uuid(db, user.uuid) return ReadByUserResponse(projects=projects) except Exception as e: logging.exception(f"Error reading project by user: {e}") diff --git a/core/services/workflow_service/views/workflow.py b/core/services/workflow_service/views/workflow.py index 65da821e..0d67e39c 100644 --- a/core/services/workflow_service/views/workflow.py +++ b/core/services/workflow_service/views/workflow.py @@ -1,6 +1,7 @@ import asyncio import logging from collections import defaultdict +from sqlalchemy.orm import Session from uuid import UUID from fastapi import ( @@ -27,29 +28,25 @@ ) from utils.database.session_injector import get_database from utils.errors.error import handle_error -from utils.security.token import User, get_user, get_user_from_token +from utils.security.token import User, get_user_from_token from utils.security.resources import get_project router = APIRouter(prefix="/workflow", tags=["workflow"]) @router.get( - "/configurations/{project_id}", + "/configurations/{project_uuid}", response_model=GetWorkflowConfigurationResponse, ) def get_workflow_configurations( - project_id: UUID | None = None, + project: Project = Depends(get_project), + db: Session = Depends(get_database) ): - if not project_id: - raise HTTPException( - status_code=422, - detail="Project ID missing", - ) - try: envs, inputs, inter, outputs, presigned, block_by_entry_id = ( workflow_controller.get_workflow_configurations( - project_id, + db, + project.uuid, ) ) @@ -94,26 +91,19 @@ def get_workflow_configurations( @router.put( - "/configurations/{project_id}", + "/configurations/{project_uuid}", status_code=200, ) def update_workflow_configurations( - project_id: UUID | None, data: UpdateWorkflowConfigurations, + project: Project = Depends(get_project), + db: Session = Depends(get_database) ): - if not project_id: - raise HTTPException( - status_code=422, - detail="Project ID missing", - ) - - db = next(get_database()) - try: with db.begin(): if data.project_name: project_controller.rename_project( - project_uuid=project_id, + project_uuid=project.uuid, new_name=data.project_name, db=db, ) @@ -165,15 +155,11 @@ def translate_project_to_dag( raise handle_error(e) -@router.post("/{project_id}/pause", status_code=200) +@router.post("/{project_uuid}/pause", status_code=200) def pause_dag( - project_id: UUID | None = None, - _: User = Depends(get_user), + project: Project = Depends(get_project) ): - if not project_id: - raise HTTPException(status_code=422, detail="Project ID missing") - - dag_id = f"dag_{str(project_id).replace("-", "_")}" + dag_id = f"dag_{str(project.uuid).replace("-", "_")}" try: workflow_controller.unpause_dag(dag_id, True) From 22dd2466d78ee819b42a84b1448b8f99c6786275 Mon Sep 17 00:00:00 2001 From: PaulKalho Date: Mon, 22 Dec 2025 17:41:39 -0600 Subject: [PATCH 4/4] wip: compute block controller --- .../controllers/workflow_controller.py | 1 - .../workflow_service/models/__init__.py | 2 +- .../workflow_service/schemas/compute_block.py | 1 - .../workflow_service/views/compute_block.py | 56 +++++++++---------- core/utils/security/resources.py | 53 +++++++++++++++++- 5 files changed, 80 insertions(+), 33 deletions(-) diff --git a/core/services/workflow_service/controllers/workflow_controller.py b/core/services/workflow_service/controllers/workflow_controller.py index 10a45708..ad7fc58e 100644 --- a/core/services/workflow_service/controllers/workflow_controller.py +++ b/core/services/workflow_service/controllers/workflow_controller.py @@ -50,7 +50,6 @@ ) from utils.config.environment import ENV from utils.data.file_handling import bulk_presigned_urls_from_ios -from utils.database.session_injector import get_database if TYPE_CHECKING: from uuid import UUID diff --git a/core/services/workflow_service/models/__init__.py b/core/services/workflow_service/models/__init__.py index c5eb9688..bd7bf5d6 100644 --- a/core/services/workflow_service/models/__init__.py +++ b/core/services/workflow_service/models/__init__.py @@ -1,6 +1,6 @@ from .block import Block from .entrypoint import Entrypoint -from .input_output import InputOutput +from .input_output import InputOutput, InputOutputType from .project import Project __all__ = [ diff --git a/core/services/workflow_service/schemas/compute_block.py b/core/services/workflow_service/schemas/compute_block.py index 122bd7df..a42150f3 100644 --- a/core/services/workflow_service/schemas/compute_block.py +++ b/core/services/workflow_service/schemas/compute_block.py @@ -355,7 +355,6 @@ def from_sdk_compute_block(cls, cb): class CreateComputeBlockRequest(BaseModel): - project_id: UUID cbc_url: str name: str custom_name: str diff --git a/core/services/workflow_service/views/compute_block.py b/core/services/workflow_service/views/compute_block.py index 9bbbf6c7..f5d677b6 100644 --- a/core/services/workflow_service/views/compute_block.py +++ b/core/services/workflow_service/views/compute_block.py @@ -32,7 +32,17 @@ update_block, update_ios_with_uploads ) +from services.workflow_service.models import ( + Project, + Entrypoint, + InputOutput +) from utils.security.token import User, get_user +from utils.security.resources import ( + get_project, + get_entrypoint, + get_ios_by_entrypoint_uuid +) router = APIRouter(prefix="/compute_block", tags=["compute_block"]) @@ -94,19 +104,15 @@ async def create( @router.get( - "/by_project/{project_id}", + "/by_project/{project_uuid}", response_model=GetNodesByProjectResponse, ) async def get_by_project( - project_id: UUID | None = None, - _: User = Depends(get_user), + project: Project = Depends(get_project), ): - if not project_id: - raise HTTPException(status_code=422, detail="Project ID is required.") - try: - compute_blocks = get_compute_blocks_by_project(project_id) - status = workflow_controller.dag_status(project_id) + compute_blocks = get_compute_blocks_by_project(project.uuid) + status = workflow_controller.dag_status(project.uuid) block_uuids = [block.uuid for block in compute_blocks] dependencies = get_block_dependencies_for_blocks(block_uuids) @@ -126,21 +132,15 @@ async def get_by_project( raise handle_error(e) -@router.get("/entrypoint/{entry_id}/envs/", response_model=ConfigType) +@router.get("/entrypoint/{entrypoint_uuid}/envs/", response_model=ConfigType) async def get_envs( - entry_id: UUID | None = None, - _: User = Depends(get_user), + entrypoint: Entrypoint = Depends(get_entrypoint) ): - if not entry_id: - raise HTTPException( - status_code=422, - detail="Entrypoint ID is required.", - ) - try: - return get_envs_for_entrypoint(entry_id) + return get_envs_for_entrypoint(entrypoint.uuid) except Exception as e: - logging.exception(f"Error getting envs of entrypoint {entry_id}: {e}") + logging.exception(f"Error getting envs of entrypoint { + entrypoint.uuid}: {e}") raise handle_error(e) @@ -169,19 +169,16 @@ async def update_compute_block( raise handle_error(e) -@router.get("/entrypoint/{entry_id}/io/", response_model=list[InputOutputDTO]) +@router.get( + "/entrypoint/{entrypoint_uuid}/io/", + response_model=list[InputOutputDTO] +) async def get_io( - entry_id: UUID, io_type: InputOutputType, - _: User = Depends(get_user), + entrypoint_uuid: UUID, + ios: list[InputOutput] = Depends(get_ios_by_entrypoint_uuid) ): - if not entry_id: - raise HTTPException( - status_code=422, - detail="Entrypoint ID is required.", - ) try: - ios = get_io_for_entrypoint(entry_id, io_type) presigned_urls = bulk_presigned_urls_from_ios(ios) return [InputOutputDTO.from_input_output( io.name, @@ -190,7 +187,8 @@ async def get_io( ] except Exception as e: logging.exception( - f"Error getting {io_type.value}s of entrypoint {entry_id}: {e}", + f"Error getting {io_type.value}s of entrypoint { + entrypoint_uuid}: {e}", ) raise handle_error(e) diff --git a/core/utils/security/resources.py b/core/utils/security/resources.py index 9f9a5367..af3c02d5 100644 --- a/core/utils/security/resources.py +++ b/core/utils/security/resources.py @@ -1,3 +1,4 @@ +from sqlalchemy import any_ from fastapi import HTTPException, Depends, Path from sqlalchemy.orm import Session from uuid import UUID @@ -7,6 +8,10 @@ from services.workflow_service.models import ( Project, + Entrypoint, + Block, + InputOutput, + InputOutputType, ) @@ -19,9 +24,55 @@ def get_project( if not project: raise HTTPException(404, f"Project {project_uuid} not found") - print(project.users) # authorize access if user.uuid not in (project.users or []): raise HTTPException(403, "Access to resource denied") return project + + +def get_entrypoint( + entrypoint_uuid: UUID = Path(...), + user: User = Depends(get_user), + db: Session = Depends(get_database) +): + # Query Entrypoint while joining Block and Project + entrypoint = ( + db.query(Entrypoint) + .join(Block, Block.selected_entrypoint_uuid == Entrypoint.uuid) + .join(Project, Project.uuid == Block.project_uuid) + .filter( + Entrypoint.uuid == entrypoint_uuid, + user.uuid == any_(Project.users) + ) + .one_or_none() + ) + + if not entrypoint: + raise HTTPException(404, f"Entrypoint {entrypoint_uuid} not found") + + return entrypoint + + +def get_ios_by_entrypoint_uuid( + io_type: InputOutputType, + entrypoint_uuid: UUID = Path(...), + user: User = Depends(get_user), + db: Session = Depends(get_database), +): + ios = ( + db.query(InputOutput) + .join(Entrypoint, InputOutput.entrypoint_uuid == Entrypoint.uuid) + .join(Block, Block.selected_entrypoint_uuid == Entrypoint.uuid) + .join(Project, Project.uuid == Block.project_uuid) + .filter( + InputOutput.entrypoint_uuid == entrypoint_uuid, + user.uuid == any_(Project.users) + ) + .all() + ) + + if not ios: + return [] + + return ios