@@ -22,6 +22,7 @@ use super::{
2222use crate :: {
2323 config:: Config ,
2424 db:: Db ,
25+ helpers:: get_time_left_day_formatted,
2526 types:: { GetReceiptsResponse , SubmitProofRequestRisc0 , SubmitProofRequestSP1 } ,
2627 verifiers:: { verify_sp1_proof, VerificationError } ,
2728} ;
@@ -56,6 +57,7 @@ impl GatewayServer {
5657 . route ( "/receipts" , web:: get ( ) . to ( Self :: get_receipts) )
5758 . route ( "/proof/sp1" , web:: post ( ) . to ( Self :: post_proof_sp1) )
5859 . route ( "/proof/risc0" , web:: post ( ) . to ( Self :: post_proof_risc0) )
60+ . route ( "/quotas/{address}" , web:: get ( ) . to ( Self :: get_quotas) )
5961 } )
6062 . bind ( ( "127.0.0.1" , port) )
6163 . expect ( "To bind socket correctly" )
@@ -147,8 +149,13 @@ impl GatewayServer {
147149 } ;
148150
149151 if daily_tasks_by_address >= state. config . max_daily_proofs_per_user {
152+ let formatted_time_left = get_time_left_day_formatted ( ) ;
153+
150154 return HttpResponse :: InternalServerError ( ) . json ( AppResponse :: new_unsucessfull (
151- "Request denied: Query limit exceeded." ,
155+ format ! (
156+ "Request denied: Query limit exceeded. Quotas renew in {formatted_time_left}"
157+ )
158+ . as_str ( ) ,
152159 400 ,
153160 ) ) ;
154161 }
@@ -322,4 +329,73 @@ impl GatewayServer {
322329 . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ,
323330 }
324331 }
332+
333+ async fn get_quotas ( req : HttpRequest ) -> impl Responder {
334+ let Some ( state) = req. app_data :: < Data < GatewayServer > > ( ) else {
335+ return HttpResponse :: InternalServerError ( ) . json ( AppResponse :: new_unsucessfull (
336+ "Internal server error: Failed to get app data" ,
337+ 500 ,
338+ ) ) ;
339+ } ;
340+
341+ let state = state. get_ref ( ) ;
342+
343+ let Some ( address_raw) = req. match_info ( ) . get ( "address" ) else {
344+ return HttpResponse :: BadRequest ( )
345+ . json ( AppResponse :: new_unsucessfull ( "Missing address" , 400 ) ) ;
346+ } ;
347+
348+ // Check that the address is a valid ethereum address
349+ if alloy:: primitives:: Address :: from_str ( address_raw. trim ( ) ) . is_err ( ) {
350+ return HttpResponse :: BadRequest ( )
351+ . json ( AppResponse :: new_unsucessfull ( "Invalid address" , 400 ) ) ;
352+ }
353+
354+ let address = address_raw. trim ( ) . to_lowercase ( ) ;
355+
356+ let Ok ( daily_tasks_by_address) = state. db . get_daily_tasks_by_address ( & address) . await else {
357+ return HttpResponse :: InternalServerError ( )
358+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
359+ } ;
360+
361+ let formatted_time_left = get_time_left_day_formatted ( ) ;
362+
363+ let now_epoch = match SystemTime :: now ( ) . duration_since ( UNIX_EPOCH ) {
364+ Ok ( duration) => duration. as_secs ( ) ,
365+ Err ( _) => {
366+ return HttpResponse :: InternalServerError ( )
367+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
368+ }
369+ } ;
370+
371+ let has_payment = match state
372+ . db
373+ . has_active_payment_event (
374+ & address,
375+ // safe unwrap the number comes from a valid u64 primitive
376+ BigDecimal :: from_str ( & now_epoch. to_string ( ) ) . unwrap ( ) ,
377+ )
378+ . await
379+ {
380+ Ok ( result) => result,
381+ Err ( _) => {
382+ return HttpResponse :: InternalServerError ( )
383+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
384+ }
385+ } ;
386+
387+ if has_payment {
388+ HttpResponse :: Ok ( ) . json ( AppResponse :: new_sucessfull ( serde_json:: json!( {
389+ "proofs_submitted" : daily_tasks_by_address,
390+ "quota_limit" : state. config. max_daily_proofs_per_user,
391+ "quota_remaining" : ( state. config. max_daily_proofs_per_user - daily_tasks_by_address) ,
392+ "quota_resets_in" : formatted_time_left. as_str( )
393+ } ) ) )
394+ } else {
395+ HttpResponse :: Ok ( ) . json ( AppResponse :: new_unsucessfull (
396+ "The address doesn't have an active subscription" ,
397+ 404 ,
398+ ) )
399+ }
400+ }
325401}
0 commit comments