application_paste_bin/
application_paste_bin.rs1use std::time::Instant;
8use std::{borrow::Borrow, sync::RwLock};
9
10use afire::internal::encoding::url;
11use afire::{Content, HeaderType, Method, Query, Response, Server, Status};
12
13const DATA_LIMIT: usize = 10_000;
14
15struct Paste {
16 name: String,
17 body: String,
18 time: Instant,
19}
20
21fn main() {
22 let mut server = Server::new("localhost", 8080).state(RwLock::new(Vec::new()));
24
25 server.route(Method::GET, "/", |_req| {
27 Response::new().content(Content::HTML).text(
28 r#"
29 <form action="/new-form" method="post">
30 <input type="text" name="name" id="name" placeholder="Title">
31
32 <br />
33 <textarea id="body" name="body" rows="5" cols="33"></textarea>
34 <input type="submit" value="Submit" />
35 </form>
36 "#,
37 )
38 });
39
40 server.stateful_route(Method::POST, "/new", move |app, req| {
42 if req.body.len() > DATA_LIMIT {
44 return Response::new()
45 .status(Status::NotFound)
46 .text("Data too big!");
47 }
48
49 let body_str = String::from_utf8_lossy(&req.body).to_string();
51
52 let name = req.headers.get("Name").unwrap_or("Untitled");
54
55 let paste = Paste {
56 name: name.to_owned(),
57 body: body_str,
58 time: Instant::now(),
59 };
60
61 let mut pastes = app.write().unwrap();
63 let id = pastes.len();
64 pastes.push(paste);
65
66 Response::new()
68 .status(Status::MovedPermanently)
69 .header(HeaderType::Location, format!("/p/{id}"))
70 .text(format!("Redirecting to /p/{id}."))
71 });
72
73 server.stateful_route(Method::POST, "/new-form", |app, req| {
75 let query = Query::from_body(String::from_utf8_lossy(&req.body).borrow());
77 let name = url::decode(query.get("name").unwrap_or("Untitled")).expect("Invalid name");
78 let body = url::decode(query.get("body").expect("No body supplied")).expect("Invalid body");
79
80 if body.len() > DATA_LIMIT {
82 return Response::new()
83 .status(Status::NotFound)
84 .text("Data too big!");
85 }
86
87 let paste = Paste {
88 name,
89 body,
90 time: Instant::now(),
91 };
92
93 let mut pastes = app.write().unwrap();
95 let id = pastes.len();
96 pastes.push(paste);
97
98 Response::new()
100 .status(Status::MovedPermanently)
101 .text("Ok")
102 .header(HeaderType::Location, format!("/p/{}", id))
103 });
104
105 server.stateful_route(Method::GET, "/p/{id}", move |app, req| {
107 let id = req.param("id").unwrap().parse::<usize>().unwrap();
109
110 let paste = &app.read().unwrap()[id];
112
113 Response::new().text(&paste.body)
115 });
116
117 server.stateful_route(Method::GET, "/pastes", move |app, _req| {
119 let mut out = String::from(
121 r#"<a href="/">New Paste</a><meta charset="UTF-8"><table><tr><th>Name</th><th>Date</th><th>Link</th></tr>"#,
122 );
123
124 for (i, e) in app.read().unwrap().iter().enumerate() {
126 out.push_str(&format!(
127 "<tr><td>{}</td><td>{}</td><td><a href=\"/p/{}\">🔗</a></td></tr>",
128 e.name,
129 fmt_relative_time(e.time.elapsed().as_secs()),
130 i
131 ));
132 }
133
134 Response::new()
136 .text(format!("{}</table>", out))
137 .content(Content::HTML)
138 });
139
140 server.start().unwrap();
141}
142
143const TIME_UNITS: &[(&str, u16)] = &[
144 ("second", 60),
145 ("minute", 60),
146 ("hour", 24),
147 ("day", 30),
148 ("month", 12),
149 ("year", 0),
150];
151
152pub fn fmt_relative_time(secs: u64) -> String {
156 if secs == 0 {
157 return "now".into();
158 }
159
160 let mut secs = secs as f64;
161 for i in TIME_UNITS {
162 if i.1 == 0 || secs < i.1 as f64 {
163 secs = secs.round();
164 return format!("{} {}{} ago", secs, i.0, if secs > 1.0 { "s" } else { "" });
165 }
166
167 secs /= i.1 as f64;
168 }
169
170 format!("{} years ago", secs.round())
171}
172
173