@@ -16,9 +16,15 @@ use axum::{
1616 Json ,
1717 extract:: { Path , State } ,
1818 http:: StatusCode ,
19- response:: IntoResponse ,
19+ response:: {
20+ IntoResponse , Sse ,
21+ sse:: { Event , KeepAlive } ,
22+ } ,
2023} ;
21- use std:: sync:: Arc ;
24+ use futures:: { Stream , StreamExt } ;
25+ use std:: { convert:: Infallible , sync:: Arc } ;
26+ use tokio_stream:: wrappers:: ReceiverStream ;
27+ use tracing:: { error, info} ;
2228
2329use crate :: {
2430 context:: Context ,
@@ -193,3 +199,59 @@ pub async fn complete_stage(
193199 let res = StageService :: complete ( ctx, & claims. id , & slug, & req. slug ) . await ?;
194200 Ok ( ( StatusCode :: OK , Json ( res) ) )
195201}
202+
203+ /// Stream the status of a specific stage for the current user.
204+ #[ utoipa:: path(
205+ operation_id = "stream_user_stage_status" ,
206+ get, path = "/v1/user/courses/{slug}/stages/{stage_slug}/status" ,
207+ params(
208+ ( "slug" = String , description = "The slug of course" ) ,
209+ ( "stage_slug" = String , description = "The slug of stage" ) ,
210+ ) ,
211+ responses(
212+ ( status = 200 , description = "Successfully started streaming stage status updates" ) ,
213+ ( status = 404 , description = "Course or stage not found" ) ,
214+ ( status = 500 , description = "Failed to stream stage status" )
215+ ) ,
216+ security( ( "JWTBearerAuth" = [ ] ) ) ,
217+ tags = [ "User" , "Stage" ]
218+ ) ]
219+ pub async fn stream_user_stage_status (
220+ claims : Claims ,
221+ State ( ctx) : State < Arc < Context > > ,
222+ Path ( ( slug, stage_slug) ) : Path < ( String , String ) > ,
223+ ) -> Sse < impl Stream < Item = axum:: response:: Result < Event , Infallible > > > {
224+ info ! (
225+ "Starting to stream status updates for stage {} in course {} for user {}..." ,
226+ stage_slug, slug, claims. id
227+ ) ;
228+
229+ // Create a channel for sending status updates.
230+ let ( sender, receiver) = tokio:: sync:: mpsc:: channel ( 100 ) ;
231+
232+ // Spawn a background task to fetch and send status updates.
233+ tokio:: spawn ( async move {
234+ let mut interval = tokio:: time:: interval ( std:: time:: Duration :: from_secs ( 5 ) ) ;
235+ loop {
236+ interval. tick ( ) . await ;
237+ let status =
238+ StageService :: get_user_stage_status ( & ctx, & claims. id , & slug, & stage_slug) . await ;
239+ if let Ok ( status) = status {
240+ let event = Event :: default ( ) . json_data ( status) . unwrap_or_else ( |e| {
241+ error ! ( "Failed to serialize status update: {}" , e) ;
242+ Event :: default ( ) . data ( "status update error" )
243+ } ) ;
244+ if sender. send ( event) . await . is_err ( ) {
245+ break ;
246+ }
247+ }
248+ }
249+ } ) ;
250+
251+ // Convert the receiver into a stream.
252+ let stream = ReceiverStream :: new ( receiver) ;
253+ let stream = stream. map ( Ok ) ;
254+
255+ // Return the SSE stream with keep-alive.
256+ Sse :: new ( stream) . keep_alive ( KeepAlive :: default ( ) )
257+ }
0 commit comments