Skip to content

Commit

Permalink
Merge pull request #2 from daystram/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
daystram authored Jan 20, 2021
2 parents 9db42e9 + ef9bea9 commit 43d8c2b
Show file tree
Hide file tree
Showing 44 changed files with 1,354 additions and 231 deletions.
9 changes: 9 additions & 0 deletions cut-be/.example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
HOST=localhost
PORT=9090
ENVIRONMENT=development

OAUTH_ISSUER=https://ratify.daystram.com
CLIENT_SECRET=ratify_client_secret

REDIS_HOST=localhost
REDIS_PORT=6379
2 changes: 2 additions & 0 deletions cut-be/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Created by https://www.toptal.com/developers/gitignore/api/rust,linux,macos,windows,intellij+all,visualstudiocode,vim
# Edit at https://www.toptal.com/developers/gitignore?templates=rust,linux,macos,windows,intellij+all,visualstudiocode,vim

.env

### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
Expand Down
10 changes: 9 additions & 1 deletion cut-be/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = "3"
actix-web = { version = "3", features = ["openssl"] }
dotenv = "0.15.0"
env_logger = "0.8.2"
serde = "1.0.119"
log = "0.4.13"
redis = "0.19.0"
r2d2 = "0.8.9"
r2d2_redis = "0.13.0"
rand = "0.8.2"
4 changes: 4 additions & 0 deletions cut-be/src/app/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub const VARIANT_SNIPPET: &str = "snippet";
pub const VARIANT_URL: &str = "url";

pub const HASH_LENGTH: usize = 8;
File renamed without changes.
2 changes: 2 additions & 0 deletions cut-be/src/app/controllers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod middlewares;
pub mod v1;
64 changes: 64 additions & 0 deletions cut-be/src/app/controllers/v1/cut.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::app::{
constants,
datatransfers::{
auth::TokenInfo,
cut::{CreateResponse, Cut},
},
handlers, Module,
};
use crate::core::error::HandlerErrorKind;
use actix_web::{get, post, web, HttpRequest, HttpResponse, Responder};

#[get("/{id}")]
pub async fn get_cut_raw(m: web::Data<Module>, req: HttpRequest) -> impl Responder {
let id: String = req.match_info().query("id").parse().unwrap();
match handlers::cut::get_one(m, id) {
Ok(cut) => match cut.variant.as_str() {
constants::VARIANT_SNIPPET => HttpResponse::Ok().body(cut.data),
constants::VARIANT_URL => HttpResponse::TemporaryRedirect()
.header("Location", cut.data)
.finish(),
_ => HttpResponse::NotFound().finish(),
},
Err(e) => match e.kind {
HandlerErrorKind::CutNotFoundError => HttpResponse::NotFound().finish(),
_ => HttpResponse::InternalServerError().body(format!("{:?}", e)),
},
}
}

#[get("/{id}")]
pub async fn get_cut(m: web::Data<Module>, req: HttpRequest) -> impl Responder {
let id: String = req.match_info().query("id").parse().unwrap();
match handlers::cut::get_one(m, id) {
Ok(cut) => HttpResponse::Ok().json(cut),
Err(e) => match e.kind {
HandlerErrorKind::CutNotFoundError => HttpResponse::NotFound().finish(),
_ => HttpResponse::InternalServerError().body(format!("{:?}", e)),
},
}
}

#[post("")]
pub async fn post_snippet_create(
m: web::Data<Module>,
cut: web::Json<Cut>,
req: HttpRequest,
) -> impl Responder {
let user: TokenInfo = match handlers::auth::authorize(&m, &req).await {
Ok(resp) => resp,
Err(e) => match e.kind {
HandlerErrorKind::UnauthorizedError => return HttpResponse::Unauthorized().finish(),
_ => return HttpResponse::InternalServerError().finish(),
},
};
match cut.variant.as_str() {
constants::VARIANT_SNIPPET => (),
constants::VARIANT_URL => (),
_ => return HttpResponse::BadRequest().finish(),
};
match handlers::cut::insert(m, user.sub, cut.0) {
Ok(hash) => HttpResponse::Ok().json(CreateResponse { hash: hash }),
Err(e) => HttpResponse::InternalServerError().body(format!("{:?}", e)),
}
}
2 changes: 2 additions & 0 deletions cut-be/src/app/controllers/v1/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod cut;
pub mod ping;
6 changes: 6 additions & 0 deletions cut-be/src/app/controllers/v1/ping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use actix_web::{get, HttpResponse, Responder};

