TTD4GDURZSACT7DDSH7GBP2VSA6DCEPZ3CLGRIZTSF7AI723FNIAC
use diesel::prelude::*;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
// ordinary diesel model setup
table! {
users {
id -> Integer,
first_name -> Text,
email -> Text,
}
}
#[derive(Queryable, Selectable, Debug, Clone, serde::Serialize, serde::Deserialize)]
#[diesel(table_name = users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct User {
pub id: i32,
pub first_name: String,
pub email: String,
}
table! {
school_users(school_id, user_id){
school_id -> Integer,
user_id -> Integer,
}
}
#[derive(
Queryable,
Selectable,
Debug,
Associations,
Identifiable,
PartialEq,
Clone,
serde::Serialize,
serde::Deserialize,
)]
#[diesel(table_name = school_users)]
#[diesel(belongs_to(School))]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct SchoolUser {
pub school_id: i32,
pub user_id: i32,
}
table! {
school{
id -> Integer,
name -> Text,
manager-> Integer,
}
}
#[derive(
Queryable,
Selectable,
Debug,
Identifiable,
PartialEq,
Clone,
serde::Serialize,
serde::Deserialize,
)]
#[diesel(table_name = school)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct School {
pub id: i32,
pub name: String,
pub manager: i32,
}
diesel::joinable!(school_users -> school (school_id));
diesel::allow_tables_to_appear_in_same_query!(school, school_users,);
pub async fn get_user_with_email(
email: &str,
c: actix_web::web::Data<diesel_async::pooled_connection::bb8::Pool<AsyncPgConnection>>,
) -> User {
// use diesel::ExpressionMethods;
// use diesel::QueryDsl;
// use diesel::SelectableHelper;
let conn = c.into_inner();
let mut conn = conn.get().await.unwrap();
let user: Vec<crate::users::User> = users::table
.filter(users::email.eq(email))
.select(crate::users::User::as_select())
.load(&mut conn)
.await
.unwrap();
user[0].clone()
}
pub async fn get_schools(
id: i32,
c: actix_web::web::Data<diesel_async::pooled_connection::bb8::Pool<AsyncPgConnection>>,
) -> Vec<School> {
let conn = c.into_inner();
let mut conn = conn.get().await.unwrap();
let schools: Vec<crate::users::School> = school::table
.filter(school_users::user_id.eq(id))
.select(School::as_select())
.load(&mut conn)
.await
.unwrap();
schools
}
pub mod users;
use std::future::{ready, Ready};
use actix_session::SessionExt;
use actix_web::{
body::EitherBody,
dev::{self, Service, ServiceRequest, ServiceResponse, Transform},
http, Error, HttpResponse,
};
use futures_util::future::LocalBoxFuture;
use crate::models::users::User;
pub struct CheckLogin;
impl<S, B> Transform<S, ServiceRequest> for CheckLogin
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type InitError = ();
type Transform = CheckLoginMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(CheckLoginMiddleware { service }))
}
}
pub struct CheckLoginMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for CheckLoginMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
dev::forward_ready!(service);
fn call(&self, request: ServiceRequest) -> Self::Future {
// Change this to see the change in outcome in the browser.
// Usually this boolean would be acquired from a password check or other auth verification.
let is_logged_in = request.get_session().get::<User>("user").is_ok();
// Don't forward to `/login` if we are already on `/login`.
if !is_logged_in && request.path() != "/login" {
let (request, _pl) = request.into_parts();
let response = HttpResponse::Found()
.insert_header((http::header::LOCATION, "/login"))
.finish()
// constructed responses map to "right" body
.map_into_right_body();
return Box::pin(async { Ok(ServiceResponse::new(request, response)) });
}
let res = self.service.call(request);
Box::pin(async move {
// forwarded responses map to "left" body
res.await.map(ServiceResponse::map_into_left_body)
})
}
}
use actix_session::{
config::PersistentSession, storage::RedisActorSessionStore, SessionMiddleware,
};
use actix_web::{
cookie::time::Duration,
middleware,
web::{self, Data},
App, HttpRequest, HttpResponse, HttpServer, Responder,
};
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
use diesel_async::AsyncPgConnection;
use diesel_async::{pooled_connection::bb8, RunQueryDsl};
use env_logger;
use keys::{add_key, is_key_exist};
use models::users;
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::io;
use crate::models::users::get_user_with_email;
mod db;
mod keys;
mod middlewares;
mod models;
type Pool = bb8::Pool<AsyncDieselConnectionManager<AsyncPgConnection>>;
const EXPIRES: i64 = 60 * 60 * 24 * 365;
#[actix_web::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
// std::env::set_var("RUST_LOG", "debug");
// tracing_subscriber::fmt::init();
// get env vars
dotenvy::dotenv().ok();
log::info!("starting HTTP server at http://localhost:8080");
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
let redis = redis::Client::open("redis://127.0.0.1:6379").unwrap();
let private_key = actix_web::cookie::Key::generate();
let config = AsyncDieselConnectionManager::<diesel_async::AsyncPgConnection>::new(db_url);
let pool = bb8::Pool::builder().build(config).await.unwrap();
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(redis.clone()))
.wrap(middleware::Logger::default())
.app_data(web::Data::new(pool.clone()))
.wrap(
SessionMiddleware::builder(
RedisActorSessionStore::new("127.0.0.1:6379"),
private_key.clone(),
)
.cookie_same_site(actix_web::cookie::SameSite::Lax)
.cookie_name("name".to_string())
.cookie_secure(false)
.cookie_http_only(false)
.cookie_domain(Some("192.168.1.101".to_string()))
.session_lifecycle(actix_session::config::SessionLifecycle::PersistentSession(
PersistentSession::default().session_ttl(Duration::seconds(EXPIRES)),
))
// .execute()
.build(),
)
.service(
web::resource("/get_key/{id}")
.wrap(middlewares::CheckLogin)
.route(web::get().to(get_key)),
)
.service(
web::resource("/confirm_key/{key}")
// .wrap(middlewares::CheckLogin)
.route(web::get().to(confirm_key)),
)
.service(web::resource("/").route(web::post().to(unlock)))
.service(
web::resource("/schools")
.wrap(middlewares::CheckLogin)
.route(web::get().to(schools)),
)
.service(web::resource("/login").route(web::post().to(login)))
})
//.workers(2)
.bind(("127.0.0.1", 8080))?
.run()
.await
}
async fn get_key(
con: Data<redis::Client>,
s: actix_session::Session,
s_id: web::Path<i32>,
) -> actix_web::Result<impl Responder> {
loop {
let key = rand::thread_rng().gen_range(100000..999999);
let key = format!("{}:{}", s_id, key);
if !is_key_exist(con.clone(), &key).await {
add_key(con.clone(), &key).await;
return Ok(HttpResponse::Ok().body(key.to_string()));
}
}
}
async fn confirm_key(
con: Data<redis::Client>,
_s: actix_session::Session,
key: web::Path<String>,
) -> actix_web::Result<impl Responder> {
if !is_key_exist(con.clone(), &key.into_inner()).await {
return Ok(HttpResponse::Ok().finish());
}
return Ok(HttpResponse::BadRequest().finish());
}
async fn unlock(
con: Data<redis::Client>,
password: web::Form<String>,
) -> actix_web::Result<impl Responder> {
if is_key_exist(con.clone(), &password).await {
return Ok(HttpResponse::Ok().body(""));
}
return Ok(HttpResponse::BadRequest().into());
}
async fn login(
_form: web::Json<LoginForm>,
c: web::Data<bb8::Pool<AsyncPgConnection>>, // c: web::Data<Pool>,
session: actix_session::Session,
) -> actix_web::Result<impl Responder> {
// use diesel_async::prelude::*;
let user = get_user_with_email(&_form.email, c).await;
session.insert("user", user.clone()).unwrap();
let u = session.get::<crate::users::User>("user");
println!("{:?}", u);
return Ok(HttpResponse::Ok().json(user));
}
async fn schools(
c: web::Data<bb8::Pool<AsyncPgConnection>>, // c: web::Data<Pool>,
session: actix_session::Session,
) -> actix_web::Result<impl Responder> {
// use diesel_async::prelude::*;
use crate::users::*;
let user = session.get::<User>("user").unwrap().unwrap();
let schs = get_schools(user.id, c).await;
println!("schools = {schs:?}");
return Ok(HttpResponse::Ok().json(schs));
}
#[derive(Clone, Deserialize, Serialize)]
struct LoginForm {
email: String,
password: String,
}
use actix_web::web::Data;
use redis::{self, AsyncCommands, Client};
pub async fn is_key_exist(con: Data<Client>, k: &str) -> bool {
let mut con = con.get_connection_manager().await.unwrap();
let a: bool = con.sismember("etapkilit", k).await.unwrap();
a
}
pub async fn add_key(con: Data<Client>, k: &str) -> bool {
let mut con = con.get_connection_manager().await.unwrap();
let a: bool = con.sadd("etapkilit", k).await.unwrap();
a
}
[package]
name = "backend"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = {version="*",features=["cookies"]}
actix-session = { version = "*", features = ["redis-actor-session"] }
env_logger = "*"
log = "*"
redis = { version = "0.24", default-features = false, features = ["tokio-comp", "connection-manager"] }
serde = {version = "*", features=["derive"]}
serde_json = "*"
rand = "*"
dotenvy="*"
diesel = {version= "2.1.0", default-features=false,features=["postgres"]} # no backend features need to be enabled
diesel-async = { version = "0.4.1", features = ["postgres","bb8"] }
futures-util = "0.3.21"
rustls = "0.20.8"
rustls-native-certs = "0.6.2"
tokio = { version = "1.2.0", default-features = false, features = ["macros", "rt-multi-thread"] }
tokio-postgres = "0.7.7"
tokio-postgres-rustls = "0.9.0"