use neocrates::axum::{
Router,
extract::State,
http::StatusCode,
response::{IntoResponse, Json, Response},
routing::{get, post},
};
use neocrates::captcha::{CaptchaData, CaptchaService};
use neocrates::rediscache::RedisPool;
use neocrates::serde::{Deserialize, Serialize};
use neocrates::tokio;
use std::sync::Arc;
#[derive(Debug, Serialize)]
struct ApiResponse<T> {
success: bool,
data: Option<T>,
message: String,
}
#[derive(Debug, Deserialize)]
struct GenerateCaptchaRequest {
account: String,
#[serde(default = "default_length")]
length: usize,
}
fn default_length() -> usize {
6
}
#[derive(Debug, Deserialize)]
struct ValidateCaptchaRequest {
id: String,
code: String,
}
#[derive(Debug, Deserialize)]
struct ValidateSliderRequest {
account: String,
code: String,
}
#[derive(Debug, Deserialize)]
struct GenerateSliderRequest {
account: String,
code: String,
}
#[derive(Clone)]
struct AppState {
redis_pool: Arc<RedisPool>,
}
async fn health_check() -> Response {
(
StatusCode::OK,
Json(ApiResponse {
success: true,
data: Some("OK".to_string()),
message: "Service is healthy".to_string(),
}),
)
.into_response()
}
#[neocrates::axum::debug_handler]
async fn generate_numeric_captcha(
State(state): State<AppState>,
Json(payload): Json<GenerateCaptchaRequest>,
) -> Response {
match CaptchaService::gen_numeric_captcha(
&state.redis_pool,
"your_prefix",
&payload.account,
Some(payload.length),
Some(300),
)
.await
{
Ok(captcha) => (
StatusCode::OK,
Json(ApiResponse {
success: true,
data: Some(captcha),
message: "Numeric captcha generated successfully".to_string(),
}),
)
.into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ApiResponse::<CaptchaData> {
success: false,
data: None,
message: format!("Failed to generate captcha: {}", e),
}),
)
.into_response(),
}
}
#[neocrates::axum::debug_handler]
async fn generate_alphanumeric_captcha(
State(state): State<AppState>,
Json(payload): Json<GenerateCaptchaRequest>,
) -> Response {
match CaptchaService::gen_alphanumeric_captcha(
&state.redis_pool,
"your_prefix",
&payload.account,
Some(payload.length),
Some(300),
)
.await
{
Ok(captcha) => (
StatusCode::OK,
Json(ApiResponse {
success: true,
data: Some(captcha),
message: "Alphanumeric captcha generated successfully".to_string(),
}),
)
.into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ApiResponse::<CaptchaData> {
success: false,
data: None,
message: format!("Failed to generate captcha: {}", e),
}),
)
.into_response(),
}
}
#[neocrates::axum::debug_handler]
async fn generate_slider_captcha(
State(state): State<AppState>,
Json(payload): Json<GenerateSliderRequest>,
) -> Response {
match CaptchaService::gen_captcha_slider(
&state.redis_pool,
"your_prefix",
&payload.code,
&payload.account,
Some(300),
)
.await
{
Ok(_) => (
StatusCode::OK,
Json(ApiResponse {
success: true,
data: Some("Slider captcha generated".to_string()),
message: "Slider captcha generated successfully".to_string(),
}),
)
.into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ApiResponse::<String> {
success: false,
data: None,
message: format!("Failed to generate slider captcha: {}", e),
}),
)
.into_response(),
}
}
#[neocrates::axum::debug_handler]
async fn validate_numeric_captcha(
State(state): State<AppState>,
Json(payload): Json<ValidateCaptchaRequest>,
) -> Response {
match CaptchaService::validate_numeric_captcha(
&state.redis_pool,
"your_prefix",
&payload.id,
&payload.code,
true, )
.await
{
Ok(_) => (
StatusCode::OK,
Json(ApiResponse {
success: true,
data: Some("Valid".to_string()),
message: "Captcha validation successful".to_string(),
}),
)
.into_response(),
Err(e) => (
StatusCode::BAD_REQUEST,
Json(ApiResponse::<String> {
success: false,
data: None,
message: format!("Captcha validation failed: {}", e),
}),
)
.into_response(),
}
}
#[neocrates::axum::debug_handler]
async fn validate_alphanumeric_captcha(
State(state): State<AppState>,
Json(payload): Json<ValidateCaptchaRequest>,
) -> Response {
match CaptchaService::validate_alphanumeric_captcha(
&state.redis_pool,
"your_prefix",
&payload.id,
&payload.code,
true, )
.await
{
Ok(_) => (
StatusCode::OK,
Json(ApiResponse {
success: true,
data: Some("Valid".to_string()),
message: "Captcha validation successful".to_string(),
}),
)
.into_response(),
Err(e) => (
StatusCode::BAD_REQUEST,
Json(ApiResponse::<String> {
success: false,
data: None,
message: format!("Captcha validation failed: {}", e),
}),
)
.into_response(),
}
}
#[neocrates::axum::debug_handler]
async fn validate_slider_captcha(
State(state): State<AppState>,
Json(payload): Json<ValidateSliderRequest>,
) -> Response {
match CaptchaService::captcha_slider_valid(
&state.redis_pool,
"your_prefix",
&payload.code,
&payload.account,
true, )
.await
{
Ok(_) => (
StatusCode::OK,
Json(ApiResponse {
success: true,
data: Some("Valid".to_string()),
message: "Slider captcha validation successful".to_string(),
}),
)
.into_response(),
Err(e) => (
StatusCode::BAD_REQUEST,
Json(ApiResponse::<String> {
success: false,
data: None,
message: format!("Slider captcha validation failed: {}", e),
}),
)
.into_response(),
}
}
#[tokio::main]
async fn main() {
let redis_url =
std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
println!("Connecting to Redis at: {}", redis_url);
let redis_config = neocrates::rediscache::RedisConfig {
url: redis_url.clone(),
max_size: 10,
min_idle: Some(1),
connection_timeout: std::time::Duration::from_secs(5),
idle_timeout: Some(std::time::Duration::from_secs(600)),
max_lifetime: Some(std::time::Duration::from_secs(3600)),
};
let redis_pool = match RedisPool::new(redis_config).await {
Ok(pool) => Arc::new(pool),
Err(e) => {
eprintln!("Failed to connect to Redis: {}", e);
eprintln!("Please make sure Redis is running at {}", redis_url);
eprintln!("You can start Redis with: redis-server");
std::process::exit(1);
}
};
println!("Successfully connected to Redis");
let state = AppState { redis_pool };
let app = Router::new()
.route("/", get(health_check))
.route(
"/api/captcha/numeric/generate",
post(generate_numeric_captcha),
)
.route(
"/api/captcha/numeric/validate",
post(validate_numeric_captcha),
)
.route(
"/api/captcha/alphanumeric/generate",
post(generate_alphanumeric_captcha),
)
.route(
"/api/captcha/alphanumeric/validate",
post(validate_alphanumeric_captcha),
)
.route(
"/api/captcha/slider/generate",
post(generate_slider_captcha),
)
.route(
"/api/captcha/slider/validate",
post(validate_slider_captcha),
)
.with_state(state);
let listener = neocrates::tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
println!("\n🚀 Server running on http://127.0.0.1:3000");
println!("\n📝 Available endpoints:");
println!(" - GET / (health check)");
println!(" - POST /api/captcha/numeric/generate (generate numeric captcha)");
println!(" - POST /api/captcha/numeric/validate (validate numeric captcha)");
println!(" - POST /api/captcha/alphanumeric/generate (generate alphanumeric captcha)");
println!(" - POST /api/captcha/alphanumeric/validate (validate alphanumeric captcha)");
println!(" - POST /api/captcha/slider/generate (generate slider captcha)");
println!(" - POST /api/captcha/slider/validate (validate slider captcha)");
println!("\n💡 Example curl commands:");
println!(" # Generate numeric captcha:");
println!(r#" curl -X POST http://localhost:3000/api/captcha/numeric/generate \\"#);
println!(r#" -H "Content-Type: application/json" \\"#);
println!(r#" -d '{{"account":"user@example.com","length":6}}'"#);
println!("\n # Validate numeric captcha:");
println!(r#" curl -X POST http://localhost:3000/api/captcha/numeric/validate \\"#);
println!(r#" -H "Content-Type: application/json" \\"#);
println!(r#" -d '{{"id":"<captcha-id>","code":"123456}}'"#);
println!("\n # Generate slider captcha:");
println!(r#" curl -X POST http://localhost:3000/api/captcha/slider/generate \\"#);
println!(r#" -H "Content-Type: application/json" \\"#);
println!(r#" -d '{{"account":"user@example.com","code":"abc123"}}'"#);
println!("\n # Validate slider captcha:");
println!(r#" curl -X POST http://localhost:3000/api/captcha/slider/validate \\"#);
println!(r#" -H "Content-Type: application/json" \\"#);
println!(r#" -d '{{"account":"user@example.com","code":"abc123"}}'"#);
neocrates::axum::serve(listener, app).await.unwrap();
}