#[get("")]
pub async fn get_ping() -> impl Responder {
HttpResponse::Ok().body("Pong!")
}
Empty file removed cut-be/src/app/datatransfers/.keep
Empty file.
10 changes: 10 additions & 0 deletions cut-be/src/app/datatransfers/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct TokenInfo {
pub active: bool,
#[serde(default)]
pub sub: String,
#[serde(default)]
pub client_id: String,
}
18 changes: 18 additions & 0 deletions cut-be/src/app/datatransfers/cut.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct Cut {
pub name: String,
#[serde(skip)]
pub owner: String,
pub variant: String,
pub metadata: String,
pub data: String,
#[serde(skip_deserializing)]
pub created_at: u64,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CreateResponse {
pub hash: String,
}
2 changes: 2 additions & 0 deletions cut-be/src/app/datatransfers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod auth;
pub mod cut;
Empty file removed cut-be/src/app/handlers/.keep
Empty file.
31 changes: 31 additions & 0 deletions cut-be/src/app/handlers/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::app::{datatransfers::auth::TokenInfo, Module};
use crate::core::error::{HandlerError, HandlerErrorKind};
use actix_web::{client::Client, web, HttpRequest};

pub async fn authorize(m: &web::Data<Module>, req: &HttpRequest) -> Result<TokenInfo, HandlerError> {
let access_token: &str = match req.headers().get("Authorization") {
Some(header_value) => match header_value.to_str() {
Ok(access_token) => access_token.strip_prefix("Bearer ").unwrap_or_else(|| access_token),
Err(_) => return Err(HandlerErrorKind::GeneralError.into()),
},
_ => return Err(HandlerErrorKind::UnauthorizedError.into()),
};
match Client::default()
.post(format!("{}/oauth/introspect", m.config.oauth_issuer))
.header("Content-Type", "application/x-www-form-urlencoded")
.send_body(format!(
"token={}&client_id={}&client_secret={}&token_type_hint=access_token",
access_token, m.config.client_id, m.config.client_secret
))
.await
{
Ok(mut res) => match res.json::<TokenInfo>().await {
Ok(token_info) => match token_info.active {
true => Ok(token_info),
false => Err(HandlerErrorKind::UnauthorizedError.into()),
},
Err(_) => Err(HandlerErrorKind::UnauthorizedError.into()),
},
Err(_) => Err(HandlerErrorKind::GeneralError.into()),
}
}
74 changes: 74 additions & 0 deletions cut-be/src/app/handlers/cut.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::app::{constants::HASH_LENGTH, datatransfers::cut::Cut, Module};
use crate::core::error::{HandlerError, HandlerErrorKind};
use crate::utils::hash;
use actix_web::web;
use r2d2_redis::redis::Commands;
use std::{
collections::HashMap,
time::{SystemTime, UNIX_EPOCH},
};

pub fn get_one(m: web::Data<Module>, id: String) -> Result<Cut, HandlerError> {
let rd = &mut m.rd_pool.get()?;
match rd.hgetall::<String, HashMap<String, String>>(id) {
Ok(res) => {
if res.is_empty() {
return Err(HandlerErrorKind::CutNotFoundError.into());
}
Ok(Cut {
name: res
.get("name")
.ok_or(HandlerErrorKind::CutNotFoundError)?
.to_string(),
owner: res
.get("owner")
.ok_or(HandlerErrorKind::CutNotFoundError)?
.to_string(),
variant: res
.get("variant")
.ok_or(HandlerErrorKind::CutNotFoundError)?
.to_string(),
metadata: res
.get("metadata")
.ok_or(HandlerErrorKind::CutNotFoundError)?
.to_string(),
data: res
.get("data")
.ok_or(HandlerErrorKind::CutNotFoundError)?
.to_string(),
created_at: res
.get("created_at")
.ok_or(HandlerErrorKind::CutNotFoundError)?
.parse()?,
})
}
Err(e) => Err(e.into()),
}
}

pub fn insert(
m: web::Data<Module>,
user_subject: String,
cut: Cut,
) -> Result<String, HandlerError> {
let rd = &mut m.rd_pool.get()?;
let hash: String = hash::generate(HASH_LENGTH).into();
let created_at = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(duration) => duration.as_secs(),
Err(_) => return Err(HandlerErrorKind::GeneralError.into()),
};
match rd.hset_multiple::<String, &str, String, String>(
hash.clone(),
&[
("name", cut.name),
("owner", user_subject),
("variant", cut.variant),
("metadata", cut.metadata),
("data", cut.data),
("created_at", created_at.to_string()),
],
) {
Ok(_) => Ok(hash),
Err(e) => Err(e.into()),
}
}
2 changes: 2 additions & 0 deletions cut-be/src/app/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod auth;
pub mod cut;
Empty file removed cut-be/src/app/middlewares/.keep
Empty file.
47 changes: 47 additions & 0 deletions cut-be/src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::core::config;
use actix_web::{middleware::Logger, App, HttpServer};
use r2d2::Pool;
use r2d2_redis::RedisConnectionManager;

