From 900378d6c4451758109f8bcc6ec252eff4af7f54 Mon Sep 17 00:00:00 2001 From: Simon Thurston <44074551+sthurston99@users.noreply.github.com> Date: Mon, 4 Feb 2019 16:23:56 -0500 Subject: [PATCH 01/58] Added README.md Includes basic description of functions and links to source repos of dependencies for backend and frontend. --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f2a2cf --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +main-server +--- + +####The central server used for managing the databases of the ECE Apprengineering Team at Rowan University. + +###backend +Coded in Rust, manages database manipulation using AJAX requests from frontend. + +#####Dependencies: +* [Rouille 3.0.0](https://github.com/tomaka/rouille) +* [Diesel 1.3.3](https://github.com/diesel-rs/diesel) +* [dotenv 0.13.0](https://github.com/sgrif/rust-dotenv) +* [serde 1.0](https://github.com/serde-rs/serde) +* [serde_json 1.0](https://github.com/serde-rs/json) +* [log 0.4](https://github.com/rust-lang-nursery/log) +* [simplelog](https://github.com/drakulix/simplelog.rs) + +###frontend +Coded in Elm, makes requests to the backend to access database and returns to the user. + +#####Dependencies: +* Core Elm Packages + * [browser](https://github.com/elm/browser) + * [core](https://github.com/elm/core) + * [html](https://github.com/elm/html) + * [http](https://github.com/elm/http) + * [json](https://github.com/elm/json) + * [url](https://github.com/elm/url) + * [bytes](https://github.com/elm/bytes) + * [file](https://github.com/elm/file) + * [time](https://github.com/elm/time) + * [virtual-dom](https://github.com/elm/virtual-dom) +* [elm-json-decode-pipeline 1.0.0](https://github.com/NoRedInk/elm-json-decode-pipeline) +* [elm-format-number 6.0.2](https://github.com/cuducos/elm-format-number) +* [elm-round 1.0.4](https://github.com/myrho/elm-round) From 8a8f13f1a41fb75e7bb1fd81244bd4e948eda750 Mon Sep 17 00:00:00 2001 From: Simon Thurston <44074551+sthurston99@users.noreply.github.com> Date: Mon, 4 Feb 2019 16:24:57 -0500 Subject: [PATCH 02/58] Fixed formatting on headers --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9f2a2cf..f1f06c3 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ main-server --- -####The central server used for managing the databases of the ECE Apprengineering Team at Rowan University. +#### The central server used for managing the databases of the ECE Apprengineering Team at Rowan University. -###backend +### backend Coded in Rust, manages database manipulation using AJAX requests from frontend. -#####Dependencies: +##### Dependencies: * [Rouille 3.0.0](https://github.com/tomaka/rouille) * [Diesel 1.3.3](https://github.com/diesel-rs/diesel) * [dotenv 0.13.0](https://github.com/sgrif/rust-dotenv) @@ -15,10 +15,10 @@ Coded in Rust, manages database manipulation using AJAX requests from frontend. * [log 0.4](https://github.com/rust-lang-nursery/log) * [simplelog](https://github.com/drakulix/simplelog.rs) -###frontend +### frontend Coded in Elm, makes requests to the backend to access database and returns to the user. -#####Dependencies: +##### Dependencies: * Core Elm Packages * [browser](https://github.com/elm/browser) * [core](https://github.com/elm/core) From d0ad4fa3d65d851d1193e3061b4bb5df888f5d31 Mon Sep 17 00:00:00 2001 From: sthurston99 Date: Mon, 4 Feb 2019 16:42:18 -0500 Subject: [PATCH 03/58] Changed header formatting --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f1f06c3..da36a6b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ main-server --- -#### The central server used for managing the databases of the ECE Apprengineering Team at Rowan University. +The central server used for managing the databases of the ECE Apprengineering Team at Rowan University. -### backend +## backend Coded in Rust, manages database manipulation using AJAX requests from frontend. -##### Dependencies: +### Dependencies: * [Rouille 3.0.0](https://github.com/tomaka/rouille) * [Diesel 1.3.3](https://github.com/diesel-rs/diesel) * [dotenv 0.13.0](https://github.com/sgrif/rust-dotenv) @@ -15,10 +15,10 @@ Coded in Rust, manages database manipulation using AJAX requests from frontend. * [log 0.4](https://github.com/rust-lang-nursery/log) * [simplelog](https://github.com/drakulix/simplelog.rs) -### frontend +## frontend Coded in Elm, makes requests to the backend to access database and returns to the user. -##### Dependencies: +### Dependencies: * Core Elm Packages * [browser](https://github.com/elm/browser) * [core](https://github.com/elm/core) From 9faf1ba0f03dce8e445238050f6befb69357520f Mon Sep 17 00:00:00 2001 From: sthurston99 Date: Tue, 5 Feb 2019 15:39:50 -0500 Subject: [PATCH 04/58] Changed header formatting --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f1f06c3..da36a6b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ main-server --- -#### The central server used for managing the databases of the ECE Apprengineering Team at Rowan University. +The central server used for managing the databases of the ECE Apprengineering Team at Rowan University. -### backend +## backend Coded in Rust, manages database manipulation using AJAX requests from frontend. -##### Dependencies: +### Dependencies: * [Rouille 3.0.0](https://github.com/tomaka/rouille) * [Diesel 1.3.3](https://github.com/diesel-rs/diesel) * [dotenv 0.13.0](https://github.com/sgrif/rust-dotenv) @@ -15,10 +15,10 @@ Coded in Rust, manages database manipulation using AJAX requests from frontend. * [log 0.4](https://github.com/rust-lang-nursery/log) * [simplelog](https://github.com/drakulix/simplelog.rs) -### frontend +## frontend Coded in Elm, makes requests to the backend to access database and returns to the user. -##### Dependencies: +### Dependencies: * Core Elm Packages * [browser](https://github.com/elm/browser) * [core](https://github.com/elm/core) From 409a626da1a07341dd9ecfaf9d352171170b89d4 Mon Sep 17 00:00:00 2001 From: sthurston99 Date: Tue, 5 Feb 2019 15:41:03 -0500 Subject: [PATCH 05/58] Backend Readme Initial Push --- backend/README.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 backend/README.md diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..d6f7bdf --- /dev/null +++ b/backend/README.md @@ -0,0 +1,87 @@ +## backend +Coded in Rust, manages database manipulation using AJAX requests from frontend. + +### Dependencies: +* [Rouille 3.0.0](https://github.com/tomaka/rouille) +* [Diesel 1.3.3](https://github.com/diesel-rs/diesel) +* [dotenv 0.13.0](https://github.com/sgrif/rust-dotenv) +* [serde 1.0](https://github.com/serde-rs/serde) +* [serde_json 1.0](https://github.com/serde-rs/json) +* [log 0.4](https://github.com/rust-lang-nursery/log) +* [simplelog](https://github.com/drakulix/simplelog.rs) + + +### API Calls + +`GET /users` +Gets information about every user in the system. Returns a List of Users. + +`GET /users/{id: u64}` +Gets information about the user with the given id. Returns a single User. + +`POST /users` +Creates a new user. The body of POST should be a valid User. Returns the id of the created user. + +`POST /users/{id: u64}` +Updates a given user. + +### Data Models + +Many of the API calls share a common set of data models, represented in JSON format. + +#### User +| Property Name | Type | Optional | Description | +|---------------|--------|----------|-------------| +| id | u64 | No | The internal id of the user | +| first_name | String | No | The first name of the user | +| last_name | String | no | The last name of the user | +| banner_id | u64 | No | The banner id of the user | +| email | String | Yes | The Rowan email of the user. If the user does not have an email, this will be null of non-existent | +``` +{ + "id": 11, + "first_name": "John" + "last_name": "Smith", + "banner_id": 9162xxxxx, + "email": "smithj1@students.rowan.edu" +} +``` + +#### Partial User +| Property Name | Type | Optional | Description | +|---------------|--------|----------|-------------| +| first_name | String | Yes | The first name of the user | +| last_name | String | Yes | The last name of the user | +| banner_id | u64 | Yes | The banner id of the user | +| email | String | Yes | The Rowan email of the user. If the user does not have an email, this will be null of non-existent | +``` +{ + "first_name": "John" + "last_name": "Smith", + "banner_id": 9162xxxxx, + "email": "smithj1@students.rowan.edu" +} +``` + +#### List of Users +| Property Name | Type | Optional | Description | +|---------------|---------------|----------|-----------------| +| users | List of Users | No | A list of Users | +``` +{ + "users": [ + { + "first_name": "John" + "last_name": "Smith", + "banner_id": 9162xxxxx, + "email": "smithj1@students.rowan.edu" + }, + { + "first_name": "Mike" + "last_name": "Johnson", + "banner_id": 9162xxxxx, + "email": "johnsonm1@students.rowan.edu" + } + ] +} +``` From d3828dd4367385c438bcb974edf080bb81aa71da Mon Sep 17 00:00:00 2001 From: sthurston99 Date: Tue, 5 Feb 2019 16:48:14 -0500 Subject: [PATCH 06/58] Test form for AJAX calls --- frontend/src/form.html | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 frontend/src/form.html diff --git a/frontend/src/form.html b/frontend/src/form.html new file mode 100644 index 0000000..c199ffd --- /dev/null +++ b/frontend/src/form.html @@ -0,0 +1,21 @@ + + + + + + + +
+ +
+ + From 19b82d78509af6a90e7dc285b35366233c9b12f3 Mon Sep 17 00:00:00 2001 From: Simon Thurston <44074551+sthurston99@users.noreply.github.com> Date: Tue, 5 Feb 2019 17:08:55 -0500 Subject: [PATCH 07/58] Update README.md --- backend/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/README.md b/backend/README.md index d6f7bdf..26697bf 100644 --- a/backend/README.md +++ b/backend/README.md @@ -10,7 +10,6 @@ Coded in Rust, manages database manipulation using AJAX requests from frontend. * [log 0.4](https://github.com/rust-lang-nursery/log) * [simplelog](https://github.com/drakulix/simplelog.rs) - ### API Calls `GET /users` From 64844ffae0c325e97a53727b7dcf35870f2d2e15 Mon Sep 17 00:00:00 2001 From: kluzynick Date: Mon, 11 Feb 2019 15:07:47 -0500 Subject: [PATCH 08/58] Added a user import from csv. Doesn't check for redundancy. Only meant for an initial startup --- backend/Cargo.lock | 20 +++++++ backend/Cargo.toml | 5 ++ backend/src/bin/csv_user_import.rs | 96 ++++++++++++++++++++++++++++++ backend/src/lib.rs | 6 ++ backend/src/main.rs | 5 -- backend/src/users/models.rs | 2 +- backend/src/users/requests.rs | 2 +- 7 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 backend/src/bin/csv_user_import.rs create mode 100644 backend/src/lib.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 81feec0..c34d14f 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -135,6 +135,23 @@ dependencies = [ "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "csv" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "csv-core" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "deflate" version = "0.7.19" @@ -852,6 +869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "web_dev" version = "0.1.0" dependencies = [ + "csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "diesel 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -900,6 +918,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +"checksum csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd1c44c58078cfbeaf11fbb3eac9ae5534c23004ed770cc4bfb48e658ae4f04" +"checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65" "checksum deflate 0.7.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8a6abb26e16e8d419b5c78662aa9f82857c2386a073da266840e474d5055ec86" "checksum diesel 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "66d7d3a2f8a24763a1a52b5324737b4d24141bb294440ed9094db60bd6cd29ee" "checksum diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62a27666098617d52c487a41f70de23d44a1dc1f3aa5877ceba2790fb1f1cab4" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 19322ce..8850d1d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -12,3 +12,8 @@ serde = { version = "1.0", features = ["derive"]} serde_json = "1.0" log = "0.4" simplelog = "0.5" +csv = "1.0.5" + +[[bin]] +name = "csv_user_import" +path = "src/bin/csv_user_import.rs" diff --git a/backend/src/bin/csv_user_import.rs b/backend/src/bin/csv_user_import.rs new file mode 100644 index 0000000..d958aeb --- /dev/null +++ b/backend/src/bin/csv_user_import.rs @@ -0,0 +1,96 @@ +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 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(){ + 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"); + + use std::env; + let arg = env::args().nth(1); + let filename = match arg { + Some(name) => name, + None => { + println!("Needs a filename"); + return; + } + }; + println!("{}", filename); + + let all_users_result = csv::Reader::from_path(filename); + let mut all_users = match all_users_result{ + Ok(data) => data, + Err(e) => { + println!("Bad file. Error {}",e); + return; + } + }; + for result in all_users.deserialize(){ + let csv_user: Csv_User = match result{ + Ok(data) => data, + Err(e) => { + println!("Bad data, {:?}", e); + return; + } + }; + 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, + }; + + requests::create_user(new_user, &connection); + } +} + diff --git a/backend/src/lib.rs b/backend/src/lib.rs new file mode 100644 index 0000000..dabe155 --- /dev/null +++ b/backend/src/lib.rs @@ -0,0 +1,6 @@ +#[macro_use] +extern crate diesel; + + +pub mod errors; +pub mod users; \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 3d7ab41..8c5b610 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,6 +1,3 @@ -#[macro_use] -extern crate diesel; - use std::env; use std::sync::Mutex; @@ -18,8 +15,6 @@ use dotenv::dotenv; use self::errors::WebdevError; use self::errors::WebdevErrorKind; -mod errors; -mod users; use self::users::models::UserRequest; use self::users::requests::handle_user; diff --git a/backend/src/users/models.rs b/backend/src/users/models.rs index a3d4bc2..df7706b 100644 --- a/backend/src/users/models.rs +++ b/backend/src/users/models.rs @@ -23,7 +23,7 @@ pub struct User { pub email: Option, } -#[derive(Insertable, Serialize, Deserialize)] +#[derive(Insertable, Serialize, Deserialize, Debug)] #[table_name = "users"] pub struct NewUser { pub first_name: String, diff --git a/backend/src/users/requests.rs b/backend/src/users/requests.rs index 40f2dd2..fcf7b81 100644 --- a/backend/src/users/requests.rs +++ b/backend/src/users/requests.rs @@ -81,7 +81,7 @@ fn get_user(id: u64, database_connection: &MysqlConnection) -> Result Result { +pub fn create_user(user: NewUser, database_connection: &MysqlConnection) -> Result { diesel::insert_into(users_schema::table) .values(user) .execute(database_connection)?; From 2602177bdbbb51eb3082be643ab8398b7ea96519 Mon Sep 17 00:00:00 2001 From: kluzynick Date: Mon, 11 Feb 2019 15:31:25 -0500 Subject: [PATCH 09/58] Cleaned up and commented csv_user_import. Used logging instead of println and used handle_user instead of create_user. --- backend/src/bin/csv_import.rs | 104 ++++++++++++++++++++++++++++++++++ backend/src/users/requests.rs | 2 +- 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 backend/src/bin/csv_import.rs diff --git a/backend/src/bin/csv_import.rs b/backend/src/bin/csv_import.rs new file mode 100644 index 0000000..92bfaf5 --- /dev/null +++ b/backend/src/bin/csv_import.rs @@ -0,0 +1,104 @@ +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); +} + diff --git a/backend/src/users/requests.rs b/backend/src/users/requests.rs index fcf7b81..40f2dd2 100644 --- a/backend/src/users/requests.rs +++ b/backend/src/users/requests.rs @@ -81,7 +81,7 @@ 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)?; From 8c3b93671dcab1cb3c7e04a5f9e7b141ef8aca80 Mon Sep 17 00:00:00 2001 From: kluzynskn6 <31700915+kluzynskn6@users.noreply.github.com> Date: Tue, 12 Feb 2019 14:28:21 -0500 Subject: [PATCH 10/58] Deleted Duplicate Files --- backend/src/bin/csv_import.rs | 104 ---------------------------------- 1 file changed, 104 deletions(-) delete mode 100644 backend/src/bin/csv_import.rs diff --git a/backend/src/bin/csv_import.rs b/backend/src/bin/csv_import.rs deleted file mode 100644 index 92bfaf5..0000000 --- a/backend/src/bin/csv_import.rs +++ /dev/null @@ -1,104 +0,0 @@ -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); -} - From 4f37f4e54ad2cb30416d2b5093d3ed133e6ceeca Mon Sep 17 00:00:00 2001 From: kluzynick Date: Tue, 12 Feb 2019 14:39:16 -0500 Subject: [PATCH 11/58] Cleaned csv_import and renamed it. Changed to csv_user_import.rsx --- backend/src/bin/csv_import.rs | 104 ----------------------------- backend/src/bin/csv_user_import.rs | 26 +++++--- 2 files changed, 17 insertions(+), 113 deletions(-) delete mode 100644 backend/src/bin/csv_import.rs diff --git a/backend/src/bin/csv_import.rs b/backend/src/bin/csv_import.rs deleted file mode 100644 index 92bfaf5..0000000 --- a/backend/src/bin/csv_import.rs +++ /dev/null @@ -1,104 +0,0 @@ -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); -} - diff --git a/backend/src/bin/csv_user_import.rs b/backend/src/bin/csv_user_import.rs index d958aeb..92bfaf5 100644 --- a/backend/src/bin/csv_user_import.rs +++ b/backend/src/bin/csv_user_import.rs @@ -13,6 +13,7 @@ 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, @@ -27,8 +28,8 @@ struct Csv_User { #[serde(rename = "Department")] department: String, } - fn main(){ + //Diesel things dotenv().ok(); simplelog::TermLogger::init(simplelog::LevelFilter::Trace, simplelog::Config::default()) @@ -55,42 +56,49 @@ fn main(){ }; 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 => { - println!("Needs a filename"); + error!("Needs a filename"); return; } }; - println!("{}", filename); - + 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) => { - println!("Bad file. Error {}",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) => { - println!("Bad data, {:?}", 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, }; - - requests::create_user(new_user, &connection); + //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); } From dbdcb415d2cc41a8fd3b55e312f55c65a5c01503 Mon Sep 17 00:00:00 2001 From: kluzynick Date: Fri, 15 Feb 2019 14:56:37 -0500 Subject: [PATCH 12/58] Added very basic search. Has edit button in table but functionality not implemented yet. --- backend/src/main.rs | 8 +- frontend/elm.json | 30 -- frontend/src/Main.elm | 63 ---- frontend/src/User.elm | 509 ------------------------------ frontend/src/form.html | 21 -- frontend/www/.htaccess | 1 + frontend/www/get_users.js | 50 +++ frontend/www/hello_world.js | 5 + frontend/www/index.html | 25 ++ frontend/www/users/add.html | 22 ++ frontend/www/users/add_user.js | 0 frontend/www/users/delete_user.js | 0 frontend/www/users/edit.html | 0 frontend/www/users/get_users.js | 43 +++ frontend/www/users/index.html | 15 + frontend/www/users/search.html | 19 ++ frontend/www/users/update_user.js | 0 17 files changed, 184 insertions(+), 627 deletions(-) delete mode 100644 frontend/elm.json delete mode 100644 frontend/src/Main.elm delete mode 100644 frontend/src/User.elm delete mode 100644 frontend/src/form.html create mode 100755 frontend/www/.htaccess create mode 100644 frontend/www/get_users.js create mode 100644 frontend/www/hello_world.js create mode 100755 frontend/www/index.html create mode 100644 frontend/www/users/add.html create mode 100644 frontend/www/users/add_user.js create mode 100644 frontend/www/users/delete_user.js create mode 100644 frontend/www/users/edit.html create mode 100644 frontend/www/users/get_users.js create mode 100644 frontend/www/users/index.html create mode 100644 frontend/www/users/search.html create mode 100644 frontend/www/users/update_user.js diff --git a/backend/src/main.rs b/backend/src/main.rs index 8c5b610..5c41dc8 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -12,12 +12,12 @@ use diesel::MysqlConnection; use dotenv::dotenv; -use self::errors::WebdevError; -use self::errors::WebdevErrorKind; +use web_dev::errors::WebdevError; +use web_dev::errors::WebdevErrorKind; -use self::users::models::UserRequest; -use self::users::requests::handle_user; +use web_dev::users::models::UserRequest; +use web_dev::users::requests::handle_user; fn main() { dotenv().ok(); diff --git a/frontend/elm.json b/frontend/elm.json deleted file mode 100644 index dbb0744..0000000 --- a/frontend/elm.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "application", - "source-directories": [ - "src" - ], - "elm-version": "0.19.0", - "dependencies": { - "direct": { - "NoRedInk/elm-json-decode-pipeline": "1.0.0", - "cuducos/elm-format-number": "6.0.2", - "elm/browser": "1.0.1", - "elm/core": "1.0.2", - "elm/html": "1.0.0", - "elm/http": "2.0.0", - "elm/json": "1.1.2", - "elm/url": "1.0.0" - }, - "indirect": { - "elm/bytes": "1.0.7", - "elm/file": "1.0.1", - "elm/time": "1.0.0", - "elm/virtual-dom": "1.0.2", - "myrho/elm-round": "1.0.4" - } - }, - "test-dependencies": { - "direct": {}, - "indirect": {} - } -} \ No newline at end of file diff --git a/frontend/src/Main.elm b/frontend/src/Main.elm deleted file mode 100644 index ad8e7b2..0000000 --- a/frontend/src/Main.elm +++ /dev/null @@ -1,63 +0,0 @@ -module Main exposing (Model(..), Msg(..), Page(..), init, main, subscriptions, update, view) - -import Browser -import FormatNumber exposing (format) -import FormatNumber.Locales exposing (usLocale) -import Html exposing (Attribute, Html, div, h1, span, text, ul) -import Http -import Json.Decode as Decode exposing (Decoder, field, int, list, nullable, string) -import Json.Decode.Pipeline exposing (required) -import User - - -main = - Browser.element - { init = init - , update = update - , subscriptions = subscriptions - , view = view - } - - -type Model - = User User.UserModel - - -type Msg - = GotUserMsg User.UserMsg - - -init : () -> ( Model, Cmd Msg ) -init i = - let - ( userModel, userCmd ) = - User.init i - in - ( User userModel, Cmd.map GotUserMsg userCmd ) - - -type Page - = Users - - -update : Msg -> Model -> ( Model, Cmd Msg ) -update msg model = - case ( msg, model ) of - ( GotUserMsg user_msg, User user_model ) -> - let - ( userModel, userCmd ) = - User.update user_msg user_model - in - ( User userModel, Cmd.map GotUserMsg userCmd ) - - -subscriptions : Model -> Sub Msg -subscriptions model = - Sub.none - - -view : Model -> Html Msg -view model = - case model of - User user_model -> - Html.map GotUserMsg (User.view user_model) diff --git a/frontend/src/User.elm b/frontend/src/User.elm deleted file mode 100644 index ad472f2..0000000 --- a/frontend/src/User.elm +++ /dev/null @@ -1,509 +0,0 @@ -module User exposing (UserModel, UserMsg, init, update, view) - -import Debug -import FormatNumber exposing (format) -import FormatNumber.Locales exposing (usLocale) -import Html exposing (Attribute, Html, div, h1, input, table, td, text, tr, ul) -import Html.Attributes exposing (checked, type_, value) -import Html.Events exposing (onCheck, onClick, onInput) -import Http -import Json.Decode as Decode exposing (Decoder, field, int, list, nullable, string) -import Json.Decode.Pipeline exposing (required) -import Json.Encode as Encode -import Url.Builder exposing (crossOrigin) - - -type alias UserModel = - { user_list : Result Http.Error UserList - , first_name_search : Maybe String - , last_name_search : Maybe String - , banner_id_search : Maybe Int - , email_null_search : Bool - , email_search : Maybe String - , create_user : Maybe CreateUser - } - - -type UserMsg - = GotUsers (Result Http.Error UserList) - | SearchFirstName String - | SearchLastName String - | SearchBannerId String - | SearchNullEmail Bool - | SearchEmail String - | DeleteUser Int - | UserDeleted (Result Http.Error ()) - | ShowCreateUser () - | CreateUserFirstName String - | CreateUserLastName String - | CreateUserBannerId String - | CreateUserHasEmail Bool - | CreateUserEmail String - | SubmitCreateUser () - | SubmittedCreateUser (Result Http.Error ()) - - -type alias User = - { id : Int - , first_name : String - , last_name : String - , banner_id : Int - , email : Maybe String - } - - -type alias CreateUser = - { first_name : String - , last_name : String - , banner_id : Maybe Int - , email : Maybe String - } - - -type alias UserList = - List User - - -init : () -> ( UserModel, Cmd UserMsg ) -init _ = - ( { user_list = Ok [] - , first_name_search = Nothing - , last_name_search = Nothing - , banner_id_search = Nothing - , email_null_search = False - , email_search = Nothing - , create_user = Nothing - } - , Http.get - { url = "http://localhost:8000/users/" - , expect = Http.expectJson GotUsers decodeUserList - } - ) - - -update : UserMsg -> UserModel -> ( UserModel, Cmd UserMsg ) -update msg model = - case msg of - GotUsers result -> - case result of - Ok users -> - ( { model | user_list = Ok users }, Cmd.none ) - - Err error -> - ( { model | user_list = Err error }, Cmd.none ) - - SearchFirstName first_name -> - let - new_model = - { model | first_name_search = emptyToNothing first_name } - in - ( new_model, searchCmd new_model ) - - SearchLastName last_name -> - let - new_model = - { model | last_name_search = emptyToNothing last_name } - in - ( new_model, searchCmd new_model ) - - SearchBannerId banner_id -> - let - new_model = - { model | banner_id_search = emptyToNothingInt banner_id } - in - ( new_model, searchCmd new_model ) - - SearchNullEmail null_email -> - let - new_model = - { model | email_null_search = null_email } - in - ( new_model, searchCmd new_model ) - - SearchEmail email -> - let - new_model = - { model | email_search = emptyToNothing email } - in - ( new_model, searchCmd new_model ) - - DeleteUser id -> - ( model, deleteCmd id ) - - UserDeleted err -> - ( model, searchCmd model ) - - ShowCreateUser _ -> - ( { model | create_user = Just emptyCreateUser }, Cmd.none ) - - CreateUserFirstName first_name -> - case model.create_user of - Just create_user -> - let - new_create_user = - { create_user | first_name = first_name } - in - ( { model | create_user = Just new_create_user }, Cmd.none ) - - Nothing -> - ( model, Cmd.none ) - - CreateUserLastName last_name -> - case model.create_user of - Just create_user -> - let - new_create_user = - { create_user | last_name = last_name } - in - ( { model | create_user = Just new_create_user }, Cmd.none ) - - Nothing -> - ( model, Cmd.none ) - - CreateUserBannerId banner_id -> - case model.create_user of - Just create_user -> - let - new_create_user = - { create_user | banner_id = emptyToNothingInt banner_id } - in - ( { model | create_user = Just new_create_user }, Cmd.none ) - - Nothing -> - ( model, Cmd.none ) - - CreateUserHasEmail has_email -> - case model.create_user of - Just create_user -> - let - new_create_user = - { create_user - | email = - if has_email then - Just "" - - else - Nothing - } - in - ( { model | create_user = Just new_create_user }, Cmd.none ) - - Nothing -> - ( model, Cmd.none ) - - CreateUserEmail email -> - case model.create_user of - Just create_user -> - let - new_create_user = - { create_user | email = Just email } - in - ( { model | create_user = Just new_create_user }, Cmd.none ) - - Nothing -> - ( model, Cmd.none ) - - SubmitCreateUser _ -> - case model.create_user of - Just create_user -> - ( { model | create_user = Nothing }, createCmd create_user ) - - Nothing -> - ( model, Cmd.none ) - - SubmittedCreateUser err -> - ( model, searchCmd model ) - - -createCmd : CreateUser -> Cmd UserMsg -createCmd create_user = - case create_user.banner_id of - Just banner_id -> - let - user_body = - Encode.object - [ ( "first_name", Encode.string create_user.first_name ) - , ( "last_name", Encode.string create_user.last_name ) - , ( "banner_id", Encode.int banner_id ) - , ( "email" - , case create_user.email of - Just email -> - Encode.string email - - Nothing -> - Encode.null - ) - ] - in - Http.post - { url = "http://localhost:8000/users/" - , body = Http.jsonBody user_body - , expect = Http.expectWhatever SubmittedCreateUser - } - - Nothing -> - Cmd.none - - -emptyCreateUser : CreateUser -emptyCreateUser = - { first_name = "" - , last_name = "" - , banner_id = Nothing - , email = Nothing - } - - -emptyToNothingInt : String -> Maybe Int -emptyToNothingInt s = - if String.isEmpty s then - Nothing - - else - String.toInt s - - -emptyToNothing : String -> Maybe String -emptyToNothing s = - if String.isEmpty s then - Nothing - - else - Just s - - -deleteCmd : Int -> Cmd UserMsg -deleteCmd id = - Http.request - { method = "DELETE" - , headers = [] - , url = crossOrigin "http://localhost:8000" [ "users", String.fromInt id ] [] - , body = Http.emptyBody - , expect = Http.expectWhatever UserDeleted - , timeout = Nothing - , tracker = Nothing - } - - -searchCmd : UserModel -> Cmd UserMsg -searchCmd model = - Http.get - { url = - crossOrigin "http://localhost:8000" - [ "users/" ] - ([] - |> concatConditional - (Maybe.map (\a -> Url.Builder.string "first_name_exact" a) model.first_name_search) - |> concatConditional - (Maybe.map (\a -> Url.Builder.string "last_name_exact" a) model.last_name_search) - |> concatConditional - (Maybe.map (\a -> Url.Builder.int "banner_id_exact" a) model.banner_id_search) - |> concatConditional (hasEmailQuery model.email_search model.email_null_search) - |> concatConditional (emailQuery model.email_search model.email_null_search) - ) - , expect = Http.expectJson GotUsers decodeUserList - } - - -hasEmailQuery : Maybe String -> Bool -> Maybe Url.Builder.QueryParameter -hasEmailQuery email_search email_null_search = - case ( email_search, email_null_search ) of - ( Nothing, False ) -> - Nothing - - ( Nothing, True ) -> - Just (Url.Builder.string "has_email" "false") - - ( Just _, False ) -> - Just (Url.Builder.string "has_email" "true") - - ( Just _, True ) -> - Just (Url.Builder.string "has_email" "false") - - -emailQuery : Maybe String -> Bool -> Maybe Url.Builder.QueryParameter -emailQuery email_search email_null_search = - case ( email_search, email_null_search ) of - ( Nothing, False ) -> - Nothing - - ( Nothing, True ) -> - Nothing - - ( Just s, False ) -> - Just (Url.Builder.string "email_exact" s) - - ( Just _, True ) -> - Nothing - - -concatConditional : Maybe a -> List a -> List a -concatConditional m l = - case m of - Just value -> - l ++ [ value ] - - Nothing -> - l - - -view : UserModel -> Html UserMsg -view model = - div [] - [ h1 [] [ text "Users" ] - , div [] - [ input - [ type_ "text" - , value (Maybe.withDefault "" model.first_name_search) - , onInput SearchFirstName - ] - [] - , input - [ type_ "text" - , value (Maybe.withDefault "" model.last_name_search) - , onInput SearchLastName - ] - [] - , input - [ type_ "text" - , value (Maybe.withDefault "" (Maybe.map String.fromInt model.banner_id_search)) - , onInput SearchBannerId - ] - [] - , input - [ type_ "text" - , value (Maybe.withDefault "" model.email_search) - , onInput SearchEmail - ] - [] - , input - [ type_ "checkbox" - , checked model.email_null_search - , onCheck SearchNullEmail - ] - [] - ] - , case model.user_list of - Ok user_list -> - viewUsers user_list - - Err error -> - viewHttpError error - , case model.create_user of - Nothing -> - input [ type_ "button", value "Add User", onClick (ShowCreateUser ()) ] [] - - Just create_user -> - viewCreateUser create_user - ] - - -viewCreateUser : CreateUser -> Html UserMsg -viewCreateUser create_user = - div [] - (([ input - [ type_ "text" - , value create_user.first_name - , onInput CreateUserFirstName - ] - [] - , input - [ type_ "text" - , value create_user.last_name - , onInput CreateUserLastName - ] - [] - , input - [ type_ "text" - , value (Maybe.withDefault "" (Maybe.map String.fromInt create_user.banner_id)) - , onInput CreateUserBannerId - ] - [] - , div [] - [ text "Has email?" - , input - [ type_ "checkbox" - , checked - (case create_user.email of - Just _ -> - True - - Nothing -> - False - ) - , onCheck CreateUserHasEmail - ] - [] - ] - ] - |> concatConditional - (Maybe.map - (\email -> input [ type_ "text", value email, onInput CreateUserEmail ] []) - create_user.email - ) - ) - ++ [ input - [ type_ "button" - , value "Submit User" - , onClick (SubmitCreateUser ()) - ] - [] - ] - ) - - -viewUsers : List User -> Html UserMsg -viewUsers users = - table [] (List.map viewUserRow users) - - -viewUserRow : User -> Html UserMsg -viewUserRow user = - tr [] - [ td [] [ text user.first_name ] - , td [] [ text user.last_name ] - , td [] [ text (String.fromInt user.banner_id) ] - , td [] - [ case user.email of - Just email -> - text email - - Nothing -> - text "No email" - ] - , td [] [ input [ type_ "button", value "Delete", onClick (DeleteUser user.id) ] [] ] - ] - - -viewHttpError : Http.Error -> Html UserMsg -viewHttpError error = - case error of - Http.BadUrl string -> - text string - - Http.Timeout -> - text "Timeout!" - - Http.NetworkError -> - text "Network error!" - - Http.BadStatus status -> - text (format usLocale (toFloat status)) - - Http.BadBody body -> - text body - - -decodeUserList : Decoder (List User) -decodeUserList = - field "users" (list decodeUser) - - -decodeUser : Decoder User -decodeUser = - Decode.succeed User - |> required "id" int - |> required "first_name" string - |> required "last_name" string - |> required "banner_id" int - |> required "email" (nullable string) diff --git a/frontend/src/form.html b/frontend/src/form.html deleted file mode 100644 index c199ffd..0000000 --- a/frontend/src/form.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -
- -
- - diff --git a/frontend/www/.htaccess b/frontend/www/.htaccess new file mode 100755 index 0000000..35caa4a --- /dev/null +++ b/frontend/www/.htaccess @@ -0,0 +1 @@ +DirectoryIndex index.html \ No newline at end of file diff --git a/frontend/www/get_users.js b/frontend/www/get_users.js new file mode 100644 index 0000000..db33d48 --- /dev/null +++ b/frontend/www/get_users.js @@ -0,0 +1,50 @@ +function get_users() { + //Things to add: + //Edit/Delete + //Add + var x, fn, ln,y; + var people; + //people.id = "seach_table"; + if (document.getElementById("firstname")){ + fn = document.getElementById("firstname").value; + }else{ + fn = ""; + } + if (document.getElementById("lastname")){ + ln = document.getElementById("lastname").value; + } + else{ + ln=""; + } + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + var search_results = JSON.parse(this.responseText); + var search_table = document.createElement("TABLE"); + people = " Row First Name Last Name Email "; + for(x in search_results.users){ + //people += search_results.users[x].first_name+ " " + search_results.users[x].last_name + ", email: "+search_results.users[x].email+"
"; + y=parseInt(x)+1; + people += " "+ y +""+search_results.users[x].first_name+ "" + search_results.users[x].last_name + + ""+search_results.users[x].email+"" + ""; + } + document.getElementById("user").innerHTML = people; + } + }; + console.log("first name: \""+fn + "\" lastname: \"" + ln+"\""); + if (fn !=="" && ln !==""){ + xhttp.open("GET", "http://192.168.176.129:8000/users/?first_name_exact="+fn+"&last_name_exact="+ln, true); + }else if(fn !==""){ + xhttp.open("GET", "http://192.168.176.129:8000/users/?first_name_exact="+fn, true); + }else if(ln !==""){ + xhttp.open("GET", "http://192.168.176.129:8000/users/?last_name_exact="+ln, true); + }else { + console.log("Empty Search"); + xhttp.open("GET", "http://192.168.176.129:8000/users/", true); + } + xhttp.send(); +} +//function update_user(id){ + + +//} \ No newline at end of file diff --git a/frontend/www/hello_world.js b/frontend/www/hello_world.js new file mode 100644 index 0000000..da86379 --- /dev/null +++ b/frontend/www/hello_world.js @@ -0,0 +1,5 @@ +function hello_world(){ + x = document.getElementById("JsHi"); + x.innerHTML ="

