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(format!("users::{}::settings", self.uuid), Some(30), Some(5))
79 .await?;
80
81 Ok(UserSettingsMut {
82 user_uuid: self.uuid,
83 settings: fetch_settings(database, self.uuid).await?,
84 _lock: lock,
85 })
86 }
87}
88
89pub struct UserSettings {
90 settings: UserSettingsMap,
91}
92
93impl UserSettings {
94 pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
97 self.settings
98 .get(key)
99 .and_then(|value| serde_json::from_value(value.clone()).ok())
100 }
101}
102
103impl Deref for UserSettings {
104 type Target = UserSettingsMap;
105
106 fn deref(&self) -> &Self::Target {
107 &self.settings
108 }
109}
110
111impl serde::Serialize for UserSettings {
112 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
113 self.settings.serialize(serializer)
114 }
115}
116
117pub struct UserSettingsMut {
118 user_uuid: uuid::Uuid,
119 settings: UserSettingsMap,
120 _lock: crate::cache::CacheLock,
121}
122
123impl UserSettingsMut {
124 pub async fn save(
125 self,
126 database: &crate::database::Database,
127 ) -> Result<(), crate::database::DatabaseError> {
128 sqlx::query(
129 r#"
130 INSERT INTO user_settings (user_uuid, settings)
131 VALUES ($1, $2)
132 ON CONFLICT (user_uuid) DO UPDATE SET settings = EXCLUDED.settings
133 "#,
134 )
135 .bind(self.user_uuid)
136 .bind(serde_json::to_value(&self.settings)?)
137 .execute(database.write())
138 .await?;
139
140 database
141 .cache
142 .invalidate(&cache_key(self.user_uuid))
143 .await?;
144
145 Ok(())
146 }
147}
148
149impl Deref for UserSettingsMut {
150 type Target = UserSettingsMap;
151
152 fn deref(&self) -> &Self::Target {
153 &self.settings
154 }
155}
156
157impl DerefMut for UserSettingsMut {
158 fn deref_mut(&mut self) -> &mut Self::Target {
159 &mut self.settings
160 }
161}