Properly set up time buttons on page

This commit is contained in:
G2-Games 2024-10-23 12:22:54 -05:00
parent 26f1f70a47
commit 34142ba721
7 changed files with 247 additions and 52 deletions

129
Cargo.lock generated
View file

@ -122,6 +122,12 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "binascii" name = "binascii"
version = "0.1.4" version = "0.1.4"
@ -244,6 +250,41 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.11" version = "0.3.11"
@ -251,6 +292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde",
] ]
[[package]] [[package]]
@ -456,13 +498,19 @@ dependencies = [
"futures-sink", "futures-sink",
"futures-util", "futures-util",
"http 0.2.12", "http 0.2.12",
"indexmap", "indexmap 2.6.0",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tracing", "tracing",
] ]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.0" version = "0.15.0"
@ -481,6 +529,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.12" version = "0.2.12"
@ -573,6 +627,23 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.6.0" version = "2.6.0"
@ -580,7 +651,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.15.0",
"serde", "serde",
] ]
@ -740,6 +811,8 @@ dependencies = [
"log", "log",
"maud", "maud",
"rocket", "rocket",
"serde",
"serde_with",
"toml", "toml",
"uuid", "uuid",
] ]
@ -1069,7 +1142,7 @@ dependencies = [
"either", "either",
"figment", "figment",
"futures", "futures",
"indexmap", "indexmap 2.6.0",
"log", "log",
"memchr", "memchr",
"multer", "multer",
@ -1101,7 +1174,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46"
dependencies = [ dependencies = [
"devise", "devise",
"glob", "glob",
"indexmap", "indexmap 2.6.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rocket_http", "rocket_http",
@ -1121,7 +1194,7 @@ dependencies = [
"futures", "futures",
"http 0.2.12", "http 0.2.12",
"hyper", "hyper",
"indexmap", "indexmap 2.6.0",
"log", "log",
"memchr", "memchr",
"pear", "pear",
@ -1182,18 +1255,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.211" version = "1.0.213"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.211" version = "1.0.213"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1221,6 +1294,36 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_with"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.6.0",
"serde",
"serde_derive",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@ -1294,6 +1397,12 @@ dependencies = [
"loom", "loom",
] ]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.82" version = "2.0.82"
@ -1438,7 +1547,7 @@ version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.6.0",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",

View file

@ -10,6 +10,8 @@ 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 = { version = "0.5", features = ["json"] } rocket = { version = "0.5", features = ["json"] }
serde = { version = "1.0.213", features = ["derive"] }
serde_with = { version = "3.11.0", features = ["chrono_0_4"] }
toml = "0.8.19" toml = "0.8.19"
uuid = { version = "1.11.0", features = ["v4"] } uuid = { version = "1.11.0", features = ["v4"] }

View file

@ -4,7 +4,7 @@ mod settings;
use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}, time::Duration}; use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}, time::Duration};
use blake3::Hash; use blake3::Hash;
use chrono::{DateTime, TimeDelta, Utc}; use chrono::{DateTime, Utc};
use database::{clean_loop, Database, MochiFile}; use database::{clean_loop, Database, MochiFile};
use log::info; use log::info;
use rocket::{ use rocket::{
@ -45,17 +45,20 @@ fn home() -> Markup {
(head("Mochi")) (head("Mochi"))
center { center {
div id="durationOptions" { h1 { "Confetti Box" }
div id="durationBox" {
} }
form id="uploadForm" { form id="uploadForm" {
label for="fileUpload" class="file_upload" onclick="document.getElementById('fileInput').click()" { label for="fileUpload"
class="button"
onclick="document.getElementById('fileInput').click()"
{
"Upload File" "Upload File"
} }
input id="fileInput" type="file" name="fileUpload" onchange="formSubmit(this.parentNode)" style="display:none;"; input id="fileInput" type="file" name="fileUpload" onchange="formSubmit(this.parentNode)" style="display:none;";
br; input id="fileDuration" type="text" name="duration" minlength="2" maxlength="7" value="" style="display:none;";
input type="text" name="duration" minlength="2" maxlength="4";
} }
div class="progress_box" { div class="progress_box" {
@ -64,13 +67,13 @@ fn home() -> Markup {
} }
div id="uploadedFilesDisplay" { div id="uploadedFilesDisplay" {
h2 class="sep center" { "Uploaded Files" } h2 { "Uploaded Files" }
} }
} }
} }
} }
#[derive(FromForm)] #[derive(Debug, FromForm)]
struct Upload<'r> { struct Upload<'r> {
#[field(name = "fileUpload")] #[field(name = "fileUpload")]
file: TempFile<'r>, file: TempFile<'r>,
@ -83,16 +86,22 @@ struct Upload<'r> {
#[post("/upload", data = "<file_data>")] #[post("/upload", data = "<file_data>")]
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>>>,
settings: &State<Settings>,
) -> Result<Json<ClientResponse>, std::io::Error> { ) -> Result<Json<ClientResponse>, std::io::Error> {
let mut out_path = PathBuf::from("files/"); let mut out_path = PathBuf::from("files/");
let mut temp_dir = settings.temp_dir.clone();
let expire_time = if let Ok(t) = parse_time_string(&file_data.expire_time) { let expire_time = if let Ok(t) = parse_time_string(&file_data.expire_time) {
if t < TimeDelta::days(365) { if t > settings.duration.maximum {
t return Ok(Json(ClientResponse {
} else { status: false,
TimeDelta::days(365) response: "Duration larger than maximum",
..Default::default()
}))
} }
t
} else { } else {
return Ok(Json(ClientResponse { return Ok(Json(ClientResponse {
status: false, status: false,
@ -101,13 +110,20 @@ async fn handle_upload(
})) }))
}; };
// TODO: Properly sanitize this...
let raw_name = &*file_data.file
.raw_name()
.unwrap()
.dangerous_unsafe_unsanitized_raw()
.as_str()
.to_string();
// Get temp path and hash it // Get temp path and hash it
let temp_filename = "temp_files/".to_owned() + &Uuid::new_v4().to_string(); temp_dir.push(&Uuid::new_v4().to_string());
let temp_filename = temp_dir;
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?;
// TODO: Properly sanitize this...
let raw_name = file_data.file.raw_name().unwrap().dangerous_unsafe_unsanitized_raw().as_str();
let filename = get_id( let filename = get_id(
raw_name, raw_name,
hash.0 hash.0
@ -122,6 +138,10 @@ async fn handle_upload(
expire_time expire_time
); );
if db.read().unwrap().files.contains_key(&constructed_file.get_key()) {
info!("Key already in DB");
}
// 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)?;
@ -130,8 +150,8 @@ async fn handle_upload(
Ok(Json(ClientResponse { Ok(Json(ClientResponse {
status: true, status: true,
name: Some(constructed_file.name().clone()), name: constructed_file.name().clone(),
url: Some("files/".to_string() + &filename), url: "files/".to_string() + &filename,
expires: Some(constructed_file.get_expiry()), expires: Some(constructed_file.get_expiry()),
..Default::default() ..Default::default()
})) }))
@ -146,10 +166,10 @@ struct ClientResponse {
pub response: &'static str, pub response: &'static str,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "String::is_empty")]
pub name: Option<String>, pub name: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "String::is_empty")]
pub url: Option<String>, pub url: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub expires: Option<DateTime<Utc>>, pub expires: Option<DateTime<Utc>>,
} }
@ -181,9 +201,9 @@ async fn hash_file<P: AsRef<Path>>(input: &P) -> Result<(Hash, usize), std::io::
fn server_info(settings: &State<Settings>) -> Json<ServerInfo> { fn server_info(settings: &State<Settings>) -> Json<ServerInfo> {
Json(ServerInfo { Json(ServerInfo {
max_filesize: settings.max_filesize, max_filesize: settings.max_filesize,
max_duration: settings.duration.maximum, max_duration: settings.duration.maximum.num_seconds() as u32,
default_duration: settings.duration.default, default_duration: settings.duration.default.num_seconds() as u32,
allowed_durations: settings.duration.allowed.clone(), allowed_durations: settings.duration.allowed.clone().into_iter().map(|t| t.num_seconds() as u32).collect(),
}) })
} }

