1
0
Files
AstralRinth/apps/labrinth/src/database/models/notifications_template_item.rs
François-Xavier Talbot 902d749293 [DO NOT MERGE] Email notification system (#4338)
* Migration

* Fixup db models

* Redis

* Stuff

* Switch PKs to BIGSERIALs, insert to notifications_deliveries when inserting notifications

* Queue, templates

* Query cache

* Fixes, fixtures

* Perf, cache template data & HTML bodies

* Notification type configuration, ResetPassword notification type

* Reset password

* Query cache

* Clippy + fmt

* Traces, fix typo, fix user email in ResetPassword

* send_email

* Models, db

* Remove dead code, adjust notification settings in migration

* Clippy fmt

* Delete dead code, fixes

* Fmt

* Update apps/labrinth/src/queue/email.rs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com>

* Remove old fixtures

* Unify email retry delay

* Fix type

* External notifications

* Remove `notifications_types_preference_restrictions`, as user notification preferences is out of scope for this PR

* Query cache, fmt, clippy

* Fix join in get_many_user_exposed_on_site

* Remove migration comment

* Query cache

* Update html body urls

* Remove comment

* Add paymentfailed.service variable to PaymentFailed notification variant

* Fix compile error

* Fix deleting notifications

* Update apps/labrinth/src/database/models/user_item.rs

Co-authored-by: Josiah Glosson <soujournme@gmail.com>
Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com>

* Update apps/labrinth/src/database/models/user_item.rs

Co-authored-by: Josiah Glosson <soujournme@gmail.com>
Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com>

* Update Cargo.toml

Co-authored-by: Josiah Glosson <soujournme@gmail.com>
Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com>

* Update apps/labrinth/migrations/20250902133943_notification-extension.sql

Co-authored-by: Josiah Glosson <soujournme@gmail.com>
Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com>

* Address review comments

* Fix compliation

* Update apps/labrinth/src/database/models/users_notifications_preferences_item.rs

Co-authored-by: Josiah Glosson <soujournme@gmail.com>
Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com>

* Use strfmt to format emails

* Configurable Reply-To

* Configurable Reply-To

* Refactor for email background task

* Send some emails inline

* Fix account creation email check

* Revert "Use strfmt to format emails"

This reverts commit e0d6614afe51fa6349918377e953ba294c34ae0b.

* Reintroduce fill_template

* Set password reset email inline

* Process more emails per index

* clippy fmt

* Query cache

---------

Signed-off-by: François-Xavier Talbot <108630700+fetchfern@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Josiah Glosson <soujournme@gmail.com>
2025-09-15 19:02:29 +00:00

113 lines
3.2 KiB
Rust

use crate::database::models::DatabaseError;
use crate::database::redis::RedisPool;
use crate::models::v3::notifications::{NotificationChannel, NotificationType};
use serde::{Deserialize, Serialize};
const TEMPLATES_NAMESPACE: &str = "notifications_templates";
const TEMPLATES_HTML_DATA_NAMESPACE: &str = "notifications_templates_html_data";
const HTML_DATA_CACHE_EXPIRY: i64 = 60 * 15; // 15 minutes
#[derive(Clone, Serialize, Deserialize)]
pub struct NotificationTemplate {
pub id: i64,
pub channel: NotificationChannel,
pub notification_type: NotificationType,
pub subject_line: String,
pub body_fetch_url: String,
pub plaintext_fallback: String,
}
struct NotificationTemplateQueryResult {
id: i64,
channel: String,
notification_type: String,
subject_line: String,
body_fetch_url: String,
plaintext_fallback: String,
}
impl From<NotificationTemplateQueryResult> for NotificationTemplate {
fn from(r: NotificationTemplateQueryResult) -> Self {
NotificationTemplate {
id: r.id,
channel: NotificationChannel::from_str_or_default(&r.channel),
notification_type: NotificationType::from_str_or_default(
&r.notification_type,
),
subject_line: r.subject_line,
body_fetch_url: r.body_fetch_url,
plaintext_fallback: r.plaintext_fallback,
}
}
}
impl NotificationTemplate {
pub async fn list_channel(
channel: NotificationChannel,
exec: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
redis: &RedisPool,
) -> Result<Vec<NotificationTemplate>, DatabaseError> {
let mut redis = redis.connect().await?;
let maybe_cached_templates = redis
.get_deserialized_from_json(TEMPLATES_NAMESPACE, channel.as_str())
.await?;
if let Some(cached) = maybe_cached_templates {
return Ok(cached);
}
let results = sqlx::query_as!(
NotificationTemplateQueryResult,
r#"
SELECT * FROM notifications_templates WHERE channel = $1
"#,
channel.as_str(),
)
.fetch_all(exec)
.await?;
let templates = results.into_iter().map(Into::into).collect();
redis
.set_serialized_to_json(
TEMPLATES_NAMESPACE,
channel.as_str(),
&templates,
None,
)
.await?;
Ok(templates)
}
pub async fn get_cached_html_data(
&self,
redis: &RedisPool,
) -> Result<Option<String>, DatabaseError> {
let mut redis = redis.connect().await?;
redis
.get_deserialized_from_json(
TEMPLATES_HTML_DATA_NAMESPACE,
&self.id.to_string(),
)
.await
}
pub async fn set_cached_html_data(
&self,
data: String,
redis: &RedisPool,
) -> Result<(), DatabaseError> {
let mut redis = redis.connect().await?;
redis
.set_serialized_to_json(
TEMPLATES_HTML_DATA_NAMESPACE,
&self.id.to_string(),
&data,
Some(HTML_DATA_CACHE_EXPIRY),
)
.await
}
}