add(v1): PUT /posts
This commit is contained in:
parent
8a1d0cf0bb
commit
905baba00a
@ -9,9 +9,10 @@ pub struct Post {
|
||||
pub author_id: i32
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[derive(Insertable, Serialize, Deserialize)]
|
||||
#[diesel(table_name = crate::schema::posts)]
|
||||
pub struct NewPost<'a> {
|
||||
pub title: &'a str,
|
||||
pub content: &'a str,
|
||||
pub struct NewPost {
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
pub author_id: i32
|
||||
}
|
@ -14,9 +14,9 @@ pub struct User {
|
||||
pub updated_at: Option<NaiveDateTime>
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[derive(Insertable, Serialize, Deserialize)]
|
||||
#[diesel(table_name = crate::schema::users)]
|
||||
pub struct NewUser<'a> {
|
||||
pub username: &'a str,
|
||||
pub password: &'a str
|
||||
pub struct NewUser {
|
||||
pub username: String,
|
||||
pub password: String
|
||||
}
|
@ -15,17 +15,17 @@ static WEEK: i64 = 60 * 60 * 24 * 7;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub sub: String,
|
||||
pub user_id: i32,
|
||||
pub exp: usize,
|
||||
}
|
||||
|
||||
impl Claims {
|
||||
pub fn generate_token(secret: &str, id: &str) -> String {
|
||||
pub fn generate_token(secret: &str, user_id: i32) -> String {
|
||||
let now = Utc::now().timestamp();
|
||||
|
||||
let payload = Claims {
|
||||
exp: (now + WEEK) as usize,
|
||||
sub: id.to_string(),
|
||||
user_id,
|
||||
};
|
||||
|
||||
jsonwebtoken::encode(
|
||||
@ -39,7 +39,7 @@ impl Claims {
|
||||
#[derive(Deserialize, Debug, Serialize)]
|
||||
pub struct AuthorizedUser {
|
||||
pub token: String,
|
||||
pub sub: String,
|
||||
pub user_id: i32,
|
||||
}
|
||||
|
||||
impl FromRequest for AuthorizedUser {
|
||||
@ -63,7 +63,7 @@ impl FromRequest for AuthorizedUser {
|
||||
let parts: Vec<&str> = v.split(" ").collect();
|
||||
|
||||
if parts.len() != 2 {
|
||||
return ready(Err(ErrorBadRequest("Not Authorized")));
|
||||
return ready(Err(ErrorBadRequest("unauthorized")));
|
||||
}
|
||||
|
||||
let claims = jsonwebtoken::decode::<Claims>(
|
||||
@ -73,12 +73,12 @@ impl FromRequest for AuthorizedUser {
|
||||
);
|
||||
|
||||
if claims.is_err() {
|
||||
return ready(Err(ErrorBadRequest("Not Authorized")));
|
||||
return ready(Err(ErrorBadRequest("unauthorized")));
|
||||
}
|
||||
|
||||
let claims = claims.unwrap().claims;
|
||||
let authorized_user = AuthorizedUser {
|
||||
sub: claims.sub,
|
||||
user_id: claims.user_id,
|
||||
token: v.to_string()
|
||||
};
|
||||
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod jwt;
|
||||
pub mod jwt;
|
||||
pub mod response;
|
24
src/utils/response.rs
Normal file
24
src/utils/response.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Response<T> {
|
||||
pub ok: bool,
|
||||
pub failure: Option<String>,
|
||||
pub result: Option<T>
|
||||
}
|
||||
|
||||
pub fn failure<T>(failure: T) -> Response<T> where T: ToString {
|
||||
Response {
|
||||
ok: false,
|
||||
result: None,
|
||||
failure: Some(failure.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn success<T>(result: T) -> Response<T> {
|
||||
Response {
|
||||
ok: true,
|
||||
failure: None,
|
||||
result: Some(result)
|
||||
}
|
||||
}
|
@ -1,36 +1,24 @@
|
||||
use actix_web::{
|
||||
web::{ServiceConfig, Data, Json, scope},
|
||||
HttpResponse, Responder, post
|
||||
post,
|
||||
web::{scope, Data, Json, ServiceConfig},
|
||||
HttpResponse, Responder,
|
||||
};
|
||||
use argon2::verify_encoded;
|
||||
use diesel::{ExpressionMethods, QueryDsl, OptionalExtension, RunQueryDsl};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
|
||||
|
||||
use crate::{database::models::user::User, utils::jwt::Claims};
|
||||
use crate::schema::users::dsl::*;
|
||||
use crate::utils::response::{failure, success};
|
||||
use crate::State;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UserBody {
|
||||
pub username: String,
|
||||
pub password: String
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TokenResponse {
|
||||
ok: bool,
|
||||
token: String
|
||||
}
|
||||
use crate::{database::models::user::NewUser, schema::users::dsl::*};
|
||||
use crate::{database::models::user::User, utils::jwt::Claims};
|
||||
|
||||
#[post("/login")]
|
||||
async fn login(
|
||||
state: Data<State>,
|
||||
login_data: Json<UserBody>,
|
||||
) -> impl Responder {
|
||||
async fn login(state: Data<State>, login_data: Json<NewUser>) -> impl Responder {
|
||||
let mut conn = match state.db.get() {
|
||||
Ok(conn) => conn,
|
||||
Err(why) => return HttpResponse::InternalServerError()
|
||||
.body(format!("database connection error: {}", why)),
|
||||
Err(why) => {
|
||||
return HttpResponse::InternalServerError()
|
||||
.body(format!("database connection error: {}", why))
|
||||
}
|
||||
};
|
||||
|
||||
let secret = &state.config.jwt_secret;
|
||||
@ -42,22 +30,20 @@ async fn login(
|
||||
match user_result {
|
||||
Ok(Some(user)) => match verify_encoded(&user.password, login_data.password.as_bytes()) {
|
||||
Ok(true) => {
|
||||
let token = Claims::generate_token(secret.as_str(), &user.id.to_string());
|
||||
let token = Claims::generate_token(secret.as_str(), user.id);
|
||||
|
||||
HttpResponse::Ok().json(TokenResponse {
|
||||
ok: true,
|
||||
token
|
||||
})
|
||||
HttpResponse::Ok().json(success(token))
|
||||
}
|
||||
Ok(false) => HttpResponse::Unauthorized().body("Invalid username or password"),
|
||||
Err(err) => HttpResponse::InternalServerError().body(format!("Error verifying password: {}", err)),
|
||||
Ok(false) => HttpResponse::Unauthorized().json(failure("invalid username or password")),
|
||||
Err(err) => HttpResponse::InternalServerError()
|
||||
.json(failure(format!("error while verifying password: {}", err))),
|
||||
},
|
||||
Ok(None) | Err(_) => HttpResponse::Unauthorized().body("Invalid username or password"),
|
||||
Ok(None) | Err(_) => {
|
||||
HttpResponse::Unauthorized().json(failure("invalid username or password"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_routes(cfg: &mut ServiceConfig) {
|
||||
cfg.service(scope("/auth")
|
||||
.service(login)
|
||||
);
|
||||
}
|
||||
cfg.service(scope("/auth").service(login));
|
||||
}
|
||||
|
@ -1,17 +1,30 @@
|
||||
use actix_web::{web::{ServiceConfig, Data, scope}, HttpResponse, Responder, get};
|
||||
use diesel::RunQueryDsl;
|
||||
use actix_web::{
|
||||
get,
|
||||
http::StatusCode,
|
||||
put,
|
||||
web::{scope, Data, Json, ServiceConfig},
|
||||
HttpResponse, Responder,
|
||||
};
|
||||
use diesel::{insert_into, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
|
||||
use crate::database::models::post::Post;
|
||||
use crate::schema::posts::dsl::*;
|
||||
use crate::schema::users::dsl::{id as user_id, users};
|
||||
use crate::utils::response::failure;
|
||||
use crate::State;
|
||||
use crate::{
|
||||
database::models::post::{NewPost, Post},
|
||||
database::models::user::User,
|
||||
utils::jwt::AuthorizedUser,
|
||||
};
|
||||
|
||||
#[get("")]
|
||||
async fn get(state: Data<State>) -> impl Responder {
|
||||
let mut conn = match state.db.get() {
|
||||
Ok(conn) => conn,
|
||||
Err(why) => return HttpResponse
|
||||
::InternalServerError()
|
||||
.body(format!("database connection error: {}", why)),
|
||||
Err(why) => {
|
||||
return HttpResponse::InternalServerError()
|
||||
.body(format!("database connection error: {}", why))
|
||||
}
|
||||
};
|
||||
|
||||
match posts.load::<Post>(&mut conn) {
|
||||
@ -20,8 +33,37 @@ async fn get(state: Data<State>) -> impl Responder {
|
||||
}
|
||||
}
|
||||
|
||||
#[put("")]
|
||||
async fn create_post(
|
||||
state: Data<State>,
|
||||
auth: AuthorizedUser,
|
||||
mut post: Json<NewPost>,
|
||||
) -> impl Responder {
|
||||
let mut conn = match state.db.get() {
|
||||
Ok(conn) => conn,
|
||||
Err(why) => {
|
||||
return HttpResponse::InternalServerError()
|
||||
.body(format!("database connection error: {}", why))
|
||||
}
|
||||
};
|
||||
|
||||
let user = users
|
||||
.filter(user_id.eq(auth.user_id))
|
||||
.first::<User>(&mut conn);
|
||||
|
||||
if user.is_err() {
|
||||
return HttpResponse::InternalServerError()
|
||||
.json(failure("cannot retrieve user from JWT token. sorry."));
|
||||
} else {
|
||||
post.author_id = user.unwrap().id;
|
||||
}
|
||||
|
||||
match insert_into(posts).values(post.0).execute(&mut conn) {
|
||||
Ok(_) => HttpResponse::new(StatusCode::NO_CONTENT),
|
||||
Err(why) => HttpResponse::InternalServerError().json(failure(why.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_routes(cfg: &mut ServiceConfig) {
|
||||
cfg.service(scope("/posts")
|
||||
.service(get)
|
||||
);
|
||||
}
|
||||
cfg.service(scope("/posts").service(get).service(create_post));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user