Skip to main content

offline_intelligence/api/
login_notification_api.rs

1// User Login Notification API: sends email notification when a new user logs in
2use 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    // Try to send email notification (non-blocking — don't fail if email fails)
37    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            // Still return success - we don't want to block login if email fails
51            (
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
62/// Get the next user number for sequential tracking
63fn get_next_user_number() -> u64 {
64    let counter_path = std::path::Path::new("./data/user_counter.txt");
65    
66    // Read current counter
67    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    // Save next counter
79    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    // Get sequential user number
108    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}