use crate::models::{AdminUser, Claims, Form, LoginCredentials, Submission}; use actix_web::{web, HttpResponse, Responder}; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; use jsonwebtoken::{encode, EncodingKey, Header}; use rusqlite::{params, Connection}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; use uuid::Uuid; use crate::auth::Auth; // Structs for requests and responses #[derive(Deserialize)] pub struct LoginRequest { pub username: String, pub password: String, } #[derive(Serialize)] pub struct LoginResponse { pub token: String, } // Public: Login handler pub async fn login( db: web::Data>>, login_request: web::Json, ) -> impl Responder { let conn = db.lock().unwrap(); let user_id = match crate::db::authenticate_user(&conn, &login_request.username, &login_request.password) { Ok(Some(user_id)) => user_id, Ok(None) => return HttpResponse::Unauthorized().body("Invalid username or password"), Err(_) => return HttpResponse::InternalServerError().body("Database error"), }; let token = Uuid::new_v4().to_string(); if let Err(_) = crate::db::generate_token_for_user(&conn, &user_id, &token) { return HttpResponse::InternalServerError().body("Failed to generate token"); } HttpResponse::Ok().json(LoginResponse { token }) } // Public: Submit a form pub async fn submit_form( db: web::Data>>, path: web::Path, submission: web::Form, ) -> impl Responder { let conn = db.lock().unwrap(); let submission_id = Uuid::new_v4().to_string(); let form_id = path.into_inner(); let submission_json = serde_json::to_string(&submission.into_inner()).unwrap(); match conn.execute( "INSERT INTO submissions (id, form_id, data) VALUES (?1, ?2, ?3)", params![submission_id, form_id, submission_json], ) { Ok(_) => HttpResponse::Ok().json(submission_id), Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)), } } // Protected: Create a new form pub async fn create_form( db: web::Data>>, auth: Auth, form: web::Json, ) -> impl Responder { println!("Authenticated user: {}", auth.user_id); let conn = db.lock().unwrap(); let form_id = Uuid::new_v4().to_string(); let form_json = serde_json::to_string(&form.fields).unwrap(); match conn.execute( "INSERT INTO forms (id, name, fields) VALUES (?1, ?2, ?3)", params![form_id, form.name, form_json], ) { Ok(_) => HttpResponse::Ok().json(form_id), Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)), } } // Protected: Get all forms pub async fn get_forms(db: web::Data>>, auth: Auth) -> impl Responder { println!("Authenticated user: {}", auth.user_id); let conn = db.lock().unwrap(); let mut stmt = match conn.prepare("SELECT id, name, fields FROM forms") { Ok(stmt) => stmt, Err(e) => return HttpResponse::InternalServerError().body(format!("Error: {}", e)), }; let forms_iter = stmt .query_map([], |row| { let id: Option = row.get(0)?; let name: String = row.get(1)?; let fields: String = row.get(2)?; let fields = serde_json::from_str(&fields).unwrap(); Ok(crate::models::Form { id, name, fields }) }) .unwrap(); let forms: Vec = forms_iter.filter_map(|f| f.ok()).collect(); HttpResponse::Ok().json(forms) } // Protected: Get submissions for a form pub async fn get_submissions( db: web::Data>>, auth: Auth, path: web::Path, ) -> impl Responder { println!("Authenticated user: {}", auth.user_id); let conn = db.lock().unwrap(); let form_id = path.into_inner(); let mut stmt = match conn.prepare("SELECT id, form_id, data FROM submissions WHERE form_id = ?1") { Ok(stmt) => stmt, Err(e) => return HttpResponse::InternalServerError().body(format!("Error: {}", e)), }; let submissions_iter = stmt .query_map([form_id], |row| { let id: String = row.get(0)?; let form_id: String = row.get(1)?; let data: String = row.get(2)?; let data = serde_json::from_str(&data).unwrap(); Ok(crate::models::Submission { id, form_id, data }) }) .unwrap(); let submissions: Vec = submissions_iter.filter_map(|s| s.ok()).collect(); HttpResponse::Ok().json(submissions) } pub async fn admin_login( db: web::Data>>, credentials: web::Json, ) -> impl Responder { let conn = match db.lock() { Ok(conn) => conn, Err(_) => return HttpResponse::InternalServerError().body("Database lock error"), }; let mut stmt = match conn.prepare("SELECT username, password_hash FROM admin_users WHERE username = ?1") { Ok(stmt) => stmt, Err(e) => { return HttpResponse::InternalServerError().body(format!("Database error: {}", e)) } }; let admin: Option = match stmt.query_row([&credentials.username], |row| { Ok(AdminUser { username: row.get(0)?, password_hash: row.get(1)?, }) }) { Ok(admin) => Some(admin), Err(rusqlite::Error::QueryReturnedNoRows) => None, // No user found Err(e) => return HttpResponse::InternalServerError().body(format!("Query error: {}", e)), }; match admin { Some(user) => { let parsed_hash = match PasswordHash::new(&user.password_hash) { Ok(hash) => hash, Err(_) => { return HttpResponse::InternalServerError() .body("Invalid password hash format in database") } }; let argon2 = Argon2::default(); let is_valid = argon2 .verify_password(credentials.password.as_bytes(), &parsed_hash) .is_ok(); if is_valid { let expiration = match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(duration) => duration.as_secs() as usize + 24 * 3600, Err(_) => return HttpResponse::InternalServerError().body("System time error"), }; let claims = Claims { sub: user.username, exp: expiration, }; let token = match encode( &Header::default(), &claims, &EncodingKey::from_secret("your-secret-key".as_ref()), ) { Ok(token) => token, Err(_) => { return HttpResponse::InternalServerError().body("Token generation error") } }; HttpResponse::Ok().json(json!({ "token": token })) } else { HttpResponse::Unauthorized().json(json!({ "error": "Invalid credentials" })) } } None => HttpResponse::Unauthorized().json(json!({ "error": "Invalid credentials" })), } } pub async fn create_admin( db: web::Data>>, user: web::Json, ) -> impl Responder { let conn = db.lock().unwrap(); let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let password_hash = argon2 .hash_password(user.password.as_bytes(), &salt) .unwrap() .to_string(); match conn.execute( "INSERT INTO admin_users (username, password_hash) VALUES (?1, ?2)", params![user.username, password_hash], ) { Ok(_) => HttpResponse::Ok().json(json!({ "message": "Admin user created successfully" })), Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)), } }