Skip to content

Extended configuration for views and expiration allowing both. #189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/). <br> `512 MiB` is the maximum allowed. <br> 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` |
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
"npm-run-all": "^4.1.5",
"shelljs": "^0.8.5"
},
"packageManager": "pnpm@10.3.0"
"packageManager": "pnpm@10.10.0"
}
14 changes: 13 additions & 1 deletion packages/backend/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +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")
.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()
Expand All @@ -46,7 +54,11 @@ pub static ref IMPRINT_HTML: String = std::env::var("IMPRINT_HTML")
.unwrap_or("".to_string())
.parse()
.unwrap();
}
pub static ref DISABLE_MODE_SWITCH: bool = std::env::var("DISABLE_MODE_SWITCH")
.unwrap_or("false".to_string())
.parse()
.unwrap();
}

// THEME
lazy_static! {
Expand Down
21 changes: 18 additions & 3 deletions packages/backend/src/note/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,18 @@ pub async fn create(Json(mut n): Json<Note>) -> Response {
.into_response();
}
if !*config::ALLOW_ADVANCED {
n.views = Some(1);
n.expiration = None;
// Default value for views is 0 which means unlimited.
// 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);
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
}
_ => {}
}
Expand All @@ -75,6 +78,18 @@ pub async fn create(Json(mut n): Json<Note>) -> Response {
}
_ => {}
}

// Set default views
if n.views == None && *config::DEFAULT_VIEWS > 0 {
n.views = Some(*config::DEFAULT_VIEWS);
}

// 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(),
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/status/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_switch: bool,
// Theme
pub theme_image: String,
pub theme_text: String,
Expand All @@ -28,10 +30,12 @@ pub async fn get_status() -> (StatusCode, Json<Status>) {
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_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(),
Expand Down
6 changes: 5 additions & 1 deletion packages/backend/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/shared/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_switch: boolean
theme_image: string
theme_text: string
theme_favicon: string
Expand Down
11 changes: 8 additions & 3 deletions packages/frontend/src/lib/ui/AdvancedParameters.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
$effect(() => {
if (!hasCustomPassword) customPassword = null
})

let disableModSwitch = $status && $status?.disable_mode_switch
</script>

<div class="flex col">
Expand All @@ -32,28 +34,31 @@
type="number"
label={$t('common.views', { values: { n: 0 } })}
bind:value={note.views}
disabled={timeExpiration}
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 !disableModSwitch}
<Switch
data-testid="switch-advanced-toggle"
label={$t('common.mode')}
bind:value={timeExpiration}
color={false}
/>
{/if}
<TextInput
data-testid="field-expiration"
type="number"
label={$t('common.minutes', { values: { n: 0 } })}
bind:value={note.expiration}
disabled={!timeExpiration}
disabled={!timeExpiration && !disableModSwitch}
max={$status?.max_expiration}
validate={(v) =>
($status && v < $status?.max_expiration) ||
// 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 } })}
/>
</div>
Expand Down
13 changes: 11 additions & 2 deletions packages/frontend/src/lib/views/Create.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@

const derived = customPassword && (await AES.derive(customPassword))
const key = derived ? derived[0] : await AES.generateKey()
const disableModSwitch = $status?.disable_mode_switch

const data: Note = {
contents: '',
Expand All @@ -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 (disableModSwitch && 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)
Expand Down
Loading