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