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
/tmp
/src/files
*.mochi

14
Cargo.lock generated
View file

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

View file

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

View file

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

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

View file

@ -2,12 +2,12 @@ mod database;
use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}};
use blake3::Hash;
use chrono::TimeDelta;
use chrono::{DateTime, TimeDelta, Utc};
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, State
form::Form, fs::{FileServer, Options, TempFile}, get, post, routes, serde::{json::Json, Serialize}, tokio::{fs::File, io::AsyncReadExt}, FromForm, State
};
use uuid::Uuid;
@ -18,7 +18,7 @@ fn head(page_title: &str) -> Markup {
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"))) }
script { (PreEscaped(include_str!("static/request.js"))) }
// CSS for styling the sheets
style { (PreEscaped(include_str!("static/main.css"))) }
}
@ -29,7 +29,8 @@ fn home() -> Markup {
html! {
(head("Mochi"))
body {
div class="main-wrapper" {
main {
section class="centered" {
form id="uploadForm" {
label for="fileUpload" class="file-upload" onclick="document.getElementById('fileInput').click()" {
"Upload File"
@ -41,6 +42,11 @@ fn home() -> Markup {
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(
mut file_data: Form<Upload<'_>>,
db: &State<Arc<RwLock<Database>>>
) -> Result<(), std::io::Error> {
) -> Result<Json<FileLocation>, std::io::Error> {
let mut out_path = PathBuf::from("files/");
// 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(),
hash.0
);
out_path.push(filename);
out_path.push(filename.clone());
let constructed_file = MochiFile::new_with_expiry(
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
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();
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

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;
-webkit-appearance: none;
appearance: none;
background-color: #fffde6;
width: 100%;
height: 100%;
}
progress[value]::-webkit-progress-bar, progress[value]::-moz-progress-bar {
background-color: #a6e3a1;
::-webkit-progress-bar {
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;
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");
})