191 lines
5.9 KiB
Rust
Executable file
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???"))
|
|
}
|