Skip to content
This repository was archived by the owner on Oct 19, 2026. It is now read-only.

Commit c56f243

Browse files
authored
feat: email notification system for CL comments (#2040)
Signed-off-by: abby-ql <abby.luoqy@gmail.com>
1 parent ac46041 commit c56f243

32 files changed

Lines changed: 2188 additions & 32 deletions

‎Cargo.lock‎

Lines changed: 68 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,14 @@ tower-http = "0.6.8"
6565
tower = "0.5.3"
6666
tower-sessions = { version = "0.15", features = ["memory-store"] }
6767
time = { version = "0.3.47", features = ["serde"] }
68-
68+
lettre = { version = "0.11.19", default-features = false, features = [
69+
"builder",
70+
"smtp-transport",
71+
"tokio1",
72+
"tokio1-rustls",
73+
"ring",
74+
"rustls-platform-verifier"
75+
] }
6976
#====
7077
sea-orm = "1.1.19"
7178
sea-orm-migration = "1.1.19"

‎ceres/src/model/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod group;
1313
pub mod issue;
1414
pub mod label;
1515
pub mod merge_queue;
16+
pub mod notification;
1617
pub mod tag;
1718
pub mod third_party;
1819
pub mod user;

‎ceres/src/model/notification.rs‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use serde::{Deserialize, Serialize};
2+
use utoipa::ToSchema;
3+
4+
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
5+
pub struct NotificationEventTypeInfo {
6+
pub code: String,
7+
pub category: String,
8+
pub description: String,
9+
pub system_required: bool,
10+
pub default_enabled: bool,
11+
}
12+
13+
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
14+
pub struct UserNotificationPreferenceItem {
15+
pub event_type_code: String,
16+
pub enabled: bool,
17+
}
18+
19+
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
20+
pub struct UserNotificationConfig {
21+
pub enabled: bool,
22+
pub delivery_mode: String,
23+
pub email: String,
24+
pub preferences: Vec<UserNotificationPreferenceItem>,
25+
}
26+
27+
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
28+
pub struct UpdateUserNotificationConfig {
29+
pub enabled: Option<bool>,
30+
pub delivery_mode: Option<String>,
31+
pub preferences: Option<Vec<UserNotificationPreferenceItem>>,
32+
}

‎common/Cargo.toml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ pgp = { workspace = true }
2929
cedar-policy = { workspace = true }
3030
redis = { workspace = true }
3131
rkyv = { workspace = true}
32-
toml = { workspace = true }
32+
toml = { workspace = true }
33+
async-trait = { workspace = true }
34+
lettre = { workspace = true }

‎common/src/config/mod.rs‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ pub struct Config {
9999
pub orion_server: Option<OrionServerConfig>,
100100
#[serde(default)]
101101
pub sidebar: SidebarConfig,
102+
#[serde(default)]
103+
pub mail: Option<MailConfig>,
102104
}
103105

104106
impl Config {
@@ -137,6 +139,7 @@ impl Config {
137139
object_storage: ObjectStorageConfig::default(),
138140
orion_server: None,
139141
sidebar: SidebarConfig::default(),
142+
mail: None,
140143
}
141144
}
142145

@@ -266,6 +269,29 @@ impl Default for LogConfig {
266269
}
267270
}
268271

272+
#[derive(Deserialize, Debug, Clone)]
273+
pub struct MailConfig {
274+
#[serde(default)]
275+
pub enabled: bool,
276+
pub smtp_host: String,
277+
#[serde(default = "default_smtp_port")]
278+
pub smtp_port: u16,
279+
#[serde(default)]
280+
pub username: Option<String>,
281+
#[serde(default)]
282+
pub password: Option<String>,
283+
pub from: String,
284+
#[serde(default = "default_starttls")]
285+
pub starttls: bool,
286+
}
287+
288+
fn default_smtp_port() -> u16 {
289+
587
290+
}
291+
fn default_starttls() -> bool {
292+
true
293+
}
294+
269295
#[derive(Serialize, Deserialize, Debug, Clone)]
270296
pub struct DbConfig {
271297
pub db_type: String,
@@ -1069,4 +1095,29 @@ mod test {
10691095
};
10701096
assert!(config.validate().is_ok());
10711097
}
1098+
1099+
#[test]
1100+
fn test_mail_config_deserialize() {
1101+
use serde::Deserialize;
1102+
#[derive(Deserialize)]
1103+
struct Wrapper {
1104+
mail: MailConfig,
1105+
}
1106+
1107+
let toml = r#"
1108+
[mail]
1109+
enabled = true
1110+
smtp_host = "smtp.example.com"
1111+
smtp_port = 587
1112+
from = "no-reply@example.com"
1113+
starttls = true
1114+
"#;
1115+
1116+
let parsed: Wrapper = toml::from_str(toml).expect("MailConfig should deserialize");
1117+
assert!(parsed.mail.enabled);
1118+
assert_eq!(parsed.mail.smtp_host, "smtp.example.com");
1119+
assert_eq!(parsed.mail.smtp_port, 587);
1120+
assert_eq!(parsed.mail.from, "no-reply@example.com");
1121+
assert!(parsed.mail.starttls);
1122+
}
10721123
}

0 commit comments

Comments
 (0)