offline_intelligence/api/
login_notification_api.rs1use axum::{
3 extract::Json,
4 http::StatusCode,
5};
6use serde::{Deserialize, Serialize};
7use tracing::{info, warn};
8
9#[derive(Debug, Deserialize)]
10pub struct LoginNotificationRequest {
11 pub user_name: String,
12 pub user_email: String,
13}
14
15#[derive(Debug, Serialize)]
16pub struct LoginNotificationResponse {
17 pub success: bool,
18 pub message: String,
19}
20
21pub async fn notify_user_login(
22 Json(payload): Json<LoginNotificationRequest>,
23) -> (StatusCode, Json<LoginNotificationResponse>) {
24 if payload.user_name.trim().is_empty() || payload.user_email.trim().is_empty() {
25 return (
26 StatusCode::BAD_REQUEST,
27 Json(LoginNotificationResponse {
28 success: false,
29 message: "User name and email are required".to_string(),
30 }),
31 );
32 }
33
34 info!("Received login notification for user: {}", payload.user_email);
35
36 match send_login_notification_email(&payload).await {
38 Ok(user_number) => {
39 info!("Login notification email #{} sent to product team for user: {}", user_number, payload.user_email);
40 (
41 StatusCode::OK,
42 Json(LoginNotificationResponse {
43 success: true,
44 message: format!("Login notification sent. Welcome user #{}!", user_number),
45 }),
46 )
47 }
48 Err(e) => {
49 warn!("Login notification email not sent (SMTP may not be configured): {}", e);
50 (
52 StatusCode::OK,
53 Json(LoginNotificationResponse {
54 success: true,
55 message: "Login successful. Email notification not sent.".to_string(),
56 }),
57 )
58 }
59 }
60}
61
62fn get_next_user_number() -> u64 {
64 let counter_path = std::path::Path::new("./data/user_counter.txt");
65
66 let current = if counter_path.exists() {
68 std::fs::read_to_string(counter_path)
69 .ok()
70 .and_then(|s| s.trim().parse().ok())
71 .unwrap_or(0)
72 } else {
73 0
74 };
75
76 let next = current + 1;
77
78 if let Some(parent) = counter_path.parent() {
80 let _ = std::fs::create_dir_all(parent);
81 }
82 let _ = std::fs::write(counter_path, next.to_string());
83
84 next
85}
86
87async fn send_login_notification_email(payload: &LoginNotificationRequest) -> Result<u64, Box<dyn std::error::Error>> {
88 use lettre::{
89 Message, SmtpTransport, Transport,
90 message::header::ContentType,
91 transport::smtp::authentication::Credentials,
92 };
93
94 let smtp_user = std::env::var("SMTP_USER").unwrap_or_default();
95 let smtp_pass = std::env::var("SMTP_PASS").unwrap_or_default();
96
97 if smtp_user.is_empty() || smtp_pass.is_empty() {
98 return Err("SMTP credentials not configured".into());
99 }
100
101 let smtp_host = std::env::var("SMTP_HOST").unwrap_or_else(|_| "smtp.gmail.com".to_string());
102 let smtp_port: u16 = std::env::var("SMTP_PORT")
103 .unwrap_or_else(|_| "587".to_string())
104 .parse()
105 .unwrap_or(587);
106
107 let user_number = get_next_user_number();
109
110 let from_address = format!("Aud.io Login <{}>", smtp_user);
111
112 let email = Message::builder()
113 .from(from_address.parse()?)
114 .to("Product Team <product@offlineintelligence.io>".parse()?)
115 .subject(format!("USER #{} - New User Login", user_number))
116 .header(ContentType::TEXT_PLAIN)
117 .body(format!(
118 "USER #{}\n\nA new user has logged in to _Aud.io:\n\nName: {}\nEmail: {}\n\nTime: {}",
119 user_number,
120 payload.user_name,
121 payload.user_email,
122 chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
123 ))?;
124
125 let creds = Credentials::new(smtp_user, smtp_pass);
126
127 let mailer = SmtpTransport::starttls_relay(&smtp_host)?
128 .port(smtp_port)
129 .credentials(creds)
130 .build();
131
132 mailer.send(&email)?;
133
134 Ok(user_number)
135}