use crate::types::{NewPost, Post}; use crate::AppState; use actix_web::web::{to, Data, Path}; use actix_web::{get, post, web::Json, HttpResponse, Responder, Result}; use argon2::password_hash::SaltString; use log::*; use serde::{Deserialize, Serialize}; use uuid::Uuid; #[get("/")] pub async fn get_posts(data: Data) -> impl Responder { match data.posts.lock() { Ok(posts) => { let posts: Vec = posts.values().cloned().collect(); HttpResponse::Ok().json(posts) } Err(e) => { warn!("Error: {:?}", e); HttpResponse::InternalServerError().body("Error") } } } #[post("/")] pub async fn new_post(new_post: Json, data: Data) -> impl Responder { let post = Post::from(new_post.into_inner()); info!("Created post {:?}", post.uuid); // let q = "INSERT INTO posts (uuid, content, upvotes, downvotes) VALUES (?, ?, ?, ?)"; // let query = sqlx::query(q) // .bind(post.uuid) // .bind(post.content) // .bind(post.votes.up) // .bind(post.votes.down); match data.posts.lock() { Ok(mut posts) => { posts.insert(post.uuid, post); } Err(e) => { warn!("Error: {:?}", e); } }; HttpResponse::Ok().json("Post added!") } // This is a test route, returns "Hello, world!" #[get("/test")] pub async fn test(data: Data) -> impl Responder { match data.posts.lock() { Ok(posts) => { let posts: Vec = posts.values().cloned().collect(); HttpResponse::Ok().body(format!("Hello, world! {:?}", posts)) } Err(e) => { warn!("Error: {:?}", e); HttpResponse::InternalServerError().body("Error") } } } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum VoteDirection { Up, Down, Unupvote, Undownvote, } #[post("vote/{uuid}/{direction}")] pub async fn vote(params: Path<(Uuid, VoteDirection)>, data: Data) -> impl Responder { let (uuid, direction) = params.into_inner(); println!("Voting {:?} on post {:?}", direction, uuid); match data.posts.lock() { Ok(mut posts) => { let uuid = uuid; if let Some(post) = posts.get_mut(&uuid) { match direction { VoteDirection::Up => post.votes.up += 1, VoteDirection::Unupvote => post.votes.up -= 1, VoteDirection::Down => post.votes.down += 1, VoteDirection::Undownvote => post.votes.down -= 1, } HttpResponse::Ok().body("Downvoted!") } else { HttpResponse::NotFound().body("Post not found!") } } Err(e) => { warn!("Error: {:?}", e); HttpResponse::InternalServerError().body("Error") } } } #[derive(Debug, Serialize, Deserialize)] pub struct RegisterData { username: String, password: String, captcha: String, } use argon2::password_hash::rand_core::OsRng; use argon2::password_hash::*; use argon2::Algorithm; use argon2::Argon2; use argon2::PasswordHasher; use argon2::PasswordVerifier; use argon2::Version; #[post("/register")] pub async fn register(data: Json, state: Data) -> Result { let q = "SELECT password FROM users WHERE username = ?"; let query = sqlx::query(q).bind(&data.username); let result = query.fetch_one(&state.pool).await.ok(); if result.is_some() { info!("User \"{}\" already exists", data.username); return Ok(HttpResponse::BadRequest().json("Error")); } let password = data.password.clone(); let salt = SaltString::generate(&mut OsRng); let phc_hash = Argon2::default().hash_password(password.as_bytes(), &salt); if let Ok(phc_hash) = phc_hash { info!("User: {} registered", data.username); let phc_hash = phc_hash.to_string(); let q = "INSERT INTO users (username, password) VALUES (?, ?)"; let query = sqlx::query(q).bind(&data.username).bind(&phc_hash); query.execute(&state.pool).await.unwrap(); } else { return Ok(HttpResponse::BadRequest().json("Error")); } Ok(HttpResponse::Ok().json("User registered")) } #[derive(Debug, Serialize, Deserialize)] pub struct LoginData { username: String, password: String, } use sqlx::Row; #[derive(Debug, Serialize, Deserialize)] struct LoginResponse { username: String, token: String, } use crate::jwt::token_factory; #[post("/login")] pub async fn login(data: Json, state: Data) -> Result { let q = "SELECT password FROM users WHERE username = ?"; let query = sqlx::query(q).bind(&data.username); let result = query.fetch_one(&state.pool).await.ok(); if let Some(row) = result { let phc_from_db = row.get::("password"); let pwhash = PasswordHash::new(&phc_from_db).unwrap_or_else(|_| { warn!( "Invalid hash for user {} fetched from database (not a valid PHC string)", data.username ); panic!(); }); match Argon2::default().verify_password(data.password.as_bytes(), &pwhash) { Ok(some) => { info!("User {} logged in", data.username); let token = token_factory(&data.username).unwrap(); println!("{:?}", token); return Ok(HttpResponse::Ok().json(LoginResponse { username: data.username.clone(), token: token, })); // Sign jwt with user claims } Err(e) => { info!("User \"{}\" failed to log in", data.username); return Ok(HttpResponse::BadRequest().json("Error")); } } } Ok(HttpResponse::Ok().json("What happens here???")) }