gitdataai/lib/service/user/notification.rs

253 lines
9.8 KiB
Rust

use db::sqlx;
use model::notify::UserAppNotifyModel;
use model::users::UserNotificationModel;
use serde::{Deserialize, Serialize};
use session::Session;
use crate::{AppService, Pagination, error::AppError, session_user};
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct UserNotificationConfig {
pub email_enabled: bool,
pub in_app_enabled: bool,
pub push_enabled: bool,
pub digest_mode: String,
pub dnd_enabled: bool,
pub dnd_start_minute: Option<i32>,
pub dnd_end_minute: Option<i32>,
pub marketing_enabled: bool,
pub security_enabled: bool,
pub product_enabled: bool,
pub push_subscription_endpoint: Option<String>,
pub push_subscription_keys_p256dh: Option<String>,
pub push_subscription_keys_auth: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct AppNotificationItem {
pub id: uuid::Uuid,
pub title: String,
pub body: String,
pub notify_type: String,
#[schema(value_type = Option<String>)]
pub read_at: Option<chrono::DateTime<chrono::Utc>>,
#[schema(value_type = String)]
pub created_at: chrono::DateTime<chrono::Utc>,
}
impl From<UserAppNotifyModel> for AppNotificationItem {
fn from(n: UserAppNotifyModel) -> Self {
Self {
id: n.id,
title: n.title,
body: n.body,
notify_type: n.notify_type,
read_at: n.read_at,
created_at: n.created_at,
}
}
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct UpdateUserNotificationConfig {
pub email_enabled: Option<bool>,
pub in_app_enabled: Option<bool>,
pub push_enabled: Option<bool>,
pub digest_mode: Option<String>,
pub dnd_enabled: Option<bool>,
pub dnd_start_minute: Option<Option<i32>>,
pub dnd_end_minute: Option<Option<i32>>,
pub marketing_enabled: Option<bool>,
pub security_enabled: Option<bool>,
pub product_enabled: Option<bool>,
pub push_subscription_endpoint: Option<Option<String>>,
pub push_subscription_keys_p256dh: Option<Option<String>>,
pub push_subscription_keys_auth: Option<Option<String>>,
}
impl AppService {
pub async fn user_update_notification_config(
&self,
ctx: &Session,
params: UpdateUserNotificationConfig,
) -> Result<UserNotificationConfig, AppError> {
let user_uid = session_user(ctx)?;
let mut config = self.user_notification_config(user_uid).await?;
if let Some(email_enabled) = params.email_enabled {
config.email_enabled = email_enabled;
}
if let Some(in_app_enabled) = params.in_app_enabled {
config.in_app_enabled = in_app_enabled;
}
if let Some(push_enabled) = params.push_enabled {
config.push_enabled = push_enabled;
}
if let Some(digest_mode) = params.digest_mode {
config.digest_mode = digest_mode;
}
if let Some(dnd_enabled) = params.dnd_enabled {
config.dnd_enabled = dnd_enabled;
}
if let Some(dnd_start_minute) = params.dnd_start_minute {
config.dnd_start_minute = dnd_start_minute;
}
if let Some(dnd_end_minute) = params.dnd_end_minute {
config.dnd_end_minute = dnd_end_minute;
}
if let Some(marketing_enabled) = params.marketing_enabled {
config.marketing_enabled = marketing_enabled;
}
if let Some(security_enabled) = params.security_enabled {
config.security_enabled = security_enabled;
}
if let Some(product_enabled) = params.product_enabled {
config.product_enabled = product_enabled;
}
if let Some(push_subscription_endpoint) =
params.push_subscription_endpoint
{
config.push_subscription_endpoint = push_subscription_endpoint;
}
if let Some(push_subscription_keys_p256dh) =
params.push_subscription_keys_p256dh
{
config.push_subscription_keys_p256dh =
push_subscription_keys_p256dh;
}
if let Some(push_subscription_keys_auth) =
params.push_subscription_keys_auth
{
config.push_subscription_keys_auth = push_subscription_keys_auth;
}
let now = chrono::Utc::now();
sqlx::query(
"INSERT INTO user_notification \
(\"user\", email_enabled, in_app_enabled, push_enabled, digest_mode, dnd_enabled, dnd_start_minute, dnd_end_minute, \
marketing_enabled, security_enabled, product_enabled, push_subscription_endpoint, push_subscription_keys_p256dh, \
push_subscription_keys_auth, created_at, updated_at) \
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $15) \
ON CONFLICT (\"user\") DO UPDATE SET \
email_enabled = EXCLUDED.email_enabled, in_app_enabled = EXCLUDED.in_app_enabled, push_enabled = EXCLUDED.push_enabled, \
digest_mode = EXCLUDED.digest_mode, dnd_enabled = EXCLUDED.dnd_enabled, dnd_start_minute = EXCLUDED.dnd_start_minute, \
dnd_end_minute = EXCLUDED.dnd_end_minute, marketing_enabled = EXCLUDED.marketing_enabled, security_enabled = EXCLUDED.security_enabled, \
product_enabled = EXCLUDED.product_enabled, push_subscription_endpoint = EXCLUDED.push_subscription_endpoint, \
push_subscription_keys_p256dh = EXCLUDED.push_subscription_keys_p256dh, push_subscription_keys_auth = EXCLUDED.push_subscription_keys_auth, \
updated_at = EXCLUDED.updated_at",
)
.bind(user_uid)
.bind(config.email_enabled)
.bind(config.in_app_enabled)
.bind(config.push_enabled)
.bind(&config.digest_mode)
.bind(config.dnd_enabled)
.bind(config.dnd_start_minute)
.bind(config.dnd_end_minute)
.bind(config.marketing_enabled)
.bind(config.security_enabled)
.bind(config.product_enabled)
.bind(&config.push_subscription_endpoint)
.bind(&config.push_subscription_keys_p256dh)
.bind(&config.push_subscription_keys_auth)
.bind(now)
.execute(self.db.writer())
.await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
Ok(config)
}
pub async fn list_notifications(
&self,
ctx: &Session,
pagination: Pagination,
) -> Result<Vec<AppNotificationItem>, AppError> {
let user_uid = session_user(ctx)?;
let rows = sqlx::query_as::<_, UserAppNotifyModel>(
"SELECT id, \"user\", title, body, notify_type, target_type, target_id, metadata, read_at, archived_at, created_at, updated_at \
FROM user_app_notify WHERE \"user\" = $1 AND archived_at IS NULL \
ORDER BY created_at DESC \
OFFSET $2 LIMIT $3",
)
.bind(user_uid)
.bind(pagination.offset() as i64)
.bind(pagination.limit() as i64)
.fetch_all(self.db.reader())
.await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
Ok(rows.into_iter().map(Into::into).collect())
}
pub async fn unread_notifications_count(
&self,
user_uid: uuid::Uuid,
) -> Result<i64, AppError> {
let row: (Option<i64>,) = sqlx::query_as(
"SELECT COUNT(*) FROM user_app_notify \
WHERE \"user\" = $1 AND read_at IS NULL AND archived_at IS NULL",
)
.bind(user_uid)
.fetch_one(self.db.reader())
.await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
Ok(row.0.unwrap_or(0))
}
pub async fn user_notification_config(
&self,
user_uid: uuid::Uuid,
) -> Result<UserNotificationConfig, AppError> {
let row = sqlx::query_as::<_, UserNotificationModel>(
"SELECT \"user\", email_enabled, in_app_enabled, push_enabled, digest_mode, dnd_enabled, dnd_start_minute, dnd_end_minute, \
marketing_enabled, security_enabled, product_enabled, push_subscription_endpoint, push_subscription_keys_p256dh, \
push_subscription_keys_auth, created_at, updated_at \
FROM user_notification WHERE \"user\" = $1",
)
.bind(user_uid)
.fetch_optional(self.db.reader())
.await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
Ok(row.map(Into::into).unwrap_or_default())
}
}
impl Default for UserNotificationConfig {
fn default() -> Self {
Self {
email_enabled: true,
in_app_enabled: true,
push_enabled: false,
digest_mode: "daily".to_string(),
dnd_enabled: false,
dnd_start_minute: None,
dnd_end_minute: None,
marketing_enabled: false,
security_enabled: true,
product_enabled: true,
push_subscription_endpoint: None,
push_subscription_keys_p256dh: None,
push_subscription_keys_auth: None,
}
}
}
impl From<UserNotificationModel> for UserNotificationConfig {
fn from(value: UserNotificationModel) -> Self {
Self {
email_enabled: value.email_enabled,
in_app_enabled: value.in_app_enabled,
push_enabled: value.push_enabled,
digest_mode: value.digest_mode,
dnd_enabled: value.dnd_enabled,
dnd_start_minute: value.dnd_start_minute,
dnd_end_minute: value.dnd_end_minute,
marketing_enabled: value.marketing_enabled,
security_enabled: value.security_enabled,
product_enabled: value.product_enabled,
push_subscription_endpoint: value.push_subscription_endpoint,
push_subscription_keys_p256dh: value.push_subscription_keys_p256dh,
push_subscription_keys_auth: value.push_subscription_keys_auth,
}
}
}