Repository: redlib-org/redlib (language: rust)

Issue:
🐛 Bug Report: Restore settings link does not restore all settings

## Describe the bug
<!--
  A clear and concise description of what the bug is.
-->
When using the restore link provided on the settings page not all settings are restored. In particular:
- video quality
- blur spoiler preview
- blur nsfw preview
- autoplay videos
- hide the summary and sidebar
- hide score
- do not confirm before visiting content on reddit

Testing it shows that the settings are not encoded in the provided url:
`http://localhost:8080/settings/restore/?theme=black&front_page=popular&layout=clean&wide=on&post_sort=top&comment_sort=top&show_nsfw=on&use_hls=on&hide_hls_notification=on&hide_awards=on&fixed_navbar=off&subscriptions=&filters=`
## Steps to reproduce the bug

<!--
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
Easiest to test with temporary containers

- run local instance, change all settings
- delete cookies or use a fresh container to ensure no settings are saved in the browser
- use the link provided after changing settings to reapply them in the new context

## What's the expected behavior?
<!-- 
  A clear and concise description of what you expected to happen.
-->
All changed settings should be reapplied

## Additional context / screenshot
<!--
  Add any other context about the problem here.
-->
![Screenshot 2024-11-10 at 11-23-37 Redlib Settings](https://github.com/user-attachments/assets/fb5367bf-83fe-40b4-9837-af5556b013d8)

![Screenshot 2024-11-10 at 11-23-48 Redlib Settings](https://github.com/user-attachments/assets/e55f0371-e2e8-4ea9-a9c4-958cc6cc03db)

<!-- Mandatory -->
- [x] I checked that the instance that this was reported on is running the latest git commit, or I can reproduce it locally on the latest git commit


Discussion / hints:
it seems that the issue is in [templates/settings.html](https://github.com/redlib-org/redlib/blob/main/templates/settings.html#L165) not having the missing options.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 057234fd..819d4bc6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1384,6 +1384,7 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_json_path",
+ "serde_urlencoded",
  "serde_yaml",
  "tegen",
  "time",
@@ -1797,6 +1798,18 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
 [[package]]
 name = "serde_yaml"
 version = "0.9.34+deprecated"
diff --git a/Cargo.toml b/Cargo.toml
index 616d8e9d..a1d3ec01 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,6 +50,7 @@ async-recursion = "1.1.1"
 common-words-all = { version = "0.0.2", default-features = false, features = ["english", "one"] }
 hyper-rustls = { version = "0.24.2", features = [ "http2" ] }
 tegen = "0.1.4"
+serde_urlencoded = "0.7.1"
 
 
 [dev-dependencies]
diff --git a/src/utils.rs b/src/utils.rs
index 1edb5288..c15dceab 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -13,7 +13,7 @@ use once_cell::sync::Lazy;
 use regex::Regex;
 use rinja::Template;
 use rust_embed::RustEmbed;
-use serde::Serialize;
+use serde::{Serialize, Serializer};
 use serde_json::Value;
 use serde_json_path::{JsonPath, JsonPathExt};
 use std::collections::{HashMap, HashSet};
@@ -601,8 +601,9 @@ pub struct Params {
 	pub before: Option<String>,
 }
 
-#[derive(Default)]
+#[derive(Default, Serialize)]
 pub struct Preferences {
+	#[serde(skip)]
 	pub available_themes: Vec<String>,
 	pub theme: String,
 	pub front_page: String,
@@ -620,12 +621,21 @@ pub struct Preferences {
 	pub disable_visit_reddit_confirmation: String,
 	pub comment_sort: String,
 	pub post_sort: String,
+	#[serde(serialize_with = "serialize_vec_with_plus")]
 	pub subscriptions: Vec<String>,
+	#[serde(serialize_with = "serialize_vec_with_plus")]
 	pub filters: Vec<String>,
 	pub hide_awards: String,
 	pub hide_score: String,
 }
 
+fn serialize_vec_with_plus<S>(vec: &Vec<String>, serializer: S) -> Result<S::Ok, S::Error>
+where
+	S: Serializer,
+{
+	serializer.serialize_str(&vec.join("+"))
+}
+
 #[derive(RustEmbed)]
 #[folder = "static/themes/"]
 #[include = "*.css"]
@@ -665,6 +675,10 @@ impl Preferences {
 			hide_score: setting(req, "hide_score"),
 		}
 	}
+
+	pub fn to_urlencoded(&self) -> Result<String, String> {
+		serde_urlencoded::to_string(self).map_err(|e| e.to_string())
+	}
 }
 
 /// Gets a `HashSet` of filters from the cookie in the given `Request`.
@@ -1277,7 +1291,7 @@ pub fn get_post_url(post: &Post) -> String {
 
 #[cfg(test)]
 mod tests {
-	use super::{format_num, format_url, rewrite_urls};
+	use super::{format_num, format_url, rewrite_urls, Preferences};
 
 	#[test]
 	fn format_num_works() {
@@ -1344,6 +1358,35 @@ mod tests {
 		assert_eq!(format_url("nsfw"), "");
 		assert_eq!(format_url("spoiler"), "");
 	}
+	#[test]
+	fn serialize_prefs() {
+		let prefs = Preferences {
+			available_themes: vec![],
+			theme: "laserwave".to_owned(),
+			front_page: "default".to_owned(),
+			layout: "compact".to_owned(),
+			wide: "on".to_owned(),
+			blur_spoiler: "on".to_owned(),
+			show_nsfw: "off".to_owned(),
+			blur_nsfw: "on".to_owned(),
+			hide_hls_notification: "off".to_owned(),
+			video_quality: "best".to_owned(),
+			hide_sidebar_and_summary: "off".to_owned(),
+			use_hls: "on".to_owned(),
+			autoplay_videos: "on".to_owned(),
+			fixed_navbar: "on".to_owned(),
+			disable_visit_reddit_confirmation: "on".to_owned(),
+			comment_sort: "confidence".to_owned(),
+			post_sort: "top".to_owned(),
+			subscriptions: vec!["memes".to_owned(), "mildlyinteresting".to_owned()],
+			filters: vec![],
+			hide_awards: "off".to_owned(),
+			hide_score: "off".to_owned(),
+		};
+		let urlencoded = serde_urlencoded::to_string(prefs).expect("Failed to serialize Prefs");
+
+		assert_eq!(urlencoded, "theme=laserwave&front_page=default&layout=compact&wide=on&blur_spoiler=on&show_nsfw=off&blur_nsfw=on&hide_hls_notification=off&video_quality=best&hide_sidebar_and_summary=off&use_hls=on&autoplay_videos=on&fixed_navbar=on&disable_visit_reddit_confirmation=on&comment_sort=confidence&post_sort=top&subscriptions=memes%2Bmildlyinteresting&filters=&hide_awards=off&hide_score=off")
+	}
 }
 
 #[test]
diff --git a/templates/settings.html b/templates/settings.html
index a7d66153..fef91cf3 100644
--- a/templates/settings.html
+++ b/templates/settings.html
@@ -161,8 +161,16 @@
 	{% endif %}
 
 	<div id="settings_note">
-		<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p><br>
-		<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&post_sort={{ prefs.post_sort }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&use_hls={{ prefs.use_hls }}&hide_hls_notification={{ prefs.hide_hls_notification }}&hide_awards={{ prefs.hide_awards }}&fixed_navbar={{ prefs.fixed_navbar }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
+		<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p>
+		<br>
+		{% match prefs.to_urlencoded() %}
+		{% when Ok with (encoded_prefs) %}
+		<p>You can restore your current settings and subscriptions after clearing your cookies using <a
+				href="/settings/restore/?{{ encoded_prefs }}">this link</a>.</p>
+		{% when Err with (err) %}
+		<p>There was an error creating your restore link: {{ err }}</p>
+		<p>Please report this issue</p>
+		{% endmatch %}
 	</div>
 </div>
 


Test patch: