forked from Roflin/gamenight
Started reimplementation of the Rest api in actix-web
This commit is contained in:
60
backend-actix/src/request/error.rs
Normal file
60
backend-actix/src/request/error.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
use actix_web::{ResponseError, error::BlockingError};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use validator::ValidationErrors;
|
||||
|
||||
use crate::schema::error::DatabaseError;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ApiError {
|
||||
pub error: String
|
||||
}
|
||||
|
||||
impl Display for ApiError {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
write!(f, "{}", self.error)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for ApiError { }
|
||||
|
||||
impl From<DatabaseError> for ApiError {
|
||||
fn from(value: DatabaseError) -> Self {
|
||||
ApiError {
|
||||
error: value.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockingError> for ApiError {
|
||||
fn from(value: BlockingError) -> Self {
|
||||
ApiError {
|
||||
error: value.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for ApiError {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
ApiError {
|
||||
error: value.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jsonwebtoken::errors::Error> for ApiError {
|
||||
fn from(value: jsonwebtoken::errors::Error) -> Self {
|
||||
ApiError {
|
||||
error: value.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValidationErrors> for ApiError {
|
||||
fn from(value: ValidationErrors) -> Self {
|
||||
ApiError {
|
||||
error: value.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
98
backend-actix/src/request/handler.rs
Normal file
98
backend-actix/src/request/handler.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
|
||||
use actix_web::http::header::ContentType;
|
||||
use actix_web::{web, post, HttpResponse, Responder};
|
||||
use chrono::Utc;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use uuid::Uuid;
|
||||
use validator::ValidateArgs;
|
||||
use crate::DbPool;
|
||||
use crate::request::request_data::{Login, Register};
|
||||
use crate::request::error::ApiError;
|
||||
use crate::request::responses::LoginResponse;
|
||||
use crate::schema::user::Role;
|
||||
use crate::schema::{self};
|
||||
use serde_json;
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
|
||||
impl Into<schema::user::LoginUser> for Login {
|
||||
fn into(self) -> schema::user::LoginUser {
|
||||
schema::user::LoginUser {
|
||||
username: self.username,
|
||||
password: self.password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<schema::user::Register> for Register {
|
||||
fn into(self) -> schema::user::Register {
|
||||
schema::user::Register {
|
||||
email: self.email,
|
||||
username: self.username,
|
||||
password: self.password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
exp: i64,
|
||||
uid: Uuid,
|
||||
role: Role,
|
||||
}
|
||||
|
||||
#[post("/login")]
|
||||
pub async fn login(pool: web::Data<DbPool>, login_data: web::Json<Login>) -> Result<impl Responder, ApiError> {
|
||||
let data = login_data.into_inner();
|
||||
|
||||
let response = if let Some(user) = web::block(move || {
|
||||
let mut conn = pool.get().expect("couldn't get db connection from pool");
|
||||
schema::login(&mut conn, data.into())
|
||||
})
|
||||
.await??
|
||||
{
|
||||
let my_claims = Claims {
|
||||
exp: Utc::now().timestamp() + chrono::Duration::days(7).num_seconds(),
|
||||
uid: user.id,
|
||||
role: user.role,
|
||||
};
|
||||
|
||||
let secret = "secret";
|
||||
let token = encode(
|
||||
&Header::default(),
|
||||
&my_claims,
|
||||
&EncodingKey::from_secret(secret.as_bytes()))?;
|
||||
|
||||
LoginResponse::success(user.id, token)
|
||||
}
|
||||
else {
|
||||
LoginResponse::failure("User doesn't exist or password doesn't match".to_string())
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type(ContentType::json())
|
||||
.body(serde_json::to_string(&response)?)
|
||||
)
|
||||
}
|
||||
|
||||
#[post("/register")]
|
||||
pub async fn register(pool: web::Data<DbPool>, register_data: web::Json<Register>) -> Result<impl Responder, ApiError> {
|
||||
let data1 = register_data.clone();
|
||||
let data2 = register_data.clone();
|
||||
|
||||
let register_request : schema::user::Register = data2.into();
|
||||
let mut conn1 = pool.get().expect("couldn't get db connection from pool");
|
||||
let mut conn2 = pool.get().expect("couldn't get db connection from pool");
|
||||
|
||||
let _validation_result = web::block(move || {
|
||||
data1.validate_args((&mut conn1, &mut conn2))
|
||||
}).await??;
|
||||
|
||||
let mut conn3 = pool.get().expect("couldn't get db connection from pool");
|
||||
let _register_result = web::block(move || {
|
||||
schema::register(&mut conn3, register_request)
|
||||
}).await??;
|
||||
|
||||
return Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
|
||||
8
backend-actix/src/request/mod.rs
Normal file
8
backend-actix/src/request/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
mod request_data;
|
||||
mod responses;
|
||||
mod handler;
|
||||
mod error;
|
||||
|
||||
pub use handler::login;
|
||||
pub use handler::register;
|
||||
28
backend-actix/src/request/request_data.rs
Normal file
28
backend-actix/src/request/request_data.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::schema::user::{unique_email, unique_username};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct Login {
|
||||
pub username: String,
|
||||
pub password: String
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Validate)]
|
||||
pub struct Register {
|
||||
#[validate(
|
||||
length(min = 1),
|
||||
custom(function = "unique_username", arg = "&'v_a mut diesel::PgConnection")
|
||||
)]
|
||||
pub username: String,
|
||||
#[validate(
|
||||
email,
|
||||
custom(function = "unique_email", arg = "&'v_a mut diesel::PgConnection")
|
||||
)]
|
||||
#[validate(email)]
|
||||
pub email: String,
|
||||
#[validate(length(min = 10), must_match = "password_repeat")]
|
||||
pub password: String,
|
||||
pub password_repeat: String,
|
||||
}
|
||||
30
backend-actix/src/request/responses.rs
Normal file
30
backend-actix/src/request/responses.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LoginResponse {
|
||||
pub login_result: bool,
|
||||
pub message: Option<String>,
|
||||
pub user_id: Option<Uuid>,
|
||||
pub jwt_token: Option<String>
|
||||
}
|
||||
|
||||
impl LoginResponse {
|
||||
pub fn success(user_id: Uuid, token: String) -> Self {
|
||||
Self {
|
||||
login_result: true,
|
||||
message: None,
|
||||
user_id: Some(user_id),
|
||||
jwt_token: Some(token)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn failure(message: String) -> Self {
|
||||
Self {
|
||||
login_result: false,
|
||||
message: Some(message),
|
||||
user_id: None,
|
||||
jwt_token: None
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user