summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorache <ache@ache.one>2026-03-07 20:17:21 +0100
committerache <ache@ache.one>2026-03-07 20:17:21 +0100
commit216b92f67d9f81cf083b3abd6cde14bf1d311653 (patch)
tree87bdbb18a7685425918659e1f60ddb1c28dc5671
parentFix error message, wait second order (diff)
lintingHEADmain
-rw-r--r--src/main.rs200
1 files changed, 125 insertions, 75 deletions
diff --git a/src/main.rs b/src/main.rs
index 657c512..d5bfd09 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,33 +1,31 @@
// Init translations for current crate.
rust_i18n::i18n!("src/locales");
-use std::convert::Infallible;
-use std::net::{IpAddr, SocketAddr, Ipv4Addr};
use std::collections::HashMap;
+use std::convert::Infallible;
+use std::fs;
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::str::FromStr;
-use std::sync::mpsc::{Sender, channel};
-use std::{thread, time::Duration};
+use std::sync::mpsc::{channel, Sender};
use std::sync::Arc;
-use std::fs;
+use std::{thread, time::Duration};
-use serde_derive::{Serialize, Deserialize};
-use toml;
+use serde_derive::{Deserialize, Serialize};
use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
-use hyper::{Body, Request, Response, Server, StatusCode, Method, header::HeaderValue};
+use hyper::{header::HeaderValue, Body, Method, Request, Response, Server, StatusCode};
use rusqlite::{Connection, Transaction};
-use blake3;
use rust_i18n::t;
-
enum VustQuery {
Get,
Like,
Commit,
}
+
struct VustMessage {
req_type: VustQuery,
ip: SocketAddr,
@@ -35,38 +33,51 @@ struct VustMessage {
res: Sender<Response<Body>>,
}
-fn respond(sc :StatusCode, mes : String) -> Response<Body> {
+fn respond(sc: StatusCode, mes: String) -> Response<Body> {
Response::builder()
.status(sc)
.body(Body::from(mes))
.unwrap()
}
-fn wrap_cors(mut res: Response<Body>, config :Arc<Config>) -> Response<Body> {
- res.headers_mut().insert("Access-Control-Allow-Origin", HeaderValue::from_str(&config.cors_hosts).unwrap() );
- res.headers_mut().insert("Access-Control-Allow-Methods", HeaderValue::from_static("*"));
- res.headers_mut().insert("Access-Control-Allow-Headers", HeaderValue::from_static("*"));
+fn wrap_cors(mut res: Response<Body>, config: Arc<Config>) -> Response<Body> {
+ res.headers_mut().insert(
+ "Access-Control-Allow-Origin",
+ HeaderValue::from_str(&config.cors_hosts).unwrap(),
+ );
+ res.headers_mut().insert(
+ "Access-Control-Allow-Methods",
+ HeaderValue::from_static("*"),
+ );
+ res.headers_mut().insert(
+ "Access-Control-Allow-Headers",
+ HeaderValue::from_static("*"),
+ );
res
}
-fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config: Arc<Config>) -> Response<Body> {
- const PREFIX_PATH :&str = "/like/";
- const DEFAULT_LOCALE :&str = "fr";
+fn handle(
+ req: Request<Body>,
+ addr: SocketAddr,
+ tx: Sender<VustMessage>,
+ config: Arc<Config>,
+) -> Response<Body> {
+ const PREFIX_PATH: &str = "/like/";
+ const DEFAULT_LOCALE: &str = "fr";
let path: String = req.uri().path().to_string();
rust_i18n::set_locale(match req.headers().contains_key("lang") {
- true => match req.headers().get("lang") {
+ true => match req.headers().get("lang") {
Some(h) => match h.to_str() {
- Ok(l) => l,
+ Ok(l) => l,
Err(_) => DEFAULT_LOCALE,
},
- None => DEFAULT_LOCALE,
+ None => DEFAULT_LOCALE,
},
false => DEFAULT_LOCALE,
});
-
let addr = match req.headers().contains_key("real-ip") {
false => addr,
true => {
@@ -74,18 +85,24 @@ fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config:
Some(ip) => ip.to_str(),
None => {
return respond(StatusCode::SERVICE_UNAVAILABLE, t!("hidden_ip").to_string());
- },
+ }
};
let real_ip_str = match real_ip_str {
Ok(addr) => addr,
Err(s) => {
- return respond(StatusCode::SERVICE_UNAVAILABLE, format!("{}: {}.", t!("invalid_ip").to_string(), s.to_string()));
+ return respond(
+ StatusCode::SERVICE_UNAVAILABLE,
+ format!("{}: {}.", t!("invalid_ip"), s),
+ );
}
};
- let ip :IpAddr = match real_ip_str.parse() {
+ let ip: IpAddr = match real_ip_str.parse() {
Ok(addr) => addr,
Err(_) => {
- return respond(StatusCode::SERVICE_UNAVAILABLE, format!("{} {}.", t!("invalid_ip").to_string(), real_ip_str));
+ return respond(
+ StatusCode::SERVICE_UNAVAILABLE,
+ format!("{} {}.", t!("invalid_ip"), real_ip_str),
+ );
}
};
SocketAddr::new(ip, 0)
@@ -98,19 +115,29 @@ fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config:
Method::GET => VustQuery::Get,
Method::OPTIONS => {
return respond(StatusCode::OK, "WHAT ?".to_string());
- },
+ }
_ => {
- return respond(StatusCode::METHOD_NOT_ALLOWED, t!("method_not_allowed").to_string());
- },
+ return respond(
+ StatusCode::METHOD_NOT_ALLOWED,
+ t!("method_not_allowed").to_string(),
+ );
+ }
};
if !path.starts_with(PREFIX_PATH) {
- return respond(StatusCode::BAD_REQUEST, format!("{} {}.", t!("bad_endpoint"), PREFIX_PATH));
+ return respond(
+ StatusCode::BAD_REQUEST,
+ format!("{} {}.", t!("bad_endpoint"), PREFIX_PATH),
+ );
}
- let path : String = path.chars().skip(PREFIX_PATH.len()).collect();
+ let path: String = path.chars().skip(PREFIX_PATH.len()).collect();
- if path.contains("/") || path.len() > 128 || path.len() == 0 || !config.list_articles.contains(&path) {
+ if path.contains("/")
+ || path.is_empty()
+ || path.len() > 128
+ || !config.list_articles.contains(&path)
+ {
return respond(StatusCode::BAD_REQUEST, t!("bad_request").to_string());
}
@@ -120,8 +147,8 @@ fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config:
path
};
- let (ttx , rx) = channel();
- let message = VustMessage{
+ let (ttx, rx) = channel();
+ let message = VustMessage {
req_type: query_type,
ip: addr,
path: article_key_db,
@@ -136,11 +163,14 @@ fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config:
}
}
-fn do_get(tr: &Transaction, path : String) -> Response<Body> {
-
- for _ in [1..3] {
- let mut req_prepared = tr.prepare("SELECT cast(SUM(number) as text) FROM likes WHERE path = ?").unwrap();
- let mut rows = req_prepared.query(rusqlite::params![path.as_str()]).unwrap();
+fn do_get(tr: &Transaction, path: String) -> Response<Body> {
+ for _ in 1..3 {
+ let mut req_prepared = tr
+ .prepare("SELECT cast(SUM(number) as text) FROM likes WHERE path = ?")
+ .unwrap();
+ let mut rows = req_prepared
+ .query(rusqlite::params![path.as_str()])
+ .unwrap();
let first_row = rows.next();
@@ -151,54 +181,60 @@ fn do_get(tr: &Transaction, path : String) -> Response<Body> {
}
let first_row = first_row.unwrap();
- // Empty row ! Nobody like what I do. 😭
+ // Empty row! Nobody like what I do. 😭
if first_row.is_none() {
- return respond(StatusCode::OK, "0".to_string())
+ return respond(StatusCode::OK, "0".to_string());
}
let first_row = first_row.unwrap();
match first_row.get(0) {
Ok(nb_likes) => return respond(StatusCode::OK, nb_likes),
- // In case of NULL or not a Integer value:
- Err(_) => {
- return respond(StatusCode::OK, "β‹…".to_string())
- }
+ // In case of NULL or not an Integer value:
+ Err(_) => return respond(StatusCode::OK, "β‹…".to_string()),
}
}
respond(StatusCode::OK, "❓".to_string())
}
-fn do_like(tr: &Transaction, ip : SocketAddr, path : String) -> Response<Body> {
+fn do_like(tr: &Transaction, ip: SocketAddr, path: String) -> Response<Body> {
let hash_ip = match ip.ip() {
IpAddr::V4(ip) => blake3::hash(&ip.octets()).to_hex(),
IpAddr::V6(ip) => blake3::hash(&ip.octets()).to_hex(),
};
- for _ in [1..7] {
+ for _ in 1..7 {
let mut req_prepared = tr.prepare("SELECT number, cast(lastMod as UNSIGNED INT), cast(unixepoch() as UNSIGNED INT) FROM likes WHERE ip_hash = ? and path = ?").unwrap(); // , path.as_str()]).unwrap();
- let mut rows = req_prepared.query(rusqlite::params![hash_ip.as_str(), path.as_str()]).unwrap();
+ let mut rows = req_prepared
+ .query(rusqlite::params![hash_ip.as_str(), path.as_str()])
+ .unwrap();
let first_row = rows.next();
match first_row {
Ok(None) => {
- let res = tr.execute("INSERT OR IGNORE INTO likes VALUES (?, ?, unixepoch(), 1)", [hash_ip.as_str(), path.as_str()]);
+ let res = tr.execute(
+ "INSERT OR IGNORE INTO likes VALUES (?, ?, unixepoch(), 1)",
+ [hash_ip.as_str(), path.as_str()],
+ );
if res.is_err() {
eprintln!("Error doing the request {:?}", res.err());
continue;
}
- return respond(StatusCode::OK, t!("thanks").to_string())
- },
+ return respond(StatusCode::OK, t!("thanks").to_string());
+ }
Ok(Some(t)) => {
- let number : u64 = t.get(0).unwrap();
- let time : u64 = t.get(1).unwrap();
- let now : u64= t.get(2).unwrap();
+ let number: u64 = t.get(0).unwrap();
+ let time: u64 = t.get(1).unwrap();
+ let now: u64 = t.get(2).unwrap();
if number > 31 {
- return respond(StatusCode::RANGE_NOT_SATISFIABLE, format!("{} πŸ’• x ({})", t!("too_many_hears"), number).to_string());
+ return respond(
+ StatusCode::RANGE_NOT_SATISFIABLE,
+ format!("{} πŸ’• x ({})", t!("too_many_hears"), number).to_string(),
+ );
}
let limite = (1 << number) / 10; // 2^likes / Cst
@@ -206,7 +242,15 @@ fn do_like(tr: &Transaction, ip : SocketAddr, path : String) -> Response<Body> {
if dtime < limite {
let time_remaining = limite - dtime;
- return respond(StatusCode::TOO_MANY_REQUESTS, format!("{} {}s {}", t!("wait_prefix"), time_remaining, t!("wait_message_suffix")));
+ return respond(
+ StatusCode::TOO_MANY_REQUESTS,
+ format!(
+ "{} {}s {}",
+ t!("wait_prefix"),
+ time_remaining,
+ t!("wait_message_suffix")
+ ),
+ );
}
let res = tr.execute("UPDATE likes SET number = number + 1, lastMod = unixepoch() WHERE ip_hash = ? and path = ?", [hash_ip.as_str(), path.as_str()]);
@@ -214,17 +258,21 @@ fn do_like(tr: &Transaction, ip : SocketAddr, path : String) -> Response<Body> {
if res.is_err() {
eprintln!("Error doing the request {:?}", res.err());
- continue
+ continue;
}
- return respond(StatusCode::OK, format!("{} πŸ’• x {}", t!("thanks") , number + 1).to_string())
- },
- Err(_) => {
- continue
- },
+ return respond(
+ StatusCode::OK,
+ format!("{} πŸ’• x {}", t!("thanks"), number + 1).to_string(),
+ );
+ }
+ Err(_) => continue,
};
}
- respond(StatusCode::INTERNAL_SERVER_ERROR, t!("unknow_error").to_string())
+ respond(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ t!("unknow_error").to_string(),
+ )
}
#[derive(Serialize, Deserialize)]
@@ -263,7 +311,8 @@ fn get_config() -> String {
aliases.c-language-quirks='bizarreries-du-langage-c'
aliases.web-image-formats='formats-images-web'
aliases.rail-and-advertising='les-trains-et-la-publicit%C3%A9'
- "#.to_string();
+ "#
+ .to_string();
};
fs::read_to_string(path).expect("Unable to read config file")
@@ -276,15 +325,14 @@ async fn main() {
let addr = SocketAddr::new(ip, config.port);
eprintln!("Listening on {}", addr);
- let (tx , rx) = channel();
-
+ let (tx, rx) = channel();
let ttx = tx.clone();
thread::spawn(move || {
let tx = ttx.clone();
loop {
- let (txx , _) = channel();
- let res = tx.send(VustMessage{
+ let (txx, _) = channel();
+ let res = tx.send(VustMessage {
req_type: VustQuery::Commit,
ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
path: "".to_string(),
@@ -301,12 +349,11 @@ async fn main() {
thread::spawn(move || {
let mut conn = Connection::open("likes.db").unwrap();
loop {
-
let tr = conn.transaction().unwrap();
let mut should_commit = false;
loop {
- let recv : VustMessage = rx.recv().unwrap();
+ let recv: VustMessage = rx.recv().unwrap();
match recv.req_type {
VustQuery::Like => {
let res = do_like(&tr, recv.ip, recv.path);
@@ -316,13 +363,13 @@ async fn main() {
}
continue;
- },
+ }
VustQuery::Get => {
let res = recv.res.send(do_get(&tr, recv.path));
if res.is_ok() || res.is_err() {
continue;
}
- },
+ }
VustQuery::Commit => {
if should_commit {
break;
@@ -337,7 +384,7 @@ async fn main() {
// The closure passed to `make_service_fn` is executed each time a new
// connection is established and returns a future that resolves to a
// service.
- let make_service = make_service_fn(|conn: &AddrStream| {
+ let make_service = make_service_fn(|conn: &AddrStream| {
// The closure passed to `service_fn` is executed each time a request
// arrives on the connection and returns a future that resolves
// to a response.
@@ -346,11 +393,14 @@ async fn main() {
let config = config.clone();
async move {
- Ok::<_, Infallible>(service_fn( move |req| {
+ Ok::<_, Infallible>(service_fn(move |req| {
let tx = tx.clone();
let config = config.clone();
async move {
- Ok::<_, Infallible>(wrap_cors(handle(req, remote_addr, tx, config.clone()), config.clone()))
+ Ok::<_, Infallible>(wrap_cors(
+ handle(req, remote_addr, tx, config.clone()),
+ config.clone(),
+ ))
}
}))
}