Added auto removal of expired pastas

This commit is contained in:
Dániel Szabó 2022-04-11 14:41:28 +01:00
parent 5bae4310a3
commit 22b61fc793
7 changed files with 59 additions and 13 deletions

View file

@ -10,4 +10,4 @@ serde = { version = "1.0", features = ["derive"] }
askama = "0.10" askama = "0.10"
askama-filters = { version = "0.1.3", features = ["chrono"] } askama-filters = { version = "0.1.3", features = ["chrono"] }
chrono = "0.4.19" chrono = "0.4.19"
rand = "0.8.5" rand = "0.8.5"

20
README.MD Normal file
View file

@ -0,0 +1,20 @@
# MicroBin
![Screenshot](git/index.png)
MicroBin is a super tiny and simple self hosted pastebin app written in Rust. The executable is around 6MB and it uses 2MB memory (plus your pastas).
Features:
- No CSS or JS, super lightweight and simple
- Animal names instead of random numbers for pasta identifiers
- Automatically expiring pastas
- Never expiring pastas
- Listing and manually removing pastas
Needed improvements:
- Persisting pastas on disk (currently they are lost on restart)
- Removing pasta after N reads
- File uploads
- URL shortening
![Screenshot](git/pasta.png)

View file

@ -53,7 +53,7 @@ async fn create(data: web::Data<AppState>, pasta: web::Form<PastaFormData>) -> i
} as i64; } as i64;
let expiration = match innerPasta.expiration.as_str() { let expiration = match innerPasta.expiration.as_str() {
"firstread" => 1, "1min" => timenow + 60,
"10min" => timenow + 60 * 10, "10min" => timenow + 60 * 10,
"1hour" => timenow + 60 * 60, "1hour" => timenow + 60 * 60,
"24hour" => timenow + 60 * 60 * 24, "24hour" => timenow + 60 * 60 * 24,
@ -78,9 +78,11 @@ async fn create(data: web::Data<AppState>, pasta: web::Form<PastaFormData>) -> i
#[get("/pasta/{id}")] #[get("/pasta/{id}")]
async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse { async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
let pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
let id = to_u64(&*id.into_inner()); let id = to_u64(&*id.into_inner());
remove_expired(&mut pastas);
for pasta in pastas.iter() { for pasta in pastas.iter() {
if pasta.id == id { if pasta.id == id {
return HttpResponse::Found().content_type("text/html").body(PastaTemplate { pasta }.render().unwrap()); return HttpResponse::Found().content_type("text/html").body(PastaTemplate { pasta }.render().unwrap());
@ -92,9 +94,11 @@ async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpRespo
#[get("/rawpasta/{id}")] #[get("/rawpasta/{id}")]
async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String { async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String {
let pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
let id = to_u64(&*id.into_inner()); let id = to_u64(&*id.into_inner());
remove_expired(&mut pastas);
for pasta in pastas.iter() { for pasta in pastas.iter() {
if pasta.id == id { if pasta.id == id {
return pasta.content.to_owned(); return pasta.content.to_owned();
@ -109,6 +113,8 @@ async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRespons
let mut pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
let id = to_u64(&*id.into_inner()); let id = to_u64(&*id.into_inner());
remove_expired(&mut pastas);
for (i, pasta) in pastas.iter().enumerate() { for (i, pasta) in pastas.iter().enumerate() {
if pasta.id == id { if pasta.id == id {
pastas.remove(i); pastas.remove(i);
@ -123,6 +129,8 @@ async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRespons
async fn list(data: web::Data<AppState>) -> HttpResponse { async fn list(data: web::Data<AppState>) -> HttpResponse {
let mut pastas = data.pastas.lock().unwrap(); let mut pastas = data.pastas.lock().unwrap();
remove_expired(&mut pastas);
HttpResponse::Found().content_type("text/html").body(PastaListTemplate { pastas: &pastas }.render().unwrap()) HttpResponse::Found().content_type("text/html").body(PastaListTemplate { pastas: &pastas }.render().unwrap())
} }
@ -135,3 +143,14 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(move || App::new().app_data(data.clone()).service(index).service(create).service(getpasta).service(getrawpasta).service(remove).service(list) HttpServer::new(move || App::new().app_data(data.clone()).service(index).service(create).service(getpasta).service(getrawpasta).service(remove).service(list)
).bind("127.0.0.1:8080")?.run().await ).bind("127.0.0.1:8080")?.run().await
} }
fn remove_expired(pastas: &mut Vec<Pasta>) {
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
} as i64;
pastas.retain(|p| {
p.expiration == 0 || p.expiration > timenow
});
}

View file

@ -3,7 +3,12 @@
<title>MicroBin</title> <title>MicroBin</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<body style="max-width: 720px; margin: auto; padding-left:0.5rem; padding-right:0.5rem; line-height: 1.5; font-size: 1.1em; font-family: sans-serif"> <body style="max-width: 720px;
margin: auto;
padding-left:0.5rem;
padding-right:0.5rem;
line-height: 1.5;
font-size: 1.1em;">
<br> <br>
<b style="margin-right: 0.5rem"> <b style="margin-right: 0.5rem">

View file

@ -4,7 +4,7 @@
<label for="expiration">Expiration</label><br> <label for="expiration">Expiration</label><br>
<select name="expiration" id="expiration"> <select name="expiration" id="expiration">
<optgroup label="Expire"> <optgroup label="Expire">
<option value="firstread">First Read</option> <option value="1min">1 minute</option>
<option value="10min">10 minutes</option> <option value="10min">10 minutes</option>
<option value="1hour">1 hour</option> <option value="1hour">1 hour</option>
<option selected value="24hour">24 hours</option> <option selected value="24hour">24 hours</option>
@ -18,6 +18,6 @@
<textarea style="width: 100%; min-height: 100px" name="content"></textarea> <textarea style="width: 100%; min-height: 100px" name="content"></textarea>
<br> <br>
<br> <br>
<input style="width: 100px; background-color: limegreen" type="submit" value="Submit"/> <input style="width: 100px; background-color: limegreen"; type="submit" value="Save"/>
</form> </form>
{% include "footer.html" %} {% include "footer.html" %}

View file

@ -1,5 +1,5 @@
{% include "header.html" %} {% include "header.html" %}
<a href="/rawpasta/{{pasta.id}}">Raw Pasta</a> <a href="/rawpasta/{{pasta.idAsAnimals()}}">Raw Pasta</a>
<pre style="background: lightgray; border: 1px black solid; padding: 0.5rem; overflow: auto"> <pre style="background: lightgray; border: 1px black solid; padding: 0.5rem; overflow: auto">
{{pasta}} {{pasta}}
</pre> </pre>

View file

@ -1,4 +1,11 @@
{% include "header.html" %} {% include "header.html" %}
{% if pastas.is_empty() %}
<p>
No pastas yet. 😔 Create one <a href="/">here</a>.
</p>
{%- else %}
<table style="width: 100%" border="1"> <table style="width: 100%" border="1">
<tr style="background: lightgrey"> <tr style="background: lightgrey">
<th> <th>
@ -31,10 +38,5 @@
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
{% if pastas.is_empty() %}
<p>
No Pastas :-(
</p>
{%- endif %} {%- endif %}
{% include "footer.html" %} {% include "footer.html" %}