fix: add v1 otp routes and fail on email send errors

This commit is contained in:
Tracewebstudio Dev 2026-04-17 12:02:26 +02:00
parent b18aca10d3
commit 0e7ab9ceb8
2 changed files with 60 additions and 5 deletions

View file

@ -45,6 +45,7 @@ pub struct RegisterPayload {
pub phone: Option<String>,
pub password: String,
pub intent: Option<String>,
#[serde(alias = "role_key", alias = "roleKey")]
pub profession: Option<String>,
}
@ -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<AppState> {
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<AppState>,
Json(payload): Json<RegisterPayload>,
) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
register(State(state), Json(payload)).await
}
/// POST /api/v1/users/verify-otp
async fn v1_verify_otp(
State(state): State<AppState>,
Json(payload): Json<V1VerifyOtpPayload>,
) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
verify_email(State(state), Json(VerifyEmailPayload { otp: payload.otp })).await
}

View file

@ -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()?;