Initial commit
This commit is contained in:
commit
d42a361e95
9 changed files with 363 additions and 0 deletions
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "microbin"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "4"
|
||||||
|
actix-files = "0.6.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
askama = "0.10"
|
||||||
|
askama-filters = { version = "0.1.3", features = ["chrono"] }
|
||||||
|
chrono = "0.4.19"
|
||||||
|
rand = "0.8.5"
|
61
src/animalnumbers.rs
Normal file
61
src/animalnumbers.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
const animal_names: &[&str] = &[
|
||||||
|
"ant", "eel", "mole", "sloth",
|
||||||
|
"ape", "emu", "monkey", "snail",
|
||||||
|
"bat", "falcon", "mouse", "snake",
|
||||||
|
"bear", "fish", "otter", "spider",
|
||||||
|
"bee", "fly", "parrot", "squid",
|
||||||
|
"bird", "fox", "panda", "swan",
|
||||||
|
"bison", "frog", "pig", "tiger",
|
||||||
|
"camel", "gecko", "pigeon", "toad",
|
||||||
|
"cat", "goat", "pony", "turkey",
|
||||||
|
"cobra", "goose", "pug", "turtle",
|
||||||
|
"crow", "hawk", "rabbit", "viper",
|
||||||
|
"deer", "horse", "rat", "wasp",
|
||||||
|
"dog", "jaguar", "raven", "whale",
|
||||||
|
"dove", "koala", "seal", "wolf",
|
||||||
|
"duck", "lion", "shark", "worm",
|
||||||
|
"eagle", "lizard", "sheep", "zebra",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn to_animal_names(mut n: u64) -> String {
|
||||||
|
let mut result: Vec<&str> = Vec::new();
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
return animal_names[0].parse().unwrap();
|
||||||
|
} else if n == 1 {
|
||||||
|
return animal_names[1].parse().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// max 4 animals so 6 * 6 = 64 bits
|
||||||
|
let mut power = 6;
|
||||||
|
loop {
|
||||||
|
let d = n / animal_names.len().pow(power) as u64;
|
||||||
|
|
||||||
|
if !(result.is_empty() && d == 0) {
|
||||||
|
result.push(animal_names[d as usize]);
|
||||||
|
}
|
||||||
|
|
||||||
|
n -= d * animal_names.len().pow(power) as u64;
|
||||||
|
|
||||||
|
if power > 0 {
|
||||||
|
power -= 1;
|
||||||
|
} else { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
result.join("-")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_u64(n: &str) -> u64 {
|
||||||
|
let mut result: u64 = 0;
|
||||||
|
|
||||||
|
let mut animals: Vec<&str> = n.split("-").collect();
|
||||||
|
|
||||||
|
let mut pow = animals.len();
|
||||||
|
for i in 0..animals.len() {
|
||||||
|
pow -= 1;
|
||||||
|
result += (animal_names.iter().position(|&r| r == animals[i]).unwrap() * animal_names.len().pow(pow as u32)) as u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
137
src/main.rs
Normal file
137
src/main.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
extern crate core;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use actix_files::NamedFile;
|
||||||
|
use actix_web::{App, get, HttpRequest, HttpResponse, HttpServer, post, Responder, Result, web};
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use askama::Template;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
use crate::animalnumbers::{to_animal_names, to_u64};
|
||||||
|
use crate::pasta::{Pasta, PastaFormData};
|
||||||
|
|
||||||
|
mod pasta;
|
||||||
|
mod animalnumbers;
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
pastas: Mutex<Vec<Pasta>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "index.html")]
|
||||||
|
struct IndexTemplate {}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "pasta.html")]
|
||||||
|
struct PastaTemplate<'a> {
|
||||||
|
pasta: &'a Pasta,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "pastalist.html")]
|
||||||
|
struct PastaListTemplate<'a> {
|
||||||
|
pastas: &'a Vec<Pasta>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn index() -> impl Responder {
|
||||||
|
HttpResponse::Found().content_type("text/html").body(IndexTemplate {}.render().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/create")]
|
||||||
|
async fn create(data: web::Data<AppState>, pasta: web::Form<PastaFormData>) -> impl Responder {
|
||||||
|
let mut pastas = data.pastas.lock().unwrap();
|
||||||
|
|
||||||
|
let mut innerPasta = pasta.into_inner();
|
||||||
|
|
||||||
|
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||||
|
Ok(n) => n.as_secs(),
|
||||||
|
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||||
|
} as i64;
|
||||||
|
|
||||||
|
let expiration = match innerPasta.expiration.as_str() {
|
||||||
|
"firstread" => 1,
|
||||||
|
"10min" => timenow + 60 * 10,
|
||||||
|
"1hour" => timenow + 60 * 60,
|
||||||
|
"24hour" => timenow + 60 * 60 * 24,
|
||||||
|
"1week" => timenow + 60 * 60 * 24 * 7,
|
||||||
|
"never" => 0,
|
||||||
|
_ => panic!("Unexpected expiration time!")
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut newPasta = Pasta {
|
||||||
|
id: rand::thread_rng().gen::<u16>() as u64,
|
||||||
|
content: innerPasta.content,
|
||||||
|
created: timenow,
|
||||||
|
expiration,
|
||||||
|
};
|
||||||
|
|
||||||
|
let id = newPasta.id;
|
||||||
|
|
||||||
|
pastas.push(newPasta);
|
||||||
|
|
||||||
|
HttpResponse::Found().append_header(("Location", format!("/pasta/{}", to_animal_names(id)))).finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/pasta/{id}")]
|
||||||
|
async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||||
|
let pastas = data.pastas.lock().unwrap();
|
||||||
|
let id = to_u64(&*id.into_inner());
|
||||||
|
|
||||||
|
for pasta in pastas.iter() {
|
||||||
|
if pasta.id == id {
|
||||||
|
return HttpResponse::Found().content_type("text/html").body(PastaTemplate { pasta }.render().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Found().body("Pasta not found! :-(")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/rawpasta/{id}")]
|
||||||
|
async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String {
|
||||||
|
let pastas = data.pastas.lock().unwrap();
|
||||||
|
let id = to_u64(&*id.into_inner());
|
||||||
|
|
||||||
|
for pasta in pastas.iter() {
|
||||||
|
if pasta.id == id {
|
||||||
|
return pasta.content.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String::from("Pasta not found! :-(")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/remove/{id}")]
|
||||||
|
async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||||
|
let mut pastas = data.pastas.lock().unwrap();
|
||||||
|
let id = to_u64(&*id.into_inner());
|
||||||
|
|
||||||
|
for (i, pasta) in pastas.iter().enumerate() {
|
||||||
|
if pasta.id == id {
|
||||||
|
pastas.remove(i);
|
||||||
|
return HttpResponse::Found().append_header(("Location", "/pastalist")).finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Found().body("Pasta not found! :-(")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/pastalist")]
|
||||||
|
async fn list(data: web::Data<AppState>) -> HttpResponse {
|
||||||
|
let mut pastas = data.pastas.lock().unwrap();
|
||||||
|
|
||||||
|
HttpResponse::Found().content_type("text/html").body(PastaListTemplate { pastas: &pastas }.render().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
let data = web::Data::new(AppState {
|
||||||
|
pastas: Mutex::new(Vec::new()),
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
57
src/pasta.rs
Normal file
57
src/pasta.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use std::fmt;
|
||||||
|
use actix_web::cookie::time::macros::format_description;
|
||||||
|
use chrono::{Datelike, DateTime, NaiveDateTime, Timelike, Utc};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use crate::to_animal_names;
|
||||||
|
|
||||||
|
pub struct Pasta {
|
||||||
|
pub id: u64,
|
||||||
|
pub content: String,
|
||||||
|
pub created: i64,
|
||||||
|
pub expiration: i64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct PastaFormData {
|
||||||
|
pub content: String,
|
||||||
|
pub expiration: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pasta {
|
||||||
|
|
||||||
|
pub fn idAsAnimals(&self) -> String {
|
||||||
|
to_animal_names(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createdAsString(&self) -> String {
|
||||||
|
let date = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.created, 0), Utc);
|
||||||
|
format!(
|
||||||
|
"{}-{:02}-{:02} {}:{}",
|
||||||
|
date.year(),
|
||||||
|
date.month(),
|
||||||
|
date.day(),
|
||||||
|
date.hour(),
|
||||||
|
date.minute(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expirationAsString(&self) -> String {
|
||||||
|
let date = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.expiration, 0), Utc);
|
||||||
|
format!(
|
||||||
|
"{}-{:02}-{:02} {}:{}",
|
||||||
|
date.year(),
|
||||||
|
date.month(),
|
||||||
|
date.day(),
|
||||||
|
date.hour(),
|
||||||
|
date.minute(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl fmt::Display for Pasta {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.content)
|
||||||
|
}
|
||||||
|
}
|
7
templates/footer.html
Normal file
7
templates/footer.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<hr>
|
||||||
|
<p style="font-size: smaller">
|
||||||
|
MicroBin by Daniel Szabo. Fork me on <a href="https://github.com/szabodanika/microbin">GitHub</a>!
|
||||||
|
Let's keep the Web <b>compact</b>, <b>accessible</b> and <b>humane</b>!
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
19
templates/header.html
Normal file
19
templates/header.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>MicroBin</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<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">
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<b style="margin-right: 0.5rem">
|
||||||
|
MicroBin
|
||||||
|
</b>
|
||||||
|
|
|
||||||
|
<a href="/" style="margin-right: 0.5rem; margin-left: 0.5rem">New Pasta</a>
|
||||||
|
|
|
||||||
|
<a href="/pastalist" style="margin-right: 0.5rem; margin-left: 0.5rem">Pasta List</a>
|
||||||
|
|
|
||||||
|
<a href="https://github.com/szabodanika/microbin" style="margin-right: 0.5rem; margin-left: 0.5rem">GitHub</a>
|
||||||
|
|
||||||
|
<hr>
|
23
templates/index.html
Normal file
23
templates/index.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% include "header.html" %}
|
||||||
|
<form action="create" method="POST">
|
||||||
|
<input name="id" value="0" type="hidden">
|
||||||
|
<label for="expiration">Expiration</label><br>
|
||||||
|
<select name="expiration" id="expiration">
|
||||||
|
<optgroup label="Expire">
|
||||||
|
<option value="firstread">First Read</option>
|
||||||
|
<option value="10min">10 minutes</option>
|
||||||
|
<option value="1hour">1 hour</option>
|
||||||
|
<option selected value="24hour">24 hours</option>
|
||||||
|
<option value="1week">1 week</option>
|
||||||
|
</optgroup>
|
||||||
|
<option value="never">Never Expire</option>
|
||||||
|
</select>
|
||||||
|
<br>
|
||||||
|
<label>Content</label>
|
||||||
|
<br>
|
||||||
|
<textarea style="width: 100%; min-height: 100px" name="content"></textarea>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<input style="width: 100px; background-color: limegreen" type="submit" value="Submit"/>
|
||||||
|
</form>
|
||||||
|
{% include "footer.html" %}
|
6
templates/pasta.html
Normal file
6
templates/pasta.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{% include "header.html" %}
|
||||||
|
<a href="/rawpasta/{{pasta.id}}">Raw Pasta</a>
|
||||||
|
<pre style="background: lightgray; border: 1px black solid; padding: 0.5rem; overflow: auto">
|
||||||
|
{{pasta}}
|
||||||
|
</pre>
|
||||||
|
{% include "footer.html" %}
|
40
templates/pastalist.html
Normal file
40
templates/pastalist.html
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{% include "header.html" %}
|
||||||
|
<table style="width: 100%" border="1">
|
||||||
|
<tr style="background: lightgrey">
|
||||||
|
<th>
|
||||||
|
Key
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Created
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Expiration
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
{% for pasta in pastas %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/pasta/{{pasta.idAsAnimals()}}">{{pasta.idAsAnimals()}}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{pasta.createdAsString()}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{pasta.expirationAsString()}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="/remove/{{pasta.idAsAnimals()}}">Remove</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if pastas.is_empty() %}
|
||||||
|
<p>
|
||||||
|
No Pastas :-(
|
||||||
|
</p>
|
||||||
|
{%- endif %}
|
||||||
|
{% include "footer.html" %}
|
Loading…
Reference in a new issue