feat: update to actix-web 2.0 and implement restful api

This commit is contained in:
Kilerd Chan 2020-02-03 14:53:38 +08:00
parent 941388aceb
commit 6e91d2ce55
14 changed files with 1592 additions and 1498 deletions

2236
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,38 +7,40 @@ license = "MIT"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
dotenv = "0.14.1" dotenv = "0.15.0"
lazy_static = "1.3.0" openssl = "0.10.27"
openssl = "0.10.23"
# serde # serde
serde = "1.0.92" serde = "1.0.104"
serde_derive = "1.0.92" serde_derive = "1.0.104"
r2d2 = "0.8.5" r2d2 = "0.8.8"
diesel = { version = "1.4.2", features = ["postgres", "r2d2", "chrono"] } diesel = { version = "1.4.3", features = ["postgres", "r2d2", "chrono"] }
diesel_derives = "1.4.0" diesel_derives = "1.4.1"
diesel_migrations = "1.4.0" diesel_migrations = "1.4.0"
tera = "0.11.20" tera = "1.0.2"
pulldown-cmark = { version = "0.5.2", default-features = false } pulldown-cmark = { version = "0.6.1", default-features = false }
chrono = { version = "0.4.6", features = ["serde"] } chrono = { version = "0.4.10", features = ["serde"] }
rust-crypto = "0.2.36" rust-crypto = "0.2.36"
http = "0.1.17" http = "0.2.0"
actix-web= "1.0.2" actix-web= "2.0.0"
actix-files = "0.1.2" actix-files = "0.2.1"
futures = "0.1.27" futures = "0.3.1"
# log # log
log = "0.4.6" log = "0.4.8"
pretty_env_logger = "0.3.0" pretty_env_logger = "0.4.0"
time = "0.1.42" time = "0.1"
rand = "0.6.5" rand = "0.7.3"
rss = "1.8.0" rss = "1.9.0"
actix = "0.8.3" actix = "0.9.0"
jsonwebtoken = "6.0.1" jsonwebtoken = "7.0.1"
actix-cors = "0.1.0" actix-cors = "0.2.0"
actix-identity = "0.1.0"
actix-rt = "1.0.0"
once_cell = "1.3.1"
derive_more = "0.99.2"

41
src/error.rs Normal file
View File

@ -0,0 +1,41 @@
use actix_web::error::ResponseError;
use derive_more::Display;
use actix_web::body::Body;
use http::{StatusCode};
use actix_web::HttpResponse;
use serde::Serialize;
use std::fmt::Debug;
#[derive(Debug, Display)]
pub enum RubbleError<T> {
Unauthorized(T),
BadRequest(T),
}
#[derive(Serialize)]
struct ErrorMsg<T> {
message: T
}
impl<T> ResponseError for RubbleError<T> where T: Debug + std::fmt::Display + Serialize {
fn status_code(&self) -> StatusCode {
match self {
RubbleError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
RubbleError::BadRequest(_) => StatusCode::BAD_REQUEST
}
}
fn error_response(&self) -> HttpResponse {
match self {
RubbleError::Unauthorized(message) => {
HttpResponse::Unauthorized().json(&ErrorMsg{message})
},
RubbleError::BadRequest(message) => {
HttpResponse::BadRequest().json(&ErrorMsg{message})
},
}
}
}

View File

