127 lines
3.7 KiB
Rust
127 lines
3.7 KiB
Rust
use crate::model;
|
|
use crate::state;
|
|
use crate::utils;
|
|
use axum::extract::{Json, State};
|
|
use bcrypt::{DEFAULT_COST, hash};
|
|
use chrono::{Duration, Utc};
|
|
use std::sync::Arc;
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/register",
|
|
request_body = RegisterRequest,
|
|
responses(
|
|
(status = 200, description = "Registration successful", body = AuthResponse),
|
|
(status = 400, description = "Registration failed", body = AuthResponse)
|
|
)
|
|
)]
|
|
pub async fn register_handler(
|
|
State(state): State<Arc<state::AppState>>,
|
|
Json(payload): Json<model::RegisterRequest>,
|
|
) -> Json<model::AuthResponse> {
|
|
// Validate input
|
|
if payload.phone.len() < 5 {
|
|
return Json(model::AuthResponse {
|
|
success: false,
|
|
message: "Phone number is too short".to_string(),
|
|
username: None,
|
|
token: None,
|
|
});
|
|
}
|
|
|
|
if payload.username.len() < 2
|
|
|| payload.username.len() > state.config.limits.max_username_length
|
|
{
|
|
return Json(model::AuthResponse {
|
|
success: false,
|
|
message: format!(
|
|
"Username must be between 3 and {} characters",
|
|
state.config.limits.max_username_length
|
|
),
|
|
username: None,
|
|
token: None,
|
|
});
|
|
}
|
|
|
|
if payload.password.len() < state.config.limits.min_password_length {
|
|
return Json(model::AuthResponse {
|
|
success: false,
|
|
message: format!(
|
|
"Password must be at least {} characters",
|
|
state.config.limits.min_password_length
|
|
),
|
|
username: None,
|
|
token: None,
|
|
});
|
|
}
|
|
|
|
// Hash password
|
|
let password_hash = match hash(&payload.password, DEFAULT_COST) {
|
|
Ok(hash) => hash,
|
|
Err(_) => {
|
|
return Json(model::AuthResponse {
|
|
success: false,
|
|
message: "Error hashing password".to_string(),
|
|
username: None,
|
|
token: None,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Insert user into database
|
|
let result = sqlx::query("INSERT INTO users (phone, username, password_hash) VALUES (?, ?, ?)")
|
|
.bind(&payload.phone)
|
|
.bind(&payload.username)
|
|
.bind(&password_hash)
|
|
.execute(&state.db_pool)
|
|
.await;
|
|
|
|
match result {
|
|
Ok(_) => {
|
|
// Generate session token
|
|
let token = utils::generate_token();
|
|
let expires_at = Utc::now() + Duration::days(30);
|
|
|
|
// Store session
|
|
let _ = sqlx::query(
|
|
"INSERT INTO sessions (token, username, phone, expires_at) VALUES (?, ?, ?, ?)",
|
|
)
|
|
.bind(&token)
|
|
.bind(&payload.username)
|
|
.bind(&payload.phone)
|
|
.bind(expires_at.naive_utc())
|
|
.execute(&state.db_pool)
|
|
.await;
|
|
|
|
Json(model::AuthResponse {
|
|
success: true,
|
|
message: "Registration successful".to_string(),
|
|
username: Some(payload.username),
|
|
token: Some(token),
|
|
})
|
|
}
|
|
Err(e) => {
|
|
let error_message = if e
|
|
.to_string()
|
|
.contains("UNIQUE constraint failed: users.phone")
|
|
{
|
|
"Phone number already registered".to_string()
|
|
} else if e
|
|
.to_string()
|
|
.contains("UNIQUE constraint failed: users.username")
|
|
{
|
|
"Username already taken".to_string()
|
|
} else {
|
|
"Registration failed".to_string()
|
|
};
|
|
|
|
Json(model::AuthResponse {
|
|
success: false,
|
|
message: error_message,
|
|
username: None,
|
|
token: None,
|
|
})
|
|
}
|
|
}
|
|
}
|