use crate::error::AppError; use crate::models::notifications::Notification; use crate::service::NotificationService; use crate::session::Session; use chrono::Utc; use uuid::Uuid; use super::util::clamp_limit_offset; impl NotificationService { pub async fn list_notifications( &self, session: &Session, unread_only: bool, limit: i64, offset: i64, ) -> Result, AppError> { let user_id = session.user().ok_or(AppError::Unauthorized)?; let (limit, offset) = clamp_limit_offset(limit, offset); Notification::list_for_user(self.ctx.db.reader(), user_id, unread_only, limit, offset).await } pub async fn count_unread(&self, session: &Session) -> Result { let user_id = session.user().ok_or(AppError::Unauthorized)?; Notification::count_unread(self.ctx.db.reader(), user_id).await } pub async fn mark_as_read( &self, session: &Session, notification_id: Uuid, ) -> Result { let user_id = session.user().ok_or(AppError::Unauthorized)?; let now = Utc::now(); sqlx::query_as::<_, Notification>( "UPDATE notification SET read_at = $1, updated_at = $2 \ WHERE id = $3 AND user_id = $4 AND deleted_at IS NULL \ RETURNING id, user_id, notification_type, target_type, target_id, title, body, \ action_url, action_text, read_at, dismissed_at, created_at, updated_at, deleted_at", ) .bind(now) .bind(now) .bind(notification_id) .bind(user_id) .fetch_optional(self.ctx.db.writer()) .await .map_err(AppError::Database)? .ok_or(AppError::NotFound("notification not found".into())) } pub async fn mark_all_as_read(&self, session: &Session) -> Result { let user_id = session.user().ok_or(AppError::Unauthorized)?; let now = Utc::now(); let result = sqlx::query( "UPDATE notification SET read_at = $1, updated_at = $2 \ WHERE user_id = $3 AND deleted_at IS NULL AND read_at IS NULL", ) .bind(now) .bind(now) .bind(user_id) .execute(self.ctx.db.writer()) .await .map_err(AppError::Database)?; Ok(result.rows_affected() as i64) } pub async fn dismiss_notification( &self, session: &Session, notification_id: Uuid, ) -> Result { let user_id = session.user().ok_or(AppError::Unauthorized)?; let now = Utc::now(); sqlx::query_as::<_, Notification>( "UPDATE notification SET dismissed_at = $1, updated_at = $2 \ WHERE id = $3 AND user_id = $4 AND deleted_at IS NULL \ RETURNING id, user_id, notification_type, target_type, target_id, title, body, \ action_url, action_text, read_at, dismissed_at, created_at, updated_at, deleted_at", ) .bind(now) .bind(now) .bind(notification_id) .bind(user_id) .fetch_optional(self.ctx.db.writer()) .await .map_err(AppError::Database)? .ok_or(AppError::NotFound("notification not found".into())) } pub async fn delete_notification( &self, session: &Session, notification_id: Uuid, ) -> Result<(), AppError> { let user_id = session.user().ok_or(AppError::Unauthorized)?; let now = Utc::now(); let result = sqlx::query( "UPDATE notification SET deleted_at = $1, updated_at = $2 \ WHERE id = $3 AND user_id = $4 AND deleted_at IS NULL", ) .bind(now) .bind(now) .bind(notification_id) .bind(user_id) .execute(self.ctx.db.writer()) .await .map_err(AppError::Database)?; if result.rows_affected() == 0 { return Err(AppError::NotFound("notification not found".into())); } Ok(()) } pub async fn clear_all_notifications(&self, session: &Session) -> Result { let user_id = session.user().ok_or(AppError::Unauthorized)?; let now = Utc::now(); let result = sqlx::query( "UPDATE notification SET dismissed_at = $1, updated_at = $2 \ WHERE user_id = $3 AND deleted_at IS NULL AND dismissed_at IS NULL", ) .bind(now) .bind(now) .bind(user_id) .execute(self.ctx.db.writer()) .await .map_err(AppError::Database)?; Ok(result.rows_affected() as i64) } }