diff --git a/backend/migrations/2019-01-13-203149_create_users/down.sql b/backend/migrations/2019-01-13-203149_create_users/down.sql index c8b9a69..dc3714b 100644 --- a/backend/migrations/2019-01-13-203149_create_users/down.sql +++ b/backend/migrations/2019-01-13-203149_create_users/down.sql @@ -1,2 +1,2 @@ -- This file should undo anything in `up.sql` -DROP TABLE users \ No newline at end of file +DROP TABLE users; diff --git a/backend/migrations/2019-01-13-203149_create_users/up.sql b/backend/migrations/2019-01-13-203149_create_users/up.sql index 2968d06..36d07cd 100644 --- a/backend/migrations/2019-01-13-203149_create_users/up.sql +++ b/backend/migrations/2019-01-13-203149_create_users/up.sql @@ -5,4 +5,4 @@ CREATE TABLE users ( last_name VARCHAR(255) NOT NULL, banner_id INT(9) UNSIGNED NOT NULL, email VARCHAR(255) -) \ No newline at end of file +); diff --git a/backend/migrations/2019-03-08-171503_create_access/down.sql b/backend/migrations/2019-03-08-171503_create_access/down.sql new file mode 100644 index 0000000..fac3ecb --- /dev/null +++ b/backend/migrations/2019-03-08-171503_create_access/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP TABLE user_access; +DROP TABLE access; diff --git a/backend/migrations/2019-03-08-171503_create_access/up.sql b/backend/migrations/2019-03-08-171503_create_access/up.sql new file mode 100644 index 0000000..7e0c0f3 --- /dev/null +++ b/backend/migrations/2019-03-08-171503_create_access/up.sql @@ -0,0 +1,27 @@ +-- Your SQL goes here +CREATE TABLE access ( + id SERIAL PRIMARY KEY, + access_name VARCHAR(255) NOT NULL +); + +INSERT INTO access (access_name) VALUES + ("SearchUser"), + ("GetUser"), + ("CreateUser"), + ("UpdateUser"), + ("DeleteUser"); + +CREATE TABLE user_access ( + permission_id SERIAL PRIMARY KEY, + access_id BIGINT UNSIGNED NOT NULL, + user_id BIGINT UNSIGNED NOT NULL, + FOREIGN KEY (access_id) + REFERENCES access(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (user_id) + REFERENCES users(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + permission_level VARCHAR(255) +); diff --git a/backend/migrations/2019-04-15-150025_chemicals/down.sql b/backend/migrations/2019-04-15-150025_chemicals/down.sql new file mode 100644 index 0000000..62a3643 --- /dev/null +++ b/backend/migrations/2019-04-15-150025_chemicals/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP chemical_inventory; +DROP chemical; diff --git a/backend/migrations/2019-04-15-150025_chemicals/up.sql b/backend/migrations/2019-04-15-150025_chemicals/up.sql new file mode 100644 index 0000000..b34a79f --- /dev/null +++ b/backend/migrations/2019-04-15-150025_chemicals/up.sql @@ -0,0 +1,30 @@ +-- Your SQL goes here +CREATE TABLE chemical ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + purpose VARCHAR(1023) NOT NULL, + company_name VARCHAR(255) NOT NULL, + ingredients VARCHAR(1023) NOT NULL, + manual_link VARCHAR(1023) NOT NULL +); + +CREATE TABLE chemical_inventory ( + id SERIAL PRIMARY KEY, + purchaser_id BIGINT UNSIGNED NOT NULL, + custodian_id BIGINT UNSIGNED NOT NULL, + chemical_id BIGINT UNSIGNED NOT NULL, + storage_location VARCHAR(255) NOT NULL, + amount VARCHAR(255) NOT NULL, + FOREIGN KEY (purchaser_id) + REFERENCES users(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (custodian_id) + REFERENCES users(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (chemical_id) + REFERENCES chemical(id) + ON DELETE CASCADE + ON UPDATE CASCADE +); diff --git a/backend/src/access.rs b/backend/src/access.rs new file mode 100644 index 0000000..849c906 --- /dev/null +++ b/backend/src/access.rs @@ -0,0 +1,3 @@ +pub mod models; +pub mod requests; +pub mod schema; diff --git a/backend/src/access/models.rs b/backend/src/access/models.rs new file mode 100644 index 0000000..f19155d --- /dev/null +++ b/backend/src/access/models.rs @@ -0,0 +1,238 @@ +use diesel::Queryable; + +use rouille::router; + +use serde::Deserialize; +use serde::Serialize; + +use url::form_urlencoded; + +use log::{trace, warn}; + +use crate::errors::{WebdevError, WebdevErrorKind}; + +use crate::search::{NullableSearch, Search}; + +use super::schema::{access, user_access}; + +#[derive(Queryable, Serialize, Deserialize)] +pub struct Access { + pub id: u64, + pub access_name: String, +} + +#[derive(Insertable, Serialize, Deserialize)] +#[table_name = "access"] +pub struct NewAccess { + pub access_name: String, +} + +#[derive(AsChangeset, Serialize, Deserialize)] +#[table_name = "access"] +pub struct PartialAccess { + pub access_name: String, +} + +pub enum AccessRequest { + GetAccess(u64), //id of access name searched + CreateAccess(NewAccess), //new access type of some name to be created + UpdateAccess(u64, PartialAccess), //Contains id to be changed to new access_name + DeleteAccess(u64), //if of access to be deleted +} + +impl AccessRequest { + pub fn from_rouille( + request: &rouille::Request, + ) -> Result { + trace!("Creating AccessRequest from {:#?}", request); + + router!(request, + (GET) (/{id: u64}) => { + Ok(AccessRequest::GetAccess(id)) + }, + + (POST) (/) => { + let request_body = request.data().ok_or(WebdevError::new(WebdevErrorKind::Format))?; + let new_access: NewAccess = serde_json::from_reader(request_body)?; + + Ok(AccessRequest::CreateAccess(new_access)) + }, + + (POST) (/{id: u64}) => { + let request_body = request.data().ok_or(WebdevError::new(WebdevErrorKind::Format))?; + let update_access: PartialAccess = serde_json::from_reader(request_body)?; + + Ok(AccessRequest::UpdateAccess(id, update_access)) + }, + + (DELETE) (/{id: u64}) => { + Ok(AccessRequest::DeleteAccess(id)) + }, + + _ => { + warn!("Could not create an access request for the given rouille request"); + Err(WebdevError::new(WebdevErrorKind::NotFound)) + } + ) //end router + } +} + +pub enum AccessResponse { + OneAccess(Access), + NoResponse, +} + +impl AccessResponse { + pub fn to_rouille(self) -> rouille::Response { + match self { + AccessResponse::OneAccess(access) => { + rouille::Response::json(&access) + } + AccessResponse::NoResponse => rouille::Response::empty_204(), + } + } +} + +#[derive(Queryable, Serialize, Deserialize)] +pub struct UserAccess { + pub permission_id: u64, + pub access_id: u64, + pub user_id: u64, + pub permission_level: Option, +} + +#[derive(Insertable, Serialize, Deserialize)] +#[table_name = "user_access"] +pub struct NewUserAccess { + pub access_id: u64, + pub user_id: u64, + pub permission_level: Option, +} + +#[derive(AsChangeset, Serialize, Deserialize)] +#[table_name = "user_access"] +pub struct PartialUserAccess { + pub access_id: u64, + pub user_id: u64, + pub permission_level: Option>, +} + +pub struct SearchUserAccess { + pub access_id: Search, + pub user_id: Search, + pub permission_level: NullableSearch, +} + +pub enum UserAccessRequest { + SearchAccess(SearchUserAccess), //list of users with access id or (?) name + GetAccess(u64), //get individual access entry from its id + CheckAccess(u64, u64), //entry allowing user of user_id to perform action of action_id + CreateAccess(NewUserAccess), //entry to add to database + UpdateAccess(u64, PartialUserAccess), //entry to update with new information + DeleteAccess(u64), //entry to delete from database +} + +impl UserAccessRequest { + pub fn from_rouille( + request: &rouille::Request, + ) -> Result { + trace!("Creating UserAccessRequest from {:#?}", request); + + let url_queries = + form_urlencoded::parse(request.raw_query_string().as_bytes()); + + router!(request, + (GET) (/) => { + + let mut access_id_search = Search::NoSearch; + let mut user_id_search = Search::NoSearch; + let mut permission_level_search = NullableSearch::NoSearch; + + for (field, query) in url_queries { + match field.as_ref() as &str { + "access_id" => access_id_search = Search::from_query(query.as_ref())?, + "user_id" => user_id_search = Search::from_query(query.as_ref())?, + "permission_level" => permission_level_search = NullableSearch::from_query(query.as_ref())?, + _ => return Err(WebdevError::new(WebdevErrorKind::Format)), + } + } + + Ok(UserAccessRequest::SearchAccess(SearchUserAccess { + access_id: access_id_search, + user_id: user_id_search, + permission_level: permission_level_search, + })) + }, + + (GET) (/{permission_id: u64}) => { + Ok(UserAccessRequest::GetAccess(permission_id)) + }, + + (GET) (/{user_id:u64}/{access_id: u64}) => { + Ok(UserAccessRequest::CheckAccess(user_id, access_id)) + }, + + (POST) (/) => { + let request_body = request.data().ok_or(WebdevError::new(WebdevErrorKind::Format))?; + let new_user_access: NewUserAccess = serde_json::from_reader(request_body)?; + + Ok(UserAccessRequest::CreateAccess(new_user_access)) + }, + + (POST) (/{id: u64}) => { + let request_body = request.data().ok_or(WebdevError::new(WebdevErrorKind::Format))?; + let update_user_access: PartialUserAccess = serde_json::from_reader(request_body)?; + + Ok(UserAccessRequest::UpdateAccess(id, update_user_access)) + }, + + (DELETE) (/{id: u64}) => { + Ok(UserAccessRequest::DeleteAccess(id)) + }, + + _ => { + warn!("Could not create a user access request for the given rouille request"); + Err(WebdevError::new(WebdevErrorKind::NotFound)) + } + ) //end router + } +} + +pub enum UserAccessResponse { + AccessState(bool), + ManyUserAccess(JoinedUserAccessList), + OneUserAccess(UserAccess), + NoResponse, +} + +impl UserAccessResponse { + pub fn to_rouille(self) -> rouille::Response { + match self { + UserAccessResponse::AccessState(state) => { + rouille::Response::text(if state { "true" } else { "false" }) + } + UserAccessResponse::ManyUserAccess(user_accesses) => { + rouille::Response::json(&user_accesses) + } + UserAccessResponse::OneUserAccess(user_access) => { + rouille::Response::json(&user_access) + } + UserAccessResponse::NoResponse => rouille::Response::empty_204(), + } + } +} + +#[derive(Queryable, Serialize, Deserialize)] +pub struct JoinedUserAccess { + pub permission_id: u64, + pub user_id: u64, + pub access_id: u64, + pub first_name: String, + pub last_name: String, + pub banner_id: u32, +} + +#[derive(Serialize, Deserialize)] +pub struct JoinedUserAccessList { + pub entries: Vec, +} diff --git a/backend/src/access/requests.rs b/backend/src/access/requests.rs new file mode 100644 index 0000000..3d54eae --- /dev/null +++ b/backend/src/access/requests.rs @@ -0,0 +1,310 @@ +use diesel; +use diesel::mysql::types::Unsigned; +use diesel::mysql::Mysql; +use diesel::mysql::MysqlConnection; +use diesel::query_builder::AsQuery; +use diesel::query_builder::BoxedSelectStatement; +use diesel::types; +use diesel::ExpressionMethods; +use diesel::NullableExpressionMethods; +use diesel::QueryDsl; +use diesel::RunQueryDsl; +use diesel::TextExpressionMethods; + +use crate::errors::{WebdevError, WebdevErrorKind}; + +use crate::search::{NullableSearch, Search}; + +use super::models::{ + Access, AccessRequest, AccessResponse, JoinedUserAccess, + JoinedUserAccessList, NewAccess, NewUserAccess, PartialAccess, + PartialUserAccess, SearchUserAccess, UserAccess, UserAccessRequest, + UserAccessResponse, +}; + +use crate::users::models::{User, UserList}; + +use super::schema::access as access_schema; +use super::schema::user_access as user_access_schema; +use crate::users::schema::users as users_schema; + +pub fn handle_access( + request: AccessRequest, + database_connection: &MysqlConnection, +) -> Result { + match request { + AccessRequest::GetAccess(id) => get_access(id, database_connection) + .map(|a| AccessResponse::OneAccess(a)), + AccessRequest::CreateAccess(access) => { + create_access(access, database_connection) + .map(|a| AccessResponse::OneAccess(a)) + } + AccessRequest::UpdateAccess(id, access) => { + update_access(id, access, database_connection) + .map(|_| AccessResponse::NoResponse) + } + AccessRequest::DeleteAccess(id) => { + delete_access(id, database_connection) + .map(|_| AccessResponse::NoResponse) + } + } +} + +fn get_access( + id: u64, + database_connection: &MysqlConnection, +) -> Result { + let mut found_access = access_schema::table + .filter(access_schema::id.eq(id)) + .load::(database_connection)?; + + match found_access.pop() { + Some(access) => Ok(access), + None => Err(WebdevError::new(WebdevErrorKind::NotFound)), + } +} + +fn create_access( + access: NewAccess, + database_connection: &MysqlConnection, +) -> Result { + diesel::insert_into(access_schema::table) + .values(access) + .execute(database_connection)?; + + no_arg_sql_function!(last_insert_id, Unsigned); + + let mut inserted_accesses = access_schema::table + .filter(access_schema::id.eq(last_insert_id)) + //.filter(diesel::dsl::sql("id = LAST_INSERT_ID()")) + .load::(database_connection)?; + + if let Some(inserted_access) = inserted_accesses.pop() { + Ok(inserted_access) + } else { + Err(WebdevError::new(WebdevErrorKind::Database)) + } +} +fn update_access( + id: u64, + access: PartialAccess, + database_connection: &MysqlConnection, +) -> Result<(), WebdevError> { + diesel::update(access_schema::table) + .filter(access_schema::id.eq(id)) + .set(&access) + .execute(database_connection)?; + Ok(()) +} + +fn delete_access( + id: u64, + database_connection: &MysqlConnection, +) -> Result<(), WebdevError> { + diesel::delete(access_schema::table.filter(access_schema::id.eq(id))) + .execute(database_connection)?; + + Ok(()) +} + +pub fn handle_user_access( + request: UserAccessRequest, + database_connection: &MysqlConnection, +) -> Result { + match request { + UserAccessRequest::SearchAccess(user_access) => { + search_user_access(user_access, database_connection) + .map(|u| UserAccessResponse::ManyUserAccess(u)) + } + UserAccessRequest::GetAccess(permission_id) => { + get_user_access(permission_id, database_connection) + .map(|a| UserAccessResponse::OneUserAccess(a)) + } + UserAccessRequest::CheckAccess(user_id, access_id) => { + check_user_access(user_id, access_id, database_connection) + .map(|s| UserAccessResponse::AccessState(s)) + } + UserAccessRequest::CreateAccess(user_access) => { + create_user_access(user_access, database_connection) + .map(|a| UserAccessResponse::OneUserAccess(a)) + } + UserAccessRequest::UpdateAccess(id, user_access) => { + update_user_access(id, user_access, database_connection) + .map(|_| UserAccessResponse::NoResponse) + } + UserAccessRequest::DeleteAccess(id) => { + delete_user_access(id, database_connection) + .map(|_| UserAccessResponse::NoResponse) + } + } +} + +fn search_user_access( + user_access_search: SearchUserAccess, + database_connection: &MysqlConnection, +) -> Result { + let mut user_access_query = user_access_schema::table + .inner_join(access_schema::table) + .inner_join(users_schema::table) + .select(( + user_access_schema::permission_id, + users_schema::id, + access_schema::id, + users_schema::first_name, + users_schema::last_name, + users_schema::banner_id, + )) + .into_boxed::(); + + match user_access_search.access_id { + Search::Partial(s) => { + user_access_query = + user_access_query.filter(user_access_schema::access_id.eq(s)) + } + + Search::Exact(s) => { + user_access_query = + user_access_query.filter(user_access_schema::access_id.eq(s)) + } + + Search::NoSearch => {} + } + + match user_access_search.user_id { + Search::Partial(s) => { + user_access_query = + user_access_query.filter(user_access_schema::user_id.eq(s)) + } + + Search::Exact(s) => { + user_access_query = + user_access_query.filter(user_access_schema::user_id.eq(s)) + } + + Search::NoSearch => {} + } + + match user_access_search.permission_level { + NullableSearch::Partial(s) => { + user_access_query = user_access_query.filter( + user_access_schema::permission_level.like(format!("{}%", s)), + ) + } + + NullableSearch::Exact(s) => { + user_access_query = user_access_query + .filter(user_access_schema::permission_level.eq(s)) + } + + NullableSearch::Some => { + user_access_query = user_access_query + .filter(user_access_schema::permission_level.is_not_null()); + } + + NullableSearch::None => { + user_access_query = user_access_query + .filter(user_access_schema::permission_level.is_null()); + } + + NullableSearch::NoSearch => {} + } + + let found_access_entries = + user_access_query.load::(database_connection)?; + let joined_list = JoinedUserAccessList { + entries: found_access_entries, + }; + + Ok(joined_list) +} + +fn get_user_access( + permission_id: u64, + database_connection: &MysqlConnection, +) -> Result { + let mut found_user_accesses = user_access_schema::table + .filter(user_access_schema::permission_id.eq(permission_id)) + .load::(database_connection)?; + + match found_user_accesses.pop() { + Some(found_user_access) => Ok(found_user_access), + None => Err(WebdevError::new(WebdevErrorKind::NotFound)), + } +} + +fn check_user_access( + user_id: u64, + access_id: u64, + database_connection: &MysqlConnection, +) -> Result { + let found_user_accesses = user_access_schema::table + .filter(user_access_schema::user_id.eq(user_id)) + .filter(user_access_schema::access_id.eq(access_id)) + .execute(database_connection)?; + + if found_user_accesses != 0 { + Ok(true) + } else { + Ok(false) + } +} + +fn create_user_access( + user_access: NewUserAccess, + database_connection: &MysqlConnection, +) -> Result { + //find if permission currently exists, should not duplicate (user_id, access_id) pairs + let found_user_accesses = user_access_schema::table + .filter(user_access_schema::user_id.eq(user_access.user_id)) + .filter(user_access_schema::access_id.eq(user_access.access_id)) + .execute(database_connection)?; + + if found_user_accesses != 0 { + return Err(WebdevError::new(WebdevErrorKind::Database)); + } + + //permission most definitely does not exist at this point + + diesel::insert_into(user_access_schema::table) + .values(user_access) + .execute(database_connection)?; + + no_arg_sql_function!(last_insert_id, Unsigned); + + let mut inserted_accesses = user_access_schema::table + .filter(user_access_schema::permission_id.eq(last_insert_id)) + //.filter(diesel::dsl::sql("permission_id = LAST_INSERT_ID()")) + .load::(database_connection)?; + + if let Some(inserted_access) = inserted_accesses.pop() { + Ok(inserted_access) + } else { + Err(WebdevError::new(WebdevErrorKind::Database)) + } +} + +fn update_user_access( + id: u64, + user_access: PartialUserAccess, + database_connection: &MysqlConnection, +) -> Result<(), WebdevError> { + diesel::update(user_access_schema::table) + .filter(user_access_schema::permission_id.eq(id)) + .set(&user_access) + .execute(database_connection)?; + + Ok(()) +} + +fn delete_user_access( + id: u64, + database_connection: &MysqlConnection, +) -> Result<(), WebdevError> { + diesel::delete( + user_access_schema::table + .filter(user_access_schema::permission_id.eq(id)), + ) + .execute(database_connection)?; + + Ok(()) +} diff --git a/backend/src/access/schema.rs b/backend/src/access/schema.rs new file mode 100644 index 0000000..57bf738 --- /dev/null +++ b/backend/src/access/schema.rs @@ -0,0 +1,22 @@ +use crate::users::schema::users; + +table! { + access (id) { + id -> Unsigned, + access_name -> Varchar, + } +} + +table! { + user_access (permission_id) { + permission_id -> Unsigned, + access_id -> Unsigned, + user_id -> Unsigned, + permission_level -> Nullable, + } +} + +joinable!(user_access -> access (access_id)); +joinable!(user_access -> users (user_id)); + +allow_tables_to_appear_in_same_query!(access, user_access, users,); diff --git a/backend/src/bin/csv_user_import.rs b/backend/src/bin/csv_user_import.rs index 92bfaf5..4c5c2cc 100644 --- a/backend/src/bin/csv_user_import.rs +++ b/backend/src/bin/csv_user_import.rs @@ -1,104 +1,108 @@ -use log::debug; -use log::error; -use log::info; -use log::trace; -use log::warn; -use diesel::prelude::*; -use diesel::MysqlConnection; -use dotenv::dotenv; -use csv; -use web_dev::users::models::{NewUser,UserRequest}; -use web_dev::users::requests; -use serde::Deserialize; -use serde::Serialize; - -#[derive(Serialize, Deserialize, Debug)] -//Struct to take the data from the csv -struct Csv_User { - #[serde(rename = "Banner ID")] - banner_id: i32, - #[serde(rename = "Last Name")] - last_name: String, - #[serde(rename = "First Name")] - first_name: String, - #[serde(rename = "Email")] - email: String, - #[serde(rename = "Year")] - year: String, - #[serde(rename = "Department")] - department: String, -} -fn main(){ - //Diesel things - dotenv().ok(); - - simplelog::TermLogger::init(simplelog::LevelFilter::Trace, simplelog::Config::default()) - .unwrap(); - - info!("Connecting to database"); - - let database_url = match env::var("DATABASE_URL") { - Ok(url) => url, - Err(e) => { - error!("Could not read DATABASE_URL environment variable"); - return; - } - }; - - debug!("Connecting to {}", database_url); - - let connection = match MysqlConnection::establish(&database_url) { - Ok(c) => c, - Err(e) => { - error!("Could not connect to database: {}", e); - return; - } - }; - - debug!("Connected to database"); - //Get file name and path from args - use std::env; - let arg = env::args().nth(1); - let filename = match arg { - Some(name) => name, - None => { - error!("Needs a filename"); - return; - } - }; - debug!("{}", filename); - //Import the csv into an iterator - let mut user_count = 0; - let all_users_result = csv::Reader::from_path(filename); - let mut all_users = match all_users_result{ - Ok(data) => data, - Err(e) => { - error!("Bad file. Error {}",e); - return; - } - }; - //Go through each item in the iterator - for result in all_users.deserialize(){ - //Check to see if it's valid - let csv_user: Csv_User = match result{ - Ok(data) => data, - Err(e) => { - error!("Bad data, {:?}", e); - return; - } - }; - //Convert the user data from the csv and create a New User from it - let new_user:NewUser = NewUser{ - first_name: csv_user.first_name, - last_name: csv_user.last_name, - email: Some(csv_user.email), - banner_id: csv_user.banner_id as u32, - }; - //Import new user into database - let import_user = UserRequest::CreateUser(new_user); - requests::handle_user(import_user, &connection); - user_count = user_count+1; - } - info!("Imported {} user(s)",user_count); -} - + +use csv; +use diesel::prelude::*; +use diesel::MysqlConnection; +use dotenv::dotenv; +use log::debug; +use log::error; +use log::info; +use log::trace; +use log::warn; +use serde::Deserialize; +use serde::Serialize; +use web_dev::users::models::{NewUser, UserRequest}; +use web_dev::users::requests; + +#[derive(Serialize, Deserialize, Debug)] +//Struct to take the data from the csv +struct Csv_User { + #[serde(rename = "Banner ID")] + banner_id: i32, + #[serde(rename = "Last Name")] + last_name: String, + #[serde(rename = "First Name")] + first_name: String, + #[serde(rename = "Email")] + email: String, + #[serde(rename = "Year")] + year: String, + #[serde(rename = "Department")] + department: String, +} +fn main() { + //Diesel things + dotenv().ok(); + + simplelog::TermLogger::init( + simplelog::LevelFilter::Trace, + simplelog::Config::default(), + ) + .unwrap(); + + info!("Connecting to database"); + + let database_url = match env::var("DATABASE_URL") { + Ok(url) => url, + Err(e) => { + error!("Could not read DATABASE_URL environment variable"); + return; + } + }; + + debug!("Connecting to {}", database_url); + + let connection = match MysqlConnection::establish(&database_url) { + Ok(c) => c, + Err(e) => { + error!("Could not connect to database: {}", e); + return; + } + }; + + debug!("Connected to database"); + //Get file name and path from args + use std::env; + let arg = env::args().nth(1); + let filename = match arg { + Some(name) => name, + None => { + error!("Needs a filename"); + return; + } + }; + debug!("{}", filename); + //Import the csv into an iterator + let mut user_count = 0; + let all_users_result = csv::Reader::from_path(filename); + let mut all_users = match all_users_result { + Ok(data) => data, + Err(e) => { + error!("Bad file. Error {}", e); + return; + } + }; + //Go through each item in the iterator + for result in all_users.deserialize() { + //Check to see if it's valid + let csv_user: Csv_User = match result { + Ok(data) => data, + Err(e) => { + error!("Bad data, {:?}", e); + return; + } + }; + //Convert the user data from the csv and create a New User from it + let new_user: NewUser = NewUser { + first_name: csv_user.first_name, + last_name: csv_user.last_name, + email: Some(csv_user.email), + banner_id: csv_user.banner_id as u32, + }; + //Import new user into database + let import_user = UserRequest::CreateUser(new_user); + requests::handle_user(import_user, &connection); + user_count = user_count + 1; + } + info!("Imported {} user(s)", user_count); +} + diff --git a/backend/src/chemicals.rs b/backend/src/chemicals.rs new file mode 100644 index 0000000..849c906 --- /dev/null +++ b/backend/src/chemicals.rs @@ -0,0 +1,3 @@ +pub mod models; +pub mod requests; +pub mod schema; diff --git a/backend/src/chemicals/models.rs b/backend/src/chemicals/models.rs new file mode 100644 index 0000000..0a67756 --- /dev/null +++ b/backend/src/chemicals/models.rs @@ -0,0 +1,294 @@ +use diesel::Queryable; + +use rouille::router; + +use serde::Deserialize; +use serde::Serialize; + +use url::form_urlencoded; + +use log::{trace, warn}; + +use crate::errors::{WebdevError, WebdevErrorKind}; + +use crate::search::{NullableSearch, Search}; + +use super::schema::{chemical, chemical_inventory}; + +#[derive(Queryable, Serialize, Deserialize)] +pub struct Chemical { + pub id: u64, + pub name: String, + pub purpose: String, + pub company_name: String, + pub ingredients: String, + pub manual_link: String, +} + +#[derive(Insertable, Serialize, Deserialize)] +#[table_name = "chemical"] +pub struct NewChemical { + pub name: String, + pub purpose: String, + pub company_name: String, + pub ingredients: String, + pub manual_link: String, +} + +#[derive(AsChangeset, Serialize, Deserialize)] +#[table_name = "chemical"] +pub struct PartialChemical { + pub name: Option, + pub purpose: Option, + pub company_name: Option, + pub ingredients: Option, + pub manual_link: Option, +} + +pub struct SearchChemical { + pub name: Search, + pub purpose: Search, + pub company_name: Search, + pub ingredients: Search, + pub manual_link: Search, +} + +#[derive(Serialize, Deserialize)] +pub struct ChemicalList { + pub chemicals: Vec, +} + +pub enum ChemicalRequest { + Search(SearchChemical), + GetChemical(u64), //id of access name searched + CreateChemical(NewChemical), //new access type of some name to be created + UpdateChemical(u64, PartialChemical), //Contains id to be changed to new access_name + DeleteChemical(u64), //if of access to be deleted +} + +impl ChemicalRequest { + pub fn from_rouille( + request: &rouille::Request, + ) -> Result { + trace!("Creating ChemicalRequest from {:#?}", request); + + let url_queries = + form_urlencoded::parse(request.raw_query_string().as_bytes()); + + router!(request, + (GET) (/) => { + let mut name_search = Search::NoSearch; + let mut purpose_search = Search::NoSearch; + let mut company_name_search = Search::NoSearch; + let mut ingredients_search = Search::NoSearch; + let mut manual_link_search = Search::NoSearch; + + for (field, query) in url_queries { + match field.as_ref() as &str { + "name" => name_search = Search::from_query(query.as_ref())?, + "purpose" => purpose_search = Search::from_query(query.as_ref())?, + "company_name" => company_name_search = Search::from_query(query.as_ref())?, + "ingredients" => ingredients_search = Search::from_query(query.as_ref())?, + "manual_link" => manual_link_search = Search::from_query(query.as_ref())?, + _ => return Err(WebdevError::new(WebdevErrorKind::Format)), + } + } + + Ok(ChemicalRequest::Search(SearchChemical { + name: name_search, + purpose: purpose_search, + company_name: company_name_search, + ingredients: ingredients_search, + manual_link: manual_link_search, + })) + }, + + (GET) (/{id: u64}) => { + Ok(ChemicalRequest::GetChemical(id)) + }, + + (POST) (/) => { + let request_body = request.data().ok_or(WebdevError::new(WebdevErrorKind::Format))?; + let new_chemical: NewChemical = serde_json::from_reader(request_body)?; + + Ok(ChemicalRequest::CreateChemical(new_chemical)) + }, + + (POST) (/{id: u64}) => { + let request_body = request.data().ok_or(WebdevError::new(WebdevErrorKind::Format))?; + let update_chemical: PartialChemical = serde_json::from_reader(request_body)?; + + Ok(ChemicalRequest::UpdateChemical(id, update_chemical)) + }, + + (DELETE) (/{id: u64}) => { + Ok(ChemicalRequest::DeleteChemical(id)) + }, + + _ => { + warn!("Could not create a chemical request for the given rouille request"); + Err(WebdevError::new(WebdevErrorKind::NotFound)) + } + ) //end router + } +} + +pub enum ChemicalResponse { + OneChemical(Chemical), + ManyChemical(ChemicalList), + NoResponse, +} + +impl ChemicalResponse { + pub fn to_rouille(self) -> rouille::Response { + match self { + ChemicalResponse::OneChemical(chemical) => { + rouille::Response::json(&chemical) + } + ChemicalResponse::ManyChemical(chemicals) => { + rouille::Response::json(&chemicals.chemicals) + } + ChemicalResponse::NoResponse => rouille::Response::empty_204(), + } + } +} + +#[derive(Queryable, Serialize, Deserialize)] +pub struct ChemicalInventory { + pub id: u64, + pub purchaser_id: u64, + pub custodian_id: u64, + pub chemical_id: u64, + pub storage_location: String, + pub amount: String, +} + +#[derive(Insertable, Serialize, Deserialize)] +#[table_name = "chemical_inventory"] +pub struct NewChemicalInventory { + pub purchaser_id: u64, + pub custodian_id: u64, + pub chemical_id: u64, + pub storage_location: String, + pub amount: String, +} + +#[derive(AsChangeset, Serialize, Deserialize)] +#[table_name = "chemical_inventory"] +pub struct PartialChemicalInventory { + pub purchaser_id: Option, + pub custodian_id: Option, + pub chemical_id: Option, + pub storage_location: Option, + pub amount: Option, +} + +pub struct SearchChemicalInventory { + pub purchaser_id: Search, + pub custodian_id: Search, + pub chemical_id: Search, + pub storage_location: Search, + pub amount: Search, +} + +#[derive(Serialize, Deserialize)] +pub struct ChemicalInventoryList { + pub entries: Vec, +} + +pub enum ChemicalInventoryRequest { + SearchInventory(SearchChemicalInventory), + GetInventory(u64), + CreateInventory(NewChemicalInventory), + UpdateInventory(u64, PartialChemicalInventory), + DeleteInventory(u64), +} + +impl ChemicalInventoryRequest { + pub fn from_rouille( + request: &rouille::Request, + ) -> Result { + trace!("Creating ChemicalInvntoryRequest from {:#?}", request); + + let url_queries = + form_urlencoded::parse(request.raw_query_string().as_bytes()); + + router!(request, + (GET) (/) => { + let mut purchaser_id_search = Search::NoSearch; + let mut custodian_id_search = Search::NoSearch; + let mut chemical_id_search = Search::NoSearch; + let mut storage_location_search = Search::NoSearch; + let mut amount_search = Search::NoSearch; + + for (field, query) in url_queries { + match field.as_ref() as &str { + "purchaser_id" => purchaser_id_search = Search::from_query(query.as_ref())?, + "custodian_id" => custodian_id_search = Search::from_query(query.as_ref())?, + "chemical_id" => chemical_id_search = Search::from_query(query.as_ref())?, + "storage_location" => storage_location_search = Search::from_query(query.as_ref())?, + "amount" => amount_search = Search::from_query(query.as_ref())?, + _ => return Err(WebdevError::new(WebdevErrorKind::Format)), + } + } + + Ok(ChemicalInventoryRequest::SearchInventory(SearchChemicalInventory { + purchaser_id: purchaser_id_search, + custodian_id: custodian_id_search, + chemical_id: chemical_id_search, + storage_location: storage_location_search, + amount: amount_search, + })) + }, + + (GET) (/{permission_id: u64}) => { + Ok(ChemicalInventoryRequest::GetInventory(permission_id)) + }, + + (POST) (/) => { + let request_body = request.data().ok_or(WebdevError::new(WebdevErrorKind::Format))?; + let new_chemical_inventory: NewChemicalInventory = serde_json::from_reader(request_body)?; + + Ok(ChemicalInventoryRequest::CreateInventory(new_chemical_inventory)) + }, + + (POST) (/{id: u64}) => { + let request_body = request.data().ok_or(WebdevError::new(WebdevErrorKind::Format))?; + let update_chemical_inventory: PartialChemicalInventory = serde_json::from_reader(request_body)?; + + Ok(ChemicalInventoryRequest::UpdateInventory(id, update_chemical_inventory)) + }, + + (DELETE) (/{id: u64}) => { + Ok(ChemicalInventoryRequest::DeleteInventory(id)) + }, + + _ => { + warn!("Could not create a chemical inventory request for the given rouille request"); + Err(WebdevError::new(WebdevErrorKind::NotFound)) + } + ) //end router + } +} + +pub enum ChemicalInventoryResponse { + OneInventoryEntry(ChemicalInventory), + ManyInventoryEntries(ChemicalInventoryList), + NoResponse, +} + +impl ChemicalInventoryResponse { + pub fn to_rouille(self) -> rouille::Response { + match self { + ChemicalInventoryResponse::OneInventoryEntry(entry) => { + rouille::Response::json(&entry) + } + ChemicalInventoryResponse::ManyInventoryEntries(entries) => { + rouille::Response::json(&entries.entries) + } + ChemicalInventoryResponse::NoResponse => { + rouille::Response::empty_204() + } + } + } +} diff --git a/backend/src/chemicals/requests.rs b/backend/src/chemicals/requests.rs new file mode 100644 index 0000000..bd3d331 --- /dev/null +++ b/backend/src/chemicals/requests.rs @@ -0,0 +1,373 @@ +use diesel; +use diesel::mysql::types::Unsigned; +use diesel::mysql::Mysql; +use diesel::mysql::MysqlConnection; +use diesel::query_builder::AsQuery; +use diesel::query_builder::BoxedSelectStatement; +use diesel::types; +use diesel::ExpressionMethods; +use diesel::NullableExpressionMethods; +use diesel::QueryDsl; +use diesel::RunQueryDsl; +use diesel::TextExpressionMethods; + +use crate::errors::{WebdevError, WebdevErrorKind}; + +use crate::search::Search; + +use super::models::{ + Chemical, ChemicalInventory, ChemicalInventoryList, + ChemicalInventoryRequest, ChemicalInventoryResponse, ChemicalList, + ChemicalRequest, ChemicalResponse, NewChemical, NewChemicalInventory, + PartialChemical, PartialChemicalInventory, SearchChemical, + SearchChemicalInventory, +}; + +use super::schema::chemical as chemical_schema; +use super::schema::chemical_inventory as chemical_inventory_schema; + +pub fn handle_chemical( + request: ChemicalRequest, + database_connection: &MysqlConnection, +) -> Result { + match request { + ChemicalRequest::Search(chemical) => { + search_chemical(chemical, database_connection) + .map(|c| ChemicalResponse::ManyChemical(c)) + } + ChemicalRequest::GetChemical(id) => { + get_chemical(id, database_connection) + .map(|c| ChemicalResponse::OneChemical(c)) + } + ChemicalRequest::CreateChemical(chemical) => { + create_chemical(chemical, database_connection) + .map(|c| ChemicalResponse::OneChemical(c)) + } + ChemicalRequest::UpdateChemical(id, chemical) => { + update_chemical(id, chemical, database_connection) + .map(|_| ChemicalResponse::NoResponse) + } + ChemicalRequest::DeleteChemical(id) => { + delete_chemical(id, database_connection) + .map(|_| ChemicalResponse::NoResponse) + } + } +} + +fn search_chemical( + chemical_search: SearchChemical, + database_connection: &MysqlConnection, +) -> Result { + let mut chemical_query = chemical_schema::table.as_query().into_boxed(); + + match chemical_search.name { + Search::Partial(s) => { + chemical_query = chemical_query + .filter(chemical_schema::name.like(format!("{}%", s))) + } + + Search::Exact(s) => { + chemical_query = chemical_query.filter(chemical_schema::name.eq(s)) + } + + Search::NoSearch => {} + } + + match chemical_search.purpose { + Search::Partial(s) => { + chemical_query = chemical_query + .filter(chemical_schema::purpose.like(format!("{}%", s))) + } + + Search::Exact(s) => { + chemical_query = + chemical_query.filter(chemical_schema::purpose.eq(s)) + } + + Search::NoSearch => {} + } + + match chemical_search.company_name { + Search::Partial(s) => { + chemical_query = chemical_query + .filter(chemical_schema::company_name.like(format!("{}%", s))) + } + + Search::Exact(s) => { + chemical_query = + chemical_query.filter(chemical_schema::company_name.eq(s)) + } + + Search::NoSearch => {} + } + + match chemical_search.ingredients { + Search::Partial(s) => { + chemical_query = chemical_query + .filter(chemical_schema::ingredients.like(format!("{}%", s))) + } + + Search::Exact(s) => { + chemical_query = + chemical_query.filter(chemical_schema::ingredients.eq(s)) + } + + Search::NoSearch => {} + } + + match chemical_search.manual_link { + Search::Partial(s) => { + chemical_query = chemical_query + .filter(chemical_schema::manual_link.like(format!("{}%", s))) + } + + Search::Exact(s) => { + chemical_query = + chemical_query.filter(chemical_schema::manual_link.eq(s)) + } + + Search::NoSearch => {} + } + + let found_chemicals = + chemical_query.load::(database_connection)?; + let chemical_list = ChemicalList { + chemicals: found_chemicals, + }; + + Ok(chemical_list) +} + +fn get_chemical( + id: u64, + database_connection: &MysqlConnection, +) -> Result { + let mut found_chemical = chemical_schema::table + .filter(chemical_schema::id.eq(id)) + .load::(database_connection)?; + + match found_chemical.pop() { + Some(chemical) => Ok(chemical), + None => Err(WebdevError::new(WebdevErrorKind::NotFound)), + } +} + +fn create_chemical( + chemical: NewChemical, + database_connection: &MysqlConnection, +) -> Result { + diesel::insert_into(chemical_schema::table) + .values(chemical) + .execute(database_connection)?; + + no_arg_sql_function!(last_insert_id, Unsigned); + + let mut inserted_chemicals = chemical_schema::table + .filter(chemical_schema::id.eq(last_insert_id)) + .load::(database_connection)?; + + if let Some(inserted_chemical) = inserted_chemicals.pop() { + Ok(inserted_chemical) + } else { + Err(WebdevError::new(WebdevErrorKind::Database)) + } +} + +fn update_chemical( + id: u64, + chemical: PartialChemical, + database_connection: &MysqlConnection, +) -> Result<(), WebdevError> { + diesel::update(chemical_schema::table) + .filter(chemical_schema::id.eq(id)) + .set(&chemical) + .execute(database_connection)?; + Ok(()) +} + +fn delete_chemical( + id: u64, + database_connection: &MysqlConnection, +) -> Result<(), WebdevError> { + diesel::delete(chemical_schema::table.filter(chemical_schema::id.eq(id))) + .execute(database_connection)?; + + Ok(()) +} + +pub fn handle_chemical_inventory( + request: ChemicalInventoryRequest, + database_connection: &MysqlConnection, +) -> Result { + match request { + ChemicalInventoryRequest::SearchInventory(inventory) => { + search_chemical_inventory(inventory, database_connection) + .map(|c| ChemicalInventoryResponse::ManyInventoryEntries(c)) + } + ChemicalInventoryRequest::GetInventory(id) => { + get_chemical_inventory(id, database_connection) + .map(|c| ChemicalInventoryResponse::OneInventoryEntry(c)) + } + ChemicalInventoryRequest::CreateInventory(inventory) => { + create_chemical_inventory(inventory, database_connection) + .map(|c| ChemicalInventoryResponse::OneInventoryEntry(c)) + } + ChemicalInventoryRequest::UpdateInventory(id, inventory) => { + update_chemical_inventory(id, inventory, database_connection) + .map(|_| ChemicalInventoryResponse::NoResponse) + } + ChemicalInventoryRequest::DeleteInventory(id) => { + delete_chemical_inventory(id, database_connection) + .map(|_| ChemicalInventoryResponse::NoResponse) + } + } +} + +fn search_chemical_inventory( + chemical_inventory_search: SearchChemicalInventory, + database_connection: &MysqlConnection, +) -> Result { + let mut chemical_inventory_query = + chemical_inventory_schema::table.as_query().into_boxed(); + + match chemical_inventory_search.purchaser_id { + Search::Partial(s) => { + chemical_inventory_query = chemical_inventory_query + .filter(chemical_inventory_schema::purchaser_id.eq(s)) + } + + Search::Exact(s) => { + chemical_inventory_query = chemical_inventory_query + .filter(chemical_inventory_schema::purchaser_id.eq(s)) + } + + Search::NoSearch => {} + } + + match chemical_inventory_search.custodian_id { + Search::Partial(s) => { + chemical_inventory_query = chemical_inventory_query + .filter(chemical_inventory_schema::custodian_id.eq(s)) + } + + Search::Exact(s) => { + chemical_inventory_query = chemical_inventory_query + .filter(chemical_inventory_schema::custodian_id.eq(s)) + } + + Search::NoSearch => {} + } + + match chemical_inventory_search.chemical_id { + Search::Partial(s) => { + chemical_inventory_query = chemical_inventory_query + .filter(chemical_inventory_schema::chemical_id.eq(s)) + } + + Search::Exact(s) => { + chemical_inventory_query = chemical_inventory_query + .filter(chemical_inventory_schema::chemical_id.eq(s)) + } + + Search::NoSearch => {} + } + + match chemical_inventory_search.storage_location { + Search::Partial(s) => { + chemical_inventory_query = chemical_inventory_query.filter( + chemical_inventory_schema::storage_location + .like(format!("{}%", s)), + ) + } + + Search::Exact(s) => { + chemical_inventory_query = chemical_inventory_query + .filter(chemical_inventory_schema::storage_location.eq(s)) + } + + Search::NoSearch => {} + } + + match chemical_inventory_search.amount { + Search::Partial(s) => { + chemical_inventory_query = chemical_inventory_query.filter( + chemical_inventory_schema::amount.like(format!("{}%", s)), + ) + } + + Search::Exact(s) => { + chemical_inventory_query = chemical_inventory_query + .filter(chemical_inventory_schema::amount.eq(s)) + } + + Search::NoSearch => {} + } + + let found_entries = chemical_inventory_query + .load::(database_connection)?; + let inventory_list = ChemicalInventoryList { + entries: found_entries, + }; + + Ok(inventory_list) +} + +fn get_chemical_inventory( + id: u64, + database_connection: &MysqlConnection, +) -> Result { + let mut found_inventory = chemical_inventory_schema::table + .filter(chemical_inventory_schema::id.eq(id)) + .load::(database_connection)?; + + match found_inventory.pop() { + Some(entry) => Ok(entry), + None => Err(WebdevError::new(WebdevErrorKind::NotFound)), + } +} + +fn create_chemical_inventory( + inventory: NewChemicalInventory, + database_connection: &MysqlConnection, +) -> Result { + diesel::insert_into(chemical_inventory_schema::table) + .values(inventory) + .execute(database_connection)?; + + no_arg_sql_function!(last_insert_id, Unsigned); + + let mut inserted_inventory_entries = chemical_inventory_schema::table + .filter(chemical_inventory_schema::id.eq(last_insert_id)) + .load::(database_connection)?; + + if let Some(inserted_entry) = inserted_inventory_entries.pop() { + Ok(inserted_entry) + } else { + Err(WebdevError::new(WebdevErrorKind::Database)) + } +} + +fn update_chemical_inventory( + id: u64, + inventory: PartialChemicalInventory, + database_connection: &MysqlConnection, +) -> Result<(), WebdevError> { + diesel::update(chemical_inventory_schema::table) + .filter(chemical_inventory_schema::id.eq(id)) + .set(&inventory) + .execute(database_connection)?; + Ok(()) +} + +fn delete_chemical_inventory( + id: u64, + database_connection: &MysqlConnection, +) -> Result<(), WebdevError> { + diesel::delete( + chemical_inventory_schema::table + .filter(chemical_inventory_schema::id.eq(id)), + ) + .execute(database_connection)?; + + Ok(()) +} diff --git a/backend/src/chemicals/schema.rs b/backend/src/chemicals/schema.rs new file mode 100644 index 0000000..fce54a4 --- /dev/null +++ b/backend/src/chemicals/schema.rs @@ -0,0 +1,30 @@ +use crate::users::schema::users; + +table! { + chemical (id) { + id -> Unsigned, + name -> Varchar, + purpose -> Varchar, + company_name -> Varchar, + ingredients -> Varchar, + manual_link -> Varchar, + } +} + +table! { + chemical_inventory (id) { + id -> Unsigned, + purchaser_id -> Unsigned, + custodian_id -> Unsigned, + chemical_id -> Unsigned, + storage_location -> Varchar, + amount -> Varchar, + } +} + +//Cant seem to do this because of multiple points to users, need explicit on clause in queries +//joinable!(chemical_inventory -> users (purchaser_id)); +//joinable!(chemical_inventory -> users (custodian_id)); +joinable!(chemical_inventory -> chemical (chemical_id)); + +allow_tables_to_appear_in_same_query!(chemical, chemical_inventory, users,); diff --git a/backend/src/errors.rs b/backend/src/errors.rs index 2968a12..d3a516a 100644 --- a/backend/src/errors.rs +++ b/backend/src/errors.rs @@ -40,7 +40,10 @@ impl WebdevError { WebdevError { kind, source: None } } - pub fn with_source(kind: WebdevErrorKind, source: Box) -> WebdevError { + pub fn with_source( + kind: WebdevErrorKind, + source: Box, + ) -> WebdevError { WebdevError { kind, source: Some(source), @@ -94,7 +97,9 @@ impl From for rouille::Response { WebdevErrorKind::NotFound => { rouille::Response::text(e.to_string()).with_status_code(404) } - WebdevErrorKind::Format => rouille::Response::text(e.to_string()).with_status_code(400), + WebdevErrorKind::Format => { + rouille::Response::text(e.to_string()).with_status_code(400) + } WebdevErrorKind::Database => { rouille::Response::text(e.to_string()).with_status_code(500) } diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 2fdbd10..659e56e 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,10 +1,10 @@ -#[macro_use] -extern crate diesel; - -#[macro_use] -extern crate diesel_migrations; - - -pub mod errors; -pub mod users; -pub mod search; +#[macro_use] +extern crate diesel; + +#[macro_use] +extern crate diesel_migrations; + +pub mod access; +pub mod errors; +pub mod search; +pub mod users; diff --git a/backend/src/main.rs b/backend/src/main.rs index d0ee78b..dd0375f 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,6 +1,9 @@ #[macro_use] extern crate diesel_migrations; +#[macro_use] +extern crate diesel; + use std::env; use std::sync::Mutex; use std::thread; @@ -16,19 +19,34 @@ use diesel::MysqlConnection; use dotenv::dotenv; +mod access; +mod chemicals; +mod errors; +mod search; +mod users; + use web_dev::errors::WebdevError; use web_dev::errors::WebdevErrorKind; use web_dev::users::models::UserRequest; use web_dev::users::requests::handle_user; +use access::models::{AccessRequest, UserAccessRequest}; +use access::requests::{handle_access, handle_user_access}; + +use chemicals::models::{ChemicalInventoryRequest, ChemicalRequest}; +use chemicals::requests::{handle_chemical, handle_chemical_inventory}; + embed_migrations!(); fn main() { dotenv().ok(); - simplelog::SimpleLogger::init(simplelog::LevelFilter::Trace, simplelog::Config::default()) - .unwrap(); + simplelog::SimpleLogger::init( + simplelog::LevelFilter::Trace, + simplelog::Config::default(), + ) + .unwrap(); info!("Connecting to database"); @@ -56,7 +74,9 @@ fn main() { debug!("Connected to database"); info!("Running migrations"); - embedded_migrations::run(&connection); + if let Err(e) = embedded_migrations::run(&connection) { + warn!("Could not run migrations: {}", e); + } let connection_mutex = Mutex::new(connection); @@ -77,14 +97,19 @@ fn main() { "POST, GET, DELETE, OPTIONS", ) .with_additional_header("Access-Control-Allow-Origin", "*") - .with_additional_header("Access-Control-Allow-Headers", "X-PINGOTHER, Content-Type") + .with_additional_header( + "Access-Control-Allow-Headers", + "X-PINGOTHER, Content-Type", + ) .with_additional_header("Access-Control-Max-Age", "86400") } else { let current_connection = match connection_mutex.lock() { Ok(c) => c, Err(_e) => { error!("Could not lock database"); - return rouille::Response::from(WebdevError::new(WebdevErrorKind::Database)); + return rouille::Response::from(WebdevError::new( + WebdevErrorKind::Database, + )); } }; @@ -102,11 +127,65 @@ fn handle_request( if let Some(user_request) = request.remove_prefix("/users") { match UserRequest::from_rouille(&user_request) { Err(err) => rouille::Response::from(err), - Ok(user_request) => match handle_user(user_request, database_connection) { - Ok(user_response) => user_response.to_rouille(), + Ok(user_request) => { + match handle_user(user_request, database_connection) { + Ok(user_response) => user_response.to_rouille(), + Err(err) => rouille::Response::from(err), + } + } + } + } else if let Some(access_request) = request.remove_prefix("/access") { + match AccessRequest::from_rouille(&access_request) { + Err(err) => rouille::Response::from(err), + Ok(access_request) => { + match handle_access(access_request, database_connection) { + Ok(access_response) => access_response.to_rouille(), + Err(err) => rouille::Response::from(err), + } + } + } + } else if let Some(user_access_request) = + request.remove_prefix("/user_access") + { + match UserAccessRequest::from_rouille(&user_access_request) { + Err(err) => rouille::Response::from(err), + Ok(user_access_request) => match handle_user_access( + user_access_request, + database_connection, + ) { + Ok(user_access_response) => user_access_response.to_rouille(), + Err(err) => rouille::Response::from(err), + }, + } + } else if let Some(chem_inventory_request_url) = + request.remove_prefix("/chemical_inventory") + { + match ChemicalInventoryRequest::from_rouille( + &chem_inventory_request_url, + ) { + Err(err) => rouille::Response::from(err), + Ok(chem_inventory_request) => match handle_chemical_inventory( + chem_inventory_request, + database_connection, + ) { + Ok(chem_inventory_response) => { + chem_inventory_response.to_rouille() + } Err(err) => rouille::Response::from(err), }, } + } else if let Some(chemical_request_url) = + request.remove_prefix("/chemical") + { + match ChemicalRequest::from_rouille(&chemical_request_url) { + Err(err) => rouille::Response::from(err), + Ok(chemical_request) => { + match handle_chemical(chemical_request, database_connection) { + Ok(chemical_response) => chemical_response.to_rouille(), + Err(err) => rouille::Response::from(err), + } + } + } } else { rouille::Response::empty_404() } diff --git a/backend/src/search.rs b/backend/src/search.rs index 2b2cb92..a37bf13 100644 --- a/backend/src/search.rs +++ b/backend/src/search.rs @@ -7,8 +7,12 @@ pub enum SearchParseError { impl std::fmt::Display for SearchParseError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - SearchParseError::Kind(s) => write!(f, "Invalid search kind: {}", s), - SearchParseError::Term(s) => write!(f, "Invalid search term: {}", s), + SearchParseError::Kind(s) => { + write!(f, "Invalid search kind: {}", s) + } + SearchParseError::Term(s) => { + write!(f, "Invalid search term: {}", s) + } } } } @@ -48,7 +52,9 @@ impl Search { .parse() .map(|p| Search::Exact(p)) .map_err(|_| SearchParseError::Term(s.to_owned())), - (Some("partial"), None) => Err(SearchParseError::Term("".to_owned())), + (Some("partial"), None) => { + Err(SearchParseError::Term("".to_owned())) + } (Some("exact"), None) => Err(SearchParseError::Term("".to_owned())), (Some(k), Some(_)) => Err(SearchParseError::Kind(k.to_owned())), (Some(k), None) => Err(SearchParseError::Kind(k.to_owned())), @@ -143,7 +149,9 @@ pub enum NullableSearch { } impl NullableSearch { - pub fn from_query(query: &str) -> Result, SearchParseError> { + pub fn from_query( + query: &str, + ) -> Result, SearchParseError> { let mut query_iter = query.split(','); let kind = query_iter.next().map(|s| s.trim()); @@ -164,11 +172,17 @@ impl NullableSearch { (Some("some"), None) => Ok(NullableSearch::Some), (Some("none"), None) => Ok(NullableSearch::None), - (Some("partial"), None) => Err(SearchParseError::Term("".to_owned())), + (Some("partial"), None) => { + Err(SearchParseError::Term("".to_owned())) + } (Some("exact"), None) => Err(SearchParseError::Term("".to_owned())), - (Some("some"), Some(s)) => Err(SearchParseError::Term(s.to_owned())), - (Some("none"), Some(s)) => Err(SearchParseError::Term(s.to_owned())), + (Some("some"), Some(s)) => { + Err(SearchParseError::Term(s.to_owned())) + } + (Some("none"), Some(s)) => { + Err(SearchParseError::Term(s.to_owned())) + } (Some(k), Some(_)) => Err(SearchParseError::Kind(k.to_owned())), (Some(k), None) => Err(SearchParseError::Kind(k.to_owned())), @@ -193,55 +207,64 @@ fn parse_nullable_search_exact_search_works() { #[test] fn parse_nullable_search_some_works() { - let s: Result, _> = NullableSearch::from_query(" some "); + let s: Result, _> = + NullableSearch::from_query(" some "); assert_eq!(s, Ok(NullableSearch::Some)); } #[test] fn parse_nullable_search_none_works() { - let s: Result, _> = NullableSearch::from_query(" none "); + let s: Result, _> = + NullableSearch::from_query(" none "); assert_eq!(s, Ok(NullableSearch::None)); } #[test] fn parse_nullable_search_some_with_term_fails() { - let s: Result, _> = NullableSearch::from_query(" some, hello"); + let s: Result, _> = + NullableSearch::from_query(" some, hello"); assert_eq!(s, Err(SearchParseError::Term("hello".to_owned()))); } #[test] fn parse_nullable_search_none_with_term_fails() { - let s: Result, _> = NullableSearch::from_query(" none, hello"); + let s: Result, _> = + NullableSearch::from_query(" none, hello"); assert_eq!(s, Err(SearchParseError::Term("hello".to_owned()))); } #[test] fn parse_nullable_search_invalid_kind_with_term_fails() { - let s: Result, _> = NullableSearch::from_query("hello, bye"); + let s: Result, _> = + NullableSearch::from_query("hello, bye"); assert_eq!(s, Err(SearchParseError::Kind("hello".to_owned()))); } #[test] fn parse_nullable_search_no_kind_with_term_fails() { - let s: Result, _> = NullableSearch::from_query(", bye"); + let s: Result, _> = + NullableSearch::from_query(", bye"); assert_eq!(s, Err(SearchParseError::Kind("".to_owned()))); } #[test] fn parse_nullable_search_partial_with_no_term_fails() { - let s: Result, _> = NullableSearch::from_query(" partial"); + let s: Result, _> = + NullableSearch::from_query(" partial"); assert_eq!(s, Err(SearchParseError::Term("".to_owned()))); } #[test] fn parse_nullable_search_exact_with_no_term_fails() { - let s: Result, _> = NullableSearch::from_query(" exact"); + let s: Result, _> = + NullableSearch::from_query(" exact"); assert_eq!(s, Err(SearchParseError::Term("".to_owned()))); } #[test] fn parse_nullable_search_invalid_with_no_term_fails() { - let s: Result, _> = NullableSearch::from_query("hello"); + let s: Result, _> = + NullableSearch::from_query("hello"); assert_eq!(s, Err(SearchParseError::Kind("hello".to_owned()))); } diff --git a/backend/src/users.rs b/backend/src/users.rs index 24543b5..295ee30 100644 --- a/backend/src/users.rs +++ b/backend/src/users.rs @@ -1,6 +1,6 @@ pub mod models; pub mod requests; -mod schema; +pub mod schema; use self::schema::users as users_schema; use diesel::expression::AsExpression; diff --git a/backend/src/users/models.rs b/backend/src/users/models.rs index 62bcf29..36a0970 100644 --- a/backend/src/users/models.rs +++ b/backend/src/users/models.rs @@ -66,10 +66,13 @@ pub enum UserRequest { } impl UserRequest { - pub fn from_rouille(request: &rouille::Request) -> Result { + pub fn from_rouille( + request: &rouille::Request, + ) -> Result { trace!("Creating UserRequest from {:#?}", request); - let url_queries = form_urlencoded::parse(request.raw_query_string().as_bytes()); + let url_queries = + form_urlencoded::parse(request.raw_query_string().as_bytes()); router!(request, (GET) (/) => { diff --git a/backend/src/users/requests.rs b/backend/src/users/requests.rs index 795cf15..abb6c3c 100644 --- a/backend/src/users/requests.rs +++ b/backend/src/users/requests.rs @@ -8,10 +8,10 @@ use diesel::QueryDsl; use diesel::RunQueryDsl; use diesel::TextExpressionMethods; -use log::trace; +use log::error; use log::info; +use log::trace; use log::warn; -use log::error; use crate::errors::WebdevError; use crate::errors::WebdevErrorKind; @@ -30,20 +30,20 @@ pub fn handle_user( ) -> Result { match request { UserRequest::SearchUsers(user) => { - search_users(user, database_connection).map(|u| UserResponse::ManyUsers(u)) + search_users(user, database_connection) + .map(|u| UserResponse::ManyUsers(u)) } UserRequest::GetUser(id) => { get_user(id, database_connection).map(|u| UserResponse::OneUser(u)) } - UserRequest::CreateUser(user) => { - create_user(user, database_connection).map(|u| UserResponse::OneUser(u)) - } + UserRequest::CreateUser(user) => create_user(user, database_connection) + .map(|u| UserResponse::OneUser(u)), UserRequest::UpdateUser(id, user) => { - update_user(id, user, database_connection).map(|_| UserResponse::NoResponse) - } - UserRequest::DeleteUser(id) => { - delete_user(id, database_connection).map(|_| UserResponse::NoResponse) + update_user(id, user, database_connection) + .map(|_| UserResponse::NoResponse) } + UserRequest::DeleteUser(id) => delete_user(id, database_connection) + .map(|_| UserResponse::NoResponse), } } @@ -55,7 +55,8 @@ fn search_users( match user.first_name { Search::Partial(s) => { - users_query = users_query.filter(users_schema::first_name.like(format!("{}%", s))) + users_query = users_query + .filter(users_schema::first_name.like(format!("{}%", s))) } Search::Exact(s) => { @@ -67,7 +68,8 @@ fn search_users( match user.last_name { Search::Partial(s) => { - users_query = users_query.filter(users_schema::last_name.like(format!("{}%", s))) + users_query = users_query + .filter(users_schema::last_name.like(format!("{}%", s))) } Search::Exact(s) => { @@ -93,7 +95,8 @@ fn search_users( match user.email { NullableSearch::Partial(s) => { - users_query = users_query.filter(users_schema::email.like(format!("{}%", s))) + users_query = + users_query.filter(users_schema::email.like(format!("{}%", s))) } NullableSearch::Exact(s) => { @@ -117,7 +120,10 @@ fn search_users( Ok(user_list) } -fn get_user(id: u64, database_connection: &MysqlConnection) -> Result { +fn get_user( + id: u64, + database_connection: &MysqlConnection, +) -> Result { let mut found_users = users_schema::table .filter(users_schema::id.eq(id)) .load::(database_connection)?; @@ -128,7 +134,10 @@ fn get_user(id: u64, database_connection: &MysqlConnection) -> Result Result { +fn create_user( + user: NewUser, + database_connection: &MysqlConnection, +) -> Result { diesel::insert_into(users_schema::table) .values(user) .execute(database_connection)?; @@ -156,7 +165,10 @@ fn update_user( Ok(()) } -fn delete_user(id: u64, database_connection: &MysqlConnection) -> Result<(), WebdevError> { +fn delete_user( + id: u64, + database_connection: &MysqlConnection, +) -> Result<(), WebdevError> { diesel::delete(users_schema::table.filter(users_schema::id.eq(id))) .execute(database_connection)?;