Compare commits

...

4 Commits

Author SHA1 Message Date
~erin ac4682e20a
More styling, test results 2023-07-26 01:13:49 -04:00
~erin b6d9d117f6
Basic webpage and styling 2023-07-26 00:13:06 -04:00
~erin afcdffd9ee
Basic static site serving 2023-07-25 23:42:15 -04:00
~erin 94056699d2
Basic frontend Axum server 2023-07-25 23:24:29 -04:00
9 changed files with 497 additions and 21 deletions

156
Cargo.lock generated
View File

@ -98,6 +98,12 @@ dependencies = [
"libc",
]
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "async-compression"
version = "0.4.1"
@ -215,6 +221,19 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "bae"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a"
dependencies = [
"heck 0.3.3",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "base64"
version = "0.13.1"
@ -233,6 +252,12 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bincode"
version = "1.3.3"
@ -387,16 +412,14 @@ checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747"
[[package]]
name = "core"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"ammonia",
"axum",
"chrono",
"config",
"dirs",
"env_logger",
"fuzzy-matcher",
"log",
"scraper",
"serde",
"serde_json",
@ -727,6 +750,18 @@ dependencies = [
[[package]]
name = "frontend"
version = "0.1.0"
dependencies = [
"axum",
"chrono",
"config",
"dirs",
"ramhorns",
"serde",
"tokio",
"tracing",
"tracing-subscriber",
"url",
]
[[package]]
name = "futf"
@ -939,6 +974,15 @@ dependencies = [
"hashbrown 0.14.0",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.4.1"
@ -1242,6 +1286,29 @@ version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "logos"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-derive"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c"
dependencies = [
"beef",
"fnv",
"proc-macro2",
"quote",
"regex-syntax 0.6.29",
"syn 1.0.109",
]
[[package]]
name = "mac"
version = "0.1.1"
@ -1719,6 +1786,30 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.66"
@ -1728,6 +1819,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pulldown-cmark"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
dependencies = [
"bitflags 1.3.2",
"memchr",
"unicase",
]
[[package]]
name = "quote"
version = "1.0.32"
@ -1737,6 +1839,33 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "ramhorns"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47008ae2e2a9085a3f658203609d79f8a027829cf88a088d0c0084e18ba8f0b9"
dependencies = [
"arrayvec",
"beef",
"fnv",
"logos",
"pulldown-cmark",
"ramhorns-derive",
]
[[package]]
name = "ramhorns-derive"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ada9bbdd21adf426f932bf76b3db7d553538dffc16afd5fb8ce2ce2110a75536"
dependencies = [
"bae",
"fnv",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "rand"
version = "0.8.5"
@ -1815,7 +1944,7 @@ dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
"regex-syntax 0.7.4",
]
[[package]]
@ -1826,9 +1955,15 @@ checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"regex-syntax 0.7.4",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.4"
@ -2325,7 +2460,7 @@ checksum = "8a4a8336d278c62231d87f24e8a7a74898156e34c1c18942857be2acb29c7dfc"
dependencies = [
"dotenvy",
"either",
"heck",
"heck 0.4.1",
"hex",
"once_cell",
"proc-macro2",
@ -2811,6 +2946,15 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.13"

View File

@ -18,6 +18,8 @@ chrono = { version = "0.4.26", features = [ "serde" ] }
sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-rustls", "chrono", "macros", "sqlite" ] }
scraper = "0.17.1"
axum = { version = "0.6.19", features = [ "http2", "tracing" ] }
tracing-subscriber = "0.3.17"
tracing = "0.1.37"
[profile.release]
strip = true

View File

@ -7,8 +7,6 @@ homepage.workspace = true
license.workspace = true
[dependencies]
log.workspace = true
env_logger.workspace = true
tokio.workspace = true
sqlx.workspace = true
chrono.workspace = true
@ -17,10 +15,10 @@ dirs.workspace = true
scraper.workspace = true
axum.workspace = true
url.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
whatlang = "0.16.2"
ammonia = "3"
tracing-subscriber = "0.3.17"
tracing = "0.1.37"
serde = "1.0.175"
serde_json = "1.0.103"
fuzzy-matcher = "0.3.7"

View File

@ -1,6 +1,3 @@
#[macro_use]
extern crate log;
use ammonia::clean;
use axum::{
body::Bytes,
@ -38,8 +35,6 @@ async fn main() {
let shared_state = Arc::new(AppState { pool: pool });
let app = Router::new()
// `GET /` goes to `root`
.route("/", get(root))
.route("/api/search", get(search))
.with_state(shared_state);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
@ -51,10 +46,6 @@ async fn main() {
.unwrap();
}
async fn root() -> &'static str {
"Hello, World!"
}
#[derive(Deserialize)]
struct SearchQuery {
language: String,

View File

@ -5,3 +5,15 @@ edition.workspace = true
authors.workspace = true
homepage.workspace = true
license.workspace = true
[dependencies]
tokio.workspace = true
chrono.workspace = true
config.workspace = true
dirs.workspace = true
axum.workspace = true
url.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
ramhorns = "0.14.0"
serde = "1.0.175"

View File

@ -1,3 +1,120 @@
fn main() {
println!("Hello, world!");
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 ramhorns::{Content, Template};
use serde::Deserialize;
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], 3000));
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 SearchResult {
url: String,
size: i64,
title: String,
summary: String,
last_updated: String,
}
#[derive(Content)]
struct SearchResults {
title: String,
description: String,
query: String,
results: Vec<SearchResult>,
}
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 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(),
}
],
});
Html(rendered)
}

