mirror of
https://github.com/Dangoware/confetti-box.git
synced 2025-04-19 07:12:58 -05:00
Added database for backend operations
This commit is contained in:
parent
0681af1633
commit
af925677f9
5 changed files with 215 additions and 30 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -128,6 +128,25 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95"
|
||||
dependencies = [
|
||||
"bincode_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode_derive"
|
||||
version = "2.0.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c"
|
||||
dependencies = [
|
||||
"virtue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
|
@ -145,6 +164,7 @@ dependencies = [
|
|||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -196,6 +216,7 @@ dependencies = [
|
|||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
@ -713,11 +734,13 @@ dependencies = [
|
|||
name = "mochihost"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"blake3",
|
||||
"chrono",
|
||||
"log",
|
||||
"maud",
|
||||
"rand",
|
||||
"rocket",
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -1546,6 +1569,12 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "virtue"
|
||||
version = "0.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314"
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -4,9 +4,17 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
blake3 = "1.5.4"
|
||||
chrono = "0.4.38"
|
||||
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
|
||||
blake3 = { version = "1.5.4", features = ["serde"] }
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
log = "0.4"
|
||||
maud = { version = "0.26", features = ["rocket"] }
|
||||
rand = "0.8"
|
||||
rocket = "0.5"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
uuid = { version = "1.11.0", features = ["v4"] }
|
||||
|
||||
[profile.production]
|
||||
inherits = "release"
|
||||
lto = true
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
|
|
113
src/database.rs
Normal file
113
src/database.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use std::{collections::HashMap, fs::{self, File}, path::{Path, PathBuf}};
|
||||
|
||||
use bincode::{config::Configuration, decode_from_std_read, encode_into_std_write, Decode, Encode};
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use blake3::Hash;
|
||||
|
||||
const BINCODE_CFG: Configuration = bincode::config::standard();
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Decode, Encode)]
|
||||
pub struct Database {
|
||||
path: PathBuf,
|
||||
#[bincode(with_serde)]
|
||||
pub files: HashMap<MochiKey, MochiFile>
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn new<P: AsRef<Path>>(path: &P) -> Self {
|
||||
let mut file = File::create_new(path).expect("Could not create database!");
|
||||
|
||||
let output = Self {
|
||||
path: path.as_ref().to_path_buf(),
|
||||
files: HashMap::new()
|
||||
};
|
||||
|
||||
encode_into_std_write(&output, &mut file, BINCODE_CFG).expect("Could not write database!");
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<Path>>(path: &P) -> Self {
|
||||
if !path.as_ref().exists() {
|
||||
Self::new(path)
|
||||
} else {
|
||||
let mut file = File::open(path).expect("Could not get database file!");
|
||||
decode_from_std_read(&mut file, BINCODE_CFG).expect("Could not decode database")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
let mut out_path = self.path.clone();
|
||||
out_path.set_extension(".bkp");
|
||||
let mut file = File::create(&out_path).expect("Could not save!");
|
||||
encode_into_std_write(&self, &mut file, BINCODE_CFG).expect("Could not write out!");
|
||||
|
||||
fs::rename(out_path, &self.path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Decode, Encode)]
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct MochiFile {
|
||||
/// The original name of the file
|
||||
name: String,
|
||||
|
||||
/// Size of the file in bytes
|
||||
size: usize,
|
||||
|
||||
/// The location on disk (for deletion and management)
|
||||
filename: PathBuf,
|
||||
|
||||
/// The hashed contents of the file as a Blake3 hash
|
||||
#[bincode(with_serde)]
|
||||
hash: Hash,
|
||||
|
||||
/// The datetime when the file was uploaded
|
||||
#[bincode(with_serde)]
|
||||
upload_datetime: DateTime<Utc>,
|
||||
|
||||
/// The datetime when the file is set to expire
|
||||
#[bincode(with_serde)]
|
||||
expiry_datetime: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl MochiFile {
|
||||
/// Create a new file that expires in `expiry`.
|
||||
pub fn new_with_expiry(
|
||||
name: &str,
|
||||
size: usize,
|
||||
hash: Hash,
|
||||
filename: PathBuf,
|
||||
expire_duration: TimeDelta
|
||||
) -> Self {
|
||||
let current = Utc::now();
|
||||
let expiry = current + expire_duration;
|
||||
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
size,
|
||||
filename,
|
||||
hash,
|
||||
upload_datetime: current,
|
||||
expiry_datetime: expiry,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_key(&self) -> MochiKey {
|
||||
MochiKey {
|
||||
name: self.name.clone(),
|
||||
hash: self.hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Decode, Encode)]
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct MochiKey {
|
||||
name: String,
|
||||
#[bincode(with_serde)]
|
||||
hash: Hash,
|
||||
}
|
84
src/main.rs
84
src/main.rs
|
@ -1,8 +1,13 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
mod database;
|
||||
|
||||
use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}};
|
||||
use blake3::Hash;
|
||||
use chrono::TimeDelta;
|
||||
use database::{Database, MochiFile};
|
||||
use log::info;
|
||||
use maud::{html, Markup, DOCTYPE, PreEscaped};
|
||||
use rocket::{
|
||||
form::Form, fs::{FileServer, Options, TempFile}, get, post, routes, tokio::{fs::File, io::AsyncReadExt}, FromForm
|
||||
form::Form, fs::{FileServer, Options, TempFile}, get, post, routes, tokio::{fs::File, io::AsyncReadExt}, FromForm, State
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -10,6 +15,7 @@ fn head(page_title: &str) -> Markup {
|
|||
html! {
|
||||
(DOCTYPE)
|
||||
meta charset="UTF-8";
|
||||
meta name="viewport" content="width=device-width, initial-scale=1";
|
||||
title { (page_title) }
|
||||
// Javascript stuff for client side handling
|
||||
script { (PreEscaped(include_str!("static/form_handler.js"))) }
|
||||
|
@ -39,17 +45,6 @@ fn home() -> Markup {
|
|||
}
|
||||
}
|
||||
|
||||
struct Database {
|
||||
files: Vec<File>
|
||||
}
|
||||
|
||||
struct MochiFile {
|
||||
/// The original name of the file
|
||||
name: String,
|
||||
/// The hashed contents of the file as a Blake3 hash
|
||||
hash: Hash,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Upload<'r> {
|
||||
#[field(name = "fileUpload")]
|
||||
|
@ -58,7 +53,10 @@ struct Upload<'r> {
|
|||
|
||||
/// Handle a file upload and store it
|
||||
#[post("/upload", data = "<file_data>")]
|
||||
async fn handle_upload(mut file_data: Form<Upload<'_>>) -> Result<(), std::io::Error> {
|
||||
async fn handle_upload(
|
||||
mut file_data: Form<Upload<'_>>,
|
||||
db: &State<Arc<RwLock<Database>>>
|
||||
) -> Result<(), std::io::Error> {
|
||||
let mut out_path = PathBuf::from("files/");
|
||||
|
||||
// Get temp path and hash it
|
||||
|
@ -66,42 +64,78 @@ async fn handle_upload(mut file_data: Form<Upload<'_>>) -> Result<(), std::io::E
|
|||
file_data.file.persist_to(&temp_filename).await?;
|
||||
let hash = hash_file(&temp_filename).await?;
|
||||
|
||||
out_path.push(get_filename(
|
||||
let filename = get_filename(
|
||||
// TODO: Properly sanitize this...
|
||||
file_data.file.raw_name().unwrap().dangerous_unsafe_unsanitized_raw().as_str(),
|
||||
hash
|
||||
));
|
||||
hash.0
|
||||
);
|
||||
out_path.push(filename);
|
||||
|
||||
let constructed_file = MochiFile::new_with_expiry(
|
||||
file_data.file.raw_name().unwrap().dangerous_unsafe_unsanitized_raw().as_str(),
|
||||
hash.1,
|
||||
hash.0,
|
||||
out_path.clone(),
|
||||
TimeDelta::hours(24)
|
||||
);
|
||||
|
||||
// Move it to the new proper place
|
||||
std::fs::rename(temp_filename, out_path)?;
|
||||
|
||||
db.write().unwrap().files.insert(constructed_file.get_key(), constructed_file);
|
||||
db.write().unwrap().save();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn hash_file<P: AsRef<Path>>(input: &P) -> Result<Hash, std::io::Error> {
|
||||
/// Get the Blake3 hash of a file, without reading it all into memory, and also get the size
|
||||
async fn hash_file<P: AsRef<Path>>(input: &P) -> Result<(Hash, usize), std::io::Error> {
|
||||
let mut file = File::open(input).await?;
|
||||
let mut buf = vec![0; 5000000];
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
|
||||
let mut total = 0;
|
||||
let mut bytes_read = None;
|
||||
while bytes_read != Some(0) {
|
||||
bytes_read = Some(file.read(&mut buf).await?);
|
||||
total += bytes_read.unwrap();
|
||||
hasher.update(&buf[..bytes_read.unwrap()]);
|
||||
dbg!(bytes_read);
|
||||
}
|
||||
|
||||
Ok(hasher.finalize())
|
||||
Ok((hasher.finalize(), total))
|
||||
}
|
||||
|
||||
/// Get a random filename for use as the uploaded file's name
|
||||
fn get_filename(name: &str, hash: Hash) -> String {
|
||||
let uuid = hash.to_hex()[0..10].to_string() + "_" + name;
|
||||
uuid
|
||||
hash.to_hex()[0..10].to_string() + "_" + name
|
||||
}
|
||||
|
||||
#[rocket::launch]
|
||||
fn launch() -> _ {
|
||||
rocket::build()
|
||||
/*
|
||||
/// Handle a file upload and store it
|
||||
#[post("/query", data = "<file_data>")]
|
||||
async fn handle_upload(
|
||||
mut file_data: Form<Upload<'_>>,
|
||||
db: &State<Arc<RwLock<Database>>>
|
||||
) -> Result<(), std::io::Error> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
#[rocket::main]
|
||||
async fn main() {
|
||||
let database = Arc::new(RwLock::new(Database::open(&"database.mochi")));
|
||||
let local_db = database.clone();
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(database)
|
||||
.mount("/", routes![home, handle_upload])
|
||||
.mount("/files", FileServer::new("files/", Options::Missing | Options::NormalizeDirs))
|
||||
.launch()
|
||||
.await;
|
||||
|
||||
rocket.expect("Server failed to shutdown gracefully");
|
||||
|
||||
info!("Saving database on shutdown...");
|
||||
local_db.write().unwrap().save();
|
||||
}
|
||||
|
|
|
@ -44,7 +44,8 @@ progress[value]::-webkit-progress-value {
|
|||
.progress-box {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 500px;
|
||||
max-width: 500px;
|
||||
width: 80vw;
|
||||
height: 20px;
|
||||
margin: 10px auto;
|
||||
|
||||
|
|
Loading…
Reference in a new issue