@@ -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} ;
@@ -52,18 +53,25 @@ impl GatewayServer {
5253 HttpServer :: new ( move || {
5354 App :: new ( )
5455 . app_data ( Data :: new ( state. clone ( ) ) )
56+ . route ( "/" , web:: get ( ) . to ( Self :: get_root) )
5557 . route ( "/nonce/{address}" , web:: get ( ) . to ( Self :: get_nonce) )
5658 . route ( "/receipts" , web:: get ( ) . to ( Self :: get_receipts) )
5759 . route ( "/proof/sp1" , web:: post ( ) . to ( Self :: post_proof_sp1) )
5860 . route ( "/proof/risc0" , web:: post ( ) . to ( Self :: post_proof_risc0) )
61+ . route ( "/quotas/{address}" , web:: get ( ) . to ( Self :: get_quotas) )
5962 } )
60- . bind ( ( "127.0.0.1" , port) )
63+ . bind ( ( self . config . ip . as_str ( ) , port) )
6164 . expect ( "To bind socket correctly" )
6265 . run ( )
6366 . await
6467 . expect ( "Server to never end" ) ;
6568 }
6669
70+ // Returns an OK response (code 200), no matters what receives in the request
71+ async fn get_root ( _req : HttpRequest ) -> impl Responder {
72+ HttpResponse :: Ok ( ) . json ( AppResponse :: new_sucessfull ( serde_json:: json!( { } ) ) )
73+ }
74+
6775 // Returns the nonce (number of submitted tasks) for a given address
6876 async fn get_nonce ( req : HttpRequest ) -> impl Responder {
6977 let Some ( address_raw) = req. match_info ( ) . get ( "address" ) else {
@@ -147,8 +155,13 @@ impl GatewayServer {
147155 } ;
148156
149157 if daily_tasks_by_address >= state. config . max_daily_proofs_per_user {
158+ let formatted_time_left = get_time_left_day_formatted ( ) ;
159+
150160 return HttpResponse :: InternalServerError ( ) . json ( AppResponse :: new_unsucessfull (
151- "Request denied: Query limit exceeded." ,
161+ format ! (
162+ "Request denied: Query limit exceeded. Quotas renew in {formatted_time_left}"
163+ )
164+ . as_str ( ) ,
152165 400 ,
153166 ) ) ;
154167 }
@@ -322,4 +335,73 @@ impl GatewayServer {
322335 . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ,
323336 }
324337 }
338+
339+ async fn get_quotas ( req : HttpRequest ) -> impl Responder {
340+ let Some ( state) = req. app_data :: < Data < GatewayServer > > ( ) else {
341+ return HttpResponse :: InternalServerError ( ) . json ( AppResponse :: new_unsucessfull (
342+ "Internal server error: Failed to get app data" ,
343+ 500 ,
344+ ) ) ;
345+ } ;
346+
347+ let state = state. get_ref ( ) ;
348+
349+ let Some ( address_raw) = req. match_info ( ) . get ( "address" ) else {
350+ return HttpResponse :: BadRequest ( )
351+ . json ( AppResponse :: new_unsucessfull ( "Missing address" , 400 ) ) ;
352+ } ;
353+
354+ // Check that the address is a valid ethereum address
355+ if alloy:: primitives:: Address :: from_str ( address_raw. trim ( ) ) . is_err ( ) {
356+ return HttpResponse :: BadRequest ( )
357+ . json ( AppResponse :: new_unsucessfull ( "Invalid address" , 400 ) ) ;
358+ }
359+
360+ let address = address_raw. trim ( ) . to_lowercase ( ) ;
361+
362+ let Ok ( daily_tasks_by_address) = state. db . get_daily_tasks_by_address ( & address) . await else {
363+ return HttpResponse :: InternalServerError ( )
364+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
365+ } ;
366+
367+ let formatted_time_left = get_time_left_day_formatted ( ) ;
368+
369+ let now_epoch = match SystemTime :: now ( ) . duration_since ( UNIX_EPOCH ) {
370+ Ok ( duration) => duration. as_secs ( ) ,
371+ Err ( _) => {
372+ return HttpResponse :: InternalServerError ( )
373+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
374+ }
375+ } ;
376+
377+ let has_payment = match state
378+ . db
379+ . has_active_payment_event (
380+ & address,
381+ // safe unwrap the number comes from a valid u64 primitive
382+ BigDecimal :: from_str ( & now_epoch. to_string ( ) ) . unwrap ( ) ,
383+ )
384+ . await
385+ {
386+ Ok ( result) => result,
387+ Err ( _) => {
388+ return HttpResponse :: InternalServerError ( )
389+ . json ( AppResponse :: new_unsucessfull ( "Internal server error" , 500 ) ) ;
390+ }
391+ } ;
392+
393+ if has_payment {
394+ HttpResponse :: Ok ( ) . json ( AppResponse :: new_sucessfull ( serde_json:: json!( {
395+ "proofs_submitted" : daily_tasks_by_address,
396+ "quota_limit" : state. config. max_daily_proofs_per_user,
397+ "quota_remaining" : ( state. config. max_daily_proofs_per_user - daily_tasks_by_address) ,
398+ "quota_resets_in" : formatted_time_left. as_str( )
399+ } ) ) )
400+ } else {
401+ HttpResponse :: Ok ( ) . json ( AppResponse :: new_unsucessfull (
402+ "The address doesn't have an active subscription" ,
403+ 404 ,
404+ ) )
405+ }
406+ }
325407}
0 commit comments