use axum::{
extract::{Query, State},
Json,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use validator::Validate;
use crate::{
error::{ApiError, ApiResult},
AppState,
};
#[derive(Debug, Deserialize, Validate)]
pub struct SubscribeRequest {
#[validate(email(message = "Invalid email address"))]
pub email: String,
pub source: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct SubscribeResponse {
pub success: bool,
pub message: String,
}
#[derive(Debug, Deserialize)]
pub struct UnsubscribeQuery {
pub token: Uuid,
}
pub async fn subscribe(
State(state): State<AppState>,
Json(request): Json<SubscribeRequest>,
) -> ApiResult<Json<SubscribeResponse>> {
request.validate().map_err(|e| ApiError::InvalidRequest(e.to_string()))?;
let email = request.email.trim().to_lowercase();
let source = request.source.as_deref().unwrap_or("landing_page");
state.store.subscribe_waitlist(&email, source).await?;
tracing::info!(email = %email, source = %source, "Waitlist subscriber added");
Ok(Json(SubscribeResponse {
success: true,
message: "You're on the list! We'll notify you when we launch.".to_string(),
}))
}
pub async fn unsubscribe(
State(state): State<AppState>,
Query(query): Query<UnsubscribeQuery>,
) -> ApiResult<Json<SubscribeResponse>> {
let removed = state.store.unsubscribe_waitlist_by_token(query.token).await?;
if removed {
Ok(Json(SubscribeResponse {
success: true,
message: "You've been unsubscribed.".to_string(),
}))
} else {
Ok(Json(SubscribeResponse {
success: false,
message: "Link is invalid or you're already unsubscribed.".to_string(),
}))
}
}