mockforge_registry_server/handlers/
waitlist.rs1use axum::{
4 extract::{Query, State},
5 Json,
6};
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9use validator::Validate;
10
11use crate::{
12 error::{ApiError, ApiResult},
13 AppState,
14};
15
16#[derive(Debug, Deserialize, Validate)]
17pub struct SubscribeRequest {
18 #[validate(email(message = "Invalid email address"))]
19 pub email: String,
20 pub source: Option<String>,
22}
23
24#[derive(Debug, Serialize)]
25pub struct SubscribeResponse {
26 pub success: bool,
27 pub message: String,
28}
29
30#[derive(Debug, Deserialize)]
31pub struct UnsubscribeQuery {
32 pub token: Uuid,
33}
34
35pub async fn subscribe(
37 State(state): State<AppState>,
38 Json(request): Json<SubscribeRequest>,
39) -> ApiResult<Json<SubscribeResponse>> {
40 request.validate().map_err(|e| ApiError::InvalidRequest(e.to_string()))?;
41
42 let email = request.email.trim().to_lowercase();
43 let source = request.source.as_deref().unwrap_or("landing_page");
44
45 state.store.subscribe_waitlist(&email, source).await?;
46
47 tracing::info!(email = %email, source = %source, "Waitlist subscriber added");
48
49 Ok(Json(SubscribeResponse {
50 success: true,
51 message: "You're on the list! We'll notify you when we launch.".to_string(),
52 }))
53}
54
55pub async fn unsubscribe(
57 State(state): State<AppState>,
58 Query(query): Query<UnsubscribeQuery>,
59) -> ApiResult<Json<SubscribeResponse>> {
60 let removed = state.store.unsubscribe_waitlist_by_token(query.token).await?;
61
62 if removed {
63 Ok(Json(SubscribeResponse {
64 success: true,
65 message: "You've been unsubscribed.".to_string(),
66 }))
67 } else {
68 Ok(Json(SubscribeResponse {
69 success: false,
70 message: "Link is invalid or you're already unsubscribed.".to_string(),
71 }))
72 }
73}