koprogo_api/application/use_cases/
notification_use_cases.rs

1use crate::application::dto::{
2    CreateNotificationRequest, NotificationPreferenceResponse, NotificationResponse,
3    NotificationStats, UpdatePreferenceRequest,
4};
5use crate::application::ports::{NotificationPreferenceRepository, NotificationRepository};
6use crate::domain::entities::{
7    Notification, NotificationChannel, NotificationPreference, NotificationStatus, NotificationType,
8};
9use std::sync::Arc;
10use uuid::Uuid;
11
12pub struct NotificationUseCases {
13    notification_repository: Arc<dyn NotificationRepository>,
14    preference_repository: Arc<dyn NotificationPreferenceRepository>,
15}
16
17impl NotificationUseCases {
18    pub fn new(
19        notification_repository: Arc<dyn NotificationRepository>,
20        preference_repository: Arc<dyn NotificationPreferenceRepository>,
21    ) -> Self {
22        Self {
23            notification_repository,
24            preference_repository,
25        }
26    }
27
28    /// Create a new notification
29    pub async fn create_notification(
30        &self,
31        organization_id: Uuid,
32        request: CreateNotificationRequest,
33    ) -> Result<NotificationResponse, String> {
34        // Check if user has enabled this channel for this notification type
35        let is_enabled = self
36            .preference_repository
37            .is_channel_enabled(
38                request.user_id,
39                request.notification_type.clone(),
40                request.channel.clone(),
41            )
42            .await?;
43
44        if !is_enabled {
45            return Err("User has disabled this notification channel".to_string());
46        }
47
48        let mut notification = Notification::new(
49            organization_id,
50            request.user_id,
51            request.notification_type,
52            request.channel,
53            request.priority,
54            request.title,
55            request.message,
56        )?;
57
58        if let Some(link_url) = request.link_url {
59            notification = notification.with_link(link_url);
60        }
61
62        if let Some(metadata) = request.metadata {
63            notification = notification.with_metadata(metadata);
64        }
65
66        let created = self.notification_repository.create(&notification).await?;
67        Ok(NotificationResponse::from(created))
68    }
69
70    /// Get a notification by ID
71    pub async fn get_notification(&self, id: Uuid) -> Result<Option<NotificationResponse>, String> {
72        match self.notification_repository.find_by_id(id).await? {
73            Some(notification) => Ok(Some(NotificationResponse::from(notification))),
74            None => Ok(None),
75        }
76    }
77
78    /// List all notifications for a user
79    pub async fn list_user_notifications(
80        &self,
81        user_id: Uuid,
82    ) -> Result<Vec<NotificationResponse>, String> {
83        let notifications = self.notification_repository.find_by_user(user_id).await?;
84        Ok(notifications
85            .into_iter()
86            .map(NotificationResponse::from)
87            .collect())
88    }
89
90    /// List unread in-app notifications for a user
91    pub async fn list_unread_notifications(
92        &self,
93        user_id: Uuid,
94    ) -> Result<Vec<NotificationResponse>, String> {
95        let notifications = self
96            .notification_repository
97            .find_unread_by_user(user_id)
98            .await?;
99        Ok(notifications
100            .into_iter()
101            .map(NotificationResponse::from)
102            .collect())
103    }
104
105    /// Mark an in-app notification as read
106    pub async fn mark_as_read(&self, id: Uuid) -> Result<NotificationResponse, String> {
107        let mut notification = self
108            .notification_repository
109            .find_by_id(id)
110            .await?
111            .ok_or_else(|| "Notification not found".to_string())?;
112
113        notification.mark_read()?;
114
115        let updated = self.notification_repository.update(&notification).await?;
116        Ok(NotificationResponse::from(updated))
117    }
118
119    /// Mark all in-app notifications as read for a user
120    pub async fn mark_all_read(&self, user_id: Uuid) -> Result<i64, String> {
121        self.notification_repository
122            .mark_all_read_by_user(user_id)
123            .await
124    }
125
126    /// Delete a notification
127    pub async fn delete_notification(&self, id: Uuid) -> Result<bool, String> {
128        self.notification_repository.delete(id).await
129    }
130
131    /// Get notification statistics for a user
132    pub async fn get_user_stats(&self, user_id: Uuid) -> Result<NotificationStats, String> {
133        let total = self
134            .notification_repository
135            .find_by_user(user_id)
136            .await?
137            .len() as i64;
138
139        let unread = self
140            .notification_repository
141            .count_unread_by_user(user_id)
142            .await?;
143
144        let pending = self
145            .notification_repository
146            .count_by_user_and_status(user_id, NotificationStatus::Pending)
147            .await?;
148
149        let sent = self
150            .notification_repository
151            .count_by_user_and_status(user_id, NotificationStatus::Sent)
152            .await?;
153
154        let failed = self
155            .notification_repository
156            .count_by_user_and_status(user_id, NotificationStatus::Failed)
157            .await?;
158
159        Ok(NotificationStats {
160            total,
161            unread,
162            pending,
163            sent,
164            failed,
165        })
166    }
167
168    // ==================== Notification Preferences ====================
169
170    /// Get user's notification preferences
171    pub async fn get_user_preferences(
172        &self,
173        user_id: Uuid,
174    ) -> Result<Vec<NotificationPreferenceResponse>, String> {
175        let preferences = self.preference_repository.find_by_user(user_id).await?;
176
177        // If user has no preferences yet, create defaults
178        if preferences.is_empty() {
179            let defaults = self
180                .preference_repository
181                .create_defaults_for_user(user_id)
182                .await?;
183            return Ok(defaults
184                .into_iter()
185                .map(NotificationPreferenceResponse::from)
186                .collect());
187        }
188
189        Ok(preferences
190            .into_iter()
191            .map(NotificationPreferenceResponse::from)
192            .collect())
193    }
194
195    /// Get user's preference for a specific notification type
196    pub async fn get_preference(
197        &self,
198        user_id: Uuid,
199        notification_type: NotificationType,
200    ) -> Result<Option<NotificationPreferenceResponse>, String> {
201        match self
202            .preference_repository
203            .find_by_user_and_type(user_id, notification_type.clone())
204            .await?
205        {
206            Some(pref) => Ok(Some(NotificationPreferenceResponse::from(pref))),
207            None => {
208                // Create default preference for this type
209                let default_pref = NotificationPreference::new(user_id, notification_type);
210                let created = self.preference_repository.create(&default_pref).await?;
211                Ok(Some(NotificationPreferenceResponse::from(created)))
212            }
213        }
214    }
215
216    /// Update user's notification preference for a specific notification type
217    pub async fn update_preference(
218        &self,
219        user_id: Uuid,
220        notification_type: NotificationType,
221        request: UpdatePreferenceRequest,
222    ) -> Result<NotificationPreferenceResponse, String> {
223        let mut preference = match self
224            .preference_repository
225            .find_by_user_and_type(user_id, notification_type.clone())
226            .await?
227        {
228            Some(pref) => pref,
229            None => {
230                // Create default if doesn't exist
231                let default_pref = NotificationPreference::new(user_id, notification_type);
232                self.preference_repository.create(&default_pref).await?
233            }
234        };
235
236        // Update channels as requested
237        if let Some(email_enabled) = request.email_enabled {
238            preference.set_channel_enabled(NotificationChannel::Email, email_enabled);
239        }
240
241        if let Some(in_app_enabled) = request.in_app_enabled {
242            preference.set_channel_enabled(NotificationChannel::InApp, in_app_enabled);
243        }
244
245        if let Some(push_enabled) = request.push_enabled {
246            preference.set_channel_enabled(NotificationChannel::Push, push_enabled);
247        }
248
249        let updated = self.preference_repository.update(&preference).await?;
250        Ok(NotificationPreferenceResponse::from(updated))
251    }
252
253    // ==================== Admin/System Methods ====================
254
255    /// Get all pending notifications (for background processing)
256    pub async fn get_pending_notifications(&self) -> Result<Vec<NotificationResponse>, String> {
257        let notifications = self.notification_repository.find_pending().await?;
258        Ok(notifications
259            .into_iter()
260            .map(NotificationResponse::from)
261            .collect())
262    }
263
264    /// Get all failed notifications (for retry)
265    pub async fn get_failed_notifications(&self) -> Result<Vec<NotificationResponse>, String> {
266        let notifications = self.notification_repository.find_failed().await?;
267        Ok(notifications
268            .into_iter()
269            .map(NotificationResponse::from)
270            .collect())
271    }
272
273    /// Mark notification as sent
274    pub async fn mark_as_sent(&self, id: Uuid) -> Result<NotificationResponse, String> {
275        let mut notification = self
276            .notification_repository
277            .find_by_id(id)
278            .await?
279            .ok_or_else(|| "Notification not found".to_string())?;
280
281        notification.mark_sent();
282
283        let updated = self.notification_repository.update(&notification).await?;
284        Ok(NotificationResponse::from(updated))
285    }
286
287    /// Mark notification as failed
288    pub async fn mark_as_failed(
289        &self,
290        id: Uuid,
291        error_message: String,
292    ) -> Result<NotificationResponse, String> {
293        let mut notification = self
294            .notification_repository
295            .find_by_id(id)
296            .await?
297            .ok_or_else(|| "Notification not found".to_string())?;
298
299        notification.mark_failed(error_message);
300
301        let updated = self.notification_repository.update(&notification).await?;
302        Ok(NotificationResponse::from(updated))
303    }
304
305    /// Retry a failed notification
306    pub async fn retry_notification(&self, id: Uuid) -> Result<NotificationResponse, String> {
307        let mut notification = self
308            .notification_repository
309            .find_by_id(id)
310            .await?
311            .ok_or_else(|| "Notification not found".to_string())?;
312
313        notification.retry()?;
314
315        let updated = self.notification_repository.update(&notification).await?;
316        Ok(NotificationResponse::from(updated))
317    }
318
319    /// Delete old notifications (cleanup)
320    pub async fn cleanup_old_notifications(&self, days: i64) -> Result<i64, String> {
321        self.notification_repository.delete_older_than(days).await
322    }
323}