1- use std:: { collections:: HashMap , fmt:: Write , sync:: LazyLock } ;
1+ use std:: { collections:: HashMap , fmt:: Write , sync:: LazyLock , time :: Instant } ;
22
33use actix_web:: { HttpRequest , HttpResponse , get, post, put, web} ;
44use chrono:: { DateTime , Utc } ;
5+ use reqwest:: header:: { HeaderMap , HeaderValue , USER_AGENT } ;
56use serde:: Deserialize ;
67use sqlx:: PgPool ;
8+ use tokio:: sync:: Mutex ;
79use tracing:: info;
810
911use crate :: {
@@ -15,9 +17,8 @@ use crate::{
1517 delphi_report_item:: {
1618 DBDelphiReport , DBDelphiReportIssue ,
1719 DBDelphiReportIssueJavaClass , DecompiledJavaClassSource ,
18- DelphiReportIssueStatus , DelphiReportIssueType ,
19- DelphiReportListOrder , DelphiReportSeverity ,
20- InternalJavaClassName ,
20+ DelphiReportIssueStatus , DelphiReportListOrder ,
21+ DelphiReportSeverity , InternalJavaClassName ,
2122 } ,
2223 } ,
2324 redis:: RedisPool ,
@@ -38,10 +39,26 @@ pub fn config(cfg: &mut web::ServiceConfig) {
3839 . service ( _run)
3940 . service ( version)
4041 . service ( issues)
41- . service ( update_issue) ,
42+ . service ( update_issue)
43+ . service ( issue_type_schema) ,
4244 ) ;
4345}
4446
47+ static DELPHI_CLIENT : LazyLock < reqwest:: Client > = LazyLock :: new ( || {
48+ reqwest:: Client :: builder ( )
49+ . default_headers ( {
50+ HeaderMap :: from_iter ( [ (
51+ USER_AGENT ,
52+ HeaderValue :: from_static ( concat ! (
53+ "Labrinth/" ,
54+ env!( "COMPILATION_DATE" )
55+ ) ) ,
56+ ) ] )
57+ } )
58+ . build ( )
59+ . unwrap ( )
60+ } ) ;
61+
4562#[ derive( Deserialize ) ]
4663struct DelphiReport {
4764 pub url : String ,
@@ -53,7 +70,7 @@ struct DelphiReport {
5370 /// Delphi version that generated this report.
5471 pub delphi_version : i32 ,
5572 pub issues : HashMap <
56- DelphiReportIssueType ,
73+ String ,
5774 HashMap < InternalJavaClassName , Option < DecompiledJavaClassSource > > ,
5875 > ,
5976 pub severity : DelphiReportSeverity ,
@@ -169,9 +186,6 @@ pub async fn run(
169186 . fetch_one ( exec)
170187 . await ?;
171188
172- static DELPHI_CLIENT : LazyLock < reqwest:: Client > =
173- LazyLock :: new ( reqwest:: Client :: new) ;
174-
175189 tracing:: debug!(
176190 "Running Delphi for project {}, version {}, file {}" ,
177191 file_data. project_id. 0 ,
@@ -241,7 +255,7 @@ async fn version(
241255#[ derive( Deserialize ) ]
242256struct DelphiIssuesSearchOptions {
243257 #[ serde( rename = "type" ) ]
244- ty : Option < DelphiReportIssueType > ,
258+ ty : Option < String > ,
245259 status : Option < DelphiReportIssueStatus > ,
246260 order_by : Option < DelphiReportListOrder > ,
247261 count : Option < u16 > ,
@@ -254,7 +268,7 @@ async fn issues(
254268 pool : web:: Data < PgPool > ,
255269 redis : web:: Data < RedisPool > ,
256270 session_queue : web:: Data < AuthQueue > ,
257- search_options : web:: Query < DelphiIssuesSearchOptions > ,
271+ web :: Query ( search_options) : web:: Query < DelphiIssuesSearchOptions > ,
258272) -> Result < HttpResponse , ApiError > {
259273 check_is_moderator_from_headers (
260274 & req,
@@ -324,3 +338,50 @@ async fn update_issue(
324338 Ok ( HttpResponse :: Created ( ) . finish ( ) )
325339 }
326340}
341+
342+ #[ get( "issue_type/schema" ) ]
343+ async fn issue_type_schema (
344+ req : HttpRequest ,
345+ pool : web:: Data < PgPool > ,
346+ redis : web:: Data < RedisPool > ,
347+ session_queue : web:: Data < AuthQueue > ,
348+ ) -> Result < HttpResponse , ApiError > {
349+ check_is_moderator_from_headers (
350+ & req,
351+ & * * pool,
352+ & redis,
353+ & session_queue,
354+ Scopes :: PROJECT_READ ,
355+ )
356+ . await ?;
357+
358+ // This route is expected to be called often by the frontend, and Delphi is not necessarily
359+ // built to scale beyond malware analysis, so cache the result of its quasi-constant-valued
360+ // schema route to alleviate the load on it
361+
362+ static CACHED_ISSUE_TYPE_SCHEMA : Mutex <
363+ Option < ( serde_json:: Map < String , serde_json:: Value > , Instant ) > ,
364+ > = Mutex :: const_new ( None ) ;
365+
366+ match & mut * CACHED_ISSUE_TYPE_SCHEMA . lock ( ) . await {
367+ Some ( ( schema, last_fetch) ) if last_fetch. elapsed ( ) . as_secs ( ) < 60 => {
368+ Ok ( HttpResponse :: Ok ( ) . json ( schema) )
369+ }
370+ cache_entry => Ok ( HttpResponse :: Ok ( ) . json (
371+ & cache_entry
372+ . insert ( (
373+ DELPHI_CLIENT
374+ . get ( format ! ( "{}/schema" , dotenvy:: var( "DELPHI_URL" ) ?) )
375+ . send ( )
376+ . await
377+ . and_then ( |res| res. error_for_status ( ) )
378+ . map_err ( ApiError :: Delphi ) ?
379+ . json :: < serde_json:: Map < String , serde_json:: Value > > ( )
380+ . await
381+ . map_err ( ApiError :: Delphi ) ?,
382+ Instant :: now ( ) ,
383+ ) )
384+ . 0 ,
385+ ) ) ,
386+ }
387+ }
0 commit comments