View file

@ -1,5 +1,7 @@
use std::{fs::{self, File}, io::{self, Read, Write}, path::{Path, PathBuf}}; use std::{fs::{self, File}, io::{self, Read, Write}, path::{Path, PathBuf}};
use chrono::TimeDelta;
use serde_with::serde_as;
use rocket::serde::{Deserialize, Serialize}; use rocket::serde::{Deserialize, Serialize};
/// A response to the client from the server /// A response to the client from the server
@ -10,6 +12,11 @@ pub struct Settings {
#[serde(default)] #[serde(default)]
pub max_filesize: u64, pub max_filesize: u64,
/// Is overwiting already uploaded files with the same hash allowed, or is
/// this a no-op?
#[serde(default)]
pub overwrite: bool,
/// Settings pertaining to duration information /// Settings pertaining to duration information
pub duration: DurationSettings, pub duration: DurationSettings,
@ -34,6 +41,7 @@ impl Default for Settings {
Self { Self {
max_filesize: 128_000_000, // 128 MB max_filesize: 128_000_000, // 128 MB
duration: DurationSettings::default(), duration: DurationSettings::default(),
overwrite: true,
server: ServerSettings::default(), server: ServerSettings::default(),
path: "./settings.toml".into(), path: "./settings.toml".into(),
database_path: "./database.mochi".into(), database_path: "./database.mochi".into(),
@ -94,29 +102,37 @@ impl Default for ServerSettings {
} }
} }
#[serde_as]
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(crate = "rocket::serde")]
pub struct DurationSettings { pub struct DurationSettings {
/// Maximum file lifetime, seconds /// Maximum file lifetime, seconds
#[serde(default)] #[serde(default)]
pub maximum: u32, #[serde_as(as = "serde_with::DurationSeconds<i64>")]
pub maximum: TimeDelta,
/// Default file lifetime, seconds /// Default file lifetime, seconds
#[serde(default)] #[serde(default)]
pub default: u32, #[serde_as(as = "serde_with::DurationSeconds<i64>")]
pub default: TimeDelta,
/// List of allowed durations. An empty list means any are allowed. /// List of allowed durations. An empty list means any are allowed.
#[serde(default)] #[serde(default)]
pub allowed: Vec<u32>, #[serde_as(as = "Vec<serde_with::DurationSeconds<i64>>")]
pub allowed: Vec<TimeDelta>,
} }
impl Default for DurationSettings { impl Default for DurationSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
maximum: 259_200, // 72 hours maximum: TimeDelta::days(3), // 72 hours
default: 21_600, // 6 hours default: TimeDelta::hours(6), // 6 hours
// 1 hour, 6 hours, 24 hours, and 48 hours // 1 hour, 6 hours, 24 hours, and 48 hours
allowed: vec![3600, 21_600, 86_400, 172_800], allowed: vec![
TimeDelta::hours(1),
TimeDelta::hours(6),
TimeDelta::days(1),
TimeDelta::days(2),
],
} }
} }
} }

