feat(waitlist): admin listing endpoint (GET /api/admin/waitlist)
All checks were successful
build-and-release / build (developers) (push) Successful in 11s
build-and-release / build (companies) (push) Successful in 13s
build-and-release / build (cron) (push) Successful in 19s
build-and-release / build (employees) (push) Successful in 17s
build-and-release / build (customers) (push) Successful in 18s
build-and-release / build (catering-services) (push) Successful in 22s
build-and-release / build (fitness-trainers) (push) Successful in 7s
build-and-release / build (job-seekers) (push) Successful in 8s
build-and-release / build (makeup-artists) (push) Successful in 8s
build-and-release / build (payments) (push) Successful in 9s
build-and-release / build (graphic-designers) (push) Successful in 13s
build-and-release / build (jobs) (push) Successful in 13s
build-and-release / build (photographers) (push) Successful in 5s
build-and-release / build (social-media-managers) (push) Successful in 5s
build-and-release / build (tutors) (push) Successful in 6s
build-and-release / build (ugc-content-creators) (push) Successful in 10s
backend-integration-tests / ai-credits (push) Successful in 6s
build-and-release / build (video-editors) (push) Successful in 7s
build-and-release / build (gateway) (push) Successful in 31s
build-and-release / build (users) (push) Successful in 3m49s

Paginated, admin-only list of waitlist_signups (email + created_at,
newest first) for the upcoming admin-solid waitlist page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar Sivakumar 2026-08-14 04:05:17 +05:30
parent 77c8330b11
commit 86e1fd47d2
3 changed files with 64 additions and 1 deletions

View file

@ -98,6 +98,7 @@ impl Services {
|| path.starts_with("/api/support")
|| path.starts_with("/api/reviews")
|| path.starts_with("/api/waitlist")
|| path.starts_with("/api/admin/waitlist")
|| path.starts_with("/api/admin/roles")
|| path.starts_with("/api/admin/users")
|| path.starts_with("/api/admin/verifications")

View file

@ -4,13 +4,26 @@
//! model as a newsletter signup form.
use crate::AppState;
use axum::{extract::State, http::StatusCode, routing::post, Json, Router};
use axum::{
extract::{Query, State},
http::StatusCode,
routing::{get, post},
Json, Router,
};
use contracts::auth_middleware::{require_admin, AuthUser};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
pub fn router() -> Router<AppState> {
Router::new().route("/", post(join_waitlist))
}
/// Mounted at /api/admin/waitlist by the gateway.
pub fn admin_router() -> Router<AppState> {
Router::new().route("/", get(list_waitlist))
}
#[derive(Debug, Deserialize)]
struct JoinWaitlistRequest {
email: String,
@ -48,3 +61,51 @@ async fn join_waitlist(
Ok(Json(JoinWaitlistResponse { joined: true }))
}
#[derive(Debug, Deserialize)]
struct ListWaitlistQuery {
page: Option<i64>,
limit: Option<i64>,
}
#[derive(Debug, FromRow, Serialize)]
struct WaitlistSignupRow {
email: String,
created_at: DateTime<Utc>,
}
#[derive(Debug, Serialize)]
struct ListWaitlistResponse {
signups: Vec<WaitlistSignupRow>,
total: i64,
page: i64,
limit: i64,
}
async fn list_waitlist(
auth: AuthUser,
State(state): State<AppState>,
Query(q): Query<ListWaitlistQuery>,
) -> Result<Json<ListWaitlistResponse>, (StatusCode, String)> {
require_admin(&auth).map_err(|_| (StatusCode::FORBIDDEN, "Admin access required".to_string()))?;
let limit = q.limit.unwrap_or(200).clamp(1, 1000);
let page = q.page.unwrap_or(1).max(1);
let offset = (page - 1) * limit;
let signups = sqlx::query_as::<_, WaitlistSignupRow>(
"SELECT email, created_at FROM waitlist_signups ORDER BY created_at DESC LIMIT $1 OFFSET $2",
)
.bind(limit)
.bind(offset)
.fetch_all(&state.pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
let total: i64 = sqlx::query_scalar("SELECT count(*) FROM waitlist_signups")
.fetch_one(&state.pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
Ok(Json(ListWaitlistResponse { signups, total, page, limit }))
}

View file

@ -121,6 +121,7 @@ async fn main() {
.nest("/api/packages", handlers::pricing::public_packages_router())
.nest("/api/waitlist", handlers::waitlist::router())
.nest("/api/admin/waitlist", handlers::waitlist::admin_router())
// ── Tracecoin Packages & Reports (admin) ──────────────────────────
.nest("/api/admin/tracecoin-packages", handlers::pricing::packages_router())
.nest("/api/admin/reports", handlers::pricing::reports_router())