pub mod constants;
pub mod controllers;
pub mod datatransfers;
pub mod handlers;
pub mod router;

pub struct Module {
pub config: config::AppConfig,
pub rd_pool: r2d2::Pool<RedisConnectionManager>,
}

pub async fn start() -> std::io::Result<()> {
// init AppConfig
let config = config::init();

// init Redis
let rd_manager = RedisConnectionManager::new(format!(
"redis://{}:{}/",
config.redis_host, config.redis_port
))
.unwrap();
let rd_pool = Pool::builder().max_size(15).build(rd_manager).unwrap();
log::info!("[INIT] Redis initialized!");

// run server
HttpServer::new(move || {
App::new()
.data(Module {
config: config.clone(),
rd_pool: rd_pool.clone(),
})
.wrap(Logger::default())
.configure(router::init)
})
.bind(format!(
"{}:{}",
std::env::var("HOST").expect("HOST is required"),
std::env::var("PORT").expect("PORT is required")
))?
.run()
.await
}
15 changes: 15 additions & 0 deletions cut-be/src/app/router.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::app::controllers::v1;
use actix_web::web;

pub fn init(app: &mut web::ServiceConfig) {
app.service(web::scope("/raw").service(v1::cut::get_cut_raw))
.service(
web::scope("/api/v1")
.service(web::scope("/ping").service(v1::ping::get_ping))
.service(
web::scope("/cut")
.service(v1::cut::get_cut)
.service(v1::cut::post_snippet_create),
),
);
}
20 changes: 20 additions & 0 deletions cut-be/src/core/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::env;

#[derive(Clone)]
pub struct AppConfig {
pub oauth_issuer: String,
pub client_id: String,
pub client_secret: String,
pub redis_host: String,
pub redis_port: String,
}

pub fn init() -> AppConfig {
return AppConfig {
oauth_issuer: env::var("OAUTH_ISSUER").expect("OAUTH_ISSUER is required"),
client_id: env::var("CLIENT_ID").expect("CLIENT_ID is required"),
client_secret: env::var("CLIENT_SECRET").expect("CLIENT_SECRET is required"),
redis_host: env::var("REDIS_HOST").expect("REDIS_HOST is required"),
redis_port: env::var("REDIS_PORT").expect("REDIS_PORT is required"),
};
}
Loading

0 comments on commit 43d8c2b

Please sign in to comment.