@ -1,26 +1,23 @@
extern crate openssl;
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
#[macro_use] #[macro_use]
extern crate diesel_derives; extern crate diesel_derives;
#[macro_use] #[macro_use]
extern crate diesel_migrations; extern crate diesel_migrations;
extern crate openssl;
use std::sync::Arc; use std::sync::Arc;
use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{
middleware::{Logger, NormalizePath},
web::{FormConfig, JsonConfig},
App, HttpServer,
};
use actix_cors::Cors; use actix_cors::Cors;
use tera::compile_templates; use actix_web::{
use time::Duration; App,
HttpServer,
middleware::{Logger, NormalizePath}, web::{FormConfig, JsonConfig},
};
use once_cell::sync::Lazy;
use tera::Tera;
use dotenv::dotenv; use dotenv::dotenv;
use lazy_static::lazy_static;
use crate::{data::RubbleData, pg_pool::database_pool_establish}; use crate::{data::RubbleData, pg_pool::database_pool_establish};
@ -30,47 +27,47 @@ mod pg_pool;
mod routers; mod routers;
mod schema; mod schema;
mod utils; mod utils;
mod error;
embed_migrations!(); embed_migrations!();
lazy_static! { const TOKEN_KEY: Lazy<Vec<u8>> = Lazy::new(|| {
static ref RANDOM_TOKEN_KEY: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).collect(); std::env::var("TOKEN_KEY")
} .map(|token| Vec::from(token.as_bytes()))
.unwrap_or_else(|_| (0..32).into_iter().map(|_| rand::random::<u8>()).collect())
});
fn main() { #[actix_rt::main]
async fn main() {
dotenv().ok(); dotenv().ok();
let sys = actix::System::new("rubble");
pretty_env_logger::init(); pretty_env_logger::init();
let database_url = std::env::var("DATABASE_URL").expect("database_url must be set"); let database_url = std::env::var("DATABASE_URL").expect("database_url must be set");
let data = RubbleData { let data = RubbleData {
pool: database_pool_establish(&database_url), pool: database_pool_establish(&database_url),
tera: Arc::new(compile_templates!("templates/**/*.html")), tera: Arc::new(Tera::new("templates/**/*.html").unwrap()),
}; };
embedded_migrations::run(&data.pool.get().expect("cannot get connection")) embedded_migrations::run(&data.pool.get().expect("cannot get connection"))
.expect("panic on embedded database migration"); .expect("panic on embedded database migration");
println!("rubble is listening on 127.0.0.1:8000");
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.app_data(data.clone())
.data(data.clone()) .data(data.clone())
.data(JsonConfig::default().limit(256_000)) .data(JsonConfig::default().limit(256_000))
.data(FormConfig::default().limit(256_000)) .data(FormConfig::default().limit(256_000))
.wrap(Logger::default()) .wrap(Logger::default())
.wrap(Cors::default()) .wrap(Cors::default())
.wrap(NormalizePath) .wrap(NormalizePath)
.wrap(IdentityService::new( .configure(routers::routes)
CookieIdentityPolicy::new(&RANDOM_TOKEN_KEY)
.name("auth-cookie")
.secure(false)
.max_age_time(Duration::days(3)),
))
.service(routers::routes())
}) })
.bind(("0.0.0.0", 8000)) .bind(("0.0.0.0", 8000))
.unwrap() .unwrap()
.system_exit() .run()
.start(); .await
.unwrap()
sys.run().expect("wrong on actix system run")
} }

View File

@ -1,17 +1,16 @@
use crate::models::CRUD; use actix_web::{dev::Payload, error, FromRequest, HttpRequest};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{pg::PgConnection, result::Error};
use crate::schema::users;
use crypto::{digest::Digest, sha3::Sha3}; use crypto::{digest::Digest, sha3::Sha3};
use diesel::prelude::*; use diesel::{pg::PgConnection, result::Error};
use diesel::{AsChangeset, Insertable, Queryable}; use diesel::{AsChangeset, Insertable, Queryable};
use diesel::prelude::*;
use futures::future::{err, ok, Ready};
use serde::Serialize; use serde::Serialize;
use crate::{data::RubbleData, utils::jwt::JWTClaims}; use crate::{data::RubbleData, utils::jwt::JWTClaims};
use actix_web::{dev::Payload, error, FromRequest, HttpRequest}; use crate::models::CRUD;
use futures::IntoFuture; use crate::schema::users;
use crate::error::RubbleError;
#[derive(Queryable, Debug, Serialize, Insertable, AsChangeset)] #[derive(Queryable, Debug, Serialize, Insertable, AsChangeset)]
#[table_name = "users"] #[table_name = "users"]
@ -69,30 +68,42 @@ impl CRUD<(), User, i32> for User {
} }
impl FromRequest for User { impl FromRequest for User {
type Error = RubbleError<&'static str>;
type Future = Ready<Result<Self, Self::Error>>;
type Config = (); type Config = ();
type Error = actix_web::error::Error;
type Future = Result<Self, Self::Error>;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let tokens: Vec<&str> = req
let data = req.app_data::<RubbleData>().expect("cannot get app data");
let user = req
.headers() .headers()
.get("Authorization") .get("Authorization")
.ok_or(error::ErrorUnauthorized("cannot find authorization header"))? .ok_or(RubbleError::Unauthorized("cannot find authorization header"))
.to_str() .and_then(|header| {
.map_err(|_| error::ErrorBadRequest("error on deserialize token"))? header.to_str().map_err(|_| RubbleError::BadRequest("error on deserialize token"))
.splitn(2, ' ') })
.collect(); .map(|header| header.splitn(2, ' ').collect::<Vec<&str>>())
.and_then(|tokens| {
if tokens.len() == 2 {
Ok(tokens[1])
}else {
Err(RubbleError::BadRequest("error on deserialize token"))
}
})
.and_then(|jwt| {
JWTClaims::decode(jwt.into()).map_err(|_| RubbleError::Unauthorized("invalid jwt token"))
})
.and_then(|user_id| {
User::find_by_username(&data.postgres(), &user_id)
.map_err(|_| RubbleError::Unauthorized("error on get user"))
});
let user_id = JWTClaims::decode(tokens[1].into())
.map_err(|_| error::ErrorUnauthorized("invalid jwt token"))?;
let data = req
.app_data::<RubbleData>()
.ok_or(error::ErrorBadGateway("error on get rubble data"))?;
let result = User::find_by_username(&data.postgres(), &user_id) match user {
.map_err(|_| error::ErrorUnauthorized("error on get user"))?; Ok(user) => ok(user),
Err(e) => err(e)
}
Ok(result)
} }
} }

