From 0e7ab9ceb87f15e8f0f6a8aabd50f1cdd26f9d14 Mon Sep 17 00:00:00 2001 From: Tracewebstudio Dev Date: Fri, 17 Apr 2026 12:02:26 +0200 Subject: [PATCH] fix: add v1 otp routes and fail on email send errors --- apps/users/src/handlers/auth.rs | 62 +++++++++++++++++++++++++++++++-- crates/email/src/lib.rs | 3 +- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/apps/users/src/handlers/auth.rs b/apps/users/src/handlers/auth.rs index 7ac3850..11ffbf5 100644 --- a/apps/users/src/handlers/auth.rs +++ b/apps/users/src/handlers/auth.rs @@ -45,6 +45,7 @@ pub struct RegisterPayload { pub phone: Option, pub password: String, pub intent: Option, + #[serde(alias = "role_key", alias = "roleKey")] pub profession: Option, } @@ -337,7 +338,19 @@ async fn register( cache::otp::record_resend(&mut redis, &user.id.to_string()).await.ok(); let user_name = format!("{} {}", user.first_name.unwrap_or_default(), user.last_name.unwrap_or_default()); - let _ = state.mail.send_verification_email(&user.email, &user_name, &otp).await; + if let Err(e) = state.mail.send_verification_email(&user.email, &user_name, &otp).await { + tracing::error!( + error = %e, + email = %user.email, + endpoint = "/api/auth/register", + "Failed to send verification email" + ); + return Err(err( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to send verification email", + "SMTP_ERROR", + )); + } Ok((StatusCode::CREATED, Json(RegisterResponse { user_id: user.id.to_string(), @@ -557,7 +570,14 @@ async fn verify_email( // Get user details for welcome email if let Ok(user) = UserRepository::get_by_id(&state.pool, user_id).await { let user_name = format!("{} {}", user.first_name.unwrap_or_default(), user.last_name.unwrap_or_default()); - let _ = state.mail.send_welcome_email(&user.email, &user_name).await; + if let Err(e) = state.mail.send_welcome_email(&user.email, &user_name).await { + tracing::error!( + error = %e, + email = %user.email, + endpoint = "/api/auth/verify-email", + "Failed to send welcome email" + ); + } } Ok((StatusCode::OK, Json(serde_json::json!({ "message": "Email verified successfully" })))) @@ -594,7 +614,19 @@ async fn resend_otp( cache::otp::record_resend(&mut redis, &user.id.to_string()).await.ok(); let user_name = format!("{} {}", user.first_name.unwrap_or_default(), user.last_name.unwrap_or_default()); - let _ = state.mail.send_verification_email(&user.email, &user_name, &otp).await; + if let Err(e) = state.mail.send_verification_email(&user.email, &user_name, &otp).await { + tracing::error!( + error = %e, + email = %user.email, + endpoint = "/api/auth/resend-otp", + "Failed to resend verification email" + ); + return Err(err( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to resend verification email", + "SMTP_ERROR", + )); + } Ok(silent_ok) } @@ -729,5 +761,29 @@ async fn switch_role( pub fn v1_router() -> Router { Router::new() + .route("/sign-up", post(v1_sign_up)) + .route("/verify-otp", post(v1_verify_otp)) .route("/resend-otp", post(resend_otp)) } + +#[derive(Deserialize)] +struct V1VerifyOtpPayload { + #[serde(alias = "code")] + otp: String, +} + +/// POST /api/v1/users/sign-up +async fn v1_sign_up( + State(state): State, + Json(payload): Json, +) -> Result)> { + register(State(state), Json(payload)).await +} + +/// POST /api/v1/users/verify-otp +async fn v1_verify_otp( + State(state): State, + Json(payload): Json, +) -> Result)> { + verify_email(State(state), Json(VerifyEmailPayload { otp: payload.otp })).await +} diff --git a/crates/email/src/lib.rs b/crates/email/src/lib.rs index 207aaa5..3fadd1e 100644 --- a/crates/email/src/lib.rs +++ b/crates/email/src/lib.rs @@ -155,8 +155,7 @@ impl Mailer { async fn send_html(&self, to: &str, subject: &str, html_body: String) -> Result<()> { let Some(transport) = &self.transport else { - tracing::debug!("SMTP disabled — skipping email to {}", to); - return Ok(()); + return Err(anyhow::anyhow!("SMTP transport not configured")); }; let from: Mailbox = format!("{} <{}>", self.from_name, self.from_email).parse()?;