Compare commits
2 Commits
ac4682e20a
...
df116b1ef6
Author | SHA1 | Date |
---|---|---|
~erin | df116b1ef6 | |
~erin | dc66951808 |
|
@ -385,6 +385,15 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-humanize"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.13.3"
|
||||
|
@ -753,9 +762,11 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"axum",
|
||||
"chrono",
|
||||
"chrono-humanize",
|
||||
"config",
|
||||
"dirs",
|
||||
"ramhorns",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
|
@ -20,6 +20,7 @@ scraper = "0.17.1"
|
|||
axum = { version = "0.6.19", features = [ "http2", "tracing" ] }
|
||||
tracing-subscriber = "0.3.17"
|
||||
tracing = "0.1.37"
|
||||
reqwest = { version = "0.11", default-features = false, features = [ "rustls-tls", "gzip", "brotli", "deflate", "json" ] }
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
|
|
|
@ -16,7 +16,7 @@ env_logger.workspace = true
|
|||
chrono.workspace = true
|
||||
sqlx.workspace = true
|
||||
scraper.workspace = true
|
||||
reqwest.workspace = true
|
||||
serde = { version = "1.0.175", features = [ "derive" ] }
|
||||
reqwest = { version = "0.11", default-features = false, features = [ "rustls-tls", "gzip", "brotli", "deflate" ] }
|
||||
cacache = { version = "11.6.0", default-features = false, features = ["tokio-runtime", "mmap"] }
|
||||
bincode = "1.3.3"
|
||||
|
|
|
@ -15,5 +15,7 @@ axum.workspace = true
|
|||
url.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
reqwest.workspace = true
|
||||
ramhorns = "0.14.0"
|
||||
serde = "1.0.175"
|
||||
chrono-humanize = "0.2.3"
|
||||
|
|
|
@ -8,8 +8,11 @@ use axum::{
|
|||
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;
|
||||
|
@ -31,7 +34,7 @@ async fn main() {
|
|||
.route("/search", post(search))
|
||||
.route("/style.css", get(stylesheet));
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3001));
|
||||
tracing::debug!("listening on {}", addr);
|
||||
|
||||
axum::Server::bind(&addr)
|
||||
|
@ -72,7 +75,7 @@ struct SearchForm {
|
|||
}
|
||||
|
||||
#[derive(Content)]
|
||||
struct SearchResult {
|
||||
struct RenderSearchResult {
|
||||
url: String,
|
||||
size: i64,
|
||||
title: String,
|
||||
|
@ -80,12 +83,21 @@ struct SearchResult {
|
|||
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<SearchResult>,
|
||||
results: Vec<RenderSearchResult>,
|
||||
}
|
||||
|
||||
async fn search(Form(search): Form<SearchForm>) -> Html<String> {
|
||||
|
@ -93,28 +105,41 @@ async fn search(Form(search): Form<SearchForm>) -> Html<String> {
|
|||
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: vec![
|
||||
SearchResult {
|
||||
url: "https://example.com/".to_string(),
|
||||
size: 0,
|
||||
title: "Example Domain".to_string(),
|
||||
summary: "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.".to_string(),
|
||||
last_updated: "one week ago".to_string(),
|
||||
},
|
||||
SearchResult {
|
||||
url: "https://example.com/".to_string(),
|
||||
size: 0,
|
||||
title: "Example Domain".to_string(),
|
||||
summary: "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.".to_string(),
|
||||
last_updated: "one week ago".to_string(),
|
||||
}
|
||||
|
||||
],
|
||||
results,
|
||||
});
|
||||
Html(rendered)
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
{{#results}}
|
||||
<article>
|
||||
<h2>{{title}}</h2>
|
||||
<p class="time">Updated {{last_updated}}</p>
|
||||
<a class="result" href="{{url}}">{{url}}</a>
|
||||
<p class="result">{{summary}}</a>
|
||||
</article>
|
||||
|
|
|
@ -116,6 +116,13 @@ form.results {
|
|||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: .8em;
|
||||
font-style: italic;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
Loading…
Reference in New Issue