@@ -16,6 +16,9 @@ use crate::database::Database;
1616use crate :: error:: AppError ;
1717use crate :: fraud_service:: FraudDetectionService ;
1818use crate :: health:: HealthState ;
19+ use crate :: job_queue:: types:: { Job , JobPriority , JobType } ;
20+ use crate :: job_queue:: worker:: JobWorker ;
21+ use crate :: job_queue:: JobQueue ;
1922use crate :: webhook_service:: WebhookService ;
2023use crate :: monitoring_service:: { MonitoringService , dashboard} ;
2124use crate :: models:: {
@@ -61,6 +64,9 @@ pub async fn api_index() -> Json<serde_json::Value> {
6164 "cache_stats" : "GET /cache/stats" ,
6265 "cache_invalidate" : "POST /cache/invalidate" ,
6366 "cache_warm" : "POST /cache/warm" ,
67+ "jobs_stats" : "GET /jobs/stats" ,
68+ "jobs_enqueue" : "POST /jobs/enqueue" ,
69+ "jobs_schedule" : "POST /jobs/schedule" ,
6470 "fraud_review" : "POST /fraud/review" ,
6571 "notif_prefs_get" : "GET /notifications/preferences/:address" ,
6672 "notif_prefs_put" : "PUT /notifications/preferences/:address" ,
@@ -425,6 +431,8 @@ pub async fn update_fraud_review(
425431pub struct AppState {
426432 pub database : Arc < Database > ,
427433 pub stellar_contract_id : String ,
434+ pub job_queue : Arc < tokio:: sync:: Mutex < JobQueue > > ,
435+ pub job_worker : Arc < JobWorker > ,
428436 pub ws_manager : Arc < WebSocketManager > ,
429437 pub health : HealthState ,
430438 pub fraud_service : Arc < FraudDetectionService > ,
@@ -645,6 +653,86 @@ pub struct IntegrationLogQuery {
645653 pub limit : Option < i64 > ,
646654}
647655
656+ #[ derive( Deserialize ) ]
657+ pub struct EnqueueJobRequest {
658+ pub job_type : String ,
659+ pub event_id : Option < String > ,
660+ pub trade_id : Option < String > ,
661+ pub priority : Option < String > ,
662+ pub payload : Option < serde_json:: Value > ,
663+ pub run_at : Option < i64 > ,
664+ pub max_attempts : Option < u32 > ,
665+ }
666+
667+ /// GET /jobs/stats — job queue depths and worker status.
668+ pub async fn get_job_stats (
669+ State ( state) : State < AppState > ,
670+ ) -> Result < Json < serde_json:: Value > , AppError > {
671+ let snapshot = state. job_worker . snapshot ( ) . await ?;
672+ Ok ( Json ( serde_json:: to_value ( & snapshot) . unwrap_or_default ( ) ) )
673+ }
674+
675+ /// POST /jobs/enqueue — enqueue an immediate background job.
676+ pub async fn enqueue_job (
677+ State ( state) : State < AppState > ,
678+ Json ( body) : Json < EnqueueJobRequest > ,
679+ ) -> Result < Json < serde_json:: Value > , AppError > {
680+ let job_type = parse_job_type ( & body. job_type ) ?;
681+ let priority = parse_job_priority ( body. priority . as_deref ( ) ) ?;
682+ let mut job = Job :: new (
683+ job_type,
684+ body. event_id . unwrap_or_else ( || "manual" . to_string ( ) ) ,
685+ body. trade_id . unwrap_or_else ( || "manual" . to_string ( ) ) ,
686+ body. payload . unwrap_or_default ( ) ,
687+ priority,
688+ ) ;
689+ if let Some ( max_attempts) = body. max_attempts {
690+ job = job. with_max_attempts ( max_attempts. max ( 1 ) ) ;
691+ }
692+
693+ let mut queue = state. job_queue . lock ( ) . await ;
694+ queue. enqueue ( job. clone ( ) ) . await ?;
695+
696+ Ok ( Json ( json ! ( {
697+ "status" : "queued" ,
698+ "job_id" : job. id,
699+ "priority" : job. priority. as_str( ) ,
700+ } ) ) )
701+ }
702+
703+ /// POST /jobs/schedule — enqueue a delayed background job.
704+ pub async fn schedule_job (
705+ State ( state) : State < AppState > ,
706+ Json ( body) : Json < EnqueueJobRequest > ,
707+ ) -> Result < Json < serde_json:: Value > , AppError > {
708+ let run_at = body
709+ . run_at
710+ . ok_or_else ( || AppError :: BadRequest ( "run_at is required" . to_string ( ) ) ) ?;
711+ let job_type = parse_job_type ( & body. job_type ) ?;
712+ let priority = parse_job_priority ( body. priority . as_deref ( ) ) ?;
713+ let mut job = Job :: new (
714+ job_type,
715+ body. event_id . unwrap_or_else ( || "scheduled" . to_string ( ) ) ,
716+ body. trade_id . unwrap_or_else ( || "scheduled" . to_string ( ) ) ,
717+ body. payload . unwrap_or_default ( ) ,
718+ priority,
719+ )
720+ . scheduled_at ( run_at) ;
721+ if let Some ( max_attempts) = body. max_attempts {
722+ job = job. with_max_attempts ( max_attempts. max ( 1 ) ) ;
723+ }
724+
725+ let mut queue = state. job_queue . lock ( ) . await ;
726+ queue. enqueue_at ( job. clone ( ) , run_at) . await ?;
727+
728+ Ok ( Json ( json ! ( {
729+ "status" : "scheduled" ,
730+ "job_id" : job. id,
731+ "run_at" : run_at,
732+ "priority" : job. priority. as_str( ) ,
733+ } ) ) )
734+ }
735+
648736// =============================================================================
649737// Performance Monitoring Handlers
650738// =============================================================================
@@ -993,3 +1081,23 @@ pub async fn get_prometheus_metrics(
9931081 . body ( body)
9941082 . unwrap ( )
9951083}
1084+
1085+ fn parse_job_type ( value : & str ) -> Result < JobType , AppError > {
1086+ match value {
1087+ "event" => Ok ( JobType :: Event ) ,
1088+ "notification" => Ok ( JobType :: Notification ) ,
1089+ "cache_warm" => Ok ( JobType :: CacheWarm ) ,
1090+ "compliance" => Ok ( JobType :: Compliance ) ,
1091+ _ => Err ( AppError :: BadRequest ( format ! ( "unsupported job_type '{}'" , value) ) ) ,
1092+ }
1093+ }
1094+
1095+ fn parse_job_priority ( value : Option < & str > ) -> Result < JobPriority , AppError > {
1096+ match value. unwrap_or ( "normal" ) {
1097+ "critical" => Ok ( JobPriority :: Critical ) ,
1098+ "high" => Ok ( JobPriority :: High ) ,
1099+ "normal" => Ok ( JobPriority :: Normal ) ,
1100+ "low" => Ok ( JobPriority :: Low ) ,
1101+ other => Err ( AppError :: BadRequest ( format ! ( "unsupported priority '{}'" , other) ) ) ,
1102+ }
1103+ }
0 commit comments