From 8de10c4e3b2d415a618ffba3f3599bfb841d61e0 Mon Sep 17 00:00:00 2001 From: Franklin Date: Mon, 8 May 2023 12:46:26 -0400 Subject: [PATCH] Added arrangement, more property fields, and impls --- src/domain/arrangement/impls.rs | 56 ++++++++++++++++++++++++++++++ src/domain/arrangement/mod.rs | 10 ++++++ src/domain/format/impls.rs | 61 +++++++++++++++++++++++++++++++++ src/domain/format/mod.rs | 38 ++++++++++++++++++++ src/domain/mod.rs | 5 ++- src/domain/price.rs | 16 +++++++++ src/domain/property.rs | 20 ++++++----- src/domain/thing_pk/impls.rs | 2 +- src/dto/filter.rs | 17 +++++++++ src/dto/mod.rs | 1 + src/dto/payloads/project.rs | 10 +++--- src/dto/payloads/property.rs | 24 +++++++------ 12 files changed, 234 insertions(+), 26 deletions(-) create mode 100644 src/domain/arrangement/impls.rs create mode 100644 src/domain/arrangement/mod.rs create mode 100644 src/domain/format/impls.rs create mode 100644 src/domain/format/mod.rs create mode 100644 src/domain/price.rs create mode 100644 src/dto/filter.rs diff --git a/src/domain/arrangement/impls.rs b/src/domain/arrangement/impls.rs new file mode 100644 index 0000000..f265cad --- /dev/null +++ b/src/domain/arrangement/impls.rs @@ -0,0 +1,56 @@ +use std::{fmt::Display, str::FromStr}; + +use sqlx::{Postgres, postgres::{PgArgumentBuffer, PgValueRef, PgTypeInfo}, encode::IsNull, error::BoxDynError}; + +use crate::domain::error::FromStrError; + +use super::ArrangementType; + +impl Display for ArrangementType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ArrangementType::Rent => write!(f, "Rent"), + ArrangementType::Sale => write!(f, "Sale"), + } + } +} + +impl FromStr for ArrangementType { + type Err = FromStrError; + + fn from_str(s: &str) -> Result { + match s { + "Rent" => Ok(Self::Rent), + "Sale" => Ok(Self::Sale), + _ => Err(FromStrError) + } + } +} + +#[cfg(feature = "sqlx")] +impl sqlx::Encode<'_, Postgres> for ArrangementType { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { + let binding = serde_json::to_string(self).unwrap(); + <&str as sqlx::Encode>::encode(&binding, buf) + } +} +#[cfg(feature = "sqlx")] +impl sqlx::Decode<'_, Postgres> for ArrangementType { + fn decode(value: PgValueRef<'_>) -> Result { + let column = value.as_str()?; + match serde_json::from_str(column) { + Ok(listing_state) => Ok(listing_state), + Err(error) => Err(Box::new(error)), + } + } +} +#[cfg(feature = "sqlx")] +impl sqlx::Type for ArrangementType { + fn type_info() -> PgTypeInfo { + PgTypeInfo::with_name("VARCHAR") + } + + fn compatible(ty: &::TypeInfo) -> bool { + *ty == Self::type_info() + } +} diff --git a/src/domain/arrangement/mod.rs b/src/domain/arrangement/mod.rs new file mode 100644 index 0000000..e218139 --- /dev/null +++ b/src/domain/arrangement/mod.rs @@ -0,0 +1,10 @@ +use serde::{Serialize, Deserialize}; + +pub mod impls; + +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum ArrangementType { + #[default] + Rent, + Sale, +} \ No newline at end of file diff --git a/src/domain/format/impls.rs b/src/domain/format/impls.rs new file mode 100644 index 0000000..2ba1e67 --- /dev/null +++ b/src/domain/format/impls.rs @@ -0,0 +1,61 @@ +use std::{fmt::Display, str::FromStr}; + +use sqlx::{Postgres, postgres::{PgArgumentBuffer, PgValueRef, PgTypeInfo}, encode::IsNull, error::BoxDynError}; + +use crate::domain::error::FromStrError; + +use super::FrameFormat; + + +impl Display for FrameFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FrameFormat::Landscape => write!(f, "Landscape"), + FrameFormat::Instagram => write!(f, "Instagram"), + FrameFormat::Portrait => write!(f, "Portrait"), + FrameFormat::Square => write!(f, "Square") + } + } +} + +impl FromStr for FrameFormat { + type Err = FromStrError; + + fn from_str(s: &str) -> Result { + match s { + "Landscape" => Ok(Self::Landscape), + "Instagram" => Ok(Self::Instagram), + "Portrait" => Ok(Self::Portrait), + "Square" => Ok(Self::Square), + _ => Err(FromStrError) + } + } +} + +#[cfg(feature = "sqlx")] +impl sqlx::Encode<'_, Postgres> for FrameFormat { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { + let binding = self.to_string(); + <&str as sqlx::Encode>::encode(&binding, buf) + } +} +#[cfg(feature = "sqlx")] +impl sqlx::Decode<'_, Postgres> for FrameFormat { + fn decode(value: PgValueRef<'_>) -> Result { + let column = value.as_str()?; + match Self::from_str(column) { + Ok(listing_state) => Ok(listing_state), + Err(error) => Err(Box::new(error)), + } + } +} +#[cfg(feature = "sqlx")] +impl sqlx::Type for FrameFormat { + fn type_info() -> PgTypeInfo { + PgTypeInfo::with_name("VARCHAR") + } + + fn compatible(ty: &::TypeInfo) -> bool { + *ty == Self::type_info() + } +} diff --git a/src/domain/format/mod.rs b/src/domain/format/mod.rs new file mode 100644 index 0000000..6e8523b --- /dev/null +++ b/src/domain/format/mod.rs @@ -0,0 +1,38 @@ +pub mod impls; + +use serde::{Serialize, Deserialize}; + +/// Allows realtors to pick the size of each property to fit the carroussel. +/// This is easy for agents to use and is better than having them pick what size everything should be. +/// All sizes are in w x h in squares +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum FrameFormat { + /// 2x1 + #[default] + Landscape, + /// 2x2 + Instagram, + /// 1x2 + Portrait, + // 1x1 + Square, +} + +impl FrameFormat { + pub fn get_width(&self) -> usize { + match self { + FrameFormat::Landscape => 2, + FrameFormat::Instagram => 2, + FrameFormat::Portrait => 1, + FrameFormat::Square => 1, + } + } + pub fn get_height(&self) -> usize { + match self { + FrameFormat::Landscape => 1, + FrameFormat::Instagram => 2, + FrameFormat::Portrait => 2, + FrameFormat::Square => 1, + } + } +} \ No newline at end of file diff --git a/src/domain/mod.rs b/src/domain/mod.rs index f448341..9abf4cb 100644 --- a/src/domain/mod.rs +++ b/src/domain/mod.rs @@ -11,6 +11,9 @@ pub mod realtor; pub mod thing_pk; pub mod trackable; pub mod view; +pub mod format; +pub mod price; +pub mod arrangement; +pub mod project_type; pub mod error; -pub mod project_type; diff --git a/src/domain/price.rs b/src/domain/price.rs new file mode 100644 index 0000000..ebc37d8 --- /dev/null +++ b/src/domain/price.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::arrangement::ArrangementType; + + +/// This is what lets the backend know what a property sells for/rents for. +/// Store at least one of these per Property. +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, PartialOrd)] +pub struct PropertyPrice { + #[serde(rename = "propertyId")] + pub property_id: Uuid, + pub price: f64, + pub currency: String, + pub arrangement: ArrangementType, +} \ No newline at end of file diff --git a/src/domain/property.rs b/src/domain/property.rs index 72e6c23..cea512a 100644 --- a/src/domain/property.rs +++ b/src/domain/property.rs @@ -2,7 +2,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use super::{media::MediaList, property_sale_type::PropertySaleType, property_type::PropertyType}; +use super::{media::MediaList, property_sale_type::PropertySaleType, property_type::PropertyType, format::FrameFormat}; /// A property can belong to a project, or not. It should always belong to a realtor. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, PartialOrd)] @@ -17,20 +17,22 @@ pub struct Property { pub property_type: PropertyType, #[serde(rename = "propertySaleType")] pub property_sale_type: PropertySaleType, - #[serde(skip_serializing_if = "Option::is_none")] - pub country: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub city: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub district: Option, - #[serde(rename = "priceUsd")] - pub price_usd: f64, + pub country: String, + pub city: String, + pub district: String, + /// This gives the realtor the option to order the projects/properties + #[serde(rename = "orderIndex")] + pub order_index: i32, + #[serde(rename = "thumbnailFormat")] + pub thumbnail_format: FrameFormat, /// Amount of rooms in unit pub rooms: i16, /// Amount of bathrooms in unit pub bathrooms: f32, /// In meters squared pub area: f32, + #[serde(rename = "parkingSpots")] + pub parking_spots: i16, #[serde(rename = "adminTag")] pub admin_tag: Option, #[serde(rename = "timeCreated")] diff --git a/src/domain/thing_pk/impls.rs b/src/domain/thing_pk/impls.rs index a8d2a9f..f270b70 100644 --- a/src/domain/thing_pk/impls.rs +++ b/src/domain/thing_pk/impls.rs @@ -54,7 +54,7 @@ impl sqlx::Decode<'_, Postgres> for ThingPk { #[cfg(feature = "sqlx")] impl sqlx::Type for ThingPk { fn type_info() -> PgTypeInfo { - PgTypeInfo::with_name("TEXT") + PgTypeInfo::with_name("VARCHAR") } fn compatible(ty: &::TypeInfo) -> bool { diff --git a/src/dto/filter.rs b/src/dto/filter.rs new file mode 100644 index 0000000..1ebaee7 --- /dev/null +++ b/src/dto/filter.rs @@ -0,0 +1,17 @@ +use serde::{Serialize, Deserialize}; + + + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd)] +pub enum Filter { + Country(String), + City(String), + District(String), + + PriceGreaterThan(f64), + PriceLessThan(f64), + + Rooms(i32), + Bathrooms(i32), +} + diff --git a/src/dto/mod.rs b/src/dto/mod.rs index 01074c6..0b0978e 100644 --- a/src/dto/mod.rs +++ b/src/dto/mod.rs @@ -1 +1,2 @@ pub mod payloads; +pub mod filter; \ No newline at end of file diff --git a/src/dto/payloads/project.rs b/src/dto/payloads/project.rs index c0d7b33..9539c95 100644 --- a/src/dto/payloads/project.rs +++ b/src/dto/payloads/project.rs @@ -2,10 +2,8 @@ use chrono::{NaiveDate, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::domain::{ - media::MediaList, project::Project, project_condition::ProjectCondition, - project_state::ProjectState, project_type::ProjectType, -}; +use crate::domain::{project_type::ProjectType, project_state::ProjectState, project_condition::ProjectCondition, media::MediaList, project::Project}; + #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ProjectForCreationPayload { @@ -32,6 +30,8 @@ pub struct ProjectForCreationPayload { #[serde(rename = "orderIndex")] pub order_index: i32, } + + impl From for Project { fn from(value: ProjectForCreationPayload) -> Self { Self { @@ -54,4 +54,4 @@ impl From for Project { last_updated: Utc::now(), } } -} +} \ No newline at end of file diff --git a/src/dto/payloads/property.rs b/src/dto/payloads/property.rs index 7c74ffe..a56015b 100644 --- a/src/dto/payloads/property.rs +++ b/src/dto/payloads/property.rs @@ -4,7 +4,7 @@ use uuid::Uuid; use crate::domain::{ media::MediaList, property::Property, property_sale_type::PropertySaleType, - property_type::PropertyType, + property_type::PropertyType, format::FrameFormat, }; /// A property can belong to a project, or not. It should always belong to a realtor. @@ -19,20 +19,22 @@ pub struct PropertyForCreationPayload { pub property_type: PropertyType, #[serde(rename = "propertySaleType")] pub property_sale_type: PropertySaleType, - #[serde(skip_serializing_if = "Option::is_none")] - pub country: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub city: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub district: Option, - #[serde(rename = "priceUsd")] - pub price_usd: f64, + pub country: String, + pub city: String, + pub district: String, + /// This gives the realtor the option to order the projects/properties. On birth, + #[serde(rename = "orderIndex")] + pub order_index: i32, + #[serde(rename = "thumbnailFormat")] + pub thumbnail_format: FrameFormat, /// Amount of rooms in unit pub rooms: i16, /// Amount of bathrooms in unit pub bathrooms: f32, /// In meters squared pub area: f32, + #[serde(rename = "parkingSpots")] + pub parking_spots: i16, #[serde(rename = "adminTag")] pub admin_tag: Option, } @@ -49,10 +51,12 @@ impl From for Property { country: value.country, city: value.city, district: value.district, - price_usd: value.price_usd, + order_index: value.order_index, + thumbnail_format: value.thumbnail_format, rooms: value.rooms, bathrooms: value.bathrooms, area: value.area, + parking_spots: value.parking_spots, admin_tag: value.admin_tag, time_created: Utc::now(), last_updated: Utc::now(),