feat: CRUD(user) & login/logout with session
This commit is contained in:
		
							parent
							
								
									24a46c1d10
								
							
						
					
					
						commit
						ed02382138
					
				
							
								
								
									
										0
									
								
								rust_solid_cassandra/backend/.projectile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								rust_solid_cassandra/backend/.projectile
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										34
									
								
								rust_solid_cassandra/backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										34
									
								
								rust_solid_cassandra/backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -56,6 +56,22 @@ dependencies = [ | |||||||
|  "zstd", |  "zstd", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-identity" | ||||||
|  | version = "0.5.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1224c9f9593dc27c9077b233ce04adedc1d7febcfc35ee9f53ea3c24df180bec" | ||||||
|  | dependencies = [ | ||||||
|  |  "actix-service", | ||||||
|  |  "actix-session", | ||||||
|  |  "actix-utils", | ||||||
|  |  "actix-web", | ||||||
|  |  "anyhow", | ||||||
|  |  "futures-core", | ||||||
|  |  "serde", | ||||||
|  |  "tracing", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "actix-macros" | name = "actix-macros" | ||||||
| version = "0.2.3" | version = "0.2.3" | ||||||
| @ -321,11 +337,14 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" | |||||||
| name = "backend" | name = "backend" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  |  "actix-identity", | ||||||
|  "actix-session", |  "actix-session", | ||||||
|  "actix-web", |  "actix-web", | ||||||
|  "cassandra-cpp", |  "cassandra-cpp", | ||||||
|  "env_logger", |  "env_logger", | ||||||
|  "log", |  "log", | ||||||
|  |  "serde", | ||||||
|  |  "serde_json", | ||||||
|  "uuid", |  "uuid", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| @ -1072,6 +1091,20 @@ name = "serde" | |||||||
| version = "1.0.147" | version = "1.0.147" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" | checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" | ||||||
|  | dependencies = [ | ||||||
|  |  "serde_derive", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "serde_derive" | ||||||
|  | version = "1.0.147" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" | ||||||
|  | dependencies = [ | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "serde_json" | name = "serde_json" | ||||||
| @ -1335,6 +1368,7 @@ checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" | |||||||
| dependencies = [ | dependencies = [ | ||||||
|  "getrandom", |  "getrandom", | ||||||
|  "rand", |  "rand", | ||||||
|  |  "serde", | ||||||
|  "uuid-macro-internal", |  "uuid-macro-internal", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -8,7 +8,10 @@ edition = "2021" | |||||||
| [dependencies] | [dependencies] | ||||||
| actix-web = "4" # Webserver itself | actix-web = "4" # Webserver itself | ||||||
| actix-session = { version = "0.7", features = ["cookie-session"] } # Session middleware | actix-session = { version = "0.7", features = ["cookie-session"] } # Session middleware | ||||||
|  | actix-identity = "0.5.2" | ||||||
| env_logger = "0.9" # Logger itself | env_logger = "0.9" # Logger itself | ||||||
| log = "0.4" # Lightweight logging facade (Logging API) | log = "0.4" # Lightweight logging facade (Logging API) | ||||||
| uuid = { version = "1.2.2", features = ["v4", "fast-rng", "macro-diagnostics" ]} | uuid = { version = "1.2.2", features = ["v4", "fast-rng", "macro-diagnostics", "serde"]} | ||||||
| cassandra-cpp = "1.2" | cassandra-cpp = "1.2" | ||||||
|  | serde = { version = "1.0", features = ["derive"] } | ||||||
|  | serde_json = "1.0" | ||||||
| @ -1,79 +1,184 @@ | |||||||
| use std::{any::Any, io}; | use std::{io, sync::Arc}; | ||||||
| 
 | 
 | ||||||
| use actix_session::{storage::CookieSessionStore, Session, SessionMiddleware}; | use actix_identity::{Identity, IdentityMiddleware}; | ||||||
|  | use actix_session::{ | ||||||
|  |     config::PersistentSession, storage::CookieSessionStore, Session, SessionMiddleware, | ||||||
|  | }; | ||||||
| use actix_web::{ | use actix_web::{ | ||||||
|     get, |     cookie::{time::Duration, Key}, | ||||||
|  |     delete, get, | ||||||
|     http::{header::ContentType, Method}, |     http::{header::ContentType, Method}, | ||||||
|     middleware, |     middleware, post, put, | ||||||
|     web::{self}, |     web::{self}, | ||||||
|     App, HttpRequest, HttpResponse, HttpServer, |     App, HttpMessage, HttpRequest, HttpResponse, HttpServer, Responder, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Define our model module and use our models
 | // Define our model module
 | ||||||
| mod model; | mod model; | ||||||
|  | use model::todo::{Priority, Status, Todo}; | ||||||
| use model::user::User; | use model::user::User; | ||||||
| use model::todo::{Todo, Priority, Status}; | // Define our repo module
 | ||||||
| mod repo; | mod repo; | ||||||
|  | // use repo::todo_repository::TodoRepository;
 | ||||||
|  | use repo::user_repository::UserRepository; | ||||||
| 
 | 
 | ||||||
| async fn index(method: Method) -> HttpResponse { | async fn index(id: Option<Identity>, method: Method) -> impl Responder { | ||||||
|     match method { |     match method { | ||||||
|         Method::GET => HttpResponse::Ok() |         Method::GET => match id { | ||||||
|  |             Some(id) => HttpResponse::Ok() | ||||||
|                 .content_type(ContentType::plaintext()) |                 .content_type(ContentType::plaintext()) | ||||||
|             .body(format!("Welcome")), |                 .body(format!( | ||||||
|  |                     "You are logged in. Welcome! ({:?})", | ||||||
|  |                     id.id().unwrap() | ||||||
|  |                 )), | ||||||
|  |             None => HttpResponse::Unauthorized().finish(), | ||||||
|  |         }, | ||||||
|         _ => HttpResponse::Forbidden().finish(), |         _ => HttpResponse::Forbidden().finish(), | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[get("/counter")] | #[get("/user")] | ||||||
| async fn get_counter(req: HttpRequest, session: Session) -> actix_web::Result<HttpResponse> { | async fn get_user(id: Identity, repo: web::Data<UserRepository>) -> impl Responder { | ||||||
|     // log::info!("Request: {req:?}");
 |     match repo.read_all() { | ||||||
| 
 |         Ok(users) => { | ||||||
|     let mut counter = 1; |             log::info!("{users:?}"); | ||||||
|     if let Some(count) = session.get::<i32>("counter")? { |             HttpResponse::Ok().body(format!("{users:?}")) | ||||||
|         log::info!("Session counter: {count}"); |         } | ||||||
|         counter = count + 1; |         Err(err) => { | ||||||
|  |             log::error!("Could not read from user repo: {err}"); | ||||||
|  |             HttpResponse::InternalServerError() | ||||||
|  |                 .body(format!("Could not read from user repo: {err}")) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|     session.insert("counter", counter)?; | // TODO: Guard for login
 | ||||||
|  | #[put("/user")] | ||||||
|  | async fn put_user( | ||||||
|  |     id: Identity, | ||||||
|  |     payload: web::Json<User>, | ||||||
|  |     session: Session, | ||||||
|  |     repo: web::Data<UserRepository>, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     log::debug!("Received {payload:?}"); | ||||||
|  |     match repo.update(&payload.0) { | ||||||
|  |         Ok(_) => HttpResponse::Ok().finish(), | ||||||
|  |         Err(_) => { | ||||||
|  |             let msg = format!("No user with that id: {payload:?}"); | ||||||
|  |             log::debug!("{}", msg); | ||||||
|  |             HttpResponse::InternalServerError().body(msg) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     Ok(HttpResponse::Ok().finish()) | // TODO: For a real app, impl smth like a registration-secret or email verification
 | ||||||
|  | #[post("/user")] | ||||||
|  | async fn post_user(payload: web::Json<User>, repo: web::Data<UserRepository>) -> impl Responder { | ||||||
|  |     log::debug!("Received {payload:?}"); | ||||||
|  |     let user = User::new(payload.login(), payload.hash(), payload.salt()); | ||||||
|  |     match repo.create(&user) { | ||||||
|  |         Ok(_) => { | ||||||
|  |             log::debug!("Successfully created {user:?}"); | ||||||
|  |             HttpResponse::Created().finish() | ||||||
|  |         } | ||||||
|  |         Err(err) => { | ||||||
|  |             log::debug!("{err}"); | ||||||
|  |             HttpResponse::BadRequest().body(format!("{user:?}")) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[post("/login")] | ||||||
|  | async fn post_login( | ||||||
|  |     req: HttpRequest, | ||||||
|  |     payload: web::Json<User>, | ||||||
|  |     repo: web::Data<UserRepository>, | ||||||
|  |     session: Session, | ||||||
|  | ) -> impl Responder { | ||||||
|  |     log::debug!("Received {payload:?}"); | ||||||
|  |     match repo.read(&payload.id()) { | ||||||
|  |         Ok(Some(user)) => { | ||||||
|  |             if payload.salt() == "" { | ||||||
|  |                 log::debug!("Initial login request with empty salt: {payload:?}"); | ||||||
|  |                 HttpResponse::Ok().json(format!("{{ 'salt': '{}' }}", user.salt())) | ||||||
|  |             } else if payload.hash() == user.hash() { | ||||||
|  |                 log::debug!("User successfully logged in: {payload:?} == {user:?}"); | ||||||
|  |                 // TODO: Mayb handle more gracefully
 | ||||||
|  |                 Identity::login(&req.extensions(), format!("{}", payload.id())) | ||||||
|  |                     .expect("Log the user in"); | ||||||
|  | 
 | ||||||
|  |                 // TODO: Mayb handle more gracefully
 | ||||||
|  |                 session | ||||||
|  |                     .insert("user", payload.0) | ||||||
|  |                     .expect("Insert user into session"); | ||||||
|  |                 HttpResponse::Ok().finish() | ||||||
|  |             } else { | ||||||
|  |                 log::debug!("Wrong password hash for user: {payload:?} != {user:?}"); | ||||||
|  |                 HttpResponse::Unauthorized().finish() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok(None) => { | ||||||
|  |             log::debug!("User not found: {payload:?}"); | ||||||
|  |             HttpResponse::Unauthorized().finish() | ||||||
|  |         } | ||||||
|  |         Err(_) => { | ||||||
|  |             let msg = format!("Could not create user: {payload:?}"); | ||||||
|  |             log::debug!("{}", msg); | ||||||
|  |             HttpResponse::InternalServerError().body(msg) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[delete("/logout")] | ||||||
|  | async fn delete_logout(id: Identity) -> impl Responder { | ||||||
|  |     id.logout(); | ||||||
|  |     HttpResponse::Ok() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[actix_web::main] | #[actix_web::main] | ||||||
| async fn main() -> io::Result<()> { | async fn main() -> io::Result<()> { | ||||||
|     env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); |     env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug")); | ||||||
| 
 | 
 | ||||||
|     let user = User::new( |     let user = User::new("admin", "init_pw_hash", "init_salt"); | ||||||
|         "phga".to_string(), |  | ||||||
|         "onetuhoneuth".to_string(), |  | ||||||
|         "salt".to_string(), |  | ||||||
|     ); |  | ||||||
|     log::info!("{user:#?}"); |     log::info!("{user:#?}"); | ||||||
|     let todo = Todo::new( |     let todo = Todo::new( | ||||||
|         user.id().clone(), |         user.id().clone(), | ||||||
|         "Mein todo".to_string(), |         "Mein todo", | ||||||
|         "Es hat viele Aufgaben".to_string(), |         "Es hat viele Aufgaben", | ||||||
|         Priority::Normal, |         Priority::Normal, | ||||||
|         Status::Todo, |         Status::Todo, | ||||||
|     ); |     ); | ||||||
|     log::info!("{todo:#?}"); |     log::info!("{todo:#?}"); | ||||||
|     // Create a better session key for production
 | 
 | ||||||
|     // This one is only 0's -> 64 byte "random" string
 |     let cassandra_session = Arc::new(repo::init()); | ||||||
|     let key: &[u8] = &[0; 64]; | 
 | ||||||
|     let key = actix_web::cookie::Key::from(key); |     let user_repo = web::Data::new(UserRepository::new(Arc::clone(&cassandra_session))); | ||||||
|  |     if let Err(err) = user_repo.create(&user) { | ||||||
|  |         log::debug!("Default user already exists: {err}"); | ||||||
|  |     } | ||||||
|  |     let key = Key::generate(); | ||||||
| 
 | 
 | ||||||
|     log::info!("Starting HTTP server: http://127.0.0.1:6969"); |     log::info!("Starting HTTP server: http://127.0.0.1:6969"); | ||||||
| 
 | 
 | ||||||
|     HttpServer::new(move || { |     HttpServer::new(move || { | ||||||
|         App::new() |         App::new() | ||||||
|             .wrap(middleware::Compress::default()) |             .wrap(middleware::Compress::default()) | ||||||
|  |             .wrap(IdentityMiddleware::default()) | ||||||
|             .wrap( |             .wrap( | ||||||
|                 SessionMiddleware::builder(CookieSessionStore::default(), key.clone()) |                 SessionMiddleware::builder(CookieSessionStore::default(), key.clone()) | ||||||
|                     .cookie_secure(false) |                     .cookie_secure(false) | ||||||
|  |                     // Session lifetime
 | ||||||
|  |                     .session_lifecycle(PersistentSession::default().session_ttl(Duration::days(7))) | ||||||
|                     .build(), |                     .build(), | ||||||
|             ) |             ) | ||||||
|             .wrap(middleware::Logger::default()) |             .wrap(middleware::Logger::default()) | ||||||
|             .service(get_counter) |             .app_data(user_repo.clone()) | ||||||
|  |             // .service(get_counter)
 | ||||||
|  |             .service(get_user) | ||||||
|  |             .service(put_user) | ||||||
|  |             .service(post_user) | ||||||
|  |             .service(post_login) | ||||||
|  |             .service(delete_logout) | ||||||
|             .default_service(web::to(index)) |             .default_service(web::to(index)) | ||||||
|     }) |     }) | ||||||
|     .bind(("127.0.0.1", 6969))? |     .bind(("127.0.0.1", 6969))? | ||||||
|  | |||||||
| @ -27,16 +27,16 @@ pub struct Todo { | |||||||
| impl Todo { | impl Todo { | ||||||
|     pub fn new( |     pub fn new( | ||||||
|         user_id: Uuid, |         user_id: Uuid, | ||||||
|         title: String, |         title: &str, | ||||||
|         description: String, |         description: &str, | ||||||
|         priority: Priority, |         priority: Priority, | ||||||
|         status: Status, |         status: Status, | ||||||
|     ) -> Todo { |     ) -> Todo { | ||||||
|         Todo { |         Todo { | ||||||
|             id: Uuid::new_v4(), |             id: Uuid::new_v4(), | ||||||
|             user_id, |             user_id, | ||||||
|             title, |             title: String::from(title), | ||||||
|             description, |             description: String::from(description), | ||||||
|             priority, |             priority, | ||||||
|             status, |             status, | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
| use uuid::Uuid; | use uuid::Uuid; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug, Deserialize, Serialize)] | ||||||
| pub struct User { | pub struct User { | ||||||
|     id: Uuid, |     id: Uuid, | ||||||
|     login: String, |     login: String, | ||||||
| @ -9,15 +10,20 @@ pub struct User { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl User { | impl User { | ||||||
|     pub fn new(login: String, hash: String, salt: String) -> User { |     pub fn new(login: &str, hash: &str, salt: &str) -> User { | ||||||
|         User { |         User { | ||||||
|             id: Uuid::new_v4(), |             id: Uuid::new_v4(), | ||||||
|             login, |             login: String::from(login), | ||||||
|             hash, |             hash: String::from(hash), | ||||||
|             salt, |             salt: String::from(salt), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn from_json(json: &str) -> User { | ||||||
|  |         log::debug!("{json}"); | ||||||
|  |         serde_json::from_str::<User>(json).expect("Deserialized User object") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn id(&self) -> &Uuid { |     pub fn id(&self) -> &Uuid { | ||||||
|         &self.id |         &self.id | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,17 +1,27 @@ | |||||||
| use cassandra_cpp::{Cluster, Session, stmt}; | use std::{env, thread::sleep, time::Duration}; | ||||||
|  | 
 | ||||||
|  | use cassandra_cpp::{stmt, Cluster, Session}; | ||||||
| 
 | 
 | ||||||
| pub mod todo_repository; | pub mod todo_repository; | ||||||
| pub mod user_repository; | pub mod user_repository; | ||||||
| 
 | 
 | ||||||
| // Ideally read this from config
 | static DEFAULT_KEYSPACE_NAME: &str = "rust_solidjs_cassandra"; | ||||||
| const KEYSPACE_NAME: &str = "rust_solid_cassandra"; | 
 | ||||||
|  | pub fn init() -> Session { | ||||||
|  |     let keyspace_name = env::var("KEYSPACE_NAME").unwrap_or(DEFAULT_KEYSPACE_NAME.to_string()); | ||||||
|  |     // Definitely set it so other modules can use it
 | ||||||
|  |     env::set_var("KEYSPACE_NAME", &keyspace_name); | ||||||
| 
 | 
 | ||||||
| fn init() -> Session { |  | ||||||
|     let mut cluster = Cluster::default(); |     let mut cluster = Cluster::default(); | ||||||
|     cluster.set_contact_points("127.0.0.1").unwrap(); // Panic if not successful
 |     cluster.set_contact_points("127.0.0.1").unwrap(); // Panic if not successful
 | ||||||
|     let session = cluster.connect().unwrap(); // Session is used to exec queries
 |     let session = loop { | ||||||
|  |         match cluster.connect() { | ||||||
|  |             Ok(session) => break session, | ||||||
|  |             Err(_) => sleep(Duration::new(1, 0)), | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|     let query = stmt!(&format!( |     let query = stmt!(&format!( | ||||||
|         "CREATE KEYSPACE IF NOT EXISTS {KEYSPACE_NAME}
 |         "CREATE KEYSPACE IF NOT EXISTS {keyspace_name}
 | ||||||
|              WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': 1}};" |              WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': 1}};" | ||||||
|     )); |     )); | ||||||
| 
 | 
 | ||||||
| @ -22,56 +32,3 @@ fn init() -> Session { | |||||||
| 
 | 
 | ||||||
|     session |     session | ||||||
| } | } | ||||||
| 
 |  | ||||||
| #[cfg(test)] |  | ||||||
| mod tests { |  | ||||||
|     use cassandra_cpp::{stmt, Cluster}; |  | ||||||
| 
 |  | ||||||
|     use crate::model::user::User; |  | ||||||
| 
 |  | ||||||
|     use super::*; |  | ||||||
|     #[test] |  | ||||||
|     fn init_user_repo() { |  | ||||||
|         let session = init(); |  | ||||||
|         let ur = user_repository::UserRepository::new(&session); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[test] |  | ||||||
|     fn user_repo_create() { |  | ||||||
|         let session = init(); |  | ||||||
|         let ur = user_repository::UserRepository::new(&session); |  | ||||||
|         let u = User::new("phga".to_string(), "1337".to_string(), "salzig".to_string()); |  | ||||||
|         if let Err(err) = ur.create(&u) { |  | ||||||
|             panic!("Creating a user failed {err}"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[test] |  | ||||||
|     fn user_repo_read_all() { |  | ||||||
|         let session = init(); |  | ||||||
|         let ur = user_repository::UserRepository::new(&session); |  | ||||||
|         match ur.read_all() { |  | ||||||
|             Ok(rows) => println!("{rows}"), |  | ||||||
|             Err(err) => panic!("Reading from user table failed: {err}"), |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[test] |  | ||||||
|     fn initialize_database() { |  | ||||||
|         let create_query = stmt!( |  | ||||||
|             "CREATE KEYSPACE IF NOT EXISTS test1337
 |  | ||||||
|              WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};" |  | ||||||
|         ); |  | ||||||
|         let check_query = stmt!( |  | ||||||
|             "SELECT keyspace_name FROM system_schema.keyspaces
 |  | ||||||
|              WHERE keyspace_name = 'test1337';" |  | ||||||
|         ); |  | ||||||
|         let mut cluster = Cluster::default(); |  | ||||||
|         cluster.set_contact_points("127.0.0.1").unwrap(); |  | ||||||
|         let session = cluster.connect().unwrap(); // Session is used to exec queries
 |  | ||||||
|         let result = session.execute(&create_query).wait().unwrap(); |  | ||||||
|         println!("CREATE: {}", result); |  | ||||||
|         let result = session.execute(&check_query).wait().unwrap(); |  | ||||||
|         println!("CHECK: {}", result); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,18 +1,19 @@ | |||||||
| use std::fmt::Error; | use std::{env, sync::Arc}; | ||||||
| 
 | 
 | ||||||
| use cassandra_cpp::{stmt, CassResult, Session}; | use cassandra_cpp::{stmt, ErrorKind, Result, Session}; | ||||||
| 
 | 
 | ||||||
| use crate::model::user::User; | use crate::model::user::User; | ||||||
| 
 | 
 | ||||||
| use super::KEYSPACE_NAME; | pub struct UserRepository { | ||||||
| pub struct UserRepository<'a> { |     session: Arc<Session>, | ||||||
|     session: &'a Session, |  | ||||||
|     table: String, |     table: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<'a> UserRepository<'a> { | impl UserRepository { | ||||||
|     pub fn new(session: &'a Session) -> UserRepository<'a> { |     pub fn new(session: Arc<Session>) -> UserRepository { | ||||||
|         let table = format!("{KEYSPACE_NAME}.user"); |         let keyspace_name = | ||||||
|  |             env::var("KEYSPACE_NAME").expect("Value should be definitely set in init"); | ||||||
|  |         let table = format!("{keyspace_name}.user"); | ||||||
|         let query = stmt!(&format!( |         let query = stmt!(&format!( | ||||||
|             "CREATE TABLE IF NOT EXISTS {table} (
 |             "CREATE TABLE IF NOT EXISTS {table} (
 | ||||||
|                 id uuid, |                 id uuid, | ||||||
| @ -28,14 +29,29 @@ impl<'a> UserRepository<'a> { | |||||||
|             .wait() |             .wait() | ||||||
|             .expect("Should create user keyspace if not exists"); |             .expect("Should create user keyspace if not exists"); | ||||||
| 
 | 
 | ||||||
|  |         let query = stmt!(&format!("CREATE INDEX IF NOT EXISTS ON {table} (login);")); | ||||||
|  | 
 | ||||||
|  |         session | ||||||
|  |             .execute(&query) | ||||||
|  |             .wait() | ||||||
|  |             .expect("Should create secondary index for login column"); | ||||||
|  | 
 | ||||||
|         UserRepository { session, table } |         UserRepository { session, table } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn create(&self, user: &User) -> Result<(), cassandra_cpp::Error> { |     pub fn create(&self, user: &User) -> Result<()> { | ||||||
|  |         if let Some(u) = self.read_by_login(user.login())? { | ||||||
|  |             return Err(cassandra_cpp::Error::from(ErrorKind::Msg(format!( | ||||||
|  |                 "Creation of {u:?} failed. User already exists." | ||||||
|  |             )))); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         let mut query = stmt!(&format!( |         let mut query = stmt!(&format!( | ||||||
|             "INSERT INTO {} (id, login, hash, salt)
 |             "INSERT INTO {} (id, login, hash, salt)
 | ||||||
|              VALUES (?, ?, ?, ?);", self.table
 |              VALUES (?, ?, ?, ?);",
 | ||||||
|  |             self.table | ||||||
|         )); |         )); | ||||||
|  | 
 | ||||||
|         let uuid = cassandra_cpp::Uuid::from(user.id().clone()); |         let uuid = cassandra_cpp::Uuid::from(user.id().clone()); | ||||||
|         query.bind_uuid(0, uuid).expect("Binds the id"); |         query.bind_uuid(0, uuid).expect("Binds the id"); | ||||||
|         query.bind_string(1, user.login()).expect("Binds the login"); |         query.bind_string(1, user.login()).expect("Binds the login"); | ||||||
| @ -45,9 +61,63 @@ impl<'a> UserRepository<'a> { | |||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn read_all(&self) -> Result<CassResult, cassandra_cpp::Error> { |     pub fn update(&self, user: &User) -> Result<()> { | ||||||
|         let query = stmt!(&format!("SELECT * FROM {};", self.table)); |         match self.read(&user.id())? { | ||||||
|         let res = self.session.execute(&query).wait()?; |             Some(u) => { | ||||||
|         Ok(res) |                 log::info!("Modifying {u:?} to represent {user:?}"); | ||||||
|  | 
 | ||||||
|  |                 let mut query = stmt!(&format!( | ||||||
|  |                     "UPDATE {} SET login = ?, hash = ?, salt = ?
 | ||||||
|  |                      WHERE id = ?;",
 | ||||||
|  |                     self.table | ||||||
|  |                 )); | ||||||
|  | 
 | ||||||
|  |                 let uuid = cassandra_cpp::Uuid::from(user.id().clone()); | ||||||
|  |                 query.bind_string(0, user.login()).expect("Binds the login"); | ||||||
|  |                 query.bind_string(1, user.hash()).expect("Binds the hash"); | ||||||
|  |                 query.bind_string(2, user.salt()).expect("Binds the salt"); | ||||||
|  |                 query.bind_uuid(3, uuid).expect("Binds the id"); | ||||||
|  |                 self.session.execute(&query).wait()?; | ||||||
|  |                 Ok(()) | ||||||
|  |             } | ||||||
|  |             None => Err(cassandra_cpp::Error::from(ErrorKind::Msg(format!( | ||||||
|  |                 "User {user:?} does not exist in the database" | ||||||
|  |             )))), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn read_by_login(&self, login: &str) -> Result<Option<User>> { | ||||||
|  |         let mut query = stmt!(&format!( | ||||||
|  |             "SELECT JSON * FROM {} WHERE login = ?", | ||||||
|  |             self.table | ||||||
|  |         )); | ||||||
|  |         query.bind_string(0, login).expect("Binds login"); | ||||||
|  |         Ok(match self.session.execute(&query).wait()?.first_row() { | ||||||
|  |             Some(row) => Some(User::from_json(&format!("{row}"))), | ||||||
|  |             None => None, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn read(&self, id: &uuid::Uuid) -> Result<Option<User>> { | ||||||
|  |         let uuid = cassandra_cpp::Uuid::from(id.clone()); | ||||||
|  |         // There is no OR in cassandra statements
 | ||||||
|  |         let mut query = stmt!(&format!("SELECT JSON * FROM {} WHERE id = ?", self.table)); | ||||||
|  |         query.bind_uuid(0, uuid).expect("Binds login"); | ||||||
|  | 
 | ||||||
|  |         Ok(match self.session.execute(&query).wait()?.first_row() { | ||||||
|  |             Some(row) => Some(User::from_json(&format!("{row}"))), | ||||||
|  |             None => None, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn read_all(&self) -> Result<Vec<User>> { | ||||||
|  |         let query = stmt!(&format!("SELECT JSON * FROM {};", self.table)); | ||||||
|  |         Ok(self | ||||||
|  |             .session | ||||||
|  |             .execute(&query) | ||||||
|  |             .wait()? | ||||||
|  |             .iter() | ||||||
|  |             .map(|json| User::from_json(&format!("{json}"))) | ||||||
|  |             .collect()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user