this/events/sinks/
preferences.rs1use serde::{Deserialize, Serialize};
14use std::collections::{HashMap, HashSet};
15use tokio::sync::RwLock;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct UserPreferences {
20 pub disabled_types: HashSet<String>,
24
25 #[serde(default)]
27 pub muted: bool,
28}
29
30impl UserPreferences {
31 pub fn new() -> Self {
33 Self {
34 disabled_types: HashSet::new(),
35 muted: false,
36 }
37 }
38
39 pub fn is_type_enabled(&self, notification_type: &str) -> bool {
41 if self.muted {
42 return false;
43 }
44 !self.disabled_types.contains(notification_type)
45 }
46
47 pub fn disable_type(&mut self, notification_type: impl Into<String>) {
49 self.disabled_types.insert(notification_type.into());
50 }
51
52 pub fn enable_type(&mut self, notification_type: &str) {
54 self.disabled_types.remove(notification_type);
55 }
56}
57
58impl Default for UserPreferences {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64#[derive(Debug)]
68pub struct NotificationPreferencesStore {
69 preferences: RwLock<HashMap<String, UserPreferences>>,
70}
71
72impl NotificationPreferencesStore {
73 pub fn new() -> Self {
75 Self {
76 preferences: RwLock::new(HashMap::new()),
77 }
78 }
79
80 pub async fn get(&self, user_id: &str) -> UserPreferences {
82 let store = self.preferences.read().await;
83 store.get(user_id).cloned().unwrap_or_default()
84 }
85
86 pub async fn update(&self, user_id: impl Into<String>, prefs: UserPreferences) {
88 let mut store = self.preferences.write().await;
89 store.insert(user_id.into(), prefs);
90 }
91
92 pub async fn is_enabled(&self, user_id: &str, notification_type: &str) -> bool {
97 let store = self.preferences.read().await;
98 match store.get(user_id) {
99 Some(prefs) => prefs.is_type_enabled(notification_type),
100 None => true, }
102 }
103
104 pub async fn disable_type(&self, user_id: &str, notification_type: &str) {
106 let mut store = self.preferences.write().await;
107 let prefs = store
108 .entry(user_id.to_string())
109 .or_insert_with(UserPreferences::new);
110 prefs.disable_type(notification_type);
111 }
112
113 pub async fn enable_type(&self, user_id: &str, notification_type: &str) {
115 let mut store = self.preferences.write().await;
116 if let Some(prefs) = store.get_mut(user_id) {
117 prefs.enable_type(notification_type);
118 }
119 }
120
121 pub async fn mute(&self, user_id: &str) {
123 let mut store = self.preferences.write().await;
124 let prefs = store
125 .entry(user_id.to_string())
126 .or_insert_with(UserPreferences::new);
127 prefs.muted = true;
128 }
129
130 pub async fn unmute(&self, user_id: &str) {
132 let mut store = self.preferences.write().await;
133 if let Some(prefs) = store.get_mut(user_id) {
134 prefs.muted = false;
135 }
136 }
137}
138
139impl Default for NotificationPreferencesStore {
140 fn default() -> Self {
141 Self::new()
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_user_prefs_default_all_enabled() {
151 let prefs = UserPreferences::new();
152 assert!(prefs.is_type_enabled("new_follower"));
153 assert!(prefs.is_type_enabled("new_like"));
154 assert!(prefs.is_type_enabled("anything"));
155 assert!(!prefs.muted);
156 }
157
158 #[test]
159 fn test_user_prefs_disable_type() {
160 let mut prefs = UserPreferences::new();
161 prefs.disable_type("new_like");
162
163 assert!(!prefs.is_type_enabled("new_like"));
164 assert!(prefs.is_type_enabled("new_follower"));
165 }
166
167 #[test]
168 fn test_user_prefs_enable_type() {
169 let mut prefs = UserPreferences::new();
170 prefs.disable_type("new_like");
171 assert!(!prefs.is_type_enabled("new_like"));
172
173 prefs.enable_type("new_like");
174 assert!(prefs.is_type_enabled("new_like"));
175 }
176
177 #[test]
178 fn test_user_prefs_muted() {
179 let mut prefs = UserPreferences::new();
180 prefs.muted = true;
181
182 assert!(!prefs.is_type_enabled("new_follower"));
183 assert!(!prefs.is_type_enabled("new_like"));
184 assert!(!prefs.is_type_enabled("anything"));
185 }
186
187 #[tokio::test]
188 async fn test_store_default_all_enabled() {
189 let store = NotificationPreferencesStore::new();
190 assert!(store.is_enabled("user-A", "new_follower").await);
191 assert!(store.is_enabled("user-A", "new_like").await);
192 }
193
194 #[tokio::test]
195 async fn test_store_disable_type() {
196 let store = NotificationPreferencesStore::new();
197 store.disable_type("user-A", "new_like").await;
198
199 assert!(!store.is_enabled("user-A", "new_like").await);
200 assert!(store.is_enabled("user-A", "new_follower").await);
201 assert!(store.is_enabled("user-B", "new_like").await);
203 }
204
205 #[tokio::test]
206 async fn test_store_enable_type() {
207 let store = NotificationPreferencesStore::new();
208 store.disable_type("user-A", "new_like").await;
209 store.enable_type("user-A", "new_like").await;
210
211 assert!(store.is_enabled("user-A", "new_like").await);
212 }
213
214 #[tokio::test]
215 async fn test_store_mute_unmute() {
216 let store = NotificationPreferencesStore::new();
217 store.mute("user-A").await;
218
219 assert!(!store.is_enabled("user-A", "new_follower").await);
220 assert!(!store.is_enabled("user-A", "new_like").await);
221
222 store.unmute("user-A").await;
223 assert!(store.is_enabled("user-A", "new_follower").await);
224 }
225
226 #[tokio::test]
227 async fn test_store_update_full_preferences() {
228 let store = NotificationPreferencesStore::new();
229
230 let mut prefs = UserPreferences::new();
231 prefs.disable_type("new_follower");
232 prefs.disable_type("new_comment");
233 store.update("user-A", prefs).await;
234
235 assert!(!store.is_enabled("user-A", "new_follower").await);
236 assert!(!store.is_enabled("user-A", "new_comment").await);
237 assert!(store.is_enabled("user-A", "new_like").await);
238 }
239
240 #[tokio::test]
241 async fn test_store_get_returns_defaults() {
242 let store = NotificationPreferencesStore::new();
243 let prefs = store.get("nonexistent").await;
244 assert!(prefs.disabled_types.is_empty());
245 assert!(!prefs.muted);
246 }
247
248 #[tokio::test]
249 async fn test_store_get_returns_updated() {
250 let store = NotificationPreferencesStore::new();
251 store.disable_type("user-A", "new_like").await;
252
253 let prefs = store.get("user-A").await;
254 assert!(prefs.disabled_types.contains("new_like"));
255 }
256}