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