253 lines
9.8 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|