use crate::model; use crate::state; use crate::utils; use axum::extract::{Json, State}; use bcrypt::verify; use chrono::{Duration, Utc}; use sqlx::Row; use std::sync::Arc; #[utoipa::path( post, path = "/api/login", request_body = LoginRequest, responses( (status = 200, description = "Login successful", body = AuthResponse), (status = 400, description = "Invalid credentials", body = AuthResponse) ) )] pub async fn login_handler( State(state): State>, Json(payload): Json, ) -> Json { // Get user from database let user_result = sqlx::query("SELECT username, password_hash FROM users WHERE phone = ?") .bind(&payload.phone) .fetch_optional(&state.db_pool) .await; match user_result { Ok(Some(row)) => { let username: String = row.get("username"); let password_hash: String = row.get("password_hash"); // Verify password match verify(&payload.password, &password_hash) { Ok(true) => { // 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(&username) .bind(&payload.phone) .bind(expires_at.naive_utc()) .execute(&state.db_pool) .await; Json(model::AuthResponse { success: true, message: "Login successful".to_string(), username: Some(username), token: Some(token), }) } Ok(false) => Json(model::AuthResponse { success: false, message: "Invalid password".to_string(), username: None, token: None, }), Err(_) => Json(model::AuthResponse { success: false, message: "Error verifying password".to_string(), username: None, token: None, }), } } Ok(None) => Json(model::AuthResponse { success: false, message: "Phone number not found".to_string(), username: None, token: None, }), Err(_) => Json(model::AuthResponse { success: false, message: "Login failed".to_string(), username: None, token: None, }), } }