Merge pull request 'An upload that did not arrive says why' (#17) from upload-messages into main
This commit was merged in pull request #17.
This commit is contained in:
@@ -4,6 +4,10 @@ One line per change, grouped by the release that shipped it. Newest
|
|||||||
first. A release is a Cargo.toml bump plus a `v<version>` tag; the
|
first. A release is a Cargo.toml bump plus a `v<version>` tag; the
|
||||||
content repos pin the tag they run (see the end of this file).
|
content repos pin the tag they run (see the end of this file).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
- An upload that did not arrive says why: larger than 20 MB (checked in the browser before anything is sent), a site that takes no files (no Garage configured), not allowed from here, or anything else. It used to say "try again, or with a smaller file" whatever the reason, which sent people looking for a smaller file when the site had no storage at all.
|
||||||
|
|
||||||
## 0.6.7 (2026-09-30)
|
## 0.6.7 (2026-09-30)
|
||||||
|
|
||||||
- `leaves_after: { minutes: 10 }` on a state: a record that has been there that long leaves its bucket's lists (days, hours and minutes add up; swept every minute; the event log keeps it). Trainer: `leaves_after` per stage on a need. A Klingenberg Bygg card taken off the front page leaves the office's list ten minutes later instead of standing there greyed out.
|
- `leaves_after: { minutes: 10 }` on a state: a record that has been there that long leaves its bucket's lists (days, hours and minutes add up; swept every minute; the event log keeps it). Trainer: `leaves_after` per stage on a need. A Klingenberg Bygg card taken off the front page leaves the office's list ten minutes later instead of standing there greyed out.
|
||||||
|
|||||||
+41
-1
@@ -1310,6 +1310,12 @@ fn AlternativeCard(
|
|||||||
};
|
};
|
||||||
let Some(files) = input.files() else { continue };
|
let Some(files) = input.files() else { continue };
|
||||||
let Some(file) = files.get(0) else { continue };
|
let Some(file) = files.get(0) else { continue };
|
||||||
|
// Too big is known before a byte is sent.
|
||||||
|
if file.size() > MAX_UPLOAD_BYTES as f64 {
|
||||||
|
send_error.set(Some(t(&lang().get_untracked(), "upload_too_large").to_string()));
|
||||||
|
sending.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
match upload_file(&file, &question_id_for_submit, &alt_name_for_submit).await {
|
match upload_file(&file, &question_id_for_submit, &alt_name_for_submit).await {
|
||||||
Ok(key) => {
|
Ok(key) => {
|
||||||
map.insert(name.clone(), serde_json::Value::String(key));
|
map.insert(name.clone(), serde_json::Value::String(key));
|
||||||
@@ -1319,7 +1325,8 @@ fn AlternativeCard(
|
|||||||
// say so.
|
// say so.
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
leptos::logging::error!("upload of {name} failed: {e:?}");
|
leptos::logging::error!("upload of {name} failed: {e:?}");
|
||||||
send_error.set(Some(t(&lang().get_untracked(), "upload_failed").to_string()));
|
let status = e.as_string().and_then(|m| m.rsplit(' ').next().and_then(|n| n.parse().ok()));
|
||||||
|
send_error.set(Some(t(&lang().get_untracked(), upload_refusal(status)).to_string()));
|
||||||
sending.set(false);
|
sending.set(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2283,6 +2290,24 @@ pub fn uploaded_filename(value: &str) -> Option<&str> {
|
|||||||
rest.strip_prefix('-').filter(|name| shaped && !name.is_empty())
|
rest.strip_prefix('-').filter(|name| shaped && !name.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The largest file one upload may carry. Here, not in `upload.rs`,
|
||||||
|
/// so the browser checks the same number before it sends anything.
|
||||||
|
pub const MAX_UPLOAD_BYTES: usize = 20 * 1024 * 1024;
|
||||||
|
|
||||||
|
/// Why an upload did not arrive, in words a person can act on, from
|
||||||
|
/// the status `POST /upload` answered with (see `upload.rs`): too big,
|
||||||
|
/// a site that takes no files, a page they may not send from, or
|
||||||
|
/// anything else.
|
||||||
|
#[cfg_attr(not(feature = "hydrate"), allow(dead_code))]
|
||||||
|
fn upload_refusal(status: Option<u16>) -> &'static str {
|
||||||
|
match status {
|
||||||
|
Some(413) => "upload_too_large",
|
||||||
|
Some(503) => "upload_unavailable",
|
||||||
|
Some(403) => "upload_forbidden",
|
||||||
|
_ => "upload_failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// What a refused server call says, without the wrapping a person
|
/// What a refused server call says, without the wrapping a person
|
||||||
/// has no use for.
|
/// has no use for.
|
||||||
fn server_message(e: &ServerFnError) -> String {
|
fn server_message(e: &ServerFnError) -> String {
|
||||||
@@ -3241,6 +3266,21 @@ mod responsible_tests {
|
|||||||
mod picture_tests {
|
mod picture_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_upload_refusal_says_which_it_was() {
|
||||||
|
assert_eq!(upload_refusal(Some(413)), "upload_too_large");
|
||||||
|
assert_eq!(upload_refusal(Some(503)), "upload_unavailable");
|
||||||
|
assert_eq!(upload_refusal(Some(403)), "upload_forbidden");
|
||||||
|
assert_eq!(upload_refusal(Some(502)), "upload_failed");
|
||||||
|
assert_eq!(upload_refusal(None), "upload_failed");
|
||||||
|
for lang in ["en", "no"] {
|
||||||
|
for status in [Some(413), Some(503), Some(403), None] {
|
||||||
|
let key = upload_refusal(status);
|
||||||
|
assert_ne!(crate::i18n::t(lang, key), key, "{lang} has words for {key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_record_with_a_picture_is_shown_by_it() {
|
fn a_record_with_a_picture_is_shown_by_it() {
|
||||||
assert!(is_picture("Portrett.JPG") && is_picture("a.webp") && !is_picture("rapport.pdf"));
|
assert!(is_picture("Portrett.JPG") && is_picture("a.webp") && !is_picture("rapport.pdf"));
|
||||||
|
|||||||
+11
-2
@@ -24,7 +24,10 @@ fn lookup(lang: &str, key: &str) -> Option<&'static str> {
|
|||||||
("en", "sign_in") => "Sign in",
|
("en", "sign_in") => "Sign in",
|
||||||
("en", "sign_out") => "Sign out",
|
("en", "sign_out") => "Sign out",
|
||||||
("en", "nothing_here_yet") => "Nothing here yet.",
|
("en", "nothing_here_yet") => "Nothing here yet.",
|
||||||
("en", "upload_failed") => "The file could not be sent, so nothing was. Try again, or with a smaller file.",
|
("en", "upload_failed") => "The file did not arrive, so nothing was sent. Try again.",
|
||||||
|
("en", "upload_too_large") => "The file is larger than 20 MB, so nothing was sent. Choose a smaller one, or a picture taken at a lower resolution.",
|
||||||
|
("en", "upload_unavailable") => "This site cannot take files at the moment, so nothing was sent. Let whoever runs the site know.",
|
||||||
|
("en", "upload_forbidden") => "Files cannot be sent from here by you, so nothing was sent. Are you signed in?",
|
||||||
("en", "current_file") => "Now:",
|
("en", "current_file") => "Now:",
|
||||||
("en", "keep_file") => "Choose a new one to replace it, or leave it as it is.",
|
("en", "keep_file") => "Choose a new one to replace it, or leave it as it is.",
|
||||||
("en", "nothing_here") => "Nothing here",
|
("en", "nothing_here") => "Nothing here",
|
||||||
@@ -57,7 +60,10 @@ fn lookup(lang: &str, key: &str) -> Option<&'static str> {
|
|||||||
("no", "sign_in") => "Logg inn",
|
("no", "sign_in") => "Logg inn",
|
||||||
("no", "sign_out") => "Logg ut",
|
("no", "sign_out") => "Logg ut",
|
||||||
("no", "nothing_here_yet") => "Ingenting her ennå.",
|
("no", "nothing_here_yet") => "Ingenting her ennå.",
|
||||||
("no", "upload_failed") => "Filen kom ikke fram, så ingenting ble sendt. Prøv igjen, eller med en mindre fil.",
|
("no", "upload_failed") => "Filen kom ikke fram, så ingenting ble sendt. Prøv igjen.",
|
||||||
|
("no", "upload_too_large") => "Filen er større enn 20 MB, så ingenting ble sendt. Velg en mindre fil, eller et bilde med lavere oppløsning.",
|
||||||
|
("no", "upload_unavailable") => "Siden kan ikke ta imot filer akkurat nå, så ingenting ble sendt. Si fra til den som har siden.",
|
||||||
|
("no", "upload_forbidden") => "Du kan ikke sende filer herfra, så ingenting ble sendt. Er du logget inn?",
|
||||||
("no", "current_file") => "Nå:",
|
("no", "current_file") => "Nå:",
|
||||||
("no", "keep_file") => "Velg en ny for å bytte, eller la den være.",
|
("no", "keep_file") => "Velg en ny for å bytte, eller la den være.",
|
||||||
("no", "nothing_here") => "Ingenting her",
|
("no", "nothing_here") => "Ingenting her",
|
||||||
@@ -109,6 +115,9 @@ mod tests {
|
|||||||
"sign_out",
|
"sign_out",
|
||||||
"nothing_here_yet",
|
"nothing_here_yet",
|
||||||
"upload_failed",
|
"upload_failed",
|
||||||
|
"upload_too_large",
|
||||||
|
"upload_unavailable",
|
||||||
|
"upload_forbidden",
|
||||||
"current_file",
|
"current_file",
|
||||||
"keep_file",
|
"keep_file",
|
||||||
"nothing_here",
|
"nothing_here",
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use crate::server::AppState;
|
use crate::server::AppState;
|
||||||
|
|
||||||
pub const MAX_UPLOAD_BYTES: usize = 20 * 1024 * 1024;
|
pub const MAX_UPLOAD_BYTES: usize = crate::app::MAX_UPLOAD_BYTES;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Garage {
|
pub struct Garage {
|
||||||
|
|||||||
Reference in New Issue
Block a user