2023-01-27 14:18:33 +01:00
#![ feature(option_result_contains, once_cell) ]
use actix_web ::{ get , http ::header , web , App , Either , HttpResponse , HttpServer } ;
use data ::{ Card , CardInfo } ;
2023-01-26 23:07:16 +01:00
use filter ::SearchCard ;
2023-01-27 15:48:07 +01:00
use itertools ::Itertools ;
2023-01-27 14:18:33 +01:00
use serde ::Deserialize ;
use std ::{ collections ::HashMap , fmt ::Write , fs ::File , io ::BufReader , net ::Ipv4Addr , sync ::LazyLock , time ::Instant } ;
2023-01-26 23:07:16 +01:00
mod data ;
mod filter ;
mod parser ;
2023-01-26 15:04:39 +01:00
2023-01-27 14:45:17 +01:00
const RESULT_LIMIT : usize = 100 ;
2023-01-27 14:18:33 +01:00
static CARDS : LazyLock < Vec < Card > > = LazyLock ::new ( | | {
serde_json ::from_reader ::< _ , CardInfo > ( BufReader ::new ( File ::open ( " cards.json " ) . expect ( " cards.json not found " ) ) )
. expect ( " Could not deserialize cards " )
. data
} ) ;
static CARDS_BY_ID : LazyLock < HashMap < usize , Card > > =
LazyLock ::new ( | | CARDS . iter ( ) . map ( | c | ( c . id , Card { text : c . text . replace ( " \r " , " " ) . replace ( '\n' , " <br/> " ) , .. c . clone ( ) } ) ) . collect ( ) ) ;
static SEARCH_CARDS : LazyLock < Vec < SearchCard > > = LazyLock ::new ( | | CARDS . iter ( ) . map ( SearchCard ::from ) . collect ( ) ) ;
#[ actix_web::main ]
async fn main ( ) -> std ::io ::Result < ( ) > {
2023-01-27 00:03:00 +01:00
let now = Instant ::now ( ) ;
2023-01-27 14:18:33 +01:00
println! ( " Starting server " ) ;
// tap these so they’re initialized
let num_cards = ( CARDS_BY_ID . len ( ) + SEARCH_CARDS . len ( ) ) / 2 ;
println! ( " Read {num_cards} cards in {:?} " , now . elapsed ( ) ) ;
HttpServer ::new ( | | App ::new ( ) . service ( search ) ) . bind ( ( Ipv4Addr ::from ( [ 127 , 0 , 0 , 1 ] ) , 8080 ) ) ? . run ( ) . await
}
#[ derive(Debug, Deserialize) ]
struct Query {
q : String ,
}
2023-01-30 11:39:42 +01:00
const HEADER : & str = include_str! ( " ../static/header.html " ) ;
2023-01-27 14:18:33 +01:00
#[ get( " / " ) ]
async fn search ( q : Option < Either < web ::Query < Query > , web ::Form < Query > > > ) -> Result < HttpResponse , Box < dyn std ::error ::Error > > {
let q = match q {
Some ( Either ::Left ( web ::Query ( Query { q } ) ) ) = > Some ( q ) ,
Some ( Either ::Right ( web ::Form ( Query { q } ) ) ) = > Some ( q ) ,
None = > None ,
} ;
let mut res = String ::with_capacity ( 10_000 ) ;
2023-01-30 11:39:42 +01:00
res . write_str ( HEADER ) ? ;
2023-01-27 14:18:33 +01:00
write! (
res ,
r #"
< form action = " / " >
2023-01-30 11:39:42 +01:00
< input type = " text " name = " q " id = " searchbox " placeholder = " Enter query (e.g. l:5 c:synchro atk>2000) " value = " {} " > < input type = " submit " id = " submit " value = " 🔍 " >
2023-01-27 14:18:33 +01:00
< / form > " #,
match & q {
Some ( q ) = > q ,
None = > " " ,
}
) ? ;
if let Some ( q ) = q {
let query = parser ::parse_filters ( & q ) ? ;
let now = Instant ::now ( ) ;
2023-01-27 14:45:17 +01:00
let matches : Vec < & Card > = SEARCH_CARDS
. iter ( )
2023-01-27 15:48:07 +01:00
. filter ( | card | query . iter ( ) . all ( | ( _ , q ) | q ( card ) ) )
2023-01-27 14:45:17 +01:00
. map ( | c | CARDS_BY_ID . get ( & c . id ) . unwrap ( ) )
. take ( RESULT_LIMIT )
. collect ( ) ;
2023-01-27 15:48:07 +01:00
write! (
res ,
2023-01-30 11:39:42 +01:00
" <span class= \" meta \" >Showing {} results where {} (took {:?})</span><hr/> " ,
2023-01-27 15:48:07 +01:00
matches . len ( ) ,
query . iter ( ) . map ( | ( f , _ ) | f . to_string ( ) ) . join ( " and " ) ,
now . elapsed ( )
) ? ;
2023-01-27 14:18:33 +01:00
for card in matches {
res . push_str ( & card . to_string ( ) ) ;
2023-01-30 11:39:42 +01:00
res . push_str ( " <hr/> " ) ;
2023-01-27 14:18:33 +01:00
}
write! ( res , " </body></html> " ) ? ;
} else {
res . write_str ( " Enter a query above to search " ) ? ;
2023-01-26 15:04:39 +01:00
}
2023-01-27 14:18:33 +01:00
Ok ( HttpResponse ::Ok ( ) . insert_header ( header ::ContentType ::html ( ) ) . body ( res ) )
2022-10-05 17:57:01 +02:00
}