View File

@ -1,4 +1,3 @@
use actix_identity::Identity;
use actix_web::{ use actix_web::{
get, post, get, post,
web::{self, Form}, web::{self, Form},
@ -33,231 +32,232 @@ pub struct NewPassword {
password: String, password: String,
} }
#[get("")] //
pub fn redirect_to_admin_panel() -> impl Responder { //#[get("")]
RubbleResponder::redirect("/admin/panel") //pub fn redirect_to_admin_panel() -> impl Responder {
} // RubbleResponder::redirect("/admin/panel")
//}
#[get("/login")] //
pub fn admin_login(id: Identity, data: web::Data<RubbleData>) -> impl Responder { //#[get("/login")]
if id.identity().is_some() { //pub fn admin_login(id: Identity, data: web::Data<RubbleData>) -> impl Responder {
RubbleResponder::redirect("/admin/panel") // if id.identity().is_some() {
} else { // RubbleResponder::redirect("/admin/panel")
RubbleResponder::html(data.render("admin/login.html", &Context::new())) // } else {
} // RubbleResponder::html(data.render("admin/login.html", &Context::new()))
} // }
//}
#[post("/login")] //
pub fn admin_authentication( //#[post("/login")]
id: Identity, //pub fn admin_authentication(
user: Form<LoginForm>, // id: Identity,
data: web::Data<RubbleData>, // user: Form<LoginForm>,
) -> impl Responder { // data: web::Data<RubbleData>,
let fetched_user = User::find_by_username(&data.postgres(), &user.username); //) -> impl Responder {
// let fetched_user = User::find_by_username(&data.postgres(), &user.username);
match fetched_user { //
Ok(login_user) => { // match fetched_user {
if login_user.authenticated(&user.password) { // Ok(login_user) => {
id.remember(login_user.username); // if login_user.authenticated(&user.password) {
// id.remember(login_user.username);
RubbleResponder::redirect("/admin/panel") //
} else { // RubbleResponder::redirect("/admin/panel")
RubbleResponder::redirect("/admin/login") // } else {
} // RubbleResponder::redirect("/admin/login")
} // }
Err(_) => RubbleResponder::redirect("/admin/login"), // }
} // Err(_) => RubbleResponder::redirect("/admin/login"),
} // }
//}
#[get("/panel")] //
pub fn admin_panel(id: Identity, data: web::Data<RubbleData>) -> impl Responder { //#[get("/panel")]
if id.identity().is_none() { //pub fn admin_panel(id: Identity, data: web::Data<RubbleData>) -> impl Responder {
return RubbleResponder::redirect("/admin/login"); // if id.identity().is_none() {
} // return RubbleResponder::redirect("/admin/login");
// }
let articles = Article::read(&data.postgres()); //
let settings = Setting::load(&data.postgres()); // let articles = Article::read(&data.postgres());
// let settings = Setting::load(&data.postgres());
let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap()) //
.expect("cannot found this user"); // let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap())
// .expect("cannot found this user");
let mut context = Context::new(); //
context.insert("setting", &settings); // let mut context = Context::new();
context.insert("articles", &articles); // context.insert("setting", &settings);
context.insert("admin", &admin); // context.insert("articles", &articles);
// context.insert("admin", &admin);
RubbleResponder::html(data.render("admin/panel.html", &context)) //
} // RubbleResponder::html(data.render("admin/panel.html", &context))
//}
#[get("/{path}")] //
pub fn admin_show_page( //#[get("/{path}")]
id: Identity, //pub fn admin_show_page(
path: web::Path<String>, // id: Identity,
data: web::Data<RubbleData>, // path: web::Path<String>,
) -> impl Responder { // data: web::Data<RubbleData>,
if id.identity().is_none() { //) -> impl Responder {
return RubbleResponder::redirect("/admin/login"); // if id.identity().is_none() {
} // return RubbleResponder::redirect("/admin/login");
// }
let settings = Setting::load(&data.postgres()); //
// let settings = Setting::load(&data.postgres());
let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap()) //
.expect("cannot found this user"); // let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap())
// .expect("cannot found this user");
let mut context = Context::new(); //
context.insert("setting", &settings); // let mut context = Context::new();
context.insert("admin", &admin); // context.insert("setting", &settings);
RubbleResponder::html(data.render(&format!("admin/{}.html", path), &context)) // context.insert("admin", &admin);
} // RubbleResponder::html(data.render(&format!("admin/{}.html", path), &context))
//}
#[get("/article/new")] //
pub fn article_creation(id: Identity, data: web::Data<RubbleData>) -> impl Responder { //#[get("/article/new")]
if id.identity().is_none() { //pub fn article_creation(id: Identity, data: web::Data<RubbleData>) -> impl Responder {
return RubbleResponder::redirect("/admin/login"); // if id.identity().is_none() {
} // return RubbleResponder::redirect("/admin/login");
let settings = Setting::load(&data.postgres()); // }
let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap()) // let settings = Setting::load(&data.postgres());
.expect("cannot found this user"); // let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap())
// .expect("cannot found this user");
let mut context = Context::new(); //
// let mut context = Context::new();
let article = NewArticle { //
title: String::new(), // let article = NewArticle {
body: String::new(), // title: String::new(),
published: true, // body: String::new(),
user_id: admin.id, // published: true,
publish_at: Some(NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0)), // user_id: admin.id,
url: None, // publish_at: Some(NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0)),
keywords: vec![], // url: None,
}; // keywords: vec![],
// };
context.insert("article", &article); //
context.insert("setting", &settings); // context.insert("article", &article);
context.insert("admin", &admin); // context.insert("setting", &settings);
// context.insert("admin", &admin);
RubbleResponder::html(data.render("admin/article_add.html", &context)) //
} // RubbleResponder::html(data.render("admin/article_add.html", &context))
//}
#[get("/article/{article_id}")] //
pub fn article_edit( //#[get("/article/{article_id}")]
id: Identity, //pub fn article_edit(
article_id: web::Path<i32>, // id: Identity,
data: web::Data<RubbleData>, // article_id: web::Path<i32>,
) -> impl Responder { // data: web::Data<RubbleData>,
if id.identity().is_none() { //) -> impl Responder {
return RubbleResponder::redirect("/admin/login"); // if id.identity().is_none() {
} // return RubbleResponder::redirect("/admin/login");
let settings = Setting::load(&data.postgres()); // }
let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap()) // let settings = Setting::load(&data.postgres());
.expect("cannot found this user"); // let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap())
// .expect("cannot found this user");
let result = Article::get_by_pk(&data.postgres(), article_id.into_inner()); //
// let result = Article::get_by_pk(&data.postgres(), article_id.into_inner());
match result { //
Ok(article) => { // match result {
let mut context = Context::new(); // Ok(article) => {
context.insert("article", &article); // let mut context = Context::new();
context.insert("setting", &settings); // context.insert("article", &article);
context.insert("admin", &admin); // context.insert("setting", &settings);
RubbleResponder::html(data.render("admin/article_add.html", &context)) // context.insert("admin", &admin);
} // RubbleResponder::html(data.render("admin/article_add.html", &context))
Err(_) => RubbleResponder::redirect("/admin/panel"), // }
} // Err(_) => RubbleResponder::redirect("/admin/panel"),
} // }
//}
#[post("/article")] //
pub fn article_save( //#[post("/article")]
id: Identity, //pub fn article_save(
article: Form<crate::models::article::form::NewArticleFrom>, // id: Identity,
data: web::Data<RubbleData>, // article: Form<crate::models::article::form::NewArticleFrom>,
) -> impl Responder { // data: web::Data<RubbleData>,
if id.identity().is_none() { //) -> impl Responder {
return RubbleResponder::redirect("/admin/login"); // if id.identity().is_none() {
} // return RubbleResponder::redirect("/admin/login");
// }
let article_title = article.title.clone(); //
// let article_title = article.title.clone();
let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap()) //
.expect("cannot found this user"); // let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap())
Article::create(&data.postgres(), &article.into_inner().into()); // .expect("cannot found this user");
// Article::create(&data.postgres(), &article.into_inner().into());
RubbleResponder::redirect("/admin/panel") //
} // RubbleResponder::redirect("/admin/panel")
//}
#[post("/article/{aid}")] //
pub fn article_update( //#[post("/article/{aid}")]
id: Identity, //pub fn article_update(
aid: web::Path<i32>, // id: Identity,
article: Form<crate::models::article::form::NewArticleFrom>, // aid: web::Path<i32>,
data: web::Data<RubbleData>, // article: Form<crate::models::article::form::NewArticleFrom>,
) -> impl Responder { // data: web::Data<RubbleData>,
if id.identity().is_none() { //) -> impl Responder {
return RubbleResponder::redirect("/admin/login"); // if id.identity().is_none() {
} // return RubbleResponder::redirect("/admin/login");
// }
let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap()) //
.expect("cannot found this user"); // let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap())
Article::update(&data.postgres(), *aid, &article.into_inner().into()); // .expect("cannot found this user");
// Article::update(&data.postgres(), *aid, &article.into_inner().into());
RubbleResponder::redirect("/admin/panel") //
} // RubbleResponder::redirect("/admin/panel")
//}
#[post("/article/delete/{article_id}")] //
pub fn article_deletion( //#[post("/article/delete/{article_id}")]
id: Identity, //pub fn article_deletion(
article_id: web::Path<i32>, // id: Identity,
data: web::Data<RubbleData>, // article_id: web::Path<i32>,
) -> impl Responder { // data: web::Data<RubbleData>,
if id.identity().is_none() { //) -> impl Responder {
return RubbleResponder::redirect("/admin/login"); // if id.identity().is_none() {
} // return RubbleResponder::redirect("/admin/login");
// }
let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap()) //
.expect("cannot found this user"); // let admin = User::find_by_username(&data.postgres(), &id.identity().unwrap())
// .expect("cannot found this user");
let i = article_id.into_inner(); //
Article::delete(&data.postgres(), i); // let i = article_id.into_inner();
// Article::delete(&data.postgres(), i);
RubbleResponder::redirect("/admin/panel") //
} // RubbleResponder::redirect("/admin/panel")
//}
#[post("/password")] //
pub fn change_password( //#[post("/password")]
id: Identity, //pub fn change_password(
password: web::Form<NewPassword>, // id: Identity,
data: web::Data<RubbleData>, // password: web::Form<NewPassword>,
) -> impl Responder { // data: web::Data<RubbleData>,
if id.identity().is_none() { //) -> impl Responder {
return RubbleResponder::redirect("/admin/login"); // if id.identity().is_none() {
} // return RubbleResponder::redirect("/admin/login");
// }
let mut admin = User::find_by_username(&data.postgres(), &id.identity().unwrap()) //
.expect("cannot found this user"); // let mut admin = User::find_by_username(&data.postgres(), &id.identity().unwrap())
admin.password = User::password_generate(&password.password).to_string(); // .expect("cannot found this user");
User::update(&data.postgres(), admin.id, &admin); // admin.password = User::password_generate(&password.password).to_string();
id.forget(); // User::update(&data.postgres(), admin.id, &admin);
RubbleResponder::redirect("/admin/panel") // id.forget();
} // RubbleResponder::redirect("/admin/panel")
//}
#[post("/setting")] //
pub fn change_setting( //#[post("/setting")]
id: Identity, //pub fn change_setting(
setting: web::Form<Setting>, // id: Identity,
data: web::Data<RubbleData>, // setting: web::Form<Setting>,
) -> impl Responder { // data: web::Data<RubbleData>,
if id.identity().is_none() { //) -> impl Responder {
return RubbleResponder::redirect("/admin/login"); // if id.identity().is_none() {
} // return RubbleResponder::redirect("/admin/login");
// }
let mut admin = User::find_by_username(&data.postgres(), &id.identity().unwrap()) //
.expect("cannot found this user"); // let mut admin = User::find_by_username(&data.postgres(), &id.identity().unwrap())
// .expect("cannot found this user");
let update_setting = UpdateSetting { //
value: setting.value.clone(), // let update_setting = UpdateSetting {
}; // value: setting.value.clone(),
Setting::update(&data.postgres(), setting.name.clone(), &update_setting); // };
RubbleResponder::redirect("/admin/site-setting") // Setting::update(&data.postgres(), setting.name.clone(), &update_setting);
} // RubbleResponder::redirect("/admin/site-setting")
//}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@ -4,36 +4,37 @@ use crate::{
routers::RubbleResponder, routers::RubbleResponder,
}; };
use actix_web::{delete, get, post, put, web, Responder}; use actix_web::{delete, get, post, put, web, Responder};
use crate::error::RubbleError;
#[get("")] #[get("/articles")]
pub fn get_all_article(user: User, data: web::Data<RubbleData>) -> impl Responder { pub async fn get_all_article(user: User, data: web::Data<RubbleData>) -> impl Responder {
RubbleResponder::json(Article::read(&data.postgres())) RubbleResponder::json(Article::read(&data.postgres()))
} }
#[get("/{id}")] #[get("/articles/{id}")]
pub fn get_all_article_by_id( pub async fn get_article_by_id(
user: User, user: User,
id: web::Path<i32>, id: web::Path<i32>,
data: web::Data<RubbleData>, data: web::Data<RubbleData>,
) -> impl Responder { ) -> impl Responder {
Article::get_by_pk(&data.postgres(), *id) Article::get_by_pk(&data.postgres(), *id)
.map(|data| RubbleResponder::json(data)) .map(RubbleResponder::json)
.map_err(|_| RubbleResponder::not_found()) .map_err(|_| RubbleResponder::not_found())
} }
#[post("")] #[post("/articles")]
pub fn crate_article( pub async fn crate_article(
user: User, user: User,
article: web::Json<crate::models::article::NewArticle>, article: web::Json<crate::models::article::NewArticle>,
data: web::Data<RubbleData>, data: web::Data<RubbleData>,
) -> impl Responder { ) -> impl Responder {
Article::create(&data.postgres(), &article) Article::create(&data.postgres(), &article)
.map(RubbleResponder::json) .map(RubbleResponder::json)
.map_err(|_| RubbleResponder::bad_request("something wrong when creating article")) .map_err(|_| RubbleError::BadRequest("something wrong when creating article"))
} }
#[put("/{id}")] #[put("/articles/{id}")]
pub fn update_article_by_id( pub async fn update_article_by_id(
user: User, user: User,
id: web::Path<i32>, id: web::Path<i32>,
article: web::Json<crate::models::article::NewArticle>, article: web::Json<crate::models::article::NewArticle>,
@ -41,16 +42,16 @@ pub fn update_article_by_id(
) -> impl Responder { ) -> impl Responder {
Article::update(&data.postgres(), *id, &article) Article::update(&data.postgres(), *id, &article)
.map(|data| RubbleResponder::json(data)) .map(|data| RubbleResponder::json(data))
.map_err(|_| RubbleResponder::bad_request("something wrong when updating article")) .map_err(|_| RubbleError::BadRequest("something wrong when updating article"))
} }
#[delete("/{id}")] #[delete("/articles/{id}")]
pub fn delete_article_by_id( pub async fn delete_article_by_id(
user: User, user: User,
id: web::Path<i32>, id: web::Path<i32>,
data: web::Data<RubbleData>, data: web::Data<RubbleData>,
) -> impl Responder { ) -> impl Responder {
Article::delete(&data.postgres(), *id) Article::delete(&data.postgres(), *id)
.map(|_| RubbleResponder::json("Ok")) .map(|_| RubbleResponder::json("Ok"))
.map_err(|_| RubbleResponder::bad_request("something wrong when deleting article")) .map_err(|_| RubbleError::BadRequest("something wrong when deleting article"))
} }

View File

@ -1,3 +1,21 @@
use actix_web::web;
pub mod article; pub mod article;
pub mod setting; pub mod setting;
pub mod user; pub mod user;
pub fn routes(cfg: &mut web::ServiceConfig) {
cfg
.service(user::admin_authentication)
.service(article::get_all_article)
.service(article::crate_article)
.service(article::get_article_by_id)
.service(article::update_article_by_id)
.service(article::delete_article_by_id)
.service(setting::get_settings)
.service(setting::update_setting_by_key)
;
}

View File

@ -8,14 +8,15 @@ use crate::{
routers::RubbleResponder, routers::RubbleResponder,
}; };
use actix_web::{delete, get, post, put, web, Responder}; use actix_web::{delete, get, post, put, web, Responder};
use crate::error::RubbleError;
#[get("")] #[get("/settings")]
pub fn get_settings(user: User, data: web::Data<RubbleData>) -> impl Responder { pub async fn get_settings(user: User, data: web::Data<RubbleData>) -> impl Responder {
RubbleResponder::json(Setting::load(&data.postgres())) RubbleResponder::json(Setting::load(&data.postgres()))
} }
#[put("/{key}")] #[put("settings/{key}")]
pub fn update_setting_by_key( pub async fn update_setting_by_key(
user: User, user: User,
key: web::Path<String>, key: web::Path<String>,
value: web::Json<UpdateSetting>, value: web::Json<UpdateSetting>,
@ -24,5 +25,5 @@ pub fn update_setting_by_key(
let string = (*key).clone(); let string = (*key).clone();
Setting::update(&data.postgres(), string, &value) Setting::update(&data.postgres(), string, &value)
.map(RubbleResponder::json) .map(RubbleResponder::json)
.map_err(|_| RubbleResponder::bad_request("error on updating setting")) .map_err(|_| RubbleError::BadRequest("error on updating setting"))
} }

View File

@ -10,9 +10,10 @@ use crate::{
utils::jwt::JWTClaims, utils::jwt::JWTClaims,
}; };
use actix_web::{delete, get, post, put, web, Responder}; use actix_web::{delete, get, post, put, web, Responder};
use crate::error::RubbleError;
#[post("/token")] #[post("/user/token")]
pub fn admin_authentication( pub async fn admin_authentication(
user: web::Json<LoginForm>, user: web::Json<LoginForm>,
data: web::Data<RubbleData>, data: web::Data<RubbleData>,
) -> impl Responder { ) -> impl Responder {
@ -23,36 +24,36 @@ pub fn admin_authentication(
if login_user.authenticated(&user.password) { if login_user.authenticated(&user.password) {
let string = JWTClaims::encode(&login_user); let string = JWTClaims::encode(&login_user);
RubbleResponder::json(Token { token: string }) Ok(RubbleResponder::json(Token { token: string }))
} else { } else {
RubbleResponder::unauthorized("invalid password") Err(RubbleError::Unauthorized("invalid password"))
} }
} }
Err(_) => RubbleResponder::unauthorized("invalid username"), Err(_) => Err(RubbleError::Unauthorized("invalid username")),
} }
} }
//
#[get("")] //#[get("")]
pub fn get_all_users() -> impl Responder { //pub fn get_all_users() -> impl Responder {
unreachable!() // unreachable!()
} //}
//
#[post("")] //#[post("")]
pub fn crate_user() -> impl Responder { //pub fn crate_user() -> impl Responder {
unreachable!() // unreachable!()
} //}
//
#[put("/{id}")] //#[put("/{id}")]
pub fn update_user_by_id() -> impl Responder { //pub fn update_user_by_id() -> impl Responder {
unreachable!() // unreachable!()
} //}
//
#[delete("/{id}")] //#[delete("/{id}")]
pub fn delete_user_by_id() -> impl Responder { //pub fn delete_user_by_id() -> impl Responder {
unreachable!() // unreachable!()
} //}
//
#[put("/{id}/password")] //#[put("/{id}/password")]
pub fn update_user_password() -> impl Responder { //pub fn update_user_password() -> impl Responder {
unreachable!() // unreachable!()
} //}

