mirror of
https://github.com/Dangoware/confetti-box.git
synced 2025-04-19 23:32: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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
|
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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
@ -145,6 +164,7 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"constant_time_eq",
|
"constant_time_eq",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -196,6 +216,7 @@ dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
@ -713,11 +734,13 @@ dependencies = [
|
||||||
name = "mochihost"
|
name = "mochihost"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
"blake3",
|
"blake3",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"log",
|
||||||
"maud",
|
"maud",
|
||||||
"rand",
|
|
||||||
"rocket",
|
"rocket",
|
||||||
|
"serde",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1546,6 +1569,12 @@ version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "virtue"
|
||||||
|
version = "0.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -4,9 +4,17 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
blake3 = "1.5.4"
|
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
|
||||||
chrono = "0.4.38"
|
blake3 = { version = "1.5.4", features = ["serde"] }
|
||||||
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
|
log = "0.4"
|
||||||
maud = { version = "0.26", features = ["rocket"] }
|
maud = { version = "0.26", features = ["rocket"] }
|
||||||
rand = "0.8"
|
|
||||||
rocket = "0.5"
|
rocket = "0.5"
|
||||||
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
uuid = { version = "1.11.0", features = ["v4"] }
|
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 blake3::Hash;
|
||||||
|
use chrono::TimeDelta;
|
||||||
|
use database::{Database, MochiFile};
|
||||||
|
use log::info;
|
||||||
use maud::{html, Markup, DOCTYPE, PreEscaped};
|
use maud::{html, Markup, DOCTYPE, PreEscaped};
|
||||||
use rocket::{
|
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;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -10,6 +15,7 @@ fn head(page_title: &str) -> Markup {
|
||||||
html! {
|
html! {
|
||||||
(DOCTYPE)
|
(DOCTYPE)
|
||||||
meta charset="UTF-8";
|
meta charset="UTF-8";
|
||||||
|
meta name="viewport" content="width=device-width, initial-scale=1";
|
||||||
title { (page_title) }
|
title { (page_title) }
|
||||||
// Javascript stuff for client side handling
|
// Javascript stuff for client side handling
|
||||||
script { (PreEscaped(include_str!("static/form_handler.js"))) }
|
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)]
|
#[derive(FromForm)]
|
||||||
struct Upload<'r> {
|
struct Upload<'r> {
|
||||||
#[field(name = "fileUpload")]
|
#[field(name = "fileUpload")]
|
||||||
|
@ -58,7 +53,10 @@ struct Upload<'r> {
|
||||||
|
|
||||||
/// Handle a file upload and store it
|
/// Handle a file upload and store it
|
||||||
#[post("/upload", data = "<file_data>")]
|
#[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/");
|
let mut out_path = PathBuf::from("files/");
|
||||||
|
|
||||||
// Get temp path and hash it
|
// 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?;
|
file_data.file.persist_to(&temp_filename).await?;
|
||||||
let hash = hash_file(&temp_filename).await?;
|
let hash = hash_file(&temp_filename).await?;
|
||||||
|
|
||||||
out_path.push(get_filename(
|
let filename = get_filename(
|
||||||
// TODO: Properly sanitize this...
|
// TODO: Properly sanitize this...
|
||||||
file_data.file.raw_name().unwrap().dangerous_unsafe_unsanitized_raw().as_str(),
|
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
|
// Move it to the new proper place
|
||||||
std::fs::rename(temp_filename, out_path)?;
|
std::fs::rename(temp_filename, out_path)?;
|
||||||
|
|
||||||
|
db.write().unwrap().files.insert(constructed_file.get_key(), constructed_file);
|
||||||
|
db.write().unwrap().save();
|
||||||
|
|
||||||
Ok(())
|
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 file = File::open(input).await?;
|
||||||
let mut buf = vec![0; 5000000];
|
let mut buf = vec![0; 5000000];
|
||||||
let mut hasher = blake3::Hasher::new();
|
let mut hasher = blake3::Hasher::new();
|
||||||
|
|
||||||
|
let mut total = 0;
|
||||||
let mut bytes_read = None;
|
let mut bytes_read = None;
|
||||||
while bytes_read != Some(0) {
|
while bytes_read != Some(0) {
|
||||||
bytes_read = Some(file.read(&mut buf).await?);
|
bytes_read = Some(file.read(&mut buf).await?);
|
||||||
|
total += bytes_read.unwrap();
|
||||||
hasher.update(&buf[..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
|
/// Get a random filename for use as the uploaded file's name
|
||||||
fn get_filename(name: &str, hash: Hash) -> String {
|
fn get_filename(name: &str, hash: Hash) -> String {
|
||||||
let uuid = hash.to_hex()[0..10].to_string() + "_" + name;
|
hash.to_hex()[0..10].to_string() + "_" + name
|
||||||
uuid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::launch]
|
/*
|
||||||
fn launch() -> _ {
|
/// Handle a file upload and store it
|
||||||
rocket::build()
|
#[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("/", routes![home, handle_upload])
|
||||||
.mount("/files", FileServer::new("files/", Options::Missing | Options::NormalizeDirs))
|
.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 {
|
.progress-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 500px;
|
max-width: 500px;
|
||||||
|
width: 80vw;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue