diff --git a/Cargo.lock b/Cargo.lock index b9725a3..9478a81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1683,6 +1683,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "ignore" version = "0.4.20" @@ -2196,6 +2202,7 @@ dependencies = [ "regex", "reqwest", "rocket", + "rocket-validation", "rocket_db_pools", "rocket_dyn_templates", "rocket_okapi", @@ -2210,6 +2217,7 @@ dependencies = [ "strum_macros", "tokio", "uuid 1.3.3", + "validator", ] [[package]] @@ -2893,6 +2901,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "rocket-validation" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4de40e00ab6e44f7bcfa19320737a710339723e2d1b055a704223ec92ab7388" +dependencies = [ + "rocket", + "validator", +] + [[package]] name = "rocket_codegen" version = "0.5.0-rc.3" @@ -4439,6 +4457,48 @@ dependencies = [ "serde", ] +[[package]] +name = "validator" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd" +dependencies = [ + "idna", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af" +dependencies = [ + "if_chain", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "validator_types", +] + +[[package]] +name = "validator_types" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index c151df7..c7a85c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,8 @@ rocket_dyn_templates = { version = "0.1.0-rc.3", features = ["handlebars", "tera rocket_okapi = { version = "0.8.0-rc.3", features = ["uuid", "swagger", "rocket_db_pools", "rocket_dyn_templates"] } schemars = { version = "0.8.15" , features = ["chrono"]} okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } +rocket-validation = "0.1.3" +validator="0.16.1" [dev-dependencies] reqwest = "0.11.13" diff --git a/src/catchers.rs b/src/catchers.rs index 29582cd..92f0be1 100644 --- a/src/catchers.rs +++ b/src/catchers.rs @@ -1,20 +1,196 @@ use crate::{guards::UserErrorMessage}; -use rocket::{serde::json::{json, Value}, Request, catch}; -use crate::guards::JsonValidationError; +use rocket::{serde::json::{json, Value}, Request, catch, Data, form}; +use rocket::data::{FromData, Outcome as DataOutcome}; +use rocket::form::{DataField, FromForm, ValueField}; +use rocket::http::Status; +use rocket::outcome::Outcome; +use rocket::request::FromRequest; +use rocket::serde::json::Json; +use rocket_okapi::gen::OpenApiGenerator; +use rocket_okapi::request::OpenApiFromData; +use schemars::JsonSchema; +use validator::{Validate, ValidationErrors}; +use okapi::{ + openapi3::{MediaType, RequestBody}, + Map, +}; + +/* + The below code is a mix between json_validator + and serde handling, in order to handle serde validations + + Credit to a large portion of it is to: owlnext-fr + https://github.com/owlnext-fr/rust-microservice-skeleton/blob/main/src/core/validation.rs +*/ + + +#[derive(Clone, Debug, JsonSchema)] +pub struct Validated(pub T); + +#[derive(Clone)] +pub struct CachedValidationErrors(pub Option); + +#[derive(Clone)] +pub struct CachedParseErrors(pub Option); + +macro_rules! fn_request_body { + ($gen:ident, $ty:path, $mime_type:expr) => {{ + let schema = $gen.json_schema::<$ty>(); + Ok(RequestBody { + content: { + let mut map = Map::new(); + map.insert( + $mime_type.to_owned(), + MediaType { + schema: Some(schema), + ..MediaType::default() + }, + ); + map + }, + required: true, + ..okapi::openapi3::RequestBody::default() + }) + }}; +} + +impl<'r, D: validator::Validate + rocket::serde::Deserialize<'r> + JsonSchema> OpenApiFromData<'r> for Validated> { + fn request_body(gen: &mut OpenApiGenerator) -> rocket_okapi::Result { + fn_request_body!(gen, D, "application/json") + } +} + +#[rocket::async_trait] +impl<'r, D: validator::Validate + rocket::serde::Deserialize<'r> + JsonSchema> FromData<'r> for Validated> { + type Error = Result>; + + async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> DataOutcome<'r, Self> { + let data_outcome = as FromData<'r>>::from_data(req, data).await; + + match data_outcome { + Outcome::Failure((status, err)) => { + req.local_cache(|| CachedParseErrors(Some(err.to_string()))); + Outcome::Failure((status, Err(err))) + } + Outcome::Forward(err) => Outcome::Forward(err), + Outcome::Success(data) => match data.validate() { + Ok(_) => Outcome::Success(Validated(data)), + Err(err) => { + req.local_cache(|| CachedValidationErrors(Some(err.to_owned()))); + Outcome::Failure((Status::BadRequest, Ok(err))) + } + }, + } + } +} + +#[rocket::async_trait] +impl<'r, D: Validate + FromRequest<'r>> FromRequest<'r> for Validated { + type Error = Result; + async fn from_request(req: &'r Request<'_>) -> rocket::request::Outcome { + let data_outcome = D::from_request(req).await; + + match data_outcome { + Outcome::Failure((status, err)) => { + let error_message = format!("{err:?}"); + req.local_cache(|| CachedParseErrors(Some(error_message))); + Outcome::Failure((status, Err(err))) + } + Outcome::Forward(err) => Outcome::Forward(err), + Outcome::Success(data) => match data.validate() { + Ok(_) => Outcome::Success(Validated(data)), + Err(err) => { + req.local_cache(|| CachedValidationErrors(Some(err.to_owned()))); + Outcome::Failure((Status::BadRequest, Ok(err))) + } + }, + } + } +} + + +#[rocket::async_trait] +impl<'r, T: Validate + FromForm<'r>> FromForm<'r> for Validated { + type Context = T::Context; + + #[inline] + fn init(opts: form::Options) -> Self::Context { + T::init(opts) + } + + #[inline] + fn push_value(ctxt: &mut Self::Context, field: ValueField<'r>) { + T::push_value(ctxt, field) + } + + #[inline] + async fn push_data(ctxt: &mut Self::Context, field: DataField<'r, '_>) { + T::push_data(ctxt, field).await + } + + fn finalize(this: Self::Context) -> form::Result<'r, Self> { + match T::finalize(this) { + Err(err) => Err(err), + Ok(data) => match data.validate() { + Ok(_) => Ok(Validated(data)), + Err(err) => Err(err + .into_errors() + .into_iter() + .map(|e| form::Error { + name: Some(e.0.into()), + kind: form::error::ErrorKind::Validation(std::borrow::Cow::Borrowed(e.0)), + value: None, + entity: form::error::Entity::Value, + }) + .collect::>() + .into()), + }, + } + } +} + +#[catch(400)] +pub fn general_catcher(req: &Request) -> Value { + json!([{ + "code": "error.general", + "message": "Bad Request. The request could not be understood by the server due to malformed syntax.", + "errors": req.local_cache(|| CachedValidationErrors(None)).0.as_ref(), + }]) +} #[catch(403)] pub fn not_authorized() -> Value { - json!([{"label": "unauthorized", "message": "Not authorized to make request"}]) + json!([{"code": "error.unauthorized", "message": "Not authorized to make request"}]) } #[catch(404)] pub fn not_found() -> Value { - json!([]) + json!([{"code": "error.not_found", "message": "The requested route was not found."}]) } #[catch(422)] pub fn unprocessable_entry(req: &Request) -> Value { - json! [{"label": "failed.request", "message": "failed to service request"}] + let possible_parse_violation = req.local_cache(|| CachedParseErrors(None)).0.as_ref(); + let validation_errors = req.local_cache(|| CachedValidationErrors(None)).0.as_ref(); + + let mut message = "Failed to service request, structure parsing failed.".to_string(); + + if validation_errors.is_some() { + message.clear(); + + let erros = validation_errors.unwrap().field_errors(); + + for (_,val) in erros.iter() { + for error in val.iter() { + message.push_str(error.message.as_ref().unwrap()); + } + } + } else if possible_parse_violation.is_some() { + message.clear(); + message.push_str(possible_parse_violation.unwrap()); + } + + json! [{ "code": "error.input", "message": &message }] } #[catch(500)] @@ -22,5 +198,5 @@ pub fn internal_server_error(req: &Request) -> Value { let error_message = req .local_cache(|| Some(UserErrorMessage("Internal server error".to_owned()))); - json! [{"label": "internal.error", "message": error_message}] + json! [{"code": "error.internal", "message": error_message}] } \ No newline at end of file diff --git a/src/guards.rs b/src/guards.rs index 558cce7..07e69f3 100644 --- a/src/guards.rs +++ b/src/guards.rs @@ -1,7 +1,7 @@ use rocket::{ data::{self, Data, FromData, Limits}, http::Status, - request::{self, local_cache, FromRequest, Request}, + request::{local_cache, FromRequest, Request}, }; use serde::{Deserialize, Serialize}; use uuid::Uuid; diff --git a/src/lib.rs b/src/lib.rs index 53492e5..bed810e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,6 @@ use std::sync::mpsc; use std::sync::Arc; use std::sync::Mutex; use std::thread; -use rocket::serde::json::Json; #[cfg(feature = "process")] pub mod entities; diff --git a/src/main.rs b/src/main.rs index 7a03407..597cfd1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ pub use entities::*; pub use methods::*; #[cfg(feature = "sql")] pub use migrator::*; -use open_stock::{catchers, guards}; +use open_stock::{catchers}; #[cfg(feature = "sql")] extern crate argon2; @@ -49,9 +49,6 @@ impl Fairing for CORS { async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) { let access_origin = dotenv::var("ACCESS_ORIGIN").unwrap(); - // Permit `localhost:3000` when DEMO mode is enabled. - // `request.host().unwrap().domain().eq( &access_origin)` - response.set_header(Header::new("Access-Control-Allow-Origin", access_origin)); response.set_header(Header::new( "Access-Control-Allow-Methods", @@ -79,7 +76,8 @@ fn rocket() -> _ { catchers::not_authorized, catchers::internal_server_error, catchers::not_found, - catchers::unprocessable_entry + catchers::unprocessable_entry, + catchers::general_catcher, ]) .attach(Db::init()) .attach(CORS) diff --git a/src/methods/common.rs b/src/methods/common.rs index d60790d..f45d04b 100644 --- a/src/methods/common.rs +++ b/src/methods/common.rs @@ -28,9 +28,10 @@ use sea_orm::{ColumnTrait, DatabaseConnection, DbErr, EntityTrait, QuerySelect}; use sea_orm::ActiveValue::Set; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use validator::Validate; use crate::session::ActiveModel; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Validate)] pub struct Name { pub first: String, pub middle: String, @@ -49,7 +50,7 @@ impl Name { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Validate)] pub struct ContactInformation { pub name: String, pub mobile: MobileNumber, @@ -58,7 +59,7 @@ pub struct ContactInformation { pub address: Address, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Validate)] pub struct MobileNumber { pub number: String, pub valid: bool, @@ -108,14 +109,14 @@ pub type OrderList = Vec; pub type NoteList = Vec; pub type HistoryList = Vec>; -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct History { pub item: T, pub reason: String, pub timestamp: DateTime, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Validate)] pub struct Email { pub root: String, pub domain: String, @@ -145,7 +146,7 @@ impl Email { } } -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Validate)] pub struct Note { pub message: String, pub author: String, @@ -163,7 +164,7 @@ impl Display for Note { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Validate)] pub struct Address { pub street: String, pub street2: String, @@ -174,7 +175,7 @@ pub struct Address { pub lon: f64, } -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Validate)] pub struct Location { pub store_code: String, pub store_id: String, @@ -189,7 +190,7 @@ pub type TagList = Vec; pub type Tag = String; pub type Id = String; -#[derive(Debug, JsonSchema)] +#[derive(Debug, JsonSchema, Validate)] pub struct SessionRaw { pub id: String, pub key: String, @@ -197,7 +198,7 @@ pub struct SessionRaw { pub expiry: DateTime, } -#[derive(Debug, Clone, JsonSchema)] +#[derive(Debug, Clone, JsonSchema, Validate)] pub struct Session { pub id: String, pub key: String, @@ -220,18 +221,16 @@ impl Into for Session { impl Session { pub fn has_permission(self, permission: Action) -> bool { - let action = match self + let action = self .employee .level .into_iter() - .find(|x| x.action == permission) - { - Some(e) => e, - None => Access { + .find(|x| x.action == permission).unwrap_or_else(|| + Access { action: permission, authority: 0, - }, - }; + } + ); if action.action == Action::GenerateTemplateContent { true @@ -349,6 +348,7 @@ pub async fn cookie_status_wrapper( #[derive(Debug, Serialize, Deserialize)] pub struct ErrorResponse { message: String, + code: String } #[cfg(feature = "process")] @@ -356,30 +356,35 @@ impl ErrorResponse { pub fn create_error(message: &str) -> Error { Error::StandardError(Json(ErrorResponse { message: message.to_string(), + code: "error.custom".to_string() })) } pub fn input_error() -> Error { Error::InputError(Json(ErrorResponse { message: "Unable to update fields due to malformed inputs".to_string(), + code: "error.input".to_string() })) } pub fn unauthorized(action: Action) -> Error { Error::Unauthorized(Json(ErrorResponse { message: format!("User lacks {:?} permission.", action), + code: "error.unauthorized".to_string() })) } pub fn custom_unauthorized(message: &str) -> Error { Error::Unauthorized(Json(ErrorResponse { message: message.to_string(), + code: "error.unauthorized.custom".to_string() })) } - pub fn db_err(message: sea_orm::DbErr) -> Error { + pub fn db_err(message: DbErr) -> Error { Error::DbError(Json(ErrorResponse { message: format!("SQL error, reason: {}", message), + code: "error.database.query".to_string() })) } } diff --git a/src/methods/customer/handlers.rs b/src/methods/customer/handlers.rs index cab9f2f..2733971 100644 --- a/src/methods/customer/handlers.rs +++ b/src/methods/customer/handlers.rs @@ -14,6 +14,7 @@ use rocket::{post}; use rocket_db_pools::Connection; use rocket_okapi::{openapi, openapi_get_routes_spec}; use rocket_okapi::settings::OpenApiSettings; +use crate::catchers::Validated; use super::{Customer, CustomerInput}; @@ -178,9 +179,9 @@ async fn update( conn: Connection, id: &str, cookies: &CookieJar<'_>, - input_data: Json, + input_data: Validated>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -197,9 +198,9 @@ async fn update_contact_info( conn: Connection, id: &str, cookies: &CookieJar<'_>, - input_data: Json, + input_data: Validated>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -216,9 +217,9 @@ async fn update_contact_info( pub async fn create( conn: Connection, cookies: &CookieJar<'_>, - input_data: Json, + input_data: Validated>, ) -> Result, Error> { - let new_transaction = input_data.clone().into_inner(); + let new_transaction = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; diff --git a/src/methods/customer/structs.rs b/src/methods/customer/structs.rs index d5f3549..8f3794b 100644 --- a/src/methods/customer/structs.rs +++ b/src/methods/customer/structs.rs @@ -20,11 +20,12 @@ use sea_orm::{ use sea_orm::QueryOrder; use serde::{Deserialize, Serialize}; use serde_json::json; +use validator::Validate; use crate::entities::customer::ActiveModel; use crate::Session; #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct Customer { pub id: Id, pub name: String, @@ -41,7 +42,7 @@ pub struct Customer { } #[cfg(feature = "process")] -#[derive(Serialize, Deserialize, Clone, FromQueryResult, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, FromQueryResult, JsonSchema, Validate)] pub struct CustomerWithTransactions { pub id: Id, pub name: String, @@ -60,7 +61,7 @@ pub struct CustomerWithTransactions { } #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct CustomerWithTransactionsOut { pub id: Id, pub name: String, @@ -79,7 +80,7 @@ pub struct CustomerWithTransactionsOut { } #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct CustomerInput { pub name: String, diff --git a/src/methods/employee/handlers.rs b/src/methods/employee/handlers.rs index 3e1789a..3943697 100644 --- a/src/methods/employee/handlers.rs +++ b/src/methods/employee/handlers.rs @@ -1,10 +1,11 @@ -use std::time::Duration; - use crate::entities::session; use crate::methods::{cookie_status_wrapper, Error, ErrorResponse, History, Name}; use crate::pool::Db; -use crate::{check_permissions, example_employee, tenants, AuthenticationLog, Kiosk, Session, create_cookie}; +use crate::catchers::Validated; +use crate::{check_permissions, example_employee, tenants, AuthenticationLog, Kiosk, Session, create_cookie, LogRequest, Auth}; use chrono::{Days, Duration as ChronoDuration, Utc}; +use std::time::Duration; + use okapi::openapi3::OpenApi; use rocket::get; use rocket::http::{Cookie, CookieJar, SameSite}; @@ -14,9 +15,7 @@ use rocket::{post}; use rocket_db_pools::Connection; use rocket_okapi::{openapi, openapi_get_routes_spec}; use rocket_okapi::settings::OpenApiSettings; -use schemars::JsonSchema; use sea_orm::{EntityTrait, Set}; -use serde::{Deserialize, Serialize}; use serde_json::json; use uuid::Uuid; @@ -177,9 +176,9 @@ async fn update( conn: Connection, id: &str, cookies: &CookieJar<'_>, - input_data: Json, + input_data: Validated>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -204,22 +203,15 @@ async fn update( } } -#[derive(Deserialize, Serialize, Clone, JsonSchema)] -pub struct Auth { - pub pass: String, - pub kiosk_id: String, - pub tenant_id: String, -} - #[openapi(tag = "Employee")] #[post("/auth/", data = "")] pub async fn auth( id: &str, conn: Connection, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let input = input_data.clone().into_inner(); + let input = input_data.clone().0.into_inner(); let db = conn.into_inner(); let default_employee = example_employee(); @@ -231,7 +223,7 @@ pub async fn auth( key: String::new(), employee: default_employee.into(), expiry: Utc::now().checked_add_days(Days::new(1)).unwrap(), - tenant_id: input_data.tenant_id.clone(), + tenant_id: input.tenant_id.clone(), }, &input.pass, &db, @@ -296,10 +288,10 @@ pub async fn auth( pub async fn auth_rid( rid: &str, conn: Connection, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let input = input_data.clone().into_inner(); + let input = input_data.clone().0.into_inner(); let db = conn.into_inner(); let default_employee = example_employee(); @@ -308,7 +300,7 @@ pub async fn auth_rid( key: String::new(), employee: default_employee.into(), expiry: Utc::now().checked_add_days(Days::new(1)).unwrap(), - tenant_id: input_data.tenant_id.clone(), + tenant_id: input.tenant_id.clone(), }; match Employee::verify_with_rid(rid, session.clone(), &input.pass, &db).await { @@ -401,10 +393,10 @@ pub async fn auth_rid( #[post("/", data = "")] pub async fn create( conn: Connection, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let new_transaction = input_data.clone().into_inner(); + let new_transaction = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -430,23 +422,16 @@ pub async fn create( } } -#[derive(Serialize, Deserialize, Clone, JsonSchema)] -pub struct LogRequest { - pub kiosk: String, - pub reason: String, - pub in_or_out: String, -} - #[openapi(tag = "Employee")] #[post("/log/", data = "")] pub async fn log( conn: Connection, - input_data: Json, + input_data: Validated>, id: &str, cookies: &CookieJar<'_>, ) -> Result, Error> { let db = conn.into_inner(); - let data = input_data.into_inner(); + let data = input_data.0.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; check_permissions!(session.clone(), Action::FetchEmployee); diff --git a/src/methods/employee/structs.rs b/src/methods/employee/structs.rs index eb1f7f7..ea1479b 100644 --- a/src/methods/employee/structs.rs +++ b/src/methods/employee/structs.rs @@ -20,6 +20,13 @@ use crate::Session; #[cfg(feature = "process")] use crate::methods::convert_addr_to_geo; +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] +pub struct Auth { + pub pass: String, + pub kiosk_id: String, + pub tenant_id: String, +} + #[cfg(feature = "types")] #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] pub enum AccountType { @@ -27,8 +34,15 @@ pub enum AccountType { Managerial } +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] +pub struct LogRequest { + pub kiosk: String, + pub reason: String, + pub in_or_out: String, +} + #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Validate)] pub struct Employee { pub id: Id, pub rid: String, @@ -46,7 +60,7 @@ pub struct Employee { } #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Validate)] pub struct Access { pub action: T, pub authority: i32, @@ -115,13 +129,13 @@ pub fn all_actions() -> Vec> { /// Stores a password hash, signed as a key using the users login ID. /// Upon logging in using a client portal, the pre-sign object is signed using the provided ID - /// if the hash matches that which is given, authentication can be approved. -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Validate)] pub struct EmployeeAuth { pub hash: String, } #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct EmployeeInput { pub name: Name, pub rid: i32, @@ -170,6 +184,7 @@ impl Display for Employee { #[cfg(feature = "process")] use argon2::{self, Config}; use schemars::JsonSchema; +use validator::Validate; #[cfg(feature = "methods")] impl Employee { @@ -421,7 +436,7 @@ impl Employee { } #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Validate)] pub struct Attendance { pub track_type: TrackType, pub kiosk: Id, diff --git a/src/methods/helpers/handlers.rs b/src/methods/helpers/handlers.rs index 6ca0678..76a1449 100644 --- a/src/methods/helpers/handlers.rs +++ b/src/methods/helpers/handlers.rs @@ -4,13 +4,12 @@ use std::time::Duration; use chrono::{Days, Utc}; use geo::point; use rocket::{get, http::CookieJar, post, serde::json::Json}; -use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{check_permissions, example_employee, methods::{ cookie_status_wrapper, Action, Address, Customer, Employee, Error, ErrorResponse, Product, Promotion, Session, Store, Transaction, -}, pool::Db, All, ContactInformation, Email, EmployeeInput, Kiosk, MobileNumber, NewTenantInput, NewTenantResponse, Tenant, TenantSettings, session, all_actions, AccountType}; +}, pool::Db, All, ContactInformation, Email, EmployeeInput, Kiosk, MobileNumber, NewTenantInput, NewTenantResponse, Tenant, TenantSettings, session, all_actions, AccountType, Distance}; use geo::VincentyDistance; use okapi::openapi3::OpenApi; use photon_geocoding::{ @@ -22,9 +21,8 @@ use rocket::time::OffsetDateTime; use rocket_db_pools::Connection; use rocket_okapi::{openapi, openapi_get_routes_spec}; use rocket_okapi::settings::OpenApiSettings; -use schemars::JsonSchema; use sea_orm::EntityTrait; -use serde_json::json; +use crate::catchers::Validated; use crate::session::ActiveModel; pub fn documented_routes(_settings: &OpenApiSettings) -> (Vec, OpenApi) { @@ -128,11 +126,11 @@ pub async fn generate_template(conn: Connection) -> Result, Error> #[post("/new", data = "")] pub async fn new_tenant( conn: Connection, - tenant_input: Json, + tenant_input: Validated>, _cookies: &CookieJar<'_>, ) -> Result, Error> { let db = conn.into_inner(); - let data = tenant_input.into_inner(); + let data = tenant_input.0.into_inner(); // Create new Tenant let tenant_id = Uuid::new_v4().to_string(); @@ -325,13 +323,6 @@ pub async fn suggest_addr( } } -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct Distance { - store_id: String, - store_code: String, - distance: f64, -} - #[openapi(tag = "Helpers")] #[get("/distance/")] pub async fn distance_to_stores( diff --git a/src/methods/helpers/structs.rs b/src/methods/helpers/structs.rs index 8b4171a..5c0afcd 100644 --- a/src/methods/helpers/structs.rs +++ b/src/methods/helpers/structs.rs @@ -1,10 +1,18 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use validator::Validate; use crate::{Customer, Employee, Kiosk, Product, Promotion, Store, Tenant, Transaction}; +#[derive(Serialize, Deserialize, JsonSchema, Validate)] +pub struct Distance { + pub store_id: String, + pub store_code: String, + pub distance: f64, +} + #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, JsonSchema)] +#[derive(Serialize, Deserialize, JsonSchema, Validate)] pub struct All { pub employee: Employee, pub stores: Vec, @@ -16,7 +24,7 @@ pub struct All { pub kiosk: Kiosk, } -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct NewTenantInput { pub(crate) name: String, pub(crate) email: String, @@ -24,7 +32,7 @@ pub struct NewTenantInput { pub(crate) password: String, } -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct NewTenantResponse { pub tenant_id: String, pub api_key: String, diff --git a/src/methods/kiosk/handlers.rs b/src/methods/kiosk/handlers.rs index 6d5955a..7a2ccba 100644 --- a/src/methods/kiosk/handlers.rs +++ b/src/methods/kiosk/handlers.rs @@ -8,6 +8,7 @@ use rocket::{get, post}; use rocket_db_pools::Connection; use rocket_okapi::{openapi, openapi_get_routes_spec}; use rocket_okapi::settings::OpenApiSettings; +use crate::catchers::Validated; use crate::{ check_permissions, @@ -49,10 +50,10 @@ pub async fn get( #[post("/", data = "")] pub async fn initialize( conn: Connection, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -73,10 +74,10 @@ pub async fn initialize( pub async fn update( conn: Connection, id: &str, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -93,10 +94,10 @@ pub async fn update( pub async fn update_preferences( conn: Connection, id: &str, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -149,10 +150,10 @@ pub async fn delete( pub async fn auth_log( conn: Connection, id: &str, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result<(), Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; diff --git a/src/methods/kiosk/structs.rs b/src/methods/kiosk/structs.rs index 0716ba3..c478ac7 100644 --- a/src/methods/kiosk/structs.rs +++ b/src/methods/kiosk/structs.rs @@ -14,23 +14,24 @@ use sea_orm::{ use serde::{Deserialize, Serialize}; use serde_json::json; use uuid::Uuid; +use validator::Validate; use crate::entities::kiosk::Model; #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct KioskPreferences { pub printer_id: String, } #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Validate)] pub struct AuthenticationLog { pub employee_id: String, pub successful: bool, } #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct Kiosk { /// Standard Unique Identification pub id: String, @@ -52,7 +53,7 @@ pub struct Kiosk { } #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct KioskInit { name: String, store_id: String, diff --git a/src/methods/payment/structs.rs b/src/methods/payment/structs.rs index 9cbf9eb..4928046 100644 --- a/src/methods/payment/structs.rs +++ b/src/methods/payment/structs.rs @@ -3,9 +3,10 @@ use std::fmt::Display; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use validator::Validate; #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct Payment { pub id: String, pub payment_method: PaymentMethod, @@ -24,14 +25,14 @@ pub struct Payment { } #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct Price { pub quantity: f32, pub currency: String, } #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct PaymentProcessor { pub location: String, pub employee: String, @@ -86,7 +87,7 @@ pub enum PaymentAction { } #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct CardDetails { pub card_brand: String, pub last_4: String, @@ -106,7 +107,7 @@ pub struct CardDetails { } #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct PaymentTimeline { pub authorized_at: String, pub captured_at: String, diff --git a/src/methods/product/handlers.rs b/src/methods/product/handlers.rs index f59b9a9..564db19 100644 --- a/src/methods/product/handlers.rs +++ b/src/methods/product/handlers.rs @@ -9,6 +9,7 @@ use rocket::{post}; use rocket_okapi::{openapi, openapi_get_routes_spec}; use rocket_db_pools::Connection; use rocket_okapi::settings::OpenApiSettings; +use crate::catchers::Validated; use super::{Product, ProductWPromotion, Promotion, PromotionInput}; @@ -147,10 +148,10 @@ pub async fn search_with_associated_promotions( async fn update( conn: Connection, id: &str, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -166,10 +167,10 @@ async fn update( #[post("/", data = "")] pub async fn create( conn: Connection, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let new_transaction = input_data.clone().into_inner(); + let new_transaction = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -249,10 +250,10 @@ pub async fn get_promotion_by_query( async fn update_promotion( conn: Connection, id: &str, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; @@ -268,10 +269,10 @@ async fn update_promotion( #[post("/promotion", data = "")] pub async fn create_promotion( conn: Connection, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let new_promotion = input_data.clone().into_inner(); + let new_promotion = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; diff --git a/src/methods/product/structs.rs b/src/methods/product/structs.rs index 4356b79..a34d1b3 100644 --- a/src/methods/product/structs.rs +++ b/src/methods/product/structs.rs @@ -32,6 +32,7 @@ use crate::{ #[cfg(feature = "process")] use futures::future::join_all; use schemars::JsonSchema; +use validator::Validate; use crate::product::example::example_products; #[cfg(feature = "types")] @@ -43,7 +44,7 @@ pub enum ProductVisibility { } #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, Default, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, Default, JsonSchema, Validate)] pub struct ProductIdentification { pub sku: String, pub ean: String, @@ -53,7 +54,7 @@ pub struct ProductIdentification { } #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] /// A product, containing a list of `Vec`, an identifiable `sku` along with identifying information such as `tags`, `description` and `specifications`. /// > Stock-relevant information about a product is kept under each variant, thus allowing for modularity of different variants and a fine-grained control over your inventory. pub struct Product { @@ -82,7 +83,7 @@ pub struct Product { } #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] pub struct ProductWPromotion { pub product: Product, pub promotions: Vec, @@ -393,7 +394,7 @@ impl Product { } } -#[derive(Debug, Clone, Serialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, JsonSchema, Validate)] pub struct ProductPurchase { // Is the barcode of the product. pub id: String, @@ -580,7 +581,7 @@ impl<'de> Deserialize<'de> for ProductPurchase { } #[cfg(feature = "types")] -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Validate)] pub struct ProductInstance { pub id: String, #[serde(default = "default_fulfillment")] @@ -597,7 +598,7 @@ fn default_fulfillment() -> FulfillmentStatus { } #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct FulfillmentStatus { pub pick_status: PickStatus, pub pick_history: Vec>, @@ -617,7 +618,7 @@ pub enum PickStatus { } #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct ProductExchange { pub method_type: TransactionType, pub product_code: ProductCode, diff --git a/src/methods/product/variant.rs b/src/methods/product/variant.rs index 24b633f..a779929 100644 --- a/src/methods/product/variant.rs +++ b/src/methods/product/variant.rs @@ -18,6 +18,7 @@ use crate::products; use crate::{ProductIdentification, Session}; use serde_json::json; use uuid::Uuid; +use validator::Validate; #[cfg(feature = "types")] pub type VariantIdTag = Vec; @@ -29,14 +30,14 @@ type VariantId = String; pub type VariantCategoryList = Vec; #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] pub struct VariantCategory { pub category: String, pub variants: Vec, } #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] /// #### Information for a Variant. /// This includes its name, identification, stock information and quantities, prices, etc. pub struct VariantInformation { @@ -96,7 +97,7 @@ impl Display for VariantInformation { } #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] pub struct Promotion { pub id: Id, pub name: String, @@ -107,7 +108,7 @@ pub struct Promotion { } #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] pub struct PromotionInput { name: String, buy: PromotionBuy, @@ -311,7 +312,7 @@ impl Promotion { /// Represents all sub-variant types; i.e. All 'White' variants, whether small, long-sleeve, ... it represents the sub-group of all which are 'White'. #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema, Validate)] pub struct Variant { pub name: String, pub images: Vec, @@ -321,7 +322,7 @@ pub struct Variant { } #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] pub struct StockInformation { pub stock_group: String, pub sales_group: String, diff --git a/src/methods/stml/order.rs b/src/methods/stml/order.rs index 932b8b9..913b6d5 100644 --- a/src/methods/stml/order.rs +++ b/src/methods/stml/order.rs @@ -7,9 +7,10 @@ use crate::methods::{ use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use validator::Validate; #[cfg(feature = "types")] -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Validate)] pub struct Order { pub id: Id, @@ -59,7 +60,7 @@ pub struct OrderState { } #[cfg(feature = "types")] -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Validate)] pub struct OrderStatusAssignment { pub status: OrderStatus, pub assigned_products: Vec, @@ -137,7 +138,7 @@ impl Display for OrderStatusAssignment { } #[cfg(feature = "types")] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, Validate)] pub struct TransitInformation { pub shipping_company: ContactInformation, pub query_url: Url, diff --git a/src/methods/stml/stock.rs b/src/methods/stml/stock.rs index 4a55e48..4cb7c16 100644 --- a/src/methods/stml/stock.rs +++ b/src/methods/stml/stock.rs @@ -1,5 +1,6 @@ use rocket_okapi::JsonSchema; use serde::{Deserialize, Serialize}; +use validator::Validate; use crate::methods::Location; @@ -7,14 +8,14 @@ use crate::methods::Location; pub type StockList = Vec; #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] pub struct Stock { pub store: Location, pub quantity: Quantity, } #[cfg(feature = "types")] -#[derive(Deserialize, Serialize, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, Validate)] pub struct Quantity { pub quantity_sellable: f32, pub quantity_unsellable: f32, diff --git a/src/methods/store/handlers.rs b/src/methods/store/handlers.rs index d9b3846..5991ddb 100644 --- a/src/methods/store/handlers.rs +++ b/src/methods/store/handlers.rs @@ -3,6 +3,7 @@ use rocket::{get, http::CookieJar, post, serde::json::Json}; use rocket_db_pools::Connection; use rocket_okapi::{openapi, openapi_get_routes_spec}; use rocket_okapi::settings::OpenApiSettings; +use crate::catchers::Validated; use crate::{ check_permissions, @@ -94,9 +95,9 @@ async fn update( conn: Connection, id: &str, cookies: &CookieJar<'_>, - input_data: Json, + input_data: Validated>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; diff --git a/src/methods/store/structs.rs b/src/methods/store/structs.rs index 13981b5..16980ca 100644 --- a/src/methods/store/structs.rs +++ b/src/methods/store/structs.rs @@ -20,10 +20,11 @@ use crate::methods::convert_addr_to_geo; use crate::methods::{ContactInformation, Id}; use crate::Session; use serde_json::json; +use validator::Validate; use crate::methods::store::example::example_stores; #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Validate)] pub struct Store { pub id: Id, pub name: String, diff --git a/src/methods/supplier/handlers.rs b/src/methods/supplier/handlers.rs index 812b322..b7ab933 100644 --- a/src/methods/supplier/handlers.rs +++ b/src/methods/supplier/handlers.rs @@ -10,6 +10,7 @@ use rocket::{post}; use rocket_db_pools::Connection; use rocket_okapi::{openapi, openapi_get_routes_spec}; use rocket_okapi::settings::OpenApiSettings; +use crate::catchers::Validated; use super::{Supplier, SupplierInput}; @@ -121,9 +122,9 @@ async fn update( conn: Connection, id: &str, cookies: &CookieJar<'_>, - input_data: Json, + input_data: Validated>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; diff --git a/src/methods/supplier/structs.rs b/src/methods/supplier/structs.rs index 1d45fc1..f2e2113 100644 --- a/src/methods/supplier/structs.rs +++ b/src/methods/supplier/structs.rs @@ -22,10 +22,11 @@ use sea_orm::ActiveValue::Set; use serde::{Deserialize, Serialize}; use serde_json::json; use uuid::Uuid; +use validator::Validate; use crate::methods::supplier::example::example_supplier; #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct Supplier { pub id: String, pub name: Name, @@ -38,7 +39,7 @@ pub struct Supplier { } #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct SupplierInput { pub name: Name, pub contact: ContactInformation, diff --git a/src/methods/tenant/structs.rs b/src/methods/tenant/structs.rs index 42ca747..4a72a54 100644 --- a/src/methods/tenant/structs.rs +++ b/src/methods/tenant/structs.rs @@ -5,16 +5,17 @@ use schemars::JsonSchema; #[cfg(feature = "process")] use sea_orm::{DbConn, DbErr, EntityTrait, InsertResult}; use serde::{Deserialize, Serialize}; +use validator::Validate; use crate::Id; #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, Default, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, Default, JsonSchema, Validate)] pub struct TenantSettings {} #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct Tenant { pub tenant_id: Id, diff --git a/src/methods/transaction/handlers.rs b/src/methods/transaction/handlers.rs index e1e4808..efd6aa9 100644 --- a/src/methods/transaction/handlers.rs +++ b/src/methods/transaction/handlers.rs @@ -6,6 +6,7 @@ use rocket::{post}; use rocket_db_pools::Connection; use rocket_okapi::{openapi, openapi_get_routes_spec}; use rocket_okapi::settings::OpenApiSettings; +use crate::catchers::Validated; use super::{Transaction, TransactionInit, TransactionInput}; use crate::methods::employee::Action; @@ -141,10 +142,10 @@ pub async fn receivables_search( async fn update( conn: Connection, id: &str, - input_data: Json, + input_data: Validated>, cookies: &CookieJar<'_>, ) -> Result, Error> { - let input_data = input_data.clone().into_inner(); + let input_data = input_data.clone().0.into_inner(); let db = conn.into_inner(); let session = cookie_status_wrapper(&db, cookies).await?; diff --git a/src/methods/transaction/structs.rs b/src/methods/transaction/structs.rs index f29420a..feff053 100644 --- a/src/methods/transaction/structs.rs +++ b/src/methods/transaction/structs.rs @@ -29,10 +29,11 @@ use crate::{ }; #[cfg(feature = "process")] use sea_orm::DbConn; +use validator::Validate; use crate::transaction::example::example_transaction; #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct TransactionCustomer { pub customer_type: CustomerType, pub customer_id: String, @@ -47,7 +48,7 @@ pub enum CustomerType { } #[cfg(feature = "process")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct QuantityAlterationIntent { pub variant_code: String, pub product_sku: String, @@ -84,7 +85,7 @@ pub enum TransactionType { /// `IN:` As a purchase order it's transaction type takes the form of "In", the customer object will be treated as the company bought from and the payment as an outward payment in exchange for the goods.
/// `OUT:` A sale - It can occur in-store or online and is comprised of the sale of goods outlined in the order list. #[cfg(feature = "types")] -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, Validate)] pub struct Transaction { pub id: Id, @@ -107,7 +108,7 @@ pub struct Transaction { #[cfg(feature = "process")] #[cfg(feature = "types")] -#[derive(Serialize, Deserialize, Clone, FromQueryResult, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, FromQueryResult, JsonSchema, Validate)] pub struct DerivableTransaction { pub id: Id, @@ -126,7 +127,7 @@ pub struct DerivableTransaction { } #[cfg(feature = "types")] -#[derive(Deserialize, Clone, JsonSchema)] +#[derive(Deserialize, Clone, JsonSchema, Validate)] pub struct TransactionInput { pub customer: TransactionCustomer, pub transaction_type: TransactionType, @@ -143,7 +144,7 @@ pub struct TransactionInput { } #[cfg(feature = "types")] -#[derive(Deserialize, Clone, JsonSchema)] +#[derive(Deserialize, Clone, JsonSchema, Validate)] pub struct TransactionInit { pub customer: TransactionCustomer, pub transaction_type: TransactionType, diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..60822bc --- /dev/null +++ b/start.sh @@ -0,0 +1,9 @@ +docker compose up -d --scale open-stock=0 && DATABASE_URL=mysql://root:root@localhost:3306/stock \ +ACCESS_ORIGIN=http://localhost:3001 \ +ROCKET_SECRET_KEY=e5c63abf90fb076d7037a32d8dc2951c4b402e7357ca84b0da8e03595f84b30f \ +DEMO=1 \ +RUST_BACKTRACE=1 \ +RUST_LOG=debug \ +ROCKET_ADDRESS=0.0.0.0 \ +ROCKET_PORT=8000 \ +ROCKET_ENV=dev cargo run \ No newline at end of file