32
static/home.html Normal file
View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/style.css">
<!-- preview metadata -->
<meta property="og:site_name" content="{{title}}"/>
<meta property="og:title" content="{{title}}x"/>
<meta name="description" content="{{description}}"/>
<meta property="og:description" content="{{description}}">
<title>{{title}}</title>
</head>
<body>
<div class="container">
<h1 class="home">{{title}}</h1>
<form class="home" action="/search" method="post">
<input class="big-search" type="text" name="query" id="query" required>
<input class="button" type="submit" value="Search!">
</form>
<footer>
<p>{{description}}</p>
<a href="{{source}}">Source Code</a>
<pre>{{version}}</pre>
</footer>
</div>
</body>
</html>

41
static/results.html Normal file
View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/style.css">
<!-- preview metadata -->
<meta property="og:site_name" content="{{title}}"/>
<meta property="og:title" content="{{title}}x"/>
<meta name="description" content="{{description}}"/>
<meta property="og:description" content="{{description}}">
<title>{{title}}</title>
</head>
<body class="results">
<div class="results-container">
<header>
<h1 class="results" >{{title}}</h1>
<form action="/search" method="post" class="results">
<input class="results-search" type="text" name="query" id="query" required value="{{query}}">
<input class="results-button" type="submit" value="Search!">
</form>
</header>
<div class="results">
{{#results}}
<article>
<h2>{{title}}</h2>
<a class="result" href="{{url}}">{{url}}</a>
<p class="result">{{summary}}</a>
</article>
<hr>
{{/results}}
{{^results}}
<p>No results found! :c</p>
{{/results}}
</div>
</div>
</body>
</html>

139
static/style.css Normal file
View File

@ -0,0 +1,139 @@
body {
margin:40px auto;
line-height:1.6;
max-width: 650px;
font-size:18px;
color: #444;
padding: 0 10px;
font-family: "Atkinson Hyperlegible";
}
body.results {
margin-top: 0;
max-width: 100%;
margin: 0;
padding: 0;
background-color: #d2d2d2;
}
h1,h2,h3{line-height:1.2}
h2 {
font-size: 1em;
margin-bottom: 0;
}
hr {
color: white;
height: 1px;
width: 100%;
}
p.result {
font-size: .9em;
margin-top: .5em;
}
a.result {
font-size: .9em;
color: #ce8500;
font-style: italic;
}
h1.home {
font-size: 3em;
}
div.results {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
margin: auto;
margin-left: 25px;
}
h1.results {
margin-top: 0;
font-size: 1.5em;
padding-left: 20px;
}
header {
width: 100%;
border-radius: 2px;
background-color: white;
padding: 0px;
padding-top: 20px;
padding-bottom: 20px;
}
.results-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
.container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
form.home {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
gap: .5em;
}
form.results {
display: flex;
flex-direction: row;
align-items: center;
gap: .5em;
padding-left: 20px;
}
.big-search {
width: 100%;
font-size: 1.5em;
}
.results-search {
font-size: 1em;
}
.button {
font-size: 1.2em;
border-radius: 2px;
}
.results-button {
font-size: 1em;
border-radius: 2px;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 25em;
}
footer p {
margin-bottom: 0;
font-style: italic;
}
footer a {
padding-top: 0px;
margin-top: 0;
margin-bottom: 0;
}
footer pre {
padding-top: 0px;
margin-top: 0;
}