Database works, uploading now shows the resulting link to the client

This commit is contained in:
G2-Games 2024-10-22 03:07:28 -05:00
parent af925677f9
commit 676fe5f1d7
9 changed files with 154 additions and 77 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target /target
/tmp /tmp
/src/files /src/files
*.mochi

14
Cargo.lock generated
View file

@ -187,9 +187,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.7.2" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
[[package]] [[package]]
name = "cc" name = "cc"
@ -740,7 +740,6 @@ dependencies = [
"log", "log",
"maud", "maud",
"rocket", "rocket",
"serde",
"uuid", "uuid",
] ]
@ -1081,6 +1080,7 @@ dependencies = [
"rocket_codegen", "rocket_codegen",
"rocket_http", "rocket_http",
"serde", "serde",
"serde_json",
"state", "state",
"tempfile", "tempfile",
"time", "time",
@ -1181,18 +1181,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.210" version = "1.0.211"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.210" version = "1.0.211"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -9,8 +9,7 @@ blake3 = { version = "1.5.4", features = ["serde"] }
chrono = { version = "0.4.38", features = ["serde"] } chrono = { version = "0.4.38", features = ["serde"] }
log = "0.4" log = "0.4"
maud = { version = "0.26", features = ["rocket"] } maud = { version = "0.26", features = ["rocket"] }
rocket = "0.5" rocket = { version = "0.5", features = ["json"] }
serde = { version = "1.0.210", features = ["derive"] }
uuid = { version = "1.11.0", features = ["v4"] } uuid = { version = "1.11.0", features = ["v4"] }
[profile.production] [profile.production]

View file

@ -4,9 +4,5 @@ port = 8950
temp_dir = "tmp" temp_dir = "tmp"
[default.limits] [default.limits]
form = "1 GiB" data-form = "10 GB"
data-form = "1 GiB" file = "10 GB"
file = "1 GiB"
bytes = "1 GiB"
json = "1 GiB"
msgpack = "1 GiB"

View file

@ -3,6 +3,7 @@ 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 bincode::{config::Configuration, decode_from_std_read, encode_into_std_write, Decode, Encode};
use chrono::{DateTime, TimeDelta, Utc}; use chrono::{DateTime, TimeDelta, Utc};
use blake3::Hash; use blake3::Hash;
use rocket::serde::{Deserialize, Serialize};
const BINCODE_CFG: Configuration = bincode::config::standard(); const BINCODE_CFG: Configuration = bincode::config::standard();
@ -49,7 +50,8 @@ impl Database {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[derive(Decode, Encode)] #[derive(Decode, Encode)]
#[derive(serde::Deserialize, serde::Serialize)] #[derive(Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct MochiFile { pub struct MochiFile {
/// The original name of the file /// The original name of the file
name: String, name: String,
@ -95,17 +97,26 @@ impl MochiFile {
} }
} }
pub fn name(&self) -> &String {
&self.name
}
pub fn get_key(&self) -> MochiKey { pub fn get_key(&self) -> MochiKey {
MochiKey { MochiKey {
name: self.name.clone(), name: self.name.clone(),
hash: self.hash hash: self.hash
} }
} }
pub fn get_expiry(&self) -> DateTime<Utc> {
self.expiry_datetime
}
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Decode, Encode)] #[derive(Decode, Encode)]
#[derive(serde::Deserialize, serde::Serialize)] #[derive(Deserialize, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct MochiKey { pub struct MochiKey {
name: String, name: String,
#[bincode(with_serde)] #[bincode(with_serde)]

View file

