Skip to content

Commit ce3ef46

Browse files
committed
FIx emailer
1 parent 8879dda commit ce3ef46

7 files changed

Lines changed: 103 additions & 170 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pony"
3-
version = "0.4.5-dev"
3+
version = "0.4.6-dev"
44
edition = "2021"
55
build = "build.rs"
66

src/bin/agent/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
5454

5555
settings.validate().expect("Wrong settings file");
5656
println!(">>> Settings: {:?}", settings.clone());
57-
println!(">>>>> VERSION: 0.4.5-dev");
57+
println!(">>> Version: 0.4.6-dev");
5858

5959
tracing_subscriber::fmt()
6060
.with_env_filter(level_from_settings(&settings.logging.level))

src/bin/api/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async fn main() -> Result<()> {
4040

4141
settings.validate().expect("Wrong settings file");
4242
println!(">>> Settings: {:?}", settings.clone());
43-
println!(">>>>> VERSION: 0.4.5-dev");
43+
println!(">>> Version: 0.4.6-dev");
4444

4545
tracing_subscriber::fmt()
4646
.with_env_filter(level_from_settings(&settings.logging.level))

src/bin/auth/email.rs

Lines changed: 92 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,52 @@
1-
use chrono::{DateTime, Utc};
21
use hmac::{Hmac, Mac};
3-
use lettre::{
4-
transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncTransport, Message,
5-
Tokio1Executor,
6-
};
2+
use lettre::AsyncTransport;
73
use sha2::Sha256;
84
use std::collections::HashMap;
9-
use std::fs::OpenOptions;
10-
use std::io::Write;
115
use std::sync::Arc;
126
use tokio::fs::File;
137
use tokio::io::{AsyncBufReadExt, BufReader};
8+
9+
use chrono::{DateTime, Utc};
10+
use tokio::fs::OpenOptions;
11+
use tokio::io::AsyncWriteExt;
1412
use tokio::sync::RwLock;
1513

1614
use super::config::SmtpConfig;
1715

1816
type HmacSha256 = Hmac<Sha256>;
1917

18+
use lettre::{
19+
transport::smtp::authentication::Credentials, AsyncSmtpTransport, Message, Tokio1Executor,
20+
};
21+
2022
#[derive(Clone)]
2123
pub struct EmailStore {
2224
pub store: Arc<RwLock<HashMap<String, DateTime<Utc>>>>,
2325
file: String,
2426
smtp: SmtpConfig,
2527
secret: Vec<u8>,
2628
pub web_host: String,
29+
30+
mailer: Arc<AsyncSmtpTransport<Tokio1Executor>>,
2731
}
2832

2933
impl EmailStore {
3034
pub fn new(file: String, smtp: SmtpConfig, secret: Vec<u8>, web_host: String) -> Self {
35+
let mailer = AsyncSmtpTransport::<Tokio1Executor>::relay(&smtp.server)
36+
.expect("invalid smtp server")
37+
.credentials(Credentials::new(
38+
smtp.username.clone(),
39+
smtp.password.clone(),
40+
))
41+
.build();
42+
3143
Self {
3244
store: Arc::new(RwLock::new(HashMap::new())),
3345
file,
3446
smtp,
3547
secret,
3648
web_host,
49+
mailer: Arc::new(mailer),
3750
}
3851
}
3952

@@ -43,31 +56,10 @@ impl EmailStore {
4356
hex::encode(mac.finalize().into_bytes())
4457
}
4558

46-
pub async fn load_trials(&self) -> std::io::Result<()> {
47-
let file = match File::open(&self.file).await {
48-
Ok(f) => f,
49-
Err(_) => return Ok(()),
50-
};
51-
52-
let reader = BufReader::new(file);
53-
let mut lines = reader.lines();
54-
55-
let mut map = HashMap::new();
56-
57-
while let Some(line) = lines.next_line().await? {
58-
let parts: Vec<&str> = line.split(',').collect();
59-
60-
if parts.len() >= 2 {
61-
if let Ok(ts) = parts[0].parse::<DateTime<Utc>>() {
62-
map.insert(parts[1].to_string(), ts);
63-
}
64-
}
65-
}
66-
67-
let mut store = self.store.write().await;
68-
*store = map;
69-
70-
Ok(())
59+
pub async fn check_email_hmac(&self, email: &str) -> bool {
60+
let email_hmac = self.hmac_email(email);
61+
let store = self.store.read().await;
62+
store.contains_key(&email_hmac)
7163
}
7264

7365
pub async fn save_trial_hmac(
@@ -84,148 +76,90 @@ impl EmailStore {
8476
store.insert(email_hmac.clone(), *time);
8577
}
8678

87-
let mut file = OpenOptions::new()
79+
let file = OpenOptions::new()
8880
.create(true)
8981
.append(true)
90-
.open(&self.file)?;
82+
.open(&self.file)
83+
.await;
9184

92-
writeln!(
93-
file,
94-
"{},{},{},{}",
85+
let line = format!(
86+
"{},{},{},{}\n",
9587
time.to_rfc3339(),
9688
email_hmac,
9789
sub_id,
9890
ref_by
99-
)?;
91+
);
92+
93+
file?.write_all(line.as_bytes()).await?;
10094

10195
Ok(())
10296
}
10397

104-
pub async fn check_email_hmac(&self, email: &str) -> bool {
105-
let email_hmac = self.hmac_email(email);
98+
pub async fn load_trials(&self) -> std::io::Result<()> {
99+
let file = match File::open(&self.file).await {
100+
Ok(f) => f,
101+
Err(_) => return Ok(()),
102+
};
106103

107-
let store = self.store.read().await;
108-
store.contains_key(&email_hmac)
109-
}
104+
let reader = BufReader::new(file);
105+
let mut lines = reader.lines();
110106

111-
pub async fn send_email(
112-
&self,
113-
to: &str,
114-
sub_id: &uuid::Uuid,
115-
) -> Result<(), Box<dyn std::error::Error>> {
116-
let html_body = format!(
117-
r#"
118-
<!DOCTYPE html>
119-
<html>
120-
<head>
121-
<meta charset="UTF-8">
122-
<title>FRKN Рилзопровод</title>
123-
<style>
124-
body {{
125-
font-family: Arial, sans-serif;
126-
background-color: #f4f4f4;
127-
margin: 0;
128-
padding: 0;
129-
}}
130-
.container {{
131-
width: 100%;
132-
max-width: 600px;
133-
margin: 0 auto;
134-
background-color: #ffffff;
135-
padding: 20px;
136-
border-radius: 12px;
137-
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
138-
}}
139-
.header {{
140-
text-align: center;
141-
margin-bottom: 20px;
142-
}}
143-
.logo {{
144-
max-width: 150px;
145-
}}
146-
h1 {{
147-
color: #1d4ed8;
148-
font-size: 24px;
149-
}}
150-
p {{
151-
color: #374151;
152-
font-size: 16px;
153-
line-height: 1.5;
154-
}}
155-
.button {{
156-
display: inline-block;
157-
padding: 12px 24px;
158-
background-color: #1d4ed8;
159-
color: #ffffff;
160-
text-decoration: none;
161-
border-radius: 8px;
162-
margin: 20px 0;
163-
font-weight: bold;
164-
}}
165-
.footer {{
166-
font-size: 12px;
167-
color: #9ca3af;
168-
text-align: center;
169-
margin-top: 20px;
170-
}}
171-
</style>
172-
</head>
173-
<body>
174-
<div class="container">
175-
<div class="header">
176-
<h1>Твоя подписка FRKN активирована!</h1>
177-
</div>
178-
<p>Привет!</p>
179-
<p>Твоя подписка для <strong>FRKN</strong> успешно активирована 🎉</p>
180-
181-
<a href="{web_host}/subscription?id={sub_id}"
182-
183-
style="
184-
display: inline-block;
185-
padding: 12px 24px;
186-
background-color: #1d4ed8;
187-
color: #ffffff !important;
188-
text-decoration: none;
189-
border-radius: 8px;
190-
font-weight: bold;">Перейти к подписке</a>
191-
192-
<br><br>
193-
<p>
194-
Твой <strong>ID:</strong> {sub_id}<br/>
195-
<div style="font-size: small;">ID это твой уникальный идентификатор подписки, рекомендуем его записать или запомнить</div>
196-
<div style="font-size: small;">Мы не храним твой email</div>
197-
198-
</p>
199-
200-
<p>Подписывайся на наш Telegram: <a href="https://t.me/frkn_org">@frkn_org</a></p>
201-
<br>
202-
<div class="footer">
203-
<a href="https://t.me/frkn_support">Поддержка</a></p> • Vive la résistance!<br/>
204-
{web_host} • © 2026 FRKN Privacy Company
205-
</div>
206-
</div>
207-
</body>
208-
</html>
209-
"#,
210-
sub_id = sub_id,
211-
web_host = self.web_host,
212-
);
107+
let mut map = HashMap::new();
213108

214-
let msg = Message::builder()
215-
.from(format!("FRKN <{}>", self.smtp.from).parse()?)
216-
.to(to.parse()?)
217-
.subject("FRKN Рилзопровод")
218-
.header(lettre::message::header::ContentType::TEXT_HTML)
219-
.body(html_body)?;
109+
while let Some(line) = lines.next_line().await? {
110+
let parts: Vec<&str> = line.split(',').collect();
220111

221-
let mailer = AsyncSmtpTransport::<Tokio1Executor>::relay(&self.smtp.server)?
222-
.credentials(Credentials::new(
223-
self.smtp.username.clone(),
224-
self.smtp.password.clone(),
225-
))
226-
.build();
112+
if parts.len() >= 2 {
113+
if let Ok(ts) = parts[0].parse::<DateTime<Utc>>() {
114+
map.insert(parts[1].to_string(), ts);
115+
}
116+
}
117+
}
118+
119+
let mut store = self.store.write().await;
120+
*store = map;
227121

228-
mailer.send(msg).await?;
229122
Ok(())
230123
}
124+
125+
pub async fn send_email_background(&self, to: String, sub_id: uuid::Uuid) {
126+
let mailer = self.mailer.clone();
127+
let web_host = self.web_host.clone();
128+
let from = self.smtp.from.clone();
129+
tokio::spawn(async move {
130+
let html_body = format!(
131+
r#"
132+
<html>
133+
<body>
134+
<h2>Твой Тест-Драйв активирован</h2>
135+
<p>Ссылка:</p>
136+
<a href="{web_host}/subscription?id={sub_id}">
137+
Открыть подписку
138+
</a>
139+
</body>
140+
</html>
141+
"#,
142+
web_host = web_host,
143+
sub_id = sub_id,
144+
);
145+
146+
let msg = match Message::builder()
147+
.from(from.parse().unwrap())
148+
.to(to.parse().unwrap())
149+
.subject("Тест-Драйв")
150+
.header(lettre::message::header::ContentType::TEXT_HTML)
151+
.body(html_body)
152+
{
153+
Ok(m) => m,
154+
Err(e) => {
155+
tracing::error!("Email build error: {}", e);
156+
return;
157+
}
158+
};
159+
160+
if let Err(e) = mailer.send(msg).await {
161+
tracing::error!("Email send error: {}", e);
162+
}
163+
});
164+
}
231165
}

src/bin/auth/handlers.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,13 @@ pub async fn trial_handler(
9696
}
9797

9898
let ref_by = req.referred_by.clone().unwrap_or_else(|| "WEB".to_string());
99+
99100
let sub =
100101
match create_subscription(&http, &api.endpoint, &api.token, DEFAULT_DAYS, &ref_by).await {
101102
Ok(s) => s,
102-
Err(e) => return Ok(http::internal_error(&format!("Subscription failed: {}", e))),
103+
Err(e) => {
104+
return Ok(http::internal_error(&format!("Subscription failed: {}", e)));
105+
}
103106
};
104107

105108
if let Err(e) = setup_connections(&http, &api, &sub.id).await {
@@ -110,6 +113,7 @@ pub async fn trial_handler(
110113
}
111114

112115
let now = chrono::Utc::now();
116+
113117
if let Err(e) = store
114118
.save_trial_hmac(&req.email, &sub.id, &now, &ref_by)
115119
.await
@@ -118,12 +122,7 @@ pub async fn trial_handler(
118122
return Ok(http::internal_error("Failed to record trial."));
119123
}
120124

121-
if let Err(e) = store.send_email(&req.email, &sub.id).await {
122-
tracing::error!("Email error: {}", e);
123-
return Ok(http::internal_error(
124-
"Trial created, but failed to send email.",
125-
));
126-
}
125+
store.send_email_background(req.email.clone(), sub.id).await;
127126

128127
Ok(http::success_response(
129128
"Trial activated. Check email".into(),

src/bin/auth/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
5757

5858
settings.validate().expect("Wrong settings file");
5959
println!(">>> Settings: {:?}", settings.clone());
60-
println!(">>>>> VERSION: 0.4.5-dev");
60+
println!(">>> Version: 0.4.6-dev");
6161

6262
tracing_subscriber::fmt()
6363
.with_env_filter(level_from_settings(&settings.logging.level))

0 commit comments

Comments
 (0)