use crate::routes::{Post, User}; use argon2::{ password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHasher, PasswordVerifier, }; use log::{info, warn}; use sqlx::SqlitePool; // Gets the latest posts from the database, ordered by created_at pub async fn db_get_latest_posts(pool: &SqlitePool, limit: i64, offset: i64) -> Vec { sqlx::query_as!( Post, "SELECT * FROM posts ORDER BY created_at DESC LIMIT ? OFFSET ?", limit, offset ) .fetch_all(pool) .await .unwrap() } // Gets the post with id from the database pub async fn db_get_post(id: i64, pool: &SqlitePool) -> Option { sqlx::query_as!(Post, "SELECT * FROM posts WHERE id = ?", id) .fetch_one(pool) .await .ok() } // Inserts a new post to the database pub async fn db_new_post(userid: i64, content: &str, pool: &SqlitePool) -> Option { info!("User with id {} submitted a post", userid); let insert_query = sqlx::query!( "INSERT INTO posts (user_id, content) VALUES (?, ?)", userid, content ) .execute(pool) .await; if insert_query.is_err() { let s = insert_query.err().unwrap(); warn!("Error inserting post into database: {}", s); return None; } // Dips into the database to get the post we just inserted let post = sqlx::query_as!( Post, "SELECT * FROM posts WHERE id = (SELECT MAX(id) FROM posts)" ) .fetch_one(pool) .await .ok()?; Some(post) } pub async fn db_user_exists(username: String, pool: &SqlitePool) -> bool { let exists = sqlx::query!("SELECT username FROM users WHERE username = ?", username) .fetch_one(pool) .await .ok() .map(|row| row.username); exists.is_some() } pub async fn db_user_login(username: String, password: String, pool: &SqlitePool) -> Option { let username = username.clone(); let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = ?", username) .fetch_one(pool) .await .ok()?; let phc_password = user.password.clone(); let phc_password = match argon2::PasswordHash::new(&phc_password) { Ok(phc_password) => phc_password, Err(_) => { warn!( "Invalid hash for user {} fetched from database (not a valid PHC string)", username ); return None; } }; let argon2 = Argon2::default(); let password = password.as_bytes(); match argon2.verify_password(password, &phc_password) { Ok(_) => Some(user), Err(_) => None, } } pub async fn db_new_user(username: String, password: String, pool: &SqlitePool) -> Option { // First check if the user already exists match db_user_exists(username.clone(), pool).await { true => { warn!("User \"{}\" already exists", username); return None; } false => {} } // Unwrapping here because if this fails, we have a serious problem let phc_hash = Argon2::default() .hash_password(password.as_bytes(), &SaltString::generate(&mut OsRng)) .unwrap() .to_string(); // Insert our new user into the database let insert_query = sqlx::query!( "INSERT INTO users (username, password) VALUES (?, ?)", username, phc_hash ) .execute(pool) .await; match insert_query { Ok(_) => { info!("User: {} registered", username); let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = ?", username) .fetch_one(pool) .await .ok()?; Some(user) } Err(e) => { warn!("Error inserting user into database: {}", e); return None; } } }