@ -2,12 +2,12 @@ mod database;
use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}}; use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}};
use blake3::Hash; use blake3::Hash;
use chrono::TimeDelta; use chrono::{DateTime, TimeDelta, Utc};
use database::{Database, MochiFile}; use database::{Database, MochiFile};
use log::info; 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, State form::Form, fs::{FileServer, Options, TempFile}, get, post, routes, serde::{json::Json, Serialize}, tokio::{fs::File, io::AsyncReadExt}, FromForm, State
}; };
use uuid::Uuid; use uuid::Uuid;
@ -18,7 +18,7 @@ fn head(page_title: &str) -> Markup {
meta name="viewport" content="width=device-width, initial-scale=1"; 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/request.js"))) }
// CSS for styling the sheets // CSS for styling the sheets
style { (PreEscaped(include_str!("static/main.css"))) } style { (PreEscaped(include_str!("static/main.css"))) }
} }
@ -29,7 +29,8 @@ fn home() -> Markup {
html! { html! {
(head("Mochi")) (head("Mochi"))
body { body {
div class="main-wrapper" { main {
section class="centered" {
form id="uploadForm" { form id="uploadForm" {
label for="fileUpload" class="file-upload" onclick="document.getElementById('fileInput').click()" { label for="fileUpload" class="file-upload" onclick="document.getElementById('fileInput').click()" {
"Upload File" "Upload File"
@ -41,6 +42,11 @@ fn home() -> Markup {
p id="uploadProgressValue" class="progress-value" { "0%" } p id="uploadProgressValue" class="progress-value" { "0%" }
} }
} }
section class="centered" id="uploadedFilesDisplay" {
h2 class="sep center" { "Uploaded Files" }
}
}
} }
} }
} }
@ -56,7 +62,7 @@ struct Upload<'r> {
async fn handle_upload( async fn handle_upload(
mut file_data: Form<Upload<'_>>, mut file_data: Form<Upload<'_>>,
db: &State<Arc<RwLock<Database>>> db: &State<Arc<RwLock<Database>>>
) -> Result<(), std::io::Error> { ) -> Result<Json<FileLocation>, 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
@ -69,7 +75,7 @@ async fn handle_upload(
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.0 hash.0
); );
out_path.push(filename); out_path.push(filename.clone());
let constructed_file = MochiFile::new_with_expiry( let constructed_file = MochiFile::new_with_expiry(
file_data.file.raw_name().unwrap().dangerous_unsafe_unsanitized_raw().as_str(), file_data.file.raw_name().unwrap().dangerous_unsafe_unsanitized_raw().as_str(),
@ -82,10 +88,26 @@ async fn handle_upload(
// 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().files.insert(constructed_file.get_key(), constructed_file.clone());
db.write().unwrap().save(); db.write().unwrap().save();
Ok(()) let location = FileLocation {
name: constructed_file.name().clone(),
status: true,
url: Some("files/".to_string() + &filename),
expires: constructed_file.get_expiry(),
};
Ok(Json(location))
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct FileLocation {
pub name: String,
pub status: bool,
pub url: Option<String>,
pub expires: DateTime<Utc>,
} }
/// Get the Blake3 hash of a file, without reading it all into memory, and also get the size /// Get the Blake3 hash of a file, without reading it all into memory, and also get the size

View file

@ -1,41 +0,0 @@
let progressBar = null;
let progressValue = null;
function formSubmit(form) {
let url = "/upload";
let request = new XMLHttpRequest();
request.open('POST', url, true);
request.onload = function() { // request successful
console.log(request.responseText);
};
request.upload.onprogress = uploadProgress;
request.onerror = function() {
console.log(request.responseText);
};
// Create and send FormData
request.send(new FormData(form));
// Reset the form data since we've successfully submitted it
form.reset();
}
function uploadProgress(progress) {
if (progress.lengthComputable) {
const progressPercent = Math.floor((progress.loaded / progress.total) * 100);
progressBar.value = progressPercent;
progressValue.textContent = progressPercent + "%";
}
}
function attachFormSubmitEvent(formId) {
document.getElementById(formId).addEventListener("submit", formSubmit);
}
document.addEventListener("DOMContentLoaded", function(_event){
attachFormSubmitEvent("uploadForm");
progressBar = document.getElementById("uploadProgress");
progressValue = document.getElementById("uploadProgressValue");
})

View file

@ -26,17 +26,22 @@ progress[value] {
border-radius: 5px; border-radius: 5px;
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
background-color: #fffde6;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
progress[value]::-webkit-progress-bar, progress[value]::-moz-progress-bar { ::-webkit-progress-bar {
background-color: #a6e3a1;
border-radius: 5px; border-radius: 5px;
background-color: #fffde6;
} }
progress[value]::-webkit-progress-value { progress[value]::-moz-progress-bar {
border-radius: 5px;
background-color: #a6e3a1;
}
::-webkit-progress-value {
background-color: #a6e3a1; background-color: #a6e3a1;
border-radius: 5px; border-radius: 5px;
} }

84
src/static/request.js Normal file
View file

@ -0,0 +1,84 @@
let progressBar = null;
let progressValue = null;
let statusNotifier = null;
let uploadInProgress = false;
let uploadedFilesDisplay = null;
async function formSubmit(form) {
let url = "/upload";
let request = new XMLHttpRequest();
request.open('POST', url, true);
request.addEventListener('load', uploadComplete, false);
request.addEventListener('error', networkErrorHandler, false);
request.upload.addEventListener('progress', uploadProgress, false);
uploadInProgress = true;
// Create and send FormData
try {
request.send(new FormData(form));
} catch (e) {
console.log(e);
}
// Reset the form data since we've successfully submitted it
form.reset();
}
function networkErrorHandler(_err) {
uploadInProgress = false;
console.log("An error occured while uploading");
progressValue.textContent = "A network error occured!";
}
function uploadComplete(response) {
let target = response.target;
console.log(target);
if (target.status === 200) {
const response = JSON.parse(target.responseText);
console.log(response);
if (response.status) {
progressValue.textContent = "Success";
addToList(response.name, response.url);
}
} else if (target.status === 413) {
progressValue.textContent = "File too large!";
}
uploadInProgress = false;
}
function addToList(filename, link) {
const link_row = uploadedFilesDisplay.appendChild(document.createElement("p"));
const new_link = link_row.appendChild(document.createElement("a"));
new_link.href = link;
new_link.textContent = filename;
}
function uploadProgress(progress) {
if (progress.lengthComputable) {
const progressPercent = Math.floor((progress.loaded / progress.total) * 100);
progressBar.value = progressPercent;
progressValue.textContent = progressPercent + "%";
}
}
function attachFormSubmitEvent(formId) {
if (uploadInProgress) {
return;
}
document.getElementById(formId).addEventListener("submit", formSubmit);
}
document.addEventListener("DOMContentLoaded", function(_event){
attachFormSubmitEvent("uploadForm");
progressBar = document.getElementById("uploadProgress");
progressValue = document.getElementById("uploadProgressValue");
statusNotifier = document.getElementById("uploadStatus");
uploadedFilesDisplay = document.getElementById("uploadedFilesDisplay");
})