@@ -583,6 +583,7 @@ async def build_public_tmp(
583583 background_tasks : LimitVertexBuildBackgroundTasks ,
584584 flow_id : uuid .UUID ,
585585 inputs : Annotated [InputValueRequest | None , Body (embed = True )] = None ,
586+ data : Annotated [FlowDataRequest | None , Body (embed = True )] = None ,
586587 files : list [str ] | None = None ,
587588 stop_component_id : str | None = None ,
588589 start_component_id : str | None = None ,
@@ -597,16 +598,10 @@ async def build_public_tmp(
597598 This endpoint is specifically for public flows that don't require authentication.
598599 It uses a client_id cookie to create a deterministic flow ID for tracking purposes.
599600
600- Security Note:
601- - The 'data' parameter is NOT accepted to prevent flow definition tampering
602- - Public flows must execute the stored flow definition only
603- - The flow definition is always loaded from the database
604-
605601 The endpoint:
606602 1. Verifies the requested flow is marked as public in the database
607603 2. Creates a deterministic UUID based on client_id and flow_id
608604 3. Uses the flow owner's permissions to build the flow
609- 4. Always loads the flow definition from the database
610605
611606 Requirements:
612607 - The flow must be marked as PUBLIC in the database
@@ -616,6 +611,7 @@ async def build_public_tmp(
616611 flow_id: UUID of the public flow to build
617612 background_tasks: Background tasks manager
618613 inputs: Optional input values for the flow
614+ data: Optional flow data
619615 files: Optional files to include
620616 stop_component_id: Optional ID of component to stop at
621617 start_component_id: Optional ID of component to start from
@@ -633,13 +629,14 @@ async def build_public_tmp(
633629 client_id = request .cookies .get ("client_id" )
634630 owner_user , new_flow_id = await verify_public_flow_and_get_user (flow_id = flow_id , client_id = client_id )
635631
636- # Start the flow build using the new flow ID
637- # data is always None for public flows - they load from database only
632+ # flow_id=new_flow_id for tracking/sessions/messages (virtual, per-user isolation).
633+ # source_flow_id=flow_id to load the actual flow data from the database.
638634 job_id = await start_flow_build (
639635 flow_id = new_flow_id ,
636+ source_flow_id = flow_id ,
640637 background_tasks = background_tasks ,
641638 inputs = inputs ,
642- data = None , # Always None - public flows load from database only
639+ data = data ,
643640 files = files ,
644641 stop_component_id = stop_component_id ,
645642 start_component_id = start_component_id ,
@@ -660,3 +657,54 @@ async def build_public_tmp(
660657 queue_service = queue_service ,
661658 event_delivery = event_delivery ,
662659 )
660+
661+
662+ @router .get ("/build_public_tmp/{job_id}/events" )
663+ async def get_build_events_public (
664+ job_id : str ,
665+ queue_service : Annotated [JobQueueService , Depends (get_queue_service )],
666+ * ,
667+ event_delivery : EventDeliveryType = EventDeliveryType .STREAMING ,
668+ ):
669+ """Get events for a public flow build job.
670+
671+ This endpoint does not require authentication, matching the public build endpoint.
672+ It is used by the shareable playground to consume build events.
673+ """
674+ return await get_flow_events_response (
675+ job_id = job_id ,
676+ queue_service = queue_service ,
677+ event_delivery = event_delivery ,
678+ )
679+
680+
681+ @router .post (
682+ "/build_public_tmp/{job_id}/cancel" ,
683+ response_model = CancelFlowResponse ,
684+ )
685+ async def cancel_build_public (
686+ job_id : str ,
687+ queue_service : Annotated [JobQueueService , Depends (get_queue_service )],
688+ ):
689+ """Cancel a public flow build job.
690+
691+ This endpoint does not require authentication, matching the public build endpoint.
692+ It is used by the shareable playground to cancel builds.
693+ """
694+ try :
695+ cancellation_success = await cancel_flow_build (job_id = job_id , queue_service = queue_service )
696+
697+ if cancellation_success :
698+ return CancelFlowResponse (success = True , message = "Flow build cancelled successfully" )
699+ return CancelFlowResponse (success = False , message = "Failed to cancel flow build" )
700+ except asyncio .CancelledError :
701+ await logger .aerror (f"Failed to cancel public flow build for job_id { job_id } (CancelledError caught)" )
702+ return CancelFlowResponse (success = False , message = "Failed to cancel flow build" )
703+ except ValueError as exc :
704+ raise HTTPException (status_code = status .HTTP_404_NOT_FOUND , detail = str (exc )) from exc
705+ except JobQueueNotFoundError as exc :
706+ await logger .aerror (f"Public job not found: { job_id } . Error: { exc !s} " )
707+ raise HTTPException (status_code = status .HTTP_404_NOT_FOUND , detail = f"Job not found: { exc !s} " ) from exc
708+ except Exception as exc :
709+ await logger .aexception (f"Error cancelling public flow build for job_id { job_id } : { exc } " )
710+ raise HTTPException (status_code = status .HTTP_500_INTERNAL_SERVER_ERROR , detail = str (exc )) from exc
0 commit comments