View File

@ -12,8 +12,8 @@ use actix_web::{get, web, Either, HttpResponse, Responder};
use std::{result::Result, sync::Arc}; use std::{result::Result, sync::Arc};
use tera::{Context, Tera}; use tera::{Context, Tera};
#[get("")] #[get("/")]
pub fn homepage(data: web::Data<RubbleData>) -> impl Responder { pub async fn homepage(data: web::Data<RubbleData>) -> impl Responder {
let vec: Vec<Article> = Article::read(&data.postgres()); let vec: Vec<Article> = Article::read(&data.postgres());
let article_view: Vec<_> = vec let article_view: Vec<_> = vec
.iter() .iter()
@ -31,7 +31,7 @@ pub fn homepage(data: web::Data<RubbleData>) -> impl Responder {
} }
#[get("archives/{archives_id}")] #[get("archives/{archives_id}")]
pub fn single_article(archives_id: web::Path<i32>, data: web::Data<RubbleData>) -> impl Responder { pub async fn single_article(archives_id: web::Path<i32>, data: web::Data<RubbleData>) -> impl Responder {
let article = Article::get_by_pk(&data.postgres(), archives_id.into_inner()); let article = Article::get_by_pk(&data.postgres(), archives_id.into_inner());
if let Err(e) = article { if let Err(e) = article {
@ -58,7 +58,7 @@ pub fn single_article(archives_id: web::Path<i32>, data: web::Data<RubbleData>)
} }
#[get("{url}")] #[get("{url}")]
pub fn get_article_by_url(url: web::Path<String>, data: web::Data<RubbleData>) -> impl Responder { pub async fn get_article_by_url(url: web::Path<String>, data: web::Data<RubbleData>) -> impl Responder {
let article = Article::find_by_url(&data.postgres(), &url.into_inner()); let article = Article::find_by_url(&data.postgres(), &url.into_inner());
if let Err(e) = article { if let Err(e) = article {

View File

@ -1,7 +1,5 @@
use actix_web::{HttpRequest, HttpResponse, Responder}; use actix_web::{error::Error, HttpRequest, HttpResponse, Responder, Scope, web};
use futures::future::{err, ok};
use actix_web::{error::Error, web, Scope};
use futures::future::{err, ok, FutureResult};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub mod admin; pub mod admin;
@ -57,53 +55,11 @@ impl RubbleResponder {
HttpResponse::NotFound().finish() HttpResponse::NotFound().finish()
} }
pub fn unauthorized(reason: impl Serialize) -> HttpResponse {
HttpResponse::Unauthorized().json(&ErrorResponse { message: reason })
}
pub fn bad_gateway(reason: impl Serialize) -> HttpResponse {
HttpResponse::BadGateway().json(&ErrorResponse { message: reason })
}
pub fn bad_request(reason: impl Serialize) -> HttpResponse {
HttpResponse::BadRequest().json(&ErrorResponse { message: reason })
}
} }
pub fn routes() -> Scope { pub fn routes(cfg: &mut web::ServiceConfig) {
web::scope("/") cfg
.service( .service(web::scope("/api").configure(api::routes))
web::scope("/api")
.service(web::scope("/users").service(api::user::admin_authentication))
.service(
web::scope("/articles")
.service(api::article::get_all_article)
.service(api::article::get_all_article_by_id)
.service(api::article::crate_article)
.service(api::article::update_article_by_id)
.service(api::article::delete_article_by_id),
)
.service(
web::scope("/settings")
.service(api::setting::get_settings)
.service(api::setting::update_setting_by_key),
),
)
.service(
web::scope("/admin")
.service(admin::redirect_to_admin_panel)
.service(admin::admin_login)
.service(admin::admin_authentication)
.service(admin::admin_panel)
.service(admin::article_creation)
.service(admin::article_deletion)
.service(admin::article_edit)
.service(admin::article_save)
.service(admin::article_update)
.service(admin::change_password)
.service(admin::change_setting)
.service(admin::admin_show_page),
)
.service(article::homepage) .service(article::homepage)
.service(article::single_article) .service(article::single_article)
.service(actix_files::Files::new( .service(actix_files::Files::new(
@ -111,5 +67,5 @@ pub fn routes() -> Scope {
"./templates/resources/", "./templates/resources/",
)) ))
.service(rss::rss_) .service(rss::rss_)
.service(article::get_article_by_url) .service(article::get_article_by_url);
} }

View File

@ -12,7 +12,7 @@ use rss::{Channel, ChannelBuilder, Item, ItemBuilder};
use std::collections::HashMap; use std::collections::HashMap;
#[get("/rss")] #[get("/rss")]
pub fn rss_(data: web::Data<RubbleData>) -> impl Responder { pub async fn rss_(data: web::Data<RubbleData>) -> impl Responder {
let articles = Article::read(&data.postgres()); let articles = Article::read(&data.postgres());
let setting = Setting::load(&data.postgres()); let setting = Setting::load(&data.postgres());

View File

@ -1,8 +1,8 @@
use crate::{models::user::User, RANDOM_TOKEN_KEY}; use crate::{models::user::User, TOKEN_KEY};
use chrono::prelude::*; use chrono::prelude::*;
use jsonwebtoken::{decode as jwt_decode, encode as jwt_encode, Algorithm, Header, Validation}; use jsonwebtoken::{decode as jwt_decode, encode as jwt_encode, Algorithm, Header, Validation, DecodingKey, EncodingKey};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Add; use std::ops::{Add, Deref};
use time::Duration; use time::Duration;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -26,13 +26,13 @@ impl JWTClaims {
username: user.username.clone(), username: user.username.clone(),
}; };
jwt_encode(&Header::default(), &claims, &RANDOM_TOKEN_KEY).unwrap() jwt_encode(&Header::default(), &claims, &EncodingKey::from_secret(&TOKEN_KEY)).unwrap()
} }
pub fn decode(token: String) -> Result<String, ()> { pub fn decode(token: String) -> Result<String, ()> {
let claims = jwt_decode::<JWTClaims>( let claims = jwt_decode::<JWTClaims>(
token.as_str(), token.as_str(),
&RANDOM_TOKEN_KEY, &DecodingKey::from_secret(&TOKEN_KEY),
&Validation::new(Algorithm::HS256), &Validation::new(Algorithm::HS256),
) )
.map_err(|_| ())?; .map_err(|_| ())?;