use std::time::Instant;
use std::{borrow::Borrow, sync::RwLock};
use afire::internal::encoding::url;
use afire::{Content, HeaderType, Method, Query, Response, Server, Status};
const DATA_LIMIT: usize = 10_000;
struct Paste {
name: String,
body: String,
time: Instant,
}
fn main() {
let mut server = Server::new("localhost", 8080).state(RwLock::new(Vec::new()));
server.route(Method::GET, "/", |_req| {
Response::new().content(Content::HTML).text(
r#"
<form action="/new-form" method="post">
<input type="text" name="name" id="name" placeholder="Title">
<br />
<textarea id="body" name="body" rows="5" cols="33"></textarea>
<input type="submit" value="Submit" />
</form>
"#,
)
});
server.stateful_route(Method::POST, "/new", move |app, req| {
if req.body.len() > DATA_LIMIT {
return Response::new()
.status(Status::NotFound)
.text("Data too big!");
}
let body_str = String::from_utf8_lossy(&req.body).to_string();
let name = req.headers.get("Name").unwrap_or("Untitled");
let paste = Paste {
name: name.to_owned(),
body: body_str,
time: Instant::now(),
};
let mut pastes = app.write().unwrap();
let id = pastes.len();
pastes.push(paste);
Response::new()
.status(Status::MovedPermanently)
.header(HeaderType::Location, format!("/p/{id}"))
.text(format!("Redirecting to /p/{id}."))
});
server.stateful_route(Method::POST, "/new-form", |app, req| {
let query = Query::from_body(String::from_utf8_lossy(&req.body).borrow());
let name = url::decode(query.get("name").unwrap_or("Untitled")).expect("Invalid name");
let body = url::decode(query.get("body").expect("No body supplied")).expect("Invalid body");
if body.len() > DATA_LIMIT {
return Response::new()
.status(Status::NotFound)
.text("Data too big!");
}
let paste = Paste {
name,
body,
time: Instant::now(),
};
let mut pastes = app.write().unwrap();
let id = pastes.len();
pastes.push(paste);
Response::new()
.status(Status::MovedPermanently)
.text("Ok")
.header(HeaderType::Location, format!("/p/{}", id))
});
server.stateful_route(Method::GET, "/p/{id}", move |app, req| {
let id = req.param("id").unwrap().parse::<usize>().unwrap();
let paste = &app.read().unwrap()[id];
Response::new().text(&paste.body)
});
server.stateful_route(Method::GET, "/pastes", move |app, _req| {
let mut out = String::from(
r#"<a href="/">New Paste</a><meta charset="UTF-8"><table><tr><th>Name</th><th>Date</th><th>Link</th></tr>"#,
);
for (i, e) in app.read().unwrap().iter().enumerate() {
out.push_str(&format!(
"<tr><td>{}</td><td>{}</td><td><a href=\"/p/{}\">🔗</a></td></tr>",
e.name,
fmt_relative_time(e.time.elapsed().as_secs()),
i
));
}
Response::new()
.text(format!("{}</table>", out))
.content(Content::HTML)
});
server.start().unwrap();
}
const TIME_UNITS: &[(&str, u16)] = &[
("second", 60),
("minute", 60),
("hour", 24),
("day", 30),
("month", 12),
("year", 0),
];
pub fn fmt_relative_time(secs: u64) -> String {
if secs == 0 {
return "now".into();
}
let mut secs = secs as f64;
for i in TIME_UNITS {
if i.1 == 0 || secs < i.1 as f64 {
secs = secs.round();
return format!("{} {}{} ago", secs, i.0, if secs > 1.0 { "s" } else { "" });
}
secs /= i.1 as f64;
}
format!("{} years ago", secs.round())
}