diff --git a/src/main.rs b/src/main.rs index 00abeff..4aa3a4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use dao::main_dao; mod dao; mod routes; +mod services; mod utils; #[tokio::main] diff --git a/src/routes/click.rs b/src/routes/click.rs index 3a1cd90..5a56043 100644 --- a/src/routes/click.rs +++ b/src/routes/click.rs @@ -1,11 +1,19 @@ +use std::sync::Arc; + +use actix_web::{ + post, + web::{self, Json}, +}; use actix_web_utils::extensions::typed_response::TypedHttpResponse; use realtor_lp_types::{domain::click::Click, dto::payloads::click::ClickForCreationPayload}; use sqlx::PgPool; -use crate::{dao, handle_db_write_op, handle_tx, success}; +use crate::{dao, handle_db_write_op, handle_tx, services, success}; -pub async fn new_click(conn: &PgPool, click: ClickForCreationPayload) -> TypedHttpResponse<()> { - let mut transaction = handle_tx!(conn.begin()); - handle_db_write_op!(dao::click::insert_click(conn, click.into()), transaction); - success!(()) +#[post("/click")] +pub async fn new_click( + db_conn: web::Data>, + click: Json, +) -> TypedHttpResponse<()> { + services::click::new_click(&db_conn, click.0).await } diff --git a/src/routes/main_router.rs b/src/routes/main_router.rs index 36835ab..bc8d4a4 100644 --- a/src/routes/main_router.rs +++ b/src/routes/main_router.rs @@ -13,6 +13,8 @@ use sqlx::PgPool; use crate::utils::s3; +use super::{click, property, realtor, trackable, view}; + pub const HOST_ADDR: &str = "0.0.0.0"; pub const HOST_PORT: u16 = 8080; @@ -30,8 +32,23 @@ pub async fn start_all_routes(start_time: i64, db_conn: Arc) -> Result<( .app_data(MultipartFormConfig::default().memory_limit(52_428_800)) .service( web::scope("/api") - .service(web::scope("/admin")) - .service(web::scope("/read")), + .service( + web::scope("/admin") + .service(property::new_property) + .service(property::update_property) + .service(realtor::new_realtor_profile) + .service(realtor::update_realtor_profile), + ) + .service( + web::scope("/public") + .service(click::new_click) + .service(property::fetch_realtor_properties_paged) + .service(realtor::get_realtor_by_shortcode) + .service(realtor::get_realtor_with_id) + .service(trackable::create_trackable_profile) + .service(trackable::validate_trackable_id) + .service(view::new_view), + ), ) /*.service( diff --git a/src/routes/project.rs b/src/routes/project.rs index 785ca08..c598039 100644 --- a/src/routes/project.rs +++ b/src/routes/project.rs @@ -1,3 +1,9 @@ +use std::sync::Arc; + +use actix_web::{ + post, + web::{self, Json}, +}; use actix_web_utils::extensions::typed_response::TypedHttpResponse; use realtor_lp_types::{ domain::project::Project, dto::payloads::project::ProjectForCreationPayload, @@ -7,19 +13,12 @@ use uuid::Uuid; use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found}; +#[post("/project")] pub async fn create_project( - conn: &PgPool, - project: ProjectForCreationPayload, + db_conn: web::Data>, + project: Json, ) -> TypedHttpResponse { - unwrap_or_not_found!( - handle_db_read_op!(dao::realtor::get_realtor_with_id(conn, &project.realtor_id)), - "Realtor" - ); - let mut transaction = handle_tx!(conn.begin()); - success!(handle_db_write_op!( - dao::project::insert_project(&mut transaction, project.into()), - transaction - )); + todo!() } pub async fn fetch_realtor_projects( diff --git a/src/routes/property.rs b/src/routes/property.rs index 92ff606..bcf0d27 100644 --- a/src/routes/property.rs +++ b/src/routes/property.rs @@ -1,100 +1,58 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use actix_web::{ - get, - web::{Data, Path}, + get, post, put, + web::{self, Data, Json, Path}, }; use actix_web_utils::extensions::typed_response::TypedHttpResponse; use err::MessageResource; use realtor_lp_types::{ - domain::property::Property, dto::payloads::property::PropertyForCreationPayload, + domain::property::Property, + dto::{filter::Filter, payloads::property::PropertyForCreationPayload}, }; use sqlx::PgPool; use uuid::Uuid; -use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found}; +use crate::{ + dao, handle_db_read_op, handle_db_write_op, handle_tx, services, success, unwrap_or_not_found, +}; -pub async fn create_property( - conn: &PgPool, - property: PropertyForCreationPayload, +/// Admin +#[post("/property")] +pub async fn new_property( + db_conn: web::Data>, + property: Json, ) -> TypedHttpResponse { - let mut transaction = handle_tx!(conn.begin()); - let realtor = unwrap_or_not_found!( - handle_db_read_op!(dao::realtor::get_realtor_with_id( - conn, - &property.realtor_id - )), - "Realtor" - ); - - // Check for project existing - match property.project_id { - Some(project_id) => { - let project = unwrap_or_not_found!( - handle_db_read_op!(dao::project::get_project_with_id(conn, &project_id)), - "Project" - ); - if project.realtor_id != realtor.id { - return TypedHttpResponse::return_standard_error( - 400, - MessageResource::new_from_str( - "Realtor Id in project does not match realtor id in property.", - ), - ); - } - } - None => {} - }; - success!(handle_db_write_op!( - dao::property::insert_property(&mut transaction, property.into()), - transaction - )) + services::property::create_property(&db_conn, property.0).await } -pub async fn udpate_property( - conn: &PgPool, - property_id: Uuid, - property: PropertyForCreationPayload, +/// Admin +#[put("/property/{property_id}")] +pub async fn update_property( + db_conn: web::Data>, + property: Json, + property_id: Path, ) -> TypedHttpResponse { - let mut transaction = handle_tx!(conn.begin()); - let realtor = unwrap_or_not_found!( - handle_db_read_op!(dao::realtor::get_realtor_with_id( - conn, - &property.realtor_id - )), - "Realtor" - ); - - // Check for project existing - match property.project_id { - Some(project_id) => { - let project = unwrap_or_not_found!( - handle_db_read_op!(dao::project::get_project_with_id(conn, &project_id)), - "Project" - ); - if project.realtor_id != realtor.id { - return TypedHttpResponse::return_standard_error( - 400, - MessageResource::new_from_str( - "Realtor Id in project does not match realtor id in property.", - ), - ); - } - } - None => {} - }; - let mut property_insertable: Property = property.into(); - property_insertable.id = property_id; - success!(handle_db_write_op!( - dao::property::update_property(&mut transaction, property_insertable), - transaction - )) + services::property::udpate_property(&db_conn, *property_id, property.0).await } #[get("/properties/{realtor_id}/{page}")] pub async fn fetch_realtor_properties_paged( conn: Data>, path_vars: Path<(Uuid, i64)>, + query_params: web::Query>, ) -> TypedHttpResponse> { - success!(Default::default()) + let filters = parse_params_into_filters(query_params.0); + services::property::fetch_realtor_properties_paged(&conn, path_vars.0, path_vars.1, filters) + .await +} + +fn parse_params_into_filters(params: HashMap) -> Vec { + let mut filters: Vec = Vec::new(); + for (key, val) in params.into_iter() { + if let Some(filter) = Filter::from_query_param(key, val) { + filters.push(filter); + } + } + filters } diff --git a/src/routes/realtor.rs b/src/routes/realtor.rs index 51b0a96..f4801f2 100644 --- a/src/routes/realtor.rs +++ b/src/routes/realtor.rs @@ -1,3 +1,9 @@ +use std::sync::Arc; + +use actix_web::{ + get, post, put, + web::{Data, Json, Path}, +}; use actix_web_utils::extensions::typed_response::TypedHttpResponse; use realtor_lp_types::{ domain::realtor::Realtor, dto::payloads::realtor::RealtorForCreationPayload, @@ -5,49 +11,39 @@ use realtor_lp_types::{ use sqlx::PgPool; use uuid::Uuid; -use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found}; +use crate::{ + dao, handle_db_read_op, handle_db_write_op, handle_tx, services, success, unwrap_or_not_found, +}; +#[post("/realtor")] pub async fn new_realtor_profile( - conn: &PgPool, - realtor: RealtorForCreationPayload, + db_conn: Data>, + realtor: Json, ) -> TypedHttpResponse { - let mut transaction = handle_tx!(conn.begin()); - success!(handle_db_write_op!( - dao::realtor::insert_realtor(conn, realtor.into()), - transaction - )); + services::realtor::new_realtor_profile(&db_conn, realtor.0).await } +#[put("/realtor/{realtor_id}")] pub async fn update_realtor_profile( - conn: &PgPool, - realtor_id: Uuid, - realtor: RealtorForCreationPayload, + db_conn: Data>, + realtor_id: Path, + realtor: Json, ) -> TypedHttpResponse { - let mut transaction = handle_tx!(conn.begin()); - let mut realtor_insertable: Realtor = realtor.into(); - realtor_insertable.id = realtor_id; - success!(handle_db_write_op!( - dao::realtor::update_realtor(conn, realtor_insertable), - transaction - )); + services::realtor::update_realtor_profile(&db_conn, *realtor_id, realtor.0).await } +#[get("/realtor/shortcode/{shortcode}")] pub async fn get_realtor_by_shortcode( - conn: &PgPool, - realtor_shortcode: String, + db_conn: Data>, + realtor_shortcode: Path, ) -> TypedHttpResponse { - success!(unwrap_or_not_found!( - handle_db_read_op!(dao::realtor::get_realtor_with_shortcode( - conn, - &realtor_shortcode - )), - "Realtors" - )) + services::realtor::get_realtor_by_shortcode(&db_conn, realtor_shortcode.clone()).await } -pub async fn get_realtor_with_id(conn: &PgPool, realtor_id: Uuid) -> TypedHttpResponse { - success!(unwrap_or_not_found!( - handle_db_read_op!(dao::realtor::get_realtor_with_id(conn, &realtor_id)), - "Realtors" - )) +#[get("/realtor/{realtor_id}")] +pub async fn get_realtor_with_id( + db_conn: Data>, + realtor_id: Path, +) -> TypedHttpResponse { + services::realtor::get_realtor_with_id(&db_conn, *realtor_id).await } diff --git a/src/routes/trackable.rs b/src/routes/trackable.rs index 46a44c9..cb95ef5 100644 --- a/src/routes/trackable.rs +++ b/src/routes/trackable.rs @@ -1,3 +1,9 @@ +use std::sync::Arc; + +use actix_web::{ + get, post, + web::{Data, Json, Path}, +}; use actix_web_utils::extensions::typed_response::TypedHttpResponse; use realtor_lp_types::{ domain::trackable::Trackable, dto::payloads::trackable::TrackableForCreationPayload, @@ -5,26 +11,22 @@ use realtor_lp_types::{ use sqlx::PgPool; use uuid::Uuid; -use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found}; +use crate::{ + dao, handle_db_read_op, handle_db_write_op, handle_tx, services, success, unwrap_or_not_found, +}; +#[post("/tr")] pub async fn create_trackable_profile( - conn: &PgPool, - trackable: TrackableForCreationPayload, + db_conn: Data>, + trackable: Json, ) -> TypedHttpResponse { - let mut transaction = handle_tx!(conn.begin()); - let persisted_trackable = handle_db_write_op!( - dao::trackable::insert_trackable(&mut transaction, trackable.into()), - transaction - ); - success!(persisted_trackable) + services::trackable::create_trackable_profile(&db_conn, trackable.0).await } +#[get("/tr/{trackable_id}")] pub async fn validate_trackable_id( - conn: &PgPool, - trackable_id: Uuid, + db_conn: Data>, + trackable_id: Path, ) -> TypedHttpResponse { - success!(unwrap_or_not_found!( - handle_db_read_op!(dao::trackable::get_trackable_with_id(conn, &trackable_id)), - "Trackables" - )) + services::trackable::validate_trackable_id(&db_conn, *trackable_id).await } diff --git a/src/routes/view.rs b/src/routes/view.rs index 450e28a..32e6a6f 100644 --- a/src/routes/view.rs +++ b/src/routes/view.rs @@ -1,14 +1,19 @@ +use std::sync::Arc; + +use actix_web::{ + post, + web::{Data, Json}, +}; use actix_web_utils::extensions::typed_response::TypedHttpResponse; use realtor_lp_types::dto::payloads::view::ViewForCreationPayload; use sqlx::PgPool; -use crate::{dao, handle_db_write_op, handle_tx, success}; +use crate::{dao, handle_db_write_op, handle_tx, services, success}; -pub async fn new_view(conn: &PgPool, view: ViewForCreationPayload) -> TypedHttpResponse<()> { - let mut transaction = handle_tx!(conn.begin()); - handle_db_write_op!( - dao::view::insert_view(&mut transaction, view.into()), - transaction - ); - success!(()) +#[post("/view")] +pub async fn new_view( + db_conn: Data>, + view: Json, +) -> TypedHttpResponse<()> { + services::view::new_view(&db_conn, view.0).await } diff --git a/src/services/click.rs b/src/services/click.rs new file mode 100644 index 0000000..3a1cd90 --- /dev/null +++ b/src/services/click.rs @@ -0,0 +1,11 @@ +use actix_web_utils::extensions::typed_response::TypedHttpResponse; +use realtor_lp_types::{domain::click::Click, dto::payloads::click::ClickForCreationPayload}; +use sqlx::PgPool; + +use crate::{dao, handle_db_write_op, handle_tx, success}; + +pub async fn new_click(conn: &PgPool, click: ClickForCreationPayload) -> TypedHttpResponse<()> { + let mut transaction = handle_tx!(conn.begin()); + handle_db_write_op!(dao::click::insert_click(conn, click.into()), transaction); + success!(()) +} diff --git a/src/services/mod.rs b/src/services/mod.rs new file mode 100644 index 0000000..538bf95 --- /dev/null +++ b/src/services/mod.rs @@ -0,0 +1,12 @@ +#[allow(unused)] +pub mod click; +#[allow(unused)] +pub mod project; +#[allow(unused)] +pub mod property; +#[allow(unused)] +pub mod realtor; +#[allow(unused)] +pub mod trackable; +#[allow(unused)] +pub mod view; diff --git a/src/services/project.rs b/src/services/project.rs new file mode 100644 index 0000000..785ca08 --- /dev/null +++ b/src/services/project.rs @@ -0,0 +1,33 @@ +use actix_web_utils::extensions::typed_response::TypedHttpResponse; +use realtor_lp_types::{ + domain::project::Project, dto::payloads::project::ProjectForCreationPayload, +}; +use sqlx::PgPool; +use uuid::Uuid; + +use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found}; + +pub async fn create_project( + conn: &PgPool, + project: ProjectForCreationPayload, +) -> TypedHttpResponse { + unwrap_or_not_found!( + handle_db_read_op!(dao::realtor::get_realtor_with_id(conn, &project.realtor_id)), + "Realtor" + ); + let mut transaction = handle_tx!(conn.begin()); + success!(handle_db_write_op!( + dao::project::insert_project(&mut transaction, project.into()), + transaction + )); +} + +pub async fn fetch_realtor_projects( + conn: &PgPool, + realtor_id: Uuid, +) -> TypedHttpResponse> { + success!(handle_db_read_op!(dao::project::fetch_with_realtor_id( + conn, + &realtor_id + ))) +} diff --git a/src/services/property.rs b/src/services/property.rs new file mode 100644 index 0000000..0511763 --- /dev/null +++ b/src/services/property.rs @@ -0,0 +1,104 @@ +use std::sync::Arc; + +use actix_web_utils::extensions::typed_response::TypedHttpResponse; +use err::MessageResource; +use realtor_lp_types::{ + domain::property::Property, + dto::{filter::Filter, payloads::property::PropertyForCreationPayload}, +}; +use sqlx::PgPool; +use uuid::Uuid; + +use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found}; + +pub async fn create_property( + conn: &PgPool, + property: PropertyForCreationPayload, +) -> TypedHttpResponse { + let mut transaction = handle_tx!(conn.begin()); + let realtor = unwrap_or_not_found!( + handle_db_read_op!(dao::realtor::get_realtor_with_id( + conn, + &property.realtor_id + )), + "Realtor" + ); + + // Check for project existing + match property.project_id { + Some(project_id) => { + let project = unwrap_or_not_found!( + handle_db_read_op!(dao::project::get_project_with_id(conn, &project_id)), + "Project" + ); + if project.realtor_id != realtor.id { + return TypedHttpResponse::return_standard_error( + 400, + MessageResource::new_from_str( + "Realtor Id in project does not match realtor id in property.", + ), + ); + } + } + None => {} + }; + success!(handle_db_write_op!( + dao::property::insert_property(&mut transaction, property.into()), + transaction + )) +} + +pub async fn udpate_property( + conn: &PgPool, + property_id: Uuid, + property: PropertyForCreationPayload, +) -> TypedHttpResponse { + let mut transaction = handle_tx!(conn.begin()); + let realtor = unwrap_or_not_found!( + handle_db_read_op!(dao::realtor::get_realtor_with_id( + conn, + &property.realtor_id + )), + "Realtor" + ); + + // Check for project existing + match property.project_id { + Some(project_id) => { + let project = unwrap_or_not_found!( + handle_db_read_op!(dao::project::get_project_with_id(conn, &project_id)), + "Project" + ); + if project.realtor_id != realtor.id { + return TypedHttpResponse::return_standard_error( + 400, + MessageResource::new_from_str( + "Realtor Id in project does not match realtor id in property.", + ), + ); + } + } + None => {} + }; + let mut property_insertable: Property = property.into(); + property_insertable.id = property_id; + success!(handle_db_write_op!( + dao::property::update_property(&mut transaction, property_insertable), + transaction + )) +} + +pub async fn fetch_realtor_properties_paged( + conn: &PgPool, + realtor_id: Uuid, + page: i64, + filters: Vec, +) -> TypedHttpResponse> { + let mut all_properties_filtered = handle_db_read_op!( + dao::property::fetch_with_realtor_id_paged(conn, &realtor_id, &filters, &page) + ); + all_properties_filtered + .iter_mut() + .for_each(|property| property.admin_tag = None); // Remove admin tag for all + success!(all_properties_filtered) +} diff --git a/src/services/realtor.rs b/src/services/realtor.rs new file mode 100644 index 0000000..51b0a96 --- /dev/null +++ b/src/services/realtor.rs @@ -0,0 +1,53 @@ +use actix_web_utils::extensions::typed_response::TypedHttpResponse; +use realtor_lp_types::{ + domain::realtor::Realtor, dto::payloads::realtor::RealtorForCreationPayload, +}; +use sqlx::PgPool; +use uuid::Uuid; + +use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found}; + +pub async fn new_realtor_profile( + conn: &PgPool, + realtor: RealtorForCreationPayload, +) -> TypedHttpResponse { + let mut transaction = handle_tx!(conn.begin()); + success!(handle_db_write_op!( + dao::realtor::insert_realtor(conn, realtor.into()), + transaction + )); +} + +pub async fn update_realtor_profile( + conn: &PgPool, + realtor_id: Uuid, + realtor: RealtorForCreationPayload, +) -> TypedHttpResponse { + let mut transaction = handle_tx!(conn.begin()); + let mut realtor_insertable: Realtor = realtor.into(); + realtor_insertable.id = realtor_id; + success!(handle_db_write_op!( + dao::realtor::update_realtor(conn, realtor_insertable), + transaction + )); +} + +pub async fn get_realtor_by_shortcode( + conn: &PgPool, + realtor_shortcode: String, +) -> TypedHttpResponse { + success!(unwrap_or_not_found!( + handle_db_read_op!(dao::realtor::get_realtor_with_shortcode( + conn, + &realtor_shortcode + )), + "Realtors" + )) +} + +pub async fn get_realtor_with_id(conn: &PgPool, realtor_id: Uuid) -> TypedHttpResponse { + success!(unwrap_or_not_found!( + handle_db_read_op!(dao::realtor::get_realtor_with_id(conn, &realtor_id)), + "Realtors" + )) +} diff --git a/src/services/trackable.rs b/src/services/trackable.rs new file mode 100644 index 0000000..46a44c9 --- /dev/null +++ b/src/services/trackable.rs @@ -0,0 +1,30 @@ +use actix_web_utils::extensions::typed_response::TypedHttpResponse; +use realtor_lp_types::{ + domain::trackable::Trackable, dto::payloads::trackable::TrackableForCreationPayload, +}; +use sqlx::PgPool; +use uuid::Uuid; + +use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found}; + +pub async fn create_trackable_profile( + conn: &PgPool, + trackable: TrackableForCreationPayload, +) -> TypedHttpResponse { + let mut transaction = handle_tx!(conn.begin()); + let persisted_trackable = handle_db_write_op!( + dao::trackable::insert_trackable(&mut transaction, trackable.into()), + transaction + ); + success!(persisted_trackable) +} + +pub async fn validate_trackable_id( + conn: &PgPool, + trackable_id: Uuid, +) -> TypedHttpResponse { + success!(unwrap_or_not_found!( + handle_db_read_op!(dao::trackable::get_trackable_with_id(conn, &trackable_id)), + "Trackables" + )) +} diff --git a/src/services/view.rs b/src/services/view.rs new file mode 100644 index 0000000..450e28a --- /dev/null +++ b/src/services/view.rs @@ -0,0 +1,14 @@ +use actix_web_utils::extensions::typed_response::TypedHttpResponse; +use realtor_lp_types::dto::payloads::view::ViewForCreationPayload; +use sqlx::PgPool; + +use crate::{dao, handle_db_write_op, handle_tx, success}; + +pub async fn new_view(conn: &PgPool, view: ViewForCreationPayload) -> TypedHttpResponse<()> { + let mut transaction = handle_tx!(conn.begin()); + handle_db_write_op!( + dao::view::insert_view(&mut transaction, view.into()), + transaction + ); + success!(()) +}