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
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:
parent
77c8330b11
commit
86e1fd47d2
3 changed files with 64 additions and 1 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 }))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue