aro/src/main.rs

87 lines
3.0 KiB
Rust
Raw Normal View History

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-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-27 14:18:33 +01:00
Ok(HttpResponse::Ok().insert_header(header::ContentType::html()).body(res))
2022-10-05 17:57:01 +02:00
}