first commit
This commit is contained in:
commit
409a28c170
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
3101
Cargo.lock
generated
Normal file
3101
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
Cargo.toml
Normal file
43
Cargo.toml
Normal file
@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "actix-demo"
|
||||
version = "0.1.0"
|
||||
authors = ["Rohan Sircar <rohansircar@tuta.io>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
actix-web = "2"
|
||||
actix-rt = "1"
|
||||
actix-service = "1.0.5"
|
||||
actix-files = "0.2.1"
|
||||
|
||||
bytes = "0.5.4"
|
||||
futures = "0.3.4"
|
||||
log = "0.4.8"
|
||||
env_logger = "0.7"
|
||||
|
||||
serde = { version = "1.0.106", features = ["derive"] }
|
||||
serde_json = "1.0.52"
|
||||
json = "0.12.4"
|
||||
|
||||
yarte = { version = "0.8.3", features = ["html-min"] }
|
||||
|
||||
listenfd = "0.3.3"
|
||||
|
||||
diesel = { version = "1.4.4", features = ["sqlite", "r2d2"] }
|
||||
dotenv = "0.15"
|
||||
r2d2 = "0.8.8"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
validator = "0.10.0"
|
||||
jsonwebtoken = "7"
|
||||
actix-identity = "0.2.1"
|
||||
actix-web-httpauth = "0.4.1"
|
||||
actix-http = "1.0.1"
|
||||
rand = "0.7.3"
|
||||
nanoid = "0.3.0"
|
||||
|
||||
[build-dependencies.yarte_helpers]
|
||||
version = "0.8"
|
||||
default-features = false
|
||||
features = ["config"]
|
5
diesel.toml
Normal file
5
diesel.toml
Normal file
@ -0,0 +1,5 @@
|
||||
# For documentation on how to configure this file,
|
||||
# see diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/schema.rs"
|
2
migrations/2020-05-02-115427_create_users/down.sql
Normal file
2
migrations/2020-05-02-115427_create_users/down.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE users
|
5
migrations/2020-05-02-115427_create_users/up.sql
Normal file
5
migrations/2020-05-02-115427_create_users/up.sql
Normal file
@ -0,0 +1,5 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE users (
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL
|
||||
)
|
67
src/actions.rs
Normal file
67
src/actions.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use diesel::prelude::*;
|
||||
|
||||
use crate::models;
|
||||
|
||||
pub fn find_user_by_uid(
|
||||
uid: i32,
|
||||
conn: &SqliteConnection,
|
||||
) -> Result<Option<models::User>, diesel::result::Error> {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
let maybe_user = users.find(uid).first::<models::User>(conn).optional();
|
||||
|
||||
// Ok(user)
|
||||
maybe_user
|
||||
}
|
||||
|
||||
pub fn find_user_by_name(
|
||||
user_name: String,
|
||||
conn: &SqliteConnection,
|
||||
) -> Result<Option<models::User>, diesel::result::Error> {
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
let maybe_user = users
|
||||
.filter(name.eq(user_name))
|
||||
.first::<models::User>(conn)
|
||||
.optional();
|
||||
|
||||
maybe_user
|
||||
}
|
||||
|
||||
pub fn get_all(
|
||||
conn: &SqliteConnection,
|
||||
) -> Result<Option<Vec<models::User>>, diesel::result::Error> {
|
||||
use crate::schema::users::dsl::*;
|
||||
users.load::<models::User>(conn).optional()
|
||||
}
|
||||
|
||||
/// Run query using Diesel to insert a new database row and return the result.
|
||||
pub fn insert_new_user(
|
||||
nu: &models::NewUser,
|
||||
conn: &SqliteConnection,
|
||||
) -> Result<models::User, diesel::result::Error> {
|
||||
// It is common when using Diesel with Actix web to import schema-related
|
||||
// modules inside a function's scope (rather than the normal module's scope)
|
||||
// to prevent import collisions and namespace pollution.
|
||||
use crate::schema::users::dsl::*;
|
||||
|
||||
// let new_user = models::User {
|
||||
// id: Uuid::new_v4().to_string(),
|
||||
// name: nu.name.to_string(),
|
||||
// };
|
||||
|
||||
// let x = users.load::<models::User>(conn).optional();
|
||||
// let target = users.find("4");
|
||||
// let test_user = models::User {
|
||||
// id: "5".to_owned(),
|
||||
// name: "who".to_owned(),
|
||||
// };
|
||||
// let update_result = diesel::update(target).set(&test_user).execute(conn);
|
||||
|
||||
diesel::insert_into(users).values(nu).execute(conn)?;
|
||||
let user = users
|
||||
.filter(name.eq(nu.name.clone()))
|
||||
.first::<models::User>(conn);
|
||||
user
|
||||
// Ok(nu.clone())
|
||||
}
|
258
src/main.rs
Normal file
258
src/main.rs
Normal file
@ -0,0 +1,258 @@
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
|
||||
use actix_web::{
|
||||
dev::ServiceRequest, error, get, middleware, post, web, App, Error, HttpRequest, HttpResponse,
|
||||
HttpServer, Responder,
|
||||
};
|
||||
|
||||
use yarte::Template;
|
||||
|
||||
use actix_web_httpauth::{extractors::basic::BasicAuth, middleware::HttpAuthentication};
|
||||
|
||||
use actix_http::cookie::SameSite;
|
||||
use actix_identity::{CookieIdentityPolicy, Identity, IdentityService};
|
||||
use rand::Rng;
|
||||
|
||||
// use actix_http::*;
|
||||
|
||||
use actix_files as fs;
|
||||
|
||||
use diesel::prelude::*;
|
||||
use diesel::r2d2::{self, ConnectionManager};
|
||||
use routes::*;
|
||||
|
||||
mod actions;
|
||||
mod models;
|
||||
mod routes;
|
||||
mod schema;
|
||||
mod types;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct MyObj {
|
||||
name: String,
|
||||
// number: i32,
|
||||
}
|
||||
|
||||
#[get("/{id}/{name}")]
|
||||
async fn index(info: web::Path<(u32, String)>) -> Result<HttpResponse, Error> {
|
||||
let (id, name) = (info.0, info.1.clone());
|
||||
let template = models::CardTemplate {
|
||||
title: "My Title",
|
||||
body: name,
|
||||
num: id,
|
||||
};
|
||||
template
|
||||
.call()
|
||||
.map(|body| HttpResponse::Ok().content_type("text/html").body(body))
|
||||
.map_err(|_| error::ErrorInternalServerError("Error while parsing template"))
|
||||
}
|
||||
|
||||
/// This handler uses json extractor
|
||||
#[post("/extractor")]
|
||||
async fn extract_my_obj(item: web::Json<MyObj>) -> HttpResponse {
|
||||
debug!("model: {:?}", item);
|
||||
HttpResponse::Ok().json(item.0) // <- send response
|
||||
}
|
||||
|
||||
pub struct UserServiceImpl;
|
||||
|
||||
impl UserServiceImpl {
|
||||
pub fn new() -> Self {
|
||||
UserServiceImpl {}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UserService {
|
||||
fn do_something(&self);
|
||||
}
|
||||
|
||||
impl UserService for UserServiceImpl {
|
||||
fn do_something(&self) {
|
||||
println!("hello");
|
||||
}
|
||||
}
|
||||
|
||||
fn fun1(user_service: &dyn UserService) {
|
||||
user_service.do_something();
|
||||
}
|
||||
|
||||
fn fun2<T>(user_service: T)
|
||||
where
|
||||
T: UserService,
|
||||
{
|
||||
user_service.do_something();
|
||||
}
|
||||
|
||||
/// In this example validator returns immediately,
|
||||
/// but since it is required to return anything
|
||||
/// that implements `IntoFuture` trait,
|
||||
/// it can be extended to query database
|
||||
/// or to do something else in a async manner.
|
||||
async fn validator(req: ServiceRequest, credentials: BasicAuth) -> Result<ServiceRequest, Error> {
|
||||
// All users are great and more than welcome!
|
||||
// let pool = req.app_data::<DbPool>();
|
||||
// let maybe_header = req.headers().get("Authorization");
|
||||
// match maybe_header {
|
||||
// Some(value) => {
|
||||
// info!("{:?}", *value);
|
||||
// let x: Result<Basic, _> = Scheme::parse(value);
|
||||
// let y = x.expect("Error parsing header");
|
||||
// println!("{}", y.user_id());
|
||||
// println!("{:?}", y.password().clone());
|
||||
// }
|
||||
// None => debug!("Header not found"),
|
||||
// }
|
||||
|
||||
// maybe_header
|
||||
// .map(|value| {
|
||||
// let x: Result<Basic, _> = Scheme::parse(value);
|
||||
// x
|
||||
// })
|
||||
// .map(|maybe_basic| {
|
||||
// maybe_basic
|
||||
// .map(|x| {
|
||||
// println!("{}", x.user_id());
|
||||
// println!("{:?}", x.password().clone());
|
||||
// })
|
||||
// .map_err(|x| println!("error parsing reason - {}", x.to_string()))
|
||||
// // maybe_basic
|
||||
// });
|
||||
// let auth = Authorization::<Basic>;
|
||||
println!("{}", credentials.user_id());
|
||||
println!("{:?}", credentials.password());
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
// fn parse(header: &HeaderValue) -> Result<Basic, ParseError> {
|
||||
// // "Basic *" length
|
||||
// if header.len() < 7 {
|
||||
// return Err(ParseError::Invalid);
|
||||
// }
|
||||
|
||||
// let mut parts = header.to_str()?.splitn(2, ' ');
|
||||
// match parts.next() {
|
||||
// Some(scheme) if scheme == "Basic" => (),
|
||||
// _ => return Err(ParseError::MissingScheme),
|
||||
// }
|
||||
|
||||
// let decoded = base64::decode(parts.next().ok_or(ParseError::Invalid)?)?;
|
||||
// let mut credentials = str::from_utf8(&decoded)?.splitn(2, ':');
|
||||
|
||||
// let user_id = credentials
|
||||
// .next()
|
||||
// .ok_or(ParseError::MissingField("user_id"))
|
||||
// .map(|user_id| user_id.to_string().into())?;
|
||||
// let password = credentials
|
||||
// .next()
|
||||
// .ok_or(ParseError::MissingField("password"))
|
||||
// .map(|password| {
|
||||
// if password.is_empty() {
|
||||
// None
|
||||
// } else {
|
||||
// Some(password.to_string().into())
|
||||
// }
|
||||
// })?;
|
||||
|
||||
// Ok(Basic { user_id, password })
|
||||
// }
|
||||
|
||||
#[get("/login")]
|
||||
async fn login(id: Identity) -> HttpResponse {
|
||||
let maybe_identity = id.identity();
|
||||
// id.remember("user1".to_owned());
|
||||
let response = if let Some(identity) = maybe_identity {
|
||||
HttpResponse::Ok()
|
||||
.header("location", "/")
|
||||
.content_type("text/plain")
|
||||
.body(format!("Already logged in {}", identity))
|
||||
} else {
|
||||
id.remember("user1".to_owned());
|
||||
HttpResponse::Found().header("location", "/").finish()
|
||||
};
|
||||
// HttpResponse::Found().header("location", "/").finish()
|
||||
response
|
||||
}
|
||||
|
||||
#[get("/logout")]
|
||||
async fn logout(id: Identity) -> HttpResponse {
|
||||
let maybe_identity = id.identity();
|
||||
// id.remember("user1".to_owned());
|
||||
let response = if let Some(identity) = maybe_identity {
|
||||
info!("Logging out {user}", user = identity);
|
||||
id.forget();
|
||||
HttpResponse::Found().header("location", "/").finish()
|
||||
} else {
|
||||
HttpResponse::Ok()
|
||||
.header("location", "/")
|
||||
.content_type("text/plain")
|
||||
.body("Not logged in")
|
||||
};
|
||||
// id.forget();
|
||||
// HttpResponse::Found().header("location", "/").finish()
|
||||
response
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn index2(id: Identity) -> String {
|
||||
format!(
|
||||
"Hello {}",
|
||||
id.identity().unwrap_or_else(|| "Anonymous".to_owned())
|
||||
)
|
||||
}
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
std::env::set_var("RUST_LOG", "debug");
|
||||
env_logger::init();
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
let user_service: Box<dyn UserService> = Box::new(UserServiceImpl::new());
|
||||
user_service.do_something();
|
||||
|
||||
fun1(user_service.as_ref());
|
||||
|
||||
let user_service_impl = UserServiceImpl::new();
|
||||
fun2(user_service_impl);
|
||||
|
||||
let basic_auth_middleware = HttpAuthentication::basic(validator);
|
||||
|
||||
// fun1(Rc::clone(&user_service).as_ref());
|
||||
// set up database connection pool
|
||||
let connspec = std::env::var("DATABASE_URL").expect("DATABASE_URL NOT FOUND");
|
||||
let manager = ConnectionManager::<SqliteConnection>::new(connspec);
|
||||
let pool = r2d2::Pool::builder()
|
||||
.build(manager)
|
||||
.expect("Failed to create pool.");
|
||||
|
||||
let addr = std::env::var("BIND_ADDRESS").expect("BIND ADDRESS NOT FOUND");
|
||||
info!("Starting server {}", addr);
|
||||
let private_key = rand::thread_rng().gen::<[u8; 32]>();
|
||||
let app = move || {
|
||||
App::new()
|
||||
.data(pool.clone())
|
||||
.wrap(IdentityService::new(
|
||||
CookieIdentityPolicy::new(&private_key)
|
||||
.name("my-app-auth")
|
||||
.secure(false)
|
||||
.same_site(SameSite::Lax), // .same_site(),
|
||||
))
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(web::scope("/chat").wrap(basic_auth_middleware.clone()))
|
||||
// .service(extract_my_obj)
|
||||
// .service(index)
|
||||
.service(get_user)
|
||||
.service(add_user)
|
||||
.service(get_all_users)
|
||||
.service(login)
|
||||
.service(logout)
|
||||
.service(index2)
|
||||
.service(fs::Files::new("/", "./static"))
|
||||
};
|
||||
HttpServer::new(app).bind(addr)?.run().await
|
||||
}
|
24
src/models.rs
Normal file
24
src/models.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::schema::users;
|
||||
use yarte::Template;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Queryable, Identifiable, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Insertable, Deserialize)]
|
||||
#[table_name = "users"]
|
||||
pub struct NewUser {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "hello.hbs")]
|
||||
pub struct CardTemplate<'a> {
|
||||
pub title: &'a str,
|
||||
pub body: String,
|
||||
pub num: u32,
|
||||
}
|
2
src/routes.rs
Normal file
2
src/routes.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod users;
|
||||
pub use self::users::*;
|
76
src/routes/users.rs
Normal file
76
src/routes/users.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use actix_web::{get, post, web, Error, HttpResponse};
|
||||
|
||||
use crate::actions;
|
||||
use crate::models;
|
||||
use crate::types::DbPool;
|
||||
|
||||
/// Finds user by UID.
|
||||
#[get("/api/authzd/users/get/{user_id}")]
|
||||
pub async fn get_user(
|
||||
pool: web::Data<DbPool>,
|
||||
user_uid: web::Path<i32>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let user_uid = user_uid.into_inner();
|
||||
// use web::block to offload blocking Diesel code without blocking server thread
|
||||
let maybe_user = web::block(move || {
|
||||
let conn = pool.get().map_err(|e| e.to_string())?;
|
||||
actions::find_user_by_uid(user_uid.into(), &conn).map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("{}", e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
})?;
|
||||
|
||||
if let Some(user) = maybe_user {
|
||||
Ok(HttpResponse::Ok().json(user))
|
||||
} else {
|
||||
let res = HttpResponse::NotFound().body(format!("No user found with uid: {}", user_uid));
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/authzd/users/get")]
|
||||
pub async fn get_all_users(pool: web::Data<DbPool>) -> Result<HttpResponse, Error> {
|
||||
// use web::block to offload blocking Diesel code without blocking server thread
|
||||
let maybe_users = web::block(move || {
|
||||
let conn = pool.get().map_err(|e| e.to_string())?;
|
||||
actions::get_all(&conn).map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("{}", e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
})?;
|
||||
|
||||
if let Some(users) = maybe_users {
|
||||
Ok(HttpResponse::Ok().json(users))
|
||||
} else {
|
||||
let res = HttpResponse::NotFound().body(format!("No users available"));
|
||||
Ok(res)
|
||||
}
|
||||
// Ok(HttpResponse::Ok().json(users))
|
||||
}
|
||||
|
||||
/// Inserts new user with name defined in form.
|
||||
#[post("/api/authzd/users/post")]
|
||||
pub async fn add_user(
|
||||
pool: web::Data<DbPool>,
|
||||
form: web::Json<models::NewUser>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
// use web::block to offload blocking Diesel code without blocking server thread
|
||||
let user = web::block(move || {
|
||||
let conn = pool.get().map_err(|e| e.to_string())?;
|
||||
actions::insert_new_user(&form, &conn).map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
.map(|user| {
|
||||
debug!("{:?}", user);
|
||||
Ok(HttpResponse::Ok().json(user))
|
||||
})
|
||||
.map_err(|e| {
|
||||
eprintln!("{}", e);
|
||||
HttpResponse::InternalServerError().finish()
|
||||
})?;
|
||||
user
|
||||
}
|
6
src/schema.rs
Normal file
6
src/schema.rs
Normal file
@ -0,0 +1,6 @@
|
||||
table! {
|
||||
users (id) {
|
||||
id -> Integer,
|
||||
name -> Text,
|
||||
}
|
||||
}
|
3
src/types.rs
Normal file
3
src/types.rs
Normal file
@ -0,0 +1,3 @@
|
||||
use diesel::prelude::*;
|
||||
use diesel::r2d2::{self, ConnectionManager};
|
||||
pub type DbPool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
|
1
static/test.js
Normal file
1
static/test.js
Normal file
@ -0,0 +1 @@
|
||||
var x = 1
|
22
templates/hello.hbs
Normal file
22
templates/hello.hbs
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="entry">
|
||||
<h1>{{title}}</h1>
|
||||
<div class="body">
|
||||
Hello {{body}}! Number input was: {{num}}
|
||||
</div>
|
||||
<h1>BTW... ZA WARUDO</h1>
|
||||
</div>
|
||||
<div class="app"></div>
|
||||
<script src="/test.js" defer></script>
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
test_old.db
Normal file
BIN
test_old.db
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user