FrostByte/server/src/routes.rs
2023-10-10 17:12:47 +02:00

191 lines
5.9 KiB
Rust
Executable file

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<AppState>) -> impl Responder {
match data.posts.lock() {
Ok(posts) => {
let posts: Vec<Post> = 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<NewPost>, data: Data<AppState>) -> 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<AppState>) -> impl Responder {
match data.posts.lock() {
Ok(posts) => {
let posts: Vec<Post> = 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<AppState>) -> 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<RegisterData>, state: Data<AppState>) -> Result<impl Responder> {
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<LoginData>, state: Data<AppState>) -> Result<impl Responder> {
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::<String, _>("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???"))
}