File upload and persistence extension
- index.html extended with form input - pasta.html and pastalist.html show link to /file/{pasta.id}/{filename} path - files are saved in pasta_data folder - all data is now stored in pasta_data/database.json - changed pastalist.html date format to exclude year - added custom 404 error handler
This commit is contained in:
parent
c98aad7256
commit
36fa6598a8
9 changed files with 371 additions and 223 deletions
30
Cargo.toml
30
Cargo.toml
|
@ -1,15 +1,21 @@
|
||||||
[package]
|
[package]
|
||||||
name = "microbin"
|
name="microbin"
|
||||||
version = "0.1.0"
|
version="0.2.0"
|
||||||
edition = "2021"
|
edition="2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4"
|
actix-web="4"
|
||||||
actix-files = "0.6.0"
|
actix-files="0.6.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde={ version = "1.0", features = ["derive"] }
|
||||||
askama = "0.10"
|
serde_json = "1.0.80"
|
||||||
askama-filters = { version = "0.1.3", features = ["chrono"] }
|
askama="0.10"
|
||||||
chrono = "0.4.19"
|
askama-filters={ version = "0.1.3", features = ["chrono"] }
|
||||||
rand = "0.8.5"
|
chrono="0.4.19"
|
||||||
linkify = "0.8.1"
|
rand="0.8.5"
|
||||||
clap = { version = "3.1.12", features = ["derive"] }
|
linkify="0.8.1"
|
||||||
|
clap={ version = "3.1.12", features = ["derive"] }
|
||||||
|
actix-multipart = "0.4.0"
|
||||||
|
futures = "0.3"
|
||||||
|
sanitize-filename = "0.3.0"
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.9.0"
|
|
@ -1,61 +1,53 @@
|
||||||
const animal_names: &[&str] = &[
|
const ANIMAL_NAMES: &[&str] = &[
|
||||||
"ant", "eel", "mole", "sloth",
|
"ant", "eel", "mole", "sloth", "ape", "emu", "monkey", "snail", "bat", "falcon", "mouse",
|
||||||
"ape", "emu", "monkey", "snail",
|
"snake", "bear", "fish", "otter", "spider", "bee", "fly", "parrot", "squid", "bird", "fox",
|
||||||
"bat", "falcon", "mouse", "snake",
|
"panda", "swan", "bison", "frog", "pig", "tiger", "camel", "gecko", "pigeon", "toad", "cat",
|
||||||
"bear", "fish", "otter", "spider",
|
"goat", "pony", "turkey", "cobra", "goose", "pug", "turtle", "crow", "hawk", "rabbit", "viper",
|
||||||
"bee", "fly", "parrot", "squid",
|
"deer", "horse", "rat", "wasp", "dog", "jaguar", "raven", "whale", "dove", "koala", "seal",
|
||||||
"bird", "fox", "panda", "swan",
|
"wolf", "duck", "lion", "shark", "worm", "eagle", "lizard", "sheep", "zebra",
|
||||||
"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 {
|
pub fn to_animal_names(mut n: u64) -> String {
|
||||||
let mut result: Vec<&str> = Vec::new();
|
let mut result: Vec<&str> = Vec::new();
|
||||||
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return animal_names[0].parse().unwrap();
|
return ANIMAL_NAMES[0].parse().unwrap();
|
||||||
} else if n == 1 {
|
} else if n == 1 {
|
||||||
return animal_names[1].parse().unwrap();
|
return ANIMAL_NAMES[1].parse().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// max 4 animals so 6 * 6 = 64 bits
|
// max 4 animals so 6 * 6 = 64 bits
|
||||||
let mut power = 6;
|
let mut power = 6;
|
||||||
loop {
|
loop {
|
||||||
let d = n / animal_names.len().pow(power) as u64;
|
let d = n / ANIMAL_NAMES.len().pow(power) as u64;
|
||||||
|
|
||||||
if !(result.is_empty() && d == 0) {
|
if !(result.is_empty() && d == 0) {
|
||||||
result.push(animal_names[d as usize]);
|
result.push(ANIMAL_NAMES[d as usize]);
|
||||||
}
|
}
|
||||||
|
|
||||||
n -= d * animal_names.len().pow(power) as u64;
|
n -= d * ANIMAL_NAMES.len().pow(power) as u64;
|
||||||
|
|
||||||
if power > 0 {
|
if power > 0 {
|
||||||
power -= 1;
|
power -= 1;
|
||||||
} else { break; }
|
} else {
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.join("-")
|
result.join("-")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_u64(n: &str) -> u64 {
|
pub fn to_u64(n: &str) -> u64 {
|
||||||
let mut result: u64 = 0;
|
let mut result: u64 = 0;
|
||||||
|
|
||||||
let mut animals: Vec<&str> = n.split("-").collect();
|
let animals: Vec<&str> = n.split("-").collect();
|
||||||
|
|
||||||
let mut pow = animals.len();
|
let mut pow = animals.len();
|
||||||
for i in 0..animals.len() {
|
for i in 0..animals.len() {
|
||||||
pow -= 1;
|
pow -= 1;
|
||||||
result += (animal_names.iter().position(|&r| r == animals[i]).unwrap() * animal_names.len().pow(pow as u32)) as u64;
|
result += (ANIMAL_NAMES.iter().position(|&r| r == animals[i]).unwrap()
|
||||||
}
|
* ANIMAL_NAMES.len().pow(pow as u32)) as u64;
|
||||||
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
56
src/dbio.rs
Normal file
56
src/dbio.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufReader, BufWriter, Error};
|
||||||
|
use std::{fmt, io};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc};
|
||||||
|
use log::log;
|
||||||
|
|
||||||
|
use crate::{to_animal_names, Pasta};
|
||||||
|
|
||||||
|
static DATABASE_PATH: &'static str = "pasta_data/database.json";
|
||||||
|
|
||||||
|
pub fn save_to_file(pasta_data: &Vec<Pasta>) {
|
||||||
|
let mut file = File::create(DATABASE_PATH);
|
||||||
|
match file {
|
||||||
|
Ok(_) => {
|
||||||
|
let mut writer = BufWriter::new(file.unwrap());
|
||||||
|
serde_json::to_writer(&mut writer, &pasta_data);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
log::info!("Database file {} not found!", DATABASE_PATH);
|
||||||
|
file = File::create(DATABASE_PATH);
|
||||||
|
match file {
|
||||||
|
Ok(_) => {
|
||||||
|
log::info!("Database file {} created.", DATABASE_PATH);
|
||||||
|
save_to_file(pasta_data);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to create database file {}: {}!",
|
||||||
|
&DATABASE_PATH,
|
||||||
|
&err
|
||||||
|
);
|
||||||
|
panic!("Failed to create database file {}: {}!", DATABASE_PATH, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_from_file() -> io::Result<Vec<Pasta>> {
|
||||||
|
let mut file = File::open(DATABASE_PATH);
|
||||||
|
match file {
|
||||||
|
Ok(_) => {
|
||||||
|
let mut reader = BufReader::new(file.unwrap());
|
||||||
|
let data: Vec<Pasta> = serde_json::from_reader(&mut reader).unwrap();
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
log::info!("Database file {} not found!", DATABASE_PATH);
|
||||||
|
save_to_file(&Vec::<Pasta>::new());
|
||||||
|
|
||||||
|
log::info!("Database file {} created.", DATABASE_PATH);
|
||||||
|
load_from_file()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
src/main.rs
161
src/main.rs
|
@ -1,20 +1,27 @@
|
||||||
extern crate core;
|
extern crate core;
|
||||||
|
|
||||||
use actix_files as fs;
|
use env_logger::Builder;
|
||||||
use actix_web::web::Data;
|
use std::io::Write;
|
||||||
use actix_web::{get, post, web, App, HttpRequest, HttpResponse, HttpServer, Responder, Result};
|
|
||||||
use askama::Template;
|
|
||||||
use clap::Parser;
|
|
||||||
use linkify::{LinkFinder, LinkKind};
|
|
||||||
use rand::Rng;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use actix_files as fs;
|
||||||
|
use actix_multipart::Multipart;
|
||||||
|
use actix_web::{get, web, App, Error, HttpResponse, HttpServer, Responder};
|
||||||
|
use askama::Template;
|
||||||
|
use chrono::Local;
|
||||||
|
use clap::Parser;
|
||||||
|
use futures::TryStreamExt as _;
|
||||||
|
use linkify::{LinkFinder, LinkKind};
|
||||||
|
use log::LevelFilter;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
use crate::animalnumbers::{to_animal_names, to_u64};
|
use crate::animalnumbers::{to_animal_names, to_u64};
|
||||||
use crate::pasta::{Pasta, PastaFormData};
|
use crate::dbio::save_to_file;
|
||||||
|
use crate::pasta::Pasta;
|
||||||
|
|
||||||
mod animalnumbers;
|
mod animalnumbers;
|
||||||
|
mod dbio;
|
||||||
mod pasta;
|
mod pasta;
|
||||||
|
|
||||||
struct AppState {
|
struct AppState {
|
||||||
|
@ -32,6 +39,10 @@ struct Args {
|
||||||
#[template(path = "index.html")]
|
#[template(path = "index.html")]
|
||||||
struct IndexTemplate {}
|
struct IndexTemplate {}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "error.html")]
|
||||||
|
struct ErrorTemplate {}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "pasta.html")]
|
#[template(path = "pasta.html")]
|
||||||
struct PastaTemplate<'a> {
|
struct PastaTemplate<'a> {
|
||||||
|
@ -51,48 +62,94 @@ async fn index() -> impl Responder {
|
||||||
.body(IndexTemplate {}.render().unwrap())
|
.body(IndexTemplate {}.render().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/create")]
|
async fn not_found() -> Result<HttpResponse, Error> {
|
||||||
async fn create(data: web::Data<AppState>, pasta: web::Form<PastaFormData>) -> impl Responder {
|
Ok(HttpResponse::Found()
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
.content_type("text/html")
|
||||||
|
.body(ErrorTemplate {}.render().unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
let inner_pasta = pasta.into_inner();
|
async fn create(data: web::Data<AppState>, mut payload: Multipart) -> Result<HttpResponse, Error> {
|
||||||
|
let mut pastas = data.pastas.lock().unwrap();
|
||||||
|
|
||||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||||
Ok(n) => n.as_secs(),
|
Ok(n) => n.as_secs(),
|
||||||
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||||
} as i64;
|
} as i64;
|
||||||
|
|
||||||
let expiration = match inner_pasta.expiration.as_str() {
|
let mut new_pasta = Pasta {
|
||||||
"1min" => timenow + 60,
|
|
||||||
"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 pasta_type = if is_valid_url(inner_pasta.content.as_str()) {
|
|
||||||
String::from("url")
|
|
||||||
} else {
|
|
||||||
String::from("text")
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_pasta = Pasta {
|
|
||||||
id: rand::thread_rng().gen::<u16>() as u64,
|
id: rand::thread_rng().gen::<u16>() as u64,
|
||||||
content: inner_pasta.content,
|
content: String::from("No Text Content"),
|
||||||
|
file: String::from("no-file"),
|
||||||
created: timenow,
|
created: timenow,
|
||||||
pasta_type,
|
pasta_type: String::from(""),
|
||||||
expiration,
|
expiration: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
while let Some(mut field) = payload.try_next().await? {
|
||||||
|
match field.name() {
|
||||||
|
"expiration" => {
|
||||||
|
while let Some(chunk) = field.try_next().await? {
|
||||||
|
new_pasta.expiration = match std::str::from_utf8(&chunk).unwrap() {
|
||||||
|
"1min" => timenow + 60,
|
||||||
|
"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!"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
"content" => {
|
||||||
|
while let Some(chunk) = field.try_next().await? {
|
||||||
|
new_pasta.content = std::str::from_utf8(&chunk).unwrap().to_string();
|
||||||
|
new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) {
|
||||||
|
String::from("url")
|
||||||
|
} else {
|
||||||
|
String::from("text")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
"file" => {
|
||||||
|
let content_disposition = field.content_disposition();
|
||||||
|
|
||||||
|
let filename = match content_disposition.get_filename() {
|
||||||
|
Some("") => continue,
|
||||||
|
Some(filename) => filename.replace(' ', "_").to_string(),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::fs::create_dir_all(format!("./pasta_data/{}", &new_pasta.id_as_animals()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let filepath = format!("./pasta_data/{}/{}", &new_pasta.id_as_animals(), &filename);
|
||||||
|
|
||||||
|
new_pasta.file = filename;
|
||||||
|
|
||||||
|
let mut f = web::block(|| std::fs::File::create(filepath)).await??;
|
||||||
|
|
||||||
|
while let Some(chunk) = field.try_next().await? {
|
||||||
|
f = web::block(move || f.write_all(&chunk).map(|_| f)).await??;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_pasta.pasta_type = String::from("text");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let id = new_pasta.id;
|
let id = new_pasta.id;
|
||||||
|
|
||||||
pastas.push(new_pasta);
|
pastas.push(new_pasta);
|
||||||
|
|
||||||
HttpResponse::Found()
|
save_to_file(&pastas);
|
||||||
|
|
||||||
|
Ok(HttpResponse::Found()
|
||||||
.append_header(("Location", format!("/pasta/{}", to_animal_names(id))))
|
.append_header(("Location", format!("/pasta/{}", to_animal_names(id))))
|
||||||
.finish()
|
.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/pasta/{id}")]
|
#[get("/pasta/{id}")]
|
||||||
|
@ -110,7 +167,9 @@ async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpRespo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse::Found().body("Pasta not found! :-(")
|
HttpResponse::Found()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(ErrorTemplate {}.render().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/url/{id}")]
|
#[get("/url/{id}")]
|
||||||
|
@ -138,6 +197,7 @@ async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpRe
|
||||||
#[get("/raw/{id}")]
|
#[get("/raw/{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 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);
|
remove_expired(&mut pastas);
|
||||||
|
@ -166,7 +226,6 @@ async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRespons
|
||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse::Found().body("Pasta not found! :-(")
|
HttpResponse::Found().body("Pasta not found! :-(")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,26 +243,44 @@ async fn list(data: web::Data<AppState>) -> HttpResponse {
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
println!(
|
|
||||||
"{}",
|
Builder::new()
|
||||||
format!("Listening on http://127.0.0.1:{}", args.port.to_string())
|
.format(|buf, record| {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"{} [{}] - {}",
|
||||||
|
Local::now().format("%Y-%m-%dT%H:%M:%S"),
|
||||||
|
record.level(),
|
||||||
|
record.args()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.filter(None, LevelFilter::Info)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"MicroBin listening on http://127.0.0.1:{}",
|
||||||
|
args.port.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
std::fs::create_dir_all("./pasta_data").unwrap();
|
||||||
|
|
||||||
let data = web::Data::new(AppState {
|
let data = web::Data::new(AppState {
|
||||||
pastas: Mutex::new(Vec::new()),
|
pastas: Mutex::new(dbio::load_from_file().unwrap()),
|
||||||
});
|
});
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.app_data(data.clone())
|
.app_data(data.clone())
|
||||||
.service(index)
|
.service(index)
|
||||||
.service(create)
|
|
||||||
.service(getpasta)
|
.service(getpasta)
|
||||||
.service(redirecturl)
|
.service(redirecturl)
|
||||||
.service(getrawpasta)
|
.service(getrawpasta)
|
||||||
.service(remove)
|
.service(remove)
|
||||||
.service(list)
|
.service(list)
|
||||||
.service(fs::Files::new("/static", "./static"))
|
.service(fs::Files::new("/static", "./static"))
|
||||||
|
.service(fs::Files::new("/file", "./pasta_data"))
|
||||||
|
.service(web::resource("/upload").route(web::post().to(create)))
|
||||||
|
.default_service(web::route().to(not_found))
|
||||||
})
|
})
|
||||||
.bind(format!("127.0.0.1:{}", args.port.to_string()))?
|
.bind(format!("127.0.0.1:{}", args.port.to_string()))?
|
||||||
.run()
|
.run()
|
||||||
|
|
83
src/pasta.rs
83
src/pasta.rs
|
@ -1,58 +1,51 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use actix_web::cookie::time::macros::format_description;
|
|
||||||
use chrono::{Datelike, DateTime, NaiveDateTime, Timelike, Utc};
|
use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc};
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::to_animal_names;
|
use crate::to_animal_names;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Pasta {
|
pub struct Pasta {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub created: i64,
|
pub file: String,
|
||||||
pub expiration: i64,
|
pub created: i64,
|
||||||
pub pasta_type: String
|
pub expiration: i64,
|
||||||
}
|
pub pasta_type: String,
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct PastaFormData {
|
|
||||||
pub content: String,
|
|
||||||
pub expiration: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pasta {
|
impl Pasta {
|
||||||
|
pub fn id_as_animals(&self) -> String {
|
||||||
|
to_animal_names(self.id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn idAsAnimals(&self) -> String {
|
pub fn created_as_string(&self) -> String {
|
||||||
to_animal_names(self.id)
|
let date = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.created, 0), Utc);
|
||||||
}
|
format!(
|
||||||
|
"{:02}-{:02} {}:{}",
|
||||||
pub fn createdAsString(&self) -> String {
|
date.month(),
|
||||||
let date = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.created, 0), Utc);
|
date.day(),
|
||||||
format!(
|
date.hour(),
|
||||||
"{}-{:02}-{:02} {}:{}",
|
date.minute(),
|
||||||
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(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn expiration_as_string(&self) -> String {
|
||||||
|
let date =
|
||||||
|
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.expiration, 0), Utc);
|
||||||
|
format!(
|
||||||
|
"{:02}-{:02} {}:{}",
|
||||||
|
date.month(),
|
||||||
|
date.day(),
|
||||||
|
date.hour(),
|
||||||
|
date.minute(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl fmt::Display for Pasta {
|
impl fmt::Display for Pasta {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.content)
|
write!(f, "{}", self.content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
templates/error.html
Normal file
10
templates/error.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% include "header.html" %}
|
||||||
|
<br>
|
||||||
|
<h2>404</h2>
|
||||||
|
<b>Not Found</b>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<a href="/" > Go Home</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% include "footer.html" %}
|
|
@ -1,8 +1,8 @@
|
||||||
{% include "header.html" %}
|
{% include "header.html" %}
|
||||||
<form action="create" method="POST">
|
<form action="upload" method="POST" enctype="multipart/form-data">
|
||||||
<br>
|
<br>
|
||||||
<label for="expiration">Expiration</label><br>
|
<label for="expiration">Expiration</label><br>
|
||||||
<select name="expiration" id="expiration">
|
<select style="width: 100%;" name="expiration" id="expiration">
|
||||||
<optgroup label="Expire">
|
<optgroup label="Expire">
|
||||||
<option value="1min">1 minute</option>
|
<option value="1min">1 minute</option>
|
||||||
<option value="10min">10 minutes</option>
|
<option value="10min">10 minutes</option>
|
||||||
|
@ -17,7 +17,11 @@
|
||||||
<br>
|
<br>
|
||||||
<textarea style="width: 100%; min-height: 100px" name="content" autofocus></textarea>
|
<textarea style="width: 100%; min-height: 100px" name="content" autofocus></textarea>
|
||||||
<br>
|
<br>
|
||||||
<input style="width: 100px; background-color: limegreen"; type="submit" value="Save"/>
|
<label>File attachment</label>
|
||||||
|
<br>
|
||||||
|
<input style="width: 100%;" type="file" id="file" name="file">
|
||||||
|
<br>
|
||||||
|
<input style="width: 120px; background-color: limegreen" ; type="submit" value="Save"/>
|
||||||
<br>
|
<br>
|
||||||
</form>
|
</form>
|
||||||
{% include "footer.html" %}
|
{% include "footer.html" %}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
{% include "header.html" %}
|
{% include "header.html" %}
|
||||||
<a href="/raw/{{pasta.idAsAnimals()}}">Raw Pasta</a>
|
<a style="margin-right: 0.5rem" href="/raw/{{pasta.id_as_animals()}}">Raw Text Content</a>
|
||||||
|
{% if pasta.file != "no-file" %}
|
||||||
|
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file}}">Attached file '{{pasta.file}}'</a>
|
||||||
|
{%- endif %}
|
||||||
|
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||||
<pre><code>{{pasta}}</code></pre>
|
<pre><code>{{pasta}}</code></pre>
|
||||||
|
|
||||||
{% include "footer.html" %}
|
{% include "footer.html" %}
|
||||||
|
|
|
@ -2,94 +2,99 @@
|
||||||
|
|
||||||
|
|
||||||
{% if pastas.is_empty() %}
|
{% if pastas.is_empty() %}
|
||||||
|
<br>
|
||||||
<p>
|
<p>
|
||||||
No pastas yet. 😔 Create one <a href="/">here</a>.
|
No pastas yet. 😔 Create one <a href="/">here</a>.
|
||||||
</p>
|
</p>
|
||||||
|
<br>
|
||||||
{%- else %}
|
{%- else %}
|
||||||
<br>
|
<br>
|
||||||
<table style="width: 100%">
|
<table style="width: 100%">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="4">Pastas</th>
|
<th colspan="4">Pastas</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
Key
|
Key
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Created
|
Created
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
Expiration
|
Expiration
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
|
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for pasta in pastas %}
|
{% for pasta in pastas %}
|
||||||
{% if pasta.pasta_type == "text" %}
|
{% if pasta.pasta_type == "text" %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="/pasta/{{pasta.idAsAnimals()}}">{{pasta.idAsAnimals()}}</a>
|
<a href="/pasta/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{pasta.createdAsString()}}
|
{{pasta.created_as_string()}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{pasta.expirationAsString()}}
|
{{pasta.expiration_as_string()}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a style="margin-right:1rem" href="/raw/{{pasta.idAsAnimals()}}">Raw</a>
|
<a style="margin-right:1rem" href="/raw/{{pasta.id_as_animals()}}">Raw</a>
|
||||||
<a href="/remove/{{pasta.idAsAnimals()}}">Remove</a>
|
{% if pasta.file != "no-file" %}
|
||||||
</td>
|
<a style="margin-right:1rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file}}">File</a>
|
||||||
</tr>
|
|
||||||
{%- endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<br>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="4">URL Redirects</th>
|
|
||||||
</tr>
|
|
||||||
<tr >
|
|
||||||
<th>
|
|
||||||
Key
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Created
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Expiration
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
{% for pasta in pastas %}
|
|
||||||
{% if pasta.pasta_type == "url" %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="/url/{{pasta.idAsAnimals()}}">{{pasta.idAsAnimals()}}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{pasta.createdAsString()}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{pasta.expirationAsString()}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a style="margin-right:1rem" href="/raw/{{pasta.idAsAnimals()}}">Raw</a>
|
|
||||||
<a href="/remove/{{pasta.idAsAnimals()}}">Remove</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
<a href="/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{%- endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="4">URL Redirects</th>
|
||||||
|
</tr>
|
||||||
|
<tr >
|
||||||
|
<th>
|
||||||
|
Key
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Created
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Expiration
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for pasta in pastas %}
|
||||||
|
{% if pasta.pasta_type == "url" %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/url/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{pasta.created_as_string()}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{pasta.expiration_as_string()}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a style="margin-right:1rem" href="/raw/{{pasta.id_as_animals()}}">Raw</a>
|
||||||
|
<a href="/remove/{{pasta.id_as_animals()}}">Remove</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{%- endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<br>
|
<br>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
Loading…
Reference in a new issue