From d8d425ba7260f1d6422c5b54474dc8f13579d423 Mon Sep 17 00:00:00 2001 From: minko Date: Mon, 5 May 2025 07:44:51 +0200 Subject: [PATCH 1/5] Do not show limit warning for maximum value --- package.json | 2 +- packages/frontend/src/lib/ui/AdvancedParameters.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index de22f74..d293e95 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,5 @@ "npm-run-all": "^4.1.5", "shelljs": "^0.8.5" }, - "packageManager": "pnpm@10.3.0" + "packageManager": "pnpm@10.10.0" } diff --git a/packages/frontend/src/lib/ui/AdvancedParameters.svelte b/packages/frontend/src/lib/ui/AdvancedParameters.svelte index ba755d8..f8c66d9 100644 --- a/packages/frontend/src/lib/ui/AdvancedParameters.svelte +++ b/packages/frontend/src/lib/ui/AdvancedParameters.svelte @@ -53,7 +53,7 @@ disabled={!timeExpiration} max={$status?.max_expiration} validate={(v) => - ($status && v < $status?.max_expiration) || + ($status && v <= $status?.max_expiration) || $t('home.errors.max', { values: { n: $status?.max_expiration ?? 0 } })} /> From c6dada658fd366c40492a089e87637b40c7273c9 Mon Sep 17 00:00:00 2001 From: minko Date: Mon, 5 May 2025 08:17:47 +0200 Subject: [PATCH 2/5] Added environment variables DEFAULT_EXPIRE and DISABLE_MODE_SW. Made views and expire implicitly mandatory. --- packages/backend/src/config.rs | 8 ++++++++ packages/backend/src/note/routes.rs | 15 +++++++++++++-- packages/backend/src/status/mod.rs | 4 ++++ packages/backend/src/store.rs | 6 +++++- packages/cli/src/shared/api.ts | 2 ++ .../frontend/src/lib/ui/AdvancedParameters.svelte | 9 +++++++-- packages/frontend/src/lib/views/Create.svelte | 13 +++++++++++-- 7 files changed, 50 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/config.rs b/packages/backend/src/config.rs index 318b66f..a5ee0e9 100644 --- a/packages/backend/src/config.rs +++ b/packages/backend/src/config.rs @@ -26,6 +26,10 @@ pub static ref MAX_EXPIRATION: u32 = std::env::var("MAX_EXPIRATION") .unwrap_or("360".to_string()) // 6 hours in minutes .parse() .unwrap(); +pub static ref DEFAULT_EXPIRE: u32 = std::env::var("DEFAULT_EXPIRE") + .unwrap_or("60".to_string()) + .parse() + .unwrap(); pub static ref ALLOW_ADVANCED: bool = std::env::var("ALLOW_ADVANCED") .unwrap_or("true".to_string()) .parse() @@ -46,6 +50,10 @@ pub static ref IMPRINT_HTML: String = std::env::var("IMPRINT_HTML") .unwrap_or("".to_string()) .parse() .unwrap(); +pub static ref DISABLE_MODE_SW: bool = std::env::var("DISABLE_MODE_SW") + .unwrap_or("false".to_string()) + .parse() + .unwrap(); } // THEME diff --git a/packages/backend/src/note/routes.rs b/packages/backend/src/note/routes.rs index bff47ef..47c34a8 100644 --- a/packages/backend/src/note/routes.rs +++ b/packages/backend/src/note/routes.rs @@ -54,14 +54,13 @@ pub async fn create(Json(mut n): Json) -> Response { } if !*config::ALLOW_ADVANCED { n.views = Some(1); - n.expiration = None; + n.expiration = Some(*config::DEFAULT_EXPIRE); } match n.views { Some(v) => { if v > *config::MAX_VIEWS || v < 1 { return (StatusCode::BAD_REQUEST, "Invalid views").into_response(); } - n.expiration = None; // views overrides expiration } _ => {} } @@ -75,6 +74,18 @@ pub async fn create(Json(mut n): Json) -> Response { } _ => {} } + + // Set default views + if n.views == None { + n.views = Some(1); + } + + // Set default expiration + if n.expiration == None { + let expiration = now() + (*config::DEFAULT_EXPIRE * 60); + n.expiration = Some(expiration); // Use default expiration + } + match store::set(&id.clone(), &n.clone()) { Ok(_) => (StatusCode::OK, Json(CreateResponse { id })).into_response(), Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), diff --git a/packages/backend/src/status/mod.rs b/packages/backend/src/status/mod.rs index fcec5d2..f8f6a01 100644 --- a/packages/backend/src/status/mod.rs +++ b/packages/backend/src/status/mod.rs @@ -10,10 +10,12 @@ pub struct Status { pub max_size: u32, pub max_views: u32, pub max_expiration: u32, + pub default_expire: u32, pub allow_advanced: bool, pub allow_files: bool, pub imprint_url: String, pub imprint_html: String, + pub disable_mode_sw: bool, // Theme pub theme_image: String, pub theme_text: String, @@ -28,10 +30,12 @@ pub async fn get_status() -> (StatusCode, Json) { max_size: *config::LIMIT as u32, max_views: *config::MAX_VIEWS, max_expiration: *config::MAX_EXPIRATION, + default_expire: *config::DEFAULT_EXPIRE, allow_advanced: *config::ALLOW_ADVANCED, allow_files: *config::ALLOW_FILES, imprint_url: config::IMPRINT_URL.to_string(), imprint_html: config::IMPRINT_HTML.to_string(), + disable_mode_sw: *config::DISABLE_MODE_SW, theme_new_note_notice: *config::THEME_NEW_NOTE_NOTICE, theme_image: config::THEME_IMAGE.to_string(), theme_text: config::THEME_TEXT.to_string(), diff --git a/packages/backend/src/store.rs b/packages/backend/src/store.rs index bebf2d2..c80b958 100644 --- a/packages/backend/src/store.rs +++ b/packages/backend/src/store.rs @@ -39,7 +39,11 @@ pub fn set(id: &String, note: &Note) -> Result<(), &'static str> { conn.expire(id, seconds as i64) .map_err(|_| "Unable to set expiration on notion")? } - None => {} + None => { + let seconds = 60 * 60; // Hardcoded 1 hour + conn.expire(id, seconds as i64) + .map_err(|_| "Unable to set expiration on notion")? + } }; Ok(()) } diff --git a/packages/cli/src/shared/api.ts b/packages/cli/src/shared/api.ts index 6f8f010..6127de8 100644 --- a/packages/cli/src/shared/api.ts +++ b/packages/cli/src/shared/api.ts @@ -111,10 +111,12 @@ export type Status = { max_size: number max_views: number max_expiration: number + default_expiration: number allow_advanced: boolean allow_files: boolean imprint_url: string imprint_html: string + disable_mode_sw: boolean theme_image: string theme_text: string theme_favicon: string diff --git a/packages/frontend/src/lib/ui/AdvancedParameters.svelte b/packages/frontend/src/lib/ui/AdvancedParameters.svelte index f8c66d9..bf250cf 100644 --- a/packages/frontend/src/lib/ui/AdvancedParameters.svelte +++ b/packages/frontend/src/lib/ui/AdvancedParameters.svelte @@ -23,6 +23,8 @@ $effect(() => { if (!hasCustomPassword) customPassword = null }) + + let disableModSw = $status && $status?.disable_mode_sw
@@ -32,27 +34,30 @@ type="number" label={$t('common.views', { values: { n: 0 } })} bind:value={note.views} - disabled={timeExpiration} + disabled={timeExpiration && !disableModSw} max={$status?.max_views} min={1} validate={(v) => ($status && v <= $status?.max_views && v > 0) || $t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })} /> + {#if !disableModSw} + {/if} + // Use <= insteaad of < to avoid error message when value is equals to max. ($status && v <= $status?.max_expiration) || $t('home.errors.max', { values: { n: $status?.max_expiration ?? 0 } })} /> diff --git a/packages/frontend/src/lib/views/Create.svelte b/packages/frontend/src/lib/views/Create.svelte index a79aad6..811b963 100644 --- a/packages/frontend/src/lib/views/Create.svelte +++ b/packages/frontend/src/lib/views/Create.svelte @@ -66,6 +66,7 @@ const derived = customPassword && (await AES.derive(customPassword)) const key = derived ? derived[0] : await AES.generateKey() + const disableModSw = $status?.disable_mode_sw const data: Note = { contents: '', @@ -79,8 +80,16 @@ if (note.contents === '') throw new EmptyContentError() data.contents = await Adapters.Text.encrypt(note.contents, key) } - if (timeExpiration) data.expiration = parseInt(note.expiration as any) - else data.views = parseInt(note.views as any) + if (disableModSw && advanced) { + data.views = parseInt(note.views as any) + data.expiration = parseInt(note.expiration as any) + } else { + if (timeExpiration) { + data.expiration = parseInt(note.expiration as any) + } else { + data.views = parseInt(note.views as any) + } + } loading = $t('common.uploading') const response = await API.create(data) From 66d0b6091a14caf7f8a176c9f4556cfb0bc1fb15 Mon Sep 17 00:00:00 2001 From: minko Date: Mon, 5 May 2025 11:33:31 +0200 Subject: [PATCH 3/5] Add environment variable DEFAULT_VIEWS --- packages/backend/src/config.rs | 8 ++++++-- packages/backend/src/note/routes.rs | 10 +++++++--- packages/backend/src/status/mod.rs | 4 ++-- packages/cli/src/shared/api.ts | 2 +- packages/frontend/src/lib/ui/AdvancedParameters.svelte | 8 ++++---- packages/frontend/src/lib/views/Create.svelte | 4 ++-- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/backend/src/config.rs b/packages/backend/src/config.rs index a5ee0e9..2e9e264 100644 --- a/packages/backend/src/config.rs +++ b/packages/backend/src/config.rs @@ -26,10 +26,14 @@ pub static ref MAX_EXPIRATION: u32 = std::env::var("MAX_EXPIRATION") .unwrap_or("360".to_string()) // 6 hours in minutes .parse() .unwrap(); -pub static ref DEFAULT_EXPIRE: u32 = std::env::var("DEFAULT_EXPIRE") + pub static ref DEFAULT_EXPIRE: u32 = std::env::var("DEFAULT_EXPIRE") .unwrap_or("60".to_string()) .parse() .unwrap(); +pub static ref DEFAULT_VIEWS: u32 = std::env::var("DEFAULT_VIEWS") + .unwrap_or("0".to_string()) + .parse() + .unwrap(); pub static ref ALLOW_ADVANCED: bool = std::env::var("ALLOW_ADVANCED") .unwrap_or("true".to_string()) .parse() @@ -50,7 +54,7 @@ pub static ref IMPRINT_HTML: String = std::env::var("IMPRINT_HTML") .unwrap_or("".to_string()) .parse() .unwrap(); -pub static ref DISABLE_MODE_SW: bool = std::env::var("DISABLE_MODE_SW") +pub static ref DISABLE_MODE_SWITCH: bool = std::env::var("DISABLE_MODE_SWITCH") .unwrap_or("false".to_string()) .parse() .unwrap(); diff --git a/packages/backend/src/note/routes.rs b/packages/backend/src/note/routes.rs index 47c34a8..b9a599f 100644 --- a/packages/backend/src/note/routes.rs +++ b/packages/backend/src/note/routes.rs @@ -53,7 +53,11 @@ pub async fn create(Json(mut n): Json) -> Response { .into_response(); } if !*config::ALLOW_ADVANCED { - n.views = Some(1); + // Default value for views is 0 which means unlimited. + // This wirks together with the ALLOW_ADVANCED default value wich is true. + // If ALLOW_ADVANCED is false, we need to set the default value for views + // to value 1 to keep the old behavior. + n.views = Some(*config::DEFAULT_VIEWS); n.expiration = Some(*config::DEFAULT_EXPIRE); } match n.views { @@ -76,8 +80,8 @@ pub async fn create(Json(mut n): Json) -> Response { } // Set default views - if n.views == None { - n.views = Some(1); + if n.views == None && *config::DEFAULT_VIEWS > 0 { + n.views = Some(*config::DEFAULT_VIEWS); } // Set default expiration diff --git a/packages/backend/src/status/mod.rs b/packages/backend/src/status/mod.rs index f8f6a01..6bffd49 100644 --- a/packages/backend/src/status/mod.rs +++ b/packages/backend/src/status/mod.rs @@ -15,7 +15,7 @@ pub struct Status { pub allow_files: bool, pub imprint_url: String, pub imprint_html: String, - pub disable_mode_sw: bool, + pub disable_mode_switch: bool, // Theme pub theme_image: String, pub theme_text: String, @@ -35,7 +35,7 @@ pub async fn get_status() -> (StatusCode, Json) { allow_files: *config::ALLOW_FILES, imprint_url: config::IMPRINT_URL.to_string(), imprint_html: config::IMPRINT_HTML.to_string(), - disable_mode_sw: *config::DISABLE_MODE_SW, + disable_mode_switch: *config::DISABLE_MODE_SWITCH, theme_new_note_notice: *config::THEME_NEW_NOTE_NOTICE, theme_image: config::THEME_IMAGE.to_string(), theme_text: config::THEME_TEXT.to_string(), diff --git a/packages/cli/src/shared/api.ts b/packages/cli/src/shared/api.ts index 6127de8..d9029e0 100644 --- a/packages/cli/src/shared/api.ts +++ b/packages/cli/src/shared/api.ts @@ -116,7 +116,7 @@ export type Status = { allow_files: boolean imprint_url: string imprint_html: string - disable_mode_sw: boolean + disable_mode_switch: boolean theme_image: string theme_text: string theme_favicon: string diff --git a/packages/frontend/src/lib/ui/AdvancedParameters.svelte b/packages/frontend/src/lib/ui/AdvancedParameters.svelte index bf250cf..cd52871 100644 --- a/packages/frontend/src/lib/ui/AdvancedParameters.svelte +++ b/packages/frontend/src/lib/ui/AdvancedParameters.svelte @@ -24,7 +24,7 @@ if (!hasCustomPassword) customPassword = null }) - let disableModSw = $status && $status?.disable_mode_sw + let disableModSwitch = $status && $status?.disable_mode_switch
@@ -34,14 +34,14 @@ type="number" label={$t('common.views', { values: { n: 0 } })} bind:value={note.views} - disabled={timeExpiration && !disableModSw} + disabled={timeExpiration && !disableModSwitch} max={$status?.max_views} min={1} validate={(v) => ($status && v <= $status?.max_views && v > 0) || $t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })} /> - {#if !disableModSw} + {#if !disableModSwitch} // Use <= insteaad of < to avoid error message when value is equals to max. diff --git a/packages/frontend/src/lib/views/Create.svelte b/packages/frontend/src/lib/views/Create.svelte index 811b963..e87c12a 100644 --- a/packages/frontend/src/lib/views/Create.svelte +++ b/packages/frontend/src/lib/views/Create.svelte @@ -66,7 +66,7 @@ const derived = customPassword && (await AES.derive(customPassword)) const key = derived ? derived[0] : await AES.generateKey() - const disableModSw = $status?.disable_mode_sw + const disableModSwitch = $status?.disable_mode_switch const data: Note = { contents: '', @@ -80,7 +80,7 @@ if (note.contents === '') throw new EmptyContentError() data.contents = await Adapters.Text.encrypt(note.contents, key) } - if (disableModSw && advanced) { + if (disableModSwitch && advanced) { data.views = parseInt(note.views as any) data.expiration = parseInt(note.expiration as any) } else { From 6e3d0ac2e71f59dd65ebc4a3c912f06b5cd088ed Mon Sep 17 00:00:00 2001 From: minko Date: Mon, 5 May 2025 12:29:25 +0200 Subject: [PATCH 4/5] Fixed formating. Updated readme. --- README.md | 6 +++++- packages/backend/src/config.rs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 263ddb3..feb5413 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,9 @@ of the notes even if it tried to. | `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/).
`512 MiB` is the maximum allowed.
The frontend will show that number including the ~35% encoding overhead. | | `MAX_VIEWS` | `100` | Maximal number of views. | | `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. | -| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. | +| `DEFAULT_EXPIRE` | `60` | Default expiration in minutes. Default value is used in a simple mode or when advanced mode with mode switch enabled (per default, see `DISABLE_MODE_SWITCH`) and views are limited. | +| `DEFAULT_VIEWS` | `0` | Default views. Default value is used in the advanced mode with expiration defined or when advanced mode is disabled (`ALLOW_ADVANCED` set to false). | +| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will have infinite views unless `DEFAULT_VIEWS` is set to non zerro vale and will expire after default expiration (set in `DEFAULT_EXPIRE`). | | `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. | | `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. | | `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` | @@ -88,6 +90,8 @@ of the notes even if it tried to. | `THEME_NEW_NOTE_NOTICE` | `true` | Show the message about how notes are stored in the memory and may be evicted after creating a new note. Defaults to `true`. | | `IMPRINT_URL` | `""` | Custom url for an Imprint hosted somewhere else. Must be publicly reachable. Takes precedence above `IMPRINT_HTML`. | | `IMPRINT_HTML` | `""` | Alternative to `IMPRINT_URL`, this can be used to specify the HTML code to show on `/imprint`. Only `IMPRINT_HTML` or `IMPRINT_URL` should be specified, not both. | +| `DISABLE_MODE_SWITCH` | `false` | Disables mode switch in the advanced mode. This makes both views and expiration fields editable and allows to define both limits at the same time. | + ## Deployment > ℹ️ `https` is required otherwise browsers will not support the cryptographic functions. diff --git a/packages/backend/src/config.rs b/packages/backend/src/config.rs index 2e9e264..68e1410 100644 --- a/packages/backend/src/config.rs +++ b/packages/backend/src/config.rs @@ -26,7 +26,7 @@ pub static ref MAX_EXPIRATION: u32 = std::env::var("MAX_EXPIRATION") .unwrap_or("360".to_string()) // 6 hours in minutes .parse() .unwrap(); - pub static ref DEFAULT_EXPIRE: u32 = std::env::var("DEFAULT_EXPIRE") +pub static ref DEFAULT_EXPIRE: u32 = std::env::var("DEFAULT_EXPIRE") .unwrap_or("60".to_string()) .parse() .unwrap(); @@ -58,7 +58,7 @@ pub static ref DISABLE_MODE_SWITCH: bool = std::env::var("DISABLE_MODE_SWITCH") .unwrap_or("false".to_string()) .parse() .unwrap(); -} +} // THEME lazy_static! { From e13b499478ba5cd97152a75ebfbff7f888c87e2b Mon Sep 17 00:00:00 2001 From: minko Date: Mon, 5 May 2025 15:44:27 +0200 Subject: [PATCH 5/5] Fixed a typo in a comment --- packages/backend/src/note/routes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/note/routes.rs b/packages/backend/src/note/routes.rs index b9a599f..e4519a5 100644 --- a/packages/backend/src/note/routes.rs +++ b/packages/backend/src/note/routes.rs @@ -54,7 +54,7 @@ pub async fn create(Json(mut n): Json) -> Response { } if !*config::ALLOW_ADVANCED { // Default value for views is 0 which means unlimited. - // This wirks together with the ALLOW_ADVANCED default value wich is true. + // This works together with the ALLOW_ADVANCED default value wich is true. // If ALLOW_ADVANCED is false, we need to set the default value for views // to value 1 to keep the old behavior. n.views = Some(*config::DEFAULT_VIEWS);