146 lines
3.6 KiB
Rust
146 lines
3.6 KiB
Rust
use axum::Form;
|
|
use axum::{
|
|
body::Bytes,
|
|
extract::State,
|
|
http::{header, HeaderMap, StatusCode},
|
|
response::{Html, IntoResponse},
|
|
routing::{get, post},
|
|
Json, Router,
|
|
};
|
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
|
use chrono_humanize::HumanTime;
|
|
use ramhorns::{Content, Template};
|
|
use serde::Deserialize;
|
|
use std::collections::BTreeMap;
|
|
use std::collections::HashMap;
|
|
use std::fs::File;
|
|
use std::io::prelude::*;
|
|
use std::net::SocketAddr;
|
|
use url::Url;
|
|
|
|
#[derive(Content)]
|
|
struct HomePage {
|
|
title: String,
|
|
description: String,
|
|
source: String,
|
|
version: String,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
tracing_subscriber::fmt::init();
|
|
let app = Router::new()
|
|
.route("/", get(root))
|
|
.route("/search", post(search))
|
|
.route("/style.css", get(stylesheet));
|
|
|
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3001));
|
|
tracing::debug!("listening on {}", addr);
|
|
|
|
axum::Server::bind(&addr)
|
|
.serve(app.into_make_service())
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
async fn stylesheet() -> impl IntoResponse {
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert(header::CONTENT_TYPE, "text/css".parse().unwrap());
|
|
|
|
let mut file = File::open("static/style.css").unwrap();
|
|
let mut contents = String::new();
|
|
file.read_to_string(&mut contents).unwrap();
|
|
|
|
(headers, contents)
|
|
}
|
|
|
|
async fn root() -> Html<String> {
|
|
let mut source = File::open("static/home.html").unwrap();
|
|
let mut contents = String::new();
|
|
source.read_to_string(&mut contents).unwrap();
|
|
|
|
let tpl = Template::new(contents).unwrap();
|
|
let rendered = tpl.render(&HomePage {
|
|
title: "Ferret".to_string(),
|
|
description: "A small independent search engine".to_string(),
|
|
source: "https://git.lavender.software/erin/ferret".to_string(),
|
|
version: "v0.2.0".to_string(),
|
|
});
|
|
Html(rendered)
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct SearchForm {
|
|
query: String,
|
|
}
|
|
|
|
#[derive(Content)]
|
|
struct RenderSearchResult {
|
|
url: String,
|
|
size: i64,
|
|
title: String,
|
|
summary: String,
|
|
last_updated: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct SearchResult {
|
|
url: Url,
|
|
size: i64,
|
|
title: String,
|
|
summary: String,
|
|
last_updated: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Content)]
|
|
struct SearchResults {
|
|
title: String,
|
|
description: String,
|
|
query: String,
|
|
results: Vec<RenderSearchResult>,
|
|
}
|
|
|
|
async fn search(Form(search): Form<SearchForm>) -> Html<String> {
|
|
let mut source = File::open("static/results.html").unwrap();
|
|
let mut contents = String::new();
|
|
source.read_to_string(&mut contents).unwrap();
|
|
|
|
let mut map = HashMap::new();
|
|
map.insert("language", "eng");
|
|
map.insert("include", &search.query);
|
|
map.insert("option", "Fuzzy");
|
|
|
|
let client = reqwest::Client::new();
|
|
let res = client
|
|
.get("http://127.0.0.1:3000/api/search")
|
|
.json(&map)
|
|
.send()
|
|
.await
|
|
.unwrap()
|
|
.json::<BTreeMap<i64, SearchResult>>()
|
|
.await
|
|
.unwrap();
|
|
|
|
tracing::info!("{:#?}", res);
|
|
|
|
let mut results = Vec::new();
|
|
for (i, r) in res.iter().rev() {
|
|
results.push(RenderSearchResult {
|
|
url: r.url.as_str().to_string(),
|
|
size: r.size,
|
|
title: r.title.clone(),
|
|
summary: r.summary.clone(),
|
|
last_updated: HumanTime::from(r.last_updated).to_string(),
|
|
});
|
|
}
|
|
|
|
let tpl = Template::new(contents).unwrap();
|
|
let rendered = tpl.render(&SearchResults {
|
|
title: "Ferret".to_string(),
|
|
description: "A small independent search engine".to_string(),
|
|
query: search.query,
|
|
results,
|
|
});
|
|
Html(rendered)
|
|
}
|