Hello from JavaScript

"; + +} \ No newline at end of file diff --git a/frontend/www/index.html b/frontend/www/index.html new file mode 100755 index 0000000..a24f2b3 --- /dev/null +++ b/frontend/www/index.html @@ -0,0 +1,25 @@ + + + Kluzy + + + + +

Kluzy-Test-Server

+

Note: Searches are exact only!

+ +
    +
    + + + \ No newline at end of file diff --git a/frontend/www/users/add.html b/frontend/www/users/add.html new file mode 100644 index 0000000..00a6227 --- /dev/null +++ b/frontend/www/users/add.html @@ -0,0 +1,22 @@ + + + Kluzy-Users + + + +

    Add Users

    +

    Welcome to the users database. Please select a function.

    +
    + First name:
    +
    + Last name:
    +
    + Email:
    +
    + Banner ID:
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/frontend/www/users/add_user.js b/frontend/www/users/add_user.js new file mode 100644 index 0000000..e69de29 diff --git a/frontend/www/users/delete_user.js b/frontend/www/users/delete_user.js new file mode 100644 index 0000000..e69de29 diff --git a/frontend/www/users/edit.html b/frontend/www/users/edit.html new file mode 100644 index 0000000..e69de29 diff --git a/frontend/www/users/get_users.js b/frontend/www/users/get_users.js new file mode 100644 index 0000000..214bdd9 --- /dev/null +++ b/frontend/www/users/get_users.js @@ -0,0 +1,43 @@ +function get_users() { + //Things to add: + //Edit/Delete + //Add + var x, fn, ln,people,y; + if (document.getElementById("firstname")){ + fn = document.getElementById("firstname").value; + }else{ + fn = ""; + } + if (document.getElementById("lastname")){ + ln = document.getElementById("lastname").value; + } + else{ + ln=""; + } + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + var search_results = JSON.parse(this.responseText); + var search_table = document.createElement("TABLE"); + people = " Row First Name Last Name Email "; + for(x in search_results.users){ + //people += search_results.users[x].first_name+ " " + search_results.users[x].last_name + ", email: "+search_results.users[x].email+"
    "; + y=parseInt(x)+1; + people += " "+ y +""+search_results.users[x].first_name+ "" + search_results.users[x].last_name + ""+search_results.users[x].email+""; + } + document.getElementById("user").innerHTML = people; + } + }; + console.log("first name: \""+fn + "\" lastname: \"" + ln+"\""); + if (fn !=="" && ln !==""){ + xhttp.open("GET", "http://192.168.176.129:8000/users/?first_name_exact="+fn+"&last_name_exact="+ln, true); + }else if(fn !==""){ + xhttp.open("GET", "http://192.168.176.129:8000/users/?first_name_exact="+fn, true); + }else if(ln !==""){ + xhttp.open("GET", "http://192.168.176.129:8000/users/?last_name_exact="+ln, true); + }else { + console.log("Empty Search"); + xhttp.open("GET", "http://192.168.176.129:8000/users/", true); + } + xhttp.send(); +} \ No newline at end of file diff --git a/frontend/www/users/index.html b/frontend/www/users/index.html new file mode 100644 index 0000000..38c7c69 --- /dev/null +++ b/frontend/www/users/index.html @@ -0,0 +1,15 @@ + + + Kluzy-Users + + + +

    Users-Test-Server

    +

    Welcome to the users database. Please select a function.

    +
    +
    + + \ No newline at end of file diff --git a/frontend/www/users/search.html b/frontend/www/users/search.html new file mode 100644 index 0000000..5f8dd1e --- /dev/null +++ b/frontend/www/users/search.html @@ -0,0 +1,19 @@ + + + Search Users + + + +

    Search Users Database

    +

    Note: Searches are exact only!

    +
    + First name:
    +
    + Last name:
    +

    +
    +
    + +
    + + \ No newline at end of file diff --git a/frontend/www/users/update_user.js b/frontend/www/users/update_user.js new file mode 100644 index 0000000..e69de29 From 1e4869372aa66e913b576cf04dba2bf4537e1809 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 15 Feb 2019 21:22:47 -0500 Subject: [PATCH 13/58] Better user searching Uses the `url` crate to parse the query, which is more robust than the rouille query parser. Search query parameters no longer end in `_exact` --- backend/Cargo.lock | 1 + backend/Cargo.toml | 1 + backend/src/errors.rs | 6 +++ backend/src/users/models.rs | 78 +++++++++++++++++++++-------------- backend/src/users/requests.rs | 2 +- 5 files changed, 55 insertions(+), 33 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 81feec0..52e3a99 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -859,6 +859,7 @@ dependencies = [ "serde 1.0.85 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 19322ce..791925a 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -12,3 +12,4 @@ serde = { version = "1.0", features = ["derive"]} serde_json = "1.0" log = "0.4" simplelog = "0.5" +url="1.7.2" diff --git a/backend/src/errors.rs b/backend/src/errors.rs index 484d6ae..3ef777f 100644 --- a/backend/src/errors.rs +++ b/backend/src/errors.rs @@ -75,6 +75,12 @@ impl From for WebdevError { } } +impl From for WebdevError { + fn from(s: url::ParseError) -> WebdevError { + WebdevError::with_source(WebdevErrorKind::Format, Box::new(s)) + } +} + impl From for rouille::Response { fn from(e: WebdevError) -> rouille::Response { match e.kind() { diff --git a/backend/src/users/models.rs b/backend/src/users/models.rs index a3d4bc2..4f59794 100644 --- a/backend/src/users/models.rs +++ b/backend/src/users/models.rs @@ -3,6 +3,7 @@ use rouille::router; use rouille::Request; use serde::Deserialize; use serde::Serialize; +use url::form_urlencoded; use log::trace; use log::warn; @@ -57,53 +58,66 @@ pub enum UserRequest { impl UserRequest { pub fn from_rouille(request: &rouille::Request) -> Result { trace!("Creating UserRequest from {:#?}", request); + + let url_query = form_urlencoded::parse(request.raw_query_string().as_bytes()); + router!(request, (GET) (/) => { - // TODO Searching really needs to be fixed up + let first_name_filter = url_query.clone().find_map(|(k, v)| { + if k == "first_name" { + Some(v.to_string()) + } else { + None + } + }); - let first_name_filter = request.get_param("first_name_exact"); - let last_name_filter = request.get_param("last_name_exact"); - let banner_id_filter = - if let Some(p) = request.get_param("banner_id_exact") { - Some(p.parse()?) + let last_name_filter = url_query.clone().find_map(|(k, v)| { + if k == "last_name" { + Some(v.to_string()) } else { None - }; + } + }); - let has_email_filter = - if let Some(p) = request.get_param("has_email") { - Some(p.parse()?) + let banner_id_filter = url_query.clone().find_map(|(k, v)| { + if k == "banner_id" { + Some(v.parse()) } else { None - }; - - let email_filter = request.get_param("email"); - - /* - * has_email | email | out - * None None None - * Some(t) None None ? - * Some(f) None Some(None) - * None Some(s) Some(Some(s)) - * Some(t) Some(s) Some(Some(s)) - * Some(f) Some(s) Some(None) - */ - - let email = match (has_email_filter, email_filter) { - (None, None) => None, - (Some(true), None) => None, - (Some(false), None) => Some(None), - (None, Some(s)) => Some(Some(s)), - (Some(true), Some(s)) => Some(Some(s)), - (Some(false), Some(s)) => Some(None), + } + }); + + // Propogate the error if the id could not be parsed as a u32 + let banner_id_filter = match banner_id_filter { + Some(result) => Some(result?), + None => None, }; + // TODO This email filter only covers 2 of the possibilities: + // + // No email filter: Yes + // None email: No + // Some email: No + // Some specific email: Yes + // + // Should expect a query like + // No email: email=None + // Some email: email=Some + // Some specific email: email=Some,hollabaut1@students.rowan.edu + let email_filter = url_query.clone().find_map(|(k, v)| { + if k == "email" { + Some(Some(v.to_string())) + } else { + None + } + }); + Ok(UserRequest::SearchUsers(PartialUser { first_name: first_name_filter, last_name: last_name_filter, banner_id: banner_id_filter, - email: email, + email: email_filter, })) }, diff --git a/backend/src/users/requests.rs b/backend/src/users/requests.rs index 40f2dd2..f35093b 100644 --- a/backend/src/users/requests.rs +++ b/backend/src/users/requests.rs @@ -77,7 +77,7 @@ fn get_user(id: u64, database_connection: &MysqlConnection) -> Result Ok(user), - None => Err(WebdevError::new(WebdevErrorKind::NotFound)) + None => Err(WebdevError::new(WebdevErrorKind::NotFound)), } } From bddc8eab346acc31a414b88cf8bf3d5a57107793 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 15 Feb 2019 23:54:11 -0500 Subject: [PATCH 14/58] Search can do partial or exact Now looks like `?first_name=partial,tim&last_name=exact,gayed` Note: Banner Id currently does not support partial search, since diesel does not support like clauses on non-text. (banner id is an integer). Attempting a partial search on banner id will currently result in an exact search. --- backend/src/errors.rs | 9 +- backend/src/main.rs | 1 + backend/src/search.rs | 252 ++++++++++++++++++++++++++++++++++ backend/src/users/models.rs | 84 ++++-------- backend/src/users/requests.rs | 75 ++++++++-- 5 files changed, 352 insertions(+), 69 deletions(-) create mode 100644 backend/src/search.rs diff --git a/backend/src/errors.rs b/backend/src/errors.rs index 3ef777f..2968a12 100644 --- a/backend/src/errors.rs +++ b/backend/src/errors.rs @@ -1,7 +1,8 @@ -use std::convert::From; use std::error::Error; use std::fmt; +use crate::search::SearchParseError; + #[derive(Debug, Copy, Clone)] pub enum WebdevErrorKind { Database, @@ -81,6 +82,12 @@ impl From for WebdevError { } } +impl From for WebdevError { + fn from(s: SearchParseError) -> WebdevError { + WebdevError::with_source(WebdevErrorKind::Format, Box::new(s)) + } +} + impl From for rouille::Response { fn from(e: WebdevError) -> rouille::Response { match e.kind() { diff --git a/backend/src/main.rs b/backend/src/main.rs index 3d7ab41..a3db527 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -19,6 +19,7 @@ use self::errors::WebdevError; use self::errors::WebdevErrorKind; mod errors; +mod search; mod users; use self::users::models::UserRequest; diff --git a/backend/src/search.rs b/backend/src/search.rs new file mode 100644 index 0000000..2b2cb92 --- /dev/null +++ b/backend/src/search.rs @@ -0,0 +1,252 @@ +#[derive(Debug, PartialEq)] +pub enum SearchParseError { + Kind(String), + Term(String), +} + +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), + } + } +} + +impl std::error::Error for SearchParseError {} + +/// Search for a field that cannot be null +/// +/// Use a `NullableSearch` when a field could be null instead of `Search>` +#[derive(Debug, PartialEq)] +pub enum Search { + /// Field partially matches + Partial(T), + + /// Field fully matches + Exact(T), + + /// Do not search by this field + NoSearch, +} + +impl Search { + pub fn from_query(query: &str) -> Result, SearchParseError> { + let mut query_iter = query.split(','); + + let kind = query_iter.next().map(|s| s.trim()); + let term = query_iter.next().map(|s| s.trim()); + + match (kind, term) { + (Some("partial"), Some(s)) => s + .trim() + .parse() + .map(|p| Search::Partial(p)) + .map_err(|_| SearchParseError::Term(s.to_owned())), + (Some("exact"), Some(s)) => s + .trim() + .parse() + .map(|p| Search::Exact(p)) + .map_err(|_| SearchParseError::Term(s.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())), + (None, Some(_)) => Err(SearchParseError::Kind("".to_owned())), + (None, None) => Err(SearchParseError::Kind("".to_owned())), + } + } +} + +#[test] +fn parse_search_partial_search_works() { + let s = Search::from_query(" partial , hello "); + assert_eq!(s, Ok(Search::Partial("hello".to_owned()))); +} + +#[test] +fn parse_search_exact_search_works() { + let s = Search::from_query(" exact, hello "); + assert_eq!(s, Ok(Search::Exact("hello".to_owned()))); +} + +#[test] +fn parse_search_invalid_kind_with_term_fails() { + let s: Result, _> = Search::from_query("hello, bye"); + assert_eq!(s, Err(SearchParseError::Kind("hello".to_owned()))); +} + +#[test] +fn parse_search_no_kind_with_term_fails() { + let s: Result, _> = Search::from_query(", bye"); + assert_eq!(s, Err(SearchParseError::Kind("".to_owned()))); +} + +#[test] +fn parse_search_partial_with_no_term_fails() { + let s: Result, _> = Search::from_query(" partial"); + assert_eq!(s, Err(SearchParseError::Term("".to_owned()))); +} + +#[test] +fn parse_search_exact_with_no_term_fails() { + let s: Result, _> = Search::from_query(" exact"); + assert_eq!(s, Err(SearchParseError::Term("".to_owned()))); +} + +#[test] +fn parse_search_invalid_with_no_term_fails() { + let s: Result, _> = Search::from_query("hello"); + assert_eq!(s, Err(SearchParseError::Kind("hello".to_owned()))); +} + +#[test] +fn parse_search_empty_string_fails() { + let s: Result, _> = Search::from_query(""); + assert_eq!(s, Err(SearchParseError::Kind("".to_owned()))); +} + +/// Search fo a field that can be null +/// +/// This could be done as a `Search