mirror of
https://github.com/Dangoware/confetti-box.git
synced 2025-04-19 15:22:57 -05:00
Database works, uploading now shows the resulting link to the client
This commit is contained in:
parent
af925677f9
commit
676fe5f1d7
9 changed files with 154 additions and 77 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
/tmp
|
/tmp
|
||||||
/src/files
|
/src/files
|
||||||
|
*.mochi
|
||||||
|
|
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
38
src/main.rs
38
src/main.rs
|
@ -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
|
||||||
|
|
|
@ -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");
|
|
||||||
})
|
|
|
@ -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
84
src/static/request.js
Normal 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");
|
||||||
|
})
|
Loading…
Reference in a new issue