mirror of
https://github.com/Dangoware/confetti-box.git
synced 2025-04-19 07:12:58 -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
|
||||
/tmp
|
||||
/src/files
|
||||
*.mochi
|
||||
|
|
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)]
|
||||
|
|
38
src/main.rs
38
src/main.rs
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
-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
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