View file

@ -1,16 +1,38 @@
@import url('https://g2games.dev/assets/fonts/fonts.css'); @import url('https://g2games.dev/assets/fonts/fonts.css');
body { body {
background-color: black; font-family: sans-serif;
color: white;
} }
.file_upload { h1 {
}
.button {
display: block; display: block;
width: fit-content; width: fit-content;
padding: 5px; padding: 2px;
border: 1px solid grey; border: 1px solid grey;
background-color: #EEE;
cursor: pointer; cursor: pointer;
margin: 10px; margin: 5px;
border-radius: 5px; border-radius: 5px;
} }
.button:hover {
background-color: #CCC;
}
#durationBox {
display: flex;
flex-direction: row;
width: fit-content;
gap: 10px;
}
#durationBox > p {
font-size: 10pt;
width: 40px;
height: 40px;
vertical-align: center;
}

View file

@ -2,6 +2,7 @@ let progressBar;
let progressValue; let progressValue;
let statusNotifier; let statusNotifier;
let uploadedFilesDisplay; let uploadedFilesDisplay;
let durationBox;
let uploadInProgress = false; let uploadInProgress = false;
@ -9,11 +10,6 @@ const TOO_LARGE_TEXT = "File is too large!";
const ERROR_TEXT = "An error occured!"; const ERROR_TEXT = "An error occured!";
let CAPABILITIES; let CAPABILITIES;
async function getServerCapabilities() {
CAPABILITIES = await fetch("info").then((response) => response.json());
console.log(CAPABILITIES);
}
getServerCapabilities();
async function formSubmit(form) { async function formSubmit(form) {
if (uploadInProgress) { if (uploadInProgress) {
@ -103,4 +99,34 @@ document.addEventListener("DOMContentLoaded", function(_event){
progressValue = document.getElementById("uploadProgressValue"); progressValue = document.getElementById("uploadProgressValue");
statusNotifier = document.getElementById("uploadStatus"); statusNotifier = document.getElementById("uploadStatus");
uploadedFilesDisplay = document.getElementById("uploadedFilesDisplay"); uploadedFilesDisplay = document.getElementById("uploadedFilesDisplay");
durationBox = document.getElementById("durationBox");
getServerCapabilities();
}); });
function toPrettyTime(seconds) {
var days = Math.floor(seconds / 86400);
var hours = Math.floor((seconds - (days * 86400)) / 3600);
var mins = Math.floor((seconds - (hours * 3600) - (days * 86400)) / 60);
var secs = seconds - (hours * 3600) - (mins * 60) - (days * 86400);
if(days == 0) {days = "";} else if(days == 1) {days += "<br>day"} else {days += "<br>days"}
if(hours == 0) {hours = "";} else if(hours == 1) {hours += "<br>hour"} else {hours += "<br>hours"}
if(mins == 0) {mins = "";} else if(mins == 1) {mins += "<br>minute"} else {mins += "<br>minutes"}
if(secs == 0) {secs = "";} else if(secs == 1) {secs += "<br>second"} else {secs += "<br>seconds"}
return (days + " " + hours + " " + mins + " " + secs).trim();
}
async function getServerCapabilities() {
CAPABILITIES = await fetch("info").then((response) => response.json());
let file_duration = document.getElementById("fileDuration");
file_duration.value = CAPABILITIES.default_duration + "s";
for (duration in CAPABILITIES.allowed_durations) {
const durationOption = durationBox.appendChild(document.createElement("p"));
durationOption.innerHTML = toPrettyTime(CAPABILITIES.allowed_durations[duration]);
durationOption.classList.add("button");
}
}

View file

@ -3,7 +3,7 @@ use std::error::Error;
use chrono::TimeDelta; use chrono::TimeDelta;
pub fn parse_time_string(string: &str) -> Result<TimeDelta, Box<dyn Error>> { pub fn parse_time_string(string: &str) -> Result<TimeDelta, Box<dyn Error>> {
if string.len() > 5 { if string.len() > 7 {
return Err("Not valid time string".into()) return Err("Not valid time string".into())
} }