shared/models/user/
settings.rs1use serde::de::DeserializeOwned;
2use std::{
3 collections::BTreeMap,
4 ops::{Deref, DerefMut},
5};
6
7pub type UserSettingsMap = BTreeMap<compact_str::CompactString, serde_json::Value>;
8
9#[inline]
10fn cache_key(user_uuid: uuid::Uuid) -> String {
11 format!("user::{user_uuid}::settings")
12}
13
14pub fn validate_settings_keys(
15 settings: &UserSettingsMap,
16 _context: &(),
17) -> Result<(), garde::Error> {
18 for key in settings.keys() {
19 if key.is_empty() || key.len() > 512 {
20 return Err(garde::Error::new(format!(
21 "key '{}' must be between 1 and 512 characters",
22 key
23 )));
24 }
25 }
26
27 Ok(())
28}
29
30async fn fetch_settings(
31 database: &crate::database::Database,
32 user_uuid: uuid::Uuid,
33) -> Result<UserSettingsMap, anyhow::Error> {
34 database
35 .cache
36 .cached(&cache_key(user_uuid), 60, || async {
37 let row = sqlx::query_scalar(
38 r#"
39 SELECT user_settings.settings
40 FROM user_settings
41 WHERE user_settings.user_uuid = $1
42 "#,
43 )
44 .bind(user_uuid)
45 .fetch_optional(database.read())
46 .await?;
47
48 Ok::<_, anyhow::Error>(match row {
49 Some(settings) => serde_json::from_value(settings)?,
50 None => UserSettingsMap::new(),
51 })
52 })
53 .await
54}
55
56impl super::User {
57 pub async fn get_settings(
61 &self,
62 database: &crate::database::Database,
63 ) -> Result<UserSettings, anyhow::Error> {
64 Ok(UserSettings {
65 settings: fetch_settings(database, self.uuid).await?,
66 })
67 }
68
69 pub async fn get_settings_mut(
73 &self,
74 database: &crate::database::Database,
75 ) -> Result<UserSettingsMut, anyhow::Error> {
76 let lock = database
77 .cache
78 .lock(
79 format!("users::{}::settings", self.uuid),
80 Some(30),
81 Some(5000),
82 )
83 .await?;
84
85 Ok(UserSettingsMut {
86 user_uuid: self.uuid,
87 settings: fetch_settings(database, self.uuid).await?,
88 _lock: lock,
89 })
90 }
91}
92
93pub struct UserSettings {
94 settings: UserSettingsMap,
95}
96
97impl UserSettings {
98 pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
101 self.settings
102 .get(key)
103 .and_then(|value| serde_json::from_value(value.clone()).ok())
104 }
105}
106
107impl Deref for UserSettings {
108 type Target = UserSettingsMap;
109
110 fn deref(&self) -> &Self::Target {
111 &self.settings
112 }
113}
114
115impl serde::Serialize for UserSettings {
116 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
117 self.settings.serialize(serializer)
118 }
119}
120
121pub struct UserSettingsMut {
122 user_uuid: uuid::Uuid,
123 settings: UserSettingsMap,
124 _lock: crate::cache::CacheLock,
125}
126
127impl UserSettingsMut {
128 pub async fn save(
129 self,
130 database: &crate::database::Database,
131 ) -> Result<(), crate::database::DatabaseError> {
132 sqlx::query(
133 r#"
134 INSERT INTO user_settings (user_uuid, settings)
135 VALUES ($1, $2)
136 ON CONFLICT (user_uuid) DO UPDATE SET settings = EXCLUDED.settings
137 "#,
138 )
139 .bind(self.user_uuid)
140 .bind(serde_json::to_value(&self.settings)?)
141 .execute(database.write())
142 .await?;
143
144 database
145 .cache
146 .invalidate(&cache_key(self.user_uuid))
147 .await?;
148
149 Ok(())
150 }
151}
152
153impl Deref for UserSettingsMut {
154 type Target = UserSettingsMap;
155
156 fn deref(&self) -> &Self::Target {
157 &self.settings
158 }
159}
160
161impl DerefMut for UserSettingsMut {
162 fn deref_mut(&mut self) -> &mut Self::Target {
163 &mut self.settings
164 }
165}