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
161
src/main.rs
161
src/main.rs
|
@ -1,20 +1,27 @@
|
|||
extern crate core;
|
||||
|
||||
use actix_files as fs;
|
||||
use actix_web::web::Data;
|
||||
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 env_logger::Builder;
|
||||
use std::io::Write;
|
||||
use std::sync::Mutex;
|
||||
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::pasta::{Pasta, PastaFormData};
|
||||
use crate::dbio::save_to_file;
|
||||
use crate::pasta::Pasta;
|
||||
|
||||
mod animalnumbers;
|
||||
mod dbio;
|
||||
mod pasta;
|
||||
|
||||
struct AppState {
|
||||
|
@ -32,6 +39,10 @@ struct Args {
|
|||
#[template(path = "index.html")]
|
||||
struct IndexTemplate {}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "error.html")]
|
||||
struct ErrorTemplate {}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "pasta.html")]
|
||||
struct PastaTemplate<'a> {
|
||||
|
@ -51,48 +62,94 @@ async fn index() -> impl Responder {
|
|||
.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();
|
||||
async fn not_found() -> Result<HttpResponse, Error> {
|
||||
Ok(HttpResponse::Found()
|
||||
.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) {
|
||||
Ok(n) => n.as_secs(),
|
||||
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||
} as i64;
|
||||
|
||||
let expiration = match inner_pasta.expiration.as_str() {
|
||||
"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 {
|
||||
let mut new_pasta = Pasta {
|
||||
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,
|
||||
pasta_type,
|
||||
expiration,
|
||||
pasta_type: String::from(""),
|
||||
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;
|
||||
|
||||
pastas.push(new_pasta);
|
||||
|
||||
HttpResponse::Found()
|
||||
save_to_file(&pastas);
|
||||
|
||||
Ok(HttpResponse::Found()
|
||||
.append_header(("Location", format!("/pasta/{}", to_animal_names(id))))
|
||||
.finish()
|
||||
.finish())
|
||||
}
|
||||
|
||||
#[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}")]
|
||||
|
@ -138,6 +197,7 @@ async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpRe
|
|||
#[get("/raw/{id}")]
|
||||
async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String {
|
||||
let mut pastas = data.pastas.lock().unwrap();
|
||||
|
||||
let id = to_u64(&*id.into_inner());
|
||||
|
||||
remove_expired(&mut pastas);
|
||||
|
@ -166,7 +226,6 @@ async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRespons
|
|||
.finish();
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Found().body("Pasta not found! :-(")
|
||||
}
|
||||
|
||||
|
@ -184,26 +243,44 @@ async fn list(data: web::Data<AppState>) -> HttpResponse {
|
|||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let args = Args::parse();
|
||||
println!(
|
||||
"{}",
|
||||
format!("Listening on http://127.0.0.1:{}", args.port.to_string())
|
||||
|
||||
Builder::new()
|
||||
.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 {
|
||||
pastas: Mutex::new(Vec::new()),
|
||||
pastas: Mutex::new(dbio::load_from_file().unwrap()),
|
||||
});
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.app_data(data.clone())
|
||||
.service(index)
|
||||
.service(create)
|
||||
.service(getpasta)
|
||||
.service(redirecturl)
|
||||
.service(getrawpasta)
|
||||
.service(remove)
|
||||
.service(list)
|
||||
.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()))?
|
||||
.run()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue