From 441ad463d2bf1be1dc4b774de435692d4861d50c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Greinhofer?= Date: Tue, 26 Nov 2024 10:15:31 -0600 Subject: [PATCH] Add missing routes (#157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the OAS3 specification with the missing routes and implement the endpoints. Updates BNA client accordingly. Signed-off-by: Rémy Greinhofer Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- bnaclient/src/lib.rs | 714 ++++++++++++++++++- entity/src/wrappers/census.rs | 21 + lambdas/src/core/resource/cities/adaptor.rs | 32 + lambdas/src/core/resource/cities/db.rs | 18 +- lambdas/src/core/resource/cities/endpoint.rs | 16 +- openapi.yaml | 111 ++- 6 files changed, 894 insertions(+), 18 deletions(-) diff --git a/bnaclient/src/lib.rs b/bnaclient/src/lib.rs index 2e75c2b..829f63b 100644 --- a/bnaclient/src/lib.rs +++ b/bnaclient/src/lib.rs @@ -1392,6 +1392,134 @@ pub mod types { } } + ///CensusPost + /// + ///
JSON schema + /// + /// ```json + ///{ + /// "type": "object", + /// "properties": { + /// "fips_code": { + /// "description": "Numerical city identifier given by the U.S. census, + /// or 0 for non-US cities\n", + /// "examples": [ + /// "4805000" + /// ], + /// "type": "string" + /// }, + /// "pop_size": { + /// "description": "City population size category (small (0), medium + /// (1), large (2))\n", + /// "examples": [ + /// 2 + /// ], + /// "type": "integer", + /// "enum": [ + /// 0, + /// 1, + /// 2 + /// ] + /// }, + /// "population": { + /// "description": "City population", + /// "examples": [ + /// 907779 + /// ], + /// "type": "integer" + /// } + /// } + ///} + /// ``` + ///
+ #[derive(Clone, Debug, serde :: Deserialize, serde :: Serialize)] + pub struct CensusPost { + ///Numerical city identifier given by the U.S. census, or 0 for non-US + /// cities + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fips_code: Option, + ///City population size category (small (0), medium (1), large (2)) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pop_size: Option, + ///City population + #[serde(default, skip_serializing_if = "Option::is_none")] + pub population: Option, + } + + impl From<&CensusPost> for CensusPost { + fn from(value: &CensusPost) -> Self { + value.clone() + } + } + + impl CensusPost { + pub fn builder() -> builder::CensusPost { + Default::default() + } + } + + ///City population size category (small (0), medium (1), large (2)) + /// + ///
JSON schema + /// + /// ```json + ///{ + /// "description": "City population size category (small (0), medium (1), + /// large (2))\n", + /// "examples": [ + /// 2 + /// ], + /// "type": "integer", + /// "enum": [ + /// 0, + /// 1, + /// 2 + /// ] + ///} + /// ``` + ///
+ #[derive(Clone, Debug, serde :: Serialize)] + pub struct CensusPostPopSize(i64); + impl std::ops::Deref for CensusPostPopSize { + type Target = i64; + fn deref(&self) -> &i64 { + &self.0 + } + } + + impl From for i64 { + fn from(value: CensusPostPopSize) -> Self { + value.0 + } + } + + impl From<&CensusPostPopSize> for CensusPostPopSize { + fn from(value: &CensusPostPopSize) -> Self { + value.clone() + } + } + + impl std::convert::TryFrom for CensusPostPopSize { + type Error = self::error::ConversionError; + fn try_from(value: i64) -> Result { + if ![0_i64, 1_i64, 2_i64].contains(&value) { + Err("invalid value".into()) + } else { + Ok(Self(value)) + } + } + } + + impl<'de> serde::Deserialize<'de> for CensusPostPopSize { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Self::try_from(::deserialize(deserializer)?) + .map_err(|e| ::custom(e.to_string())) + } + } + ///City /// ///
JSON schema @@ -2156,6 +2284,61 @@ pub mod types { } } + ///FargatePrice + /// + ///
JSON schema + /// + /// ```json + ///{ + /// "type": "object", + /// "properties": { + /// "created_at": { + /// "description": "Date and time", + /// "type": "string", + /// "format": "date-time" + /// }, + /// "fargate_price_id": { + /// "description": "Identifier of the Fargate Price rate used to + /// compute the cost of the pipeline run.\n", + /// "examples": [ + /// 1 + /// ], + /// "type": "number" + /// }, + /// "per_second": { + /// "description": "Cost to run Fargate for 1 second", + /// "examples": [ + /// 0.0023 + /// ], + /// "type": "number" + /// } + /// } + ///} + /// ``` + ///
+ #[derive(Clone, Debug, serde :: Deserialize, serde :: Serialize)] + pub struct FargatePrice { + ///Date and time + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_at: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fargate_price_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub per_second: Option, + } + + impl From<&FargatePrice> for FargatePrice { + fn from(value: &FargatePrice) -> Self { + value.clone() + } + } + + impl FargatePrice { + pub fn builder() -> builder::FargatePrice { + Default::default() + } + } + ///Features /// ///
JSON schema @@ -2674,6 +2857,43 @@ pub mod types { } } + ///PatchCityCensusResponseItem + /// + ///
JSON schema + /// + /// ```json + ///{ + /// "anyOf": [ + /// { + /// "$ref": "#/components/schemas/city" + /// }, + /// { + /// "$ref": "#/components/schemas/census" + /// } + /// ] + ///} + /// ``` + ///
+ #[derive(Clone, Debug, serde :: Deserialize, serde :: Serialize)] + pub struct PatchCityCensusResponseItem { + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] + pub subtype_0: Option, + #[serde(flatten, default, skip_serializing_if = "Option::is_none")] + pub subtype_1: Option, + } + + impl From<&PatchCityCensusResponseItem> for PatchCityCensusResponseItem { + fn from(value: &PatchCityCensusResponseItem) -> Self { + value.clone() + } + } + + impl PatchCityCensusResponseItem { + pub fn builder() -> builder::PatchCityCensusResponseItem { + Default::default() + } + } + ///People /// ///
JSON schema @@ -5231,6 +5451,77 @@ pub mod types { } } + #[derive(Clone, Debug)] + pub struct CensusPost { + fips_code: Result, String>, + pop_size: Result, String>, + population: Result, String>, + } + + impl Default for CensusPost { + fn default() -> Self { + Self { + fips_code: Ok(Default::default()), + pop_size: Ok(Default::default()), + population: Ok(Default::default()), + } + } + } + + impl CensusPost { + pub fn fips_code(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.fips_code = value + .try_into() + .map_err(|e| format!("error converting supplied value for fips_code: {}", e)); + self + } + pub fn pop_size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.pop_size = value + .try_into() + .map_err(|e| format!("error converting supplied value for pop_size: {}", e)); + self + } + pub fn population(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.population = value + .try_into() + .map_err(|e| format!("error converting supplied value for population: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CensusPost { + type Error = super::error::ConversionError; + fn try_from(value: CensusPost) -> Result { + Ok(Self { + fips_code: value.fips_code?, + pop_size: value.pop_size?, + population: value.population?, + }) + } + } + + impl From for CensusPost { + fn from(value: super::CensusPost) -> Self { + Self { + fips_code: Ok(value.fips_code), + pop_size: Ok(value.pop_size), + population: Ok(value.population), + } + } + } + #[derive(Clone, Debug)] pub struct City { city_id: Result, String>, @@ -5938,11 +6229,85 @@ pub mod types { } #[derive(Clone, Debug)] - pub struct Features { - people: Result, String>, - retail: Result, String>, - transit: Result, String>, - } + pub struct FargatePrice { + created_at: Result>, String>, + fargate_price_id: Result, String>, + per_second: Result, String>, + } + + impl Default for FargatePrice { + fn default() -> Self { + Self { + created_at: Ok(Default::default()), + fargate_price_id: Ok(Default::default()), + per_second: Ok(Default::default()), + } + } + } + + impl FargatePrice { + pub fn created_at(mut self, value: T) -> Self + where + T: std::convert::TryInto>>, + T::Error: std::fmt::Display, + { + self.created_at = value + .try_into() + .map_err(|e| format!("error converting supplied value for created_at: {}", e)); + self + } + pub fn fargate_price_id(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.fargate_price_id = value.try_into().map_err(|e| { + format!( + "error converting supplied value for fargate_price_id: {}", + e + ) + }); + self + } + pub fn per_second(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.per_second = value + .try_into() + .map_err(|e| format!("error converting supplied value for per_second: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::FargatePrice { + type Error = super::error::ConversionError; + fn try_from(value: FargatePrice) -> Result { + Ok(Self { + created_at: value.created_at?, + fargate_price_id: value.fargate_price_id?, + per_second: value.per_second?, + }) + } + } + + impl From for FargatePrice { + fn from(value: super::FargatePrice) -> Self { + Self { + created_at: Ok(value.created_at), + fargate_price_id: Ok(value.fargate_price_id), + per_second: Ok(value.per_second), + } + } + } + + #[derive(Clone, Debug)] + pub struct Features { + people: Result, String>, + retail: Result, String>, + transit: Result, String>, + } impl Default for Features { fn default() -> Self { @@ -6235,6 +6600,65 @@ pub mod types { } } + #[derive(Clone, Debug)] + pub struct PatchCityCensusResponseItem { + subtype_0: Result, String>, + subtype_1: Result, String>, + } + + impl Default for PatchCityCensusResponseItem { + fn default() -> Self { + Self { + subtype_0: Ok(Default::default()), + subtype_1: Ok(Default::default()), + } + } + } + + impl PatchCityCensusResponseItem { + pub fn subtype_0(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.subtype_0 = value + .try_into() + .map_err(|e| format!("error converting supplied value for subtype_0: {}", e)); + self + } + pub fn subtype_1(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.subtype_1 = value + .try_into() + .map_err(|e| format!("error converting supplied value for subtype_1: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::PatchCityCensusResponseItem { + type Error = super::error::ConversionError; + fn try_from( + value: PatchCityCensusResponseItem, + ) -> Result { + Ok(Self { + subtype_0: value.subtype_0?, + subtype_1: value.subtype_1?, + }) + } + } + + impl From for PatchCityCensusResponseItem { + fn from(value: super::PatchCityCensusResponseItem) -> Self { + Self { + subtype_0: Ok(value.subtype_0), + subtype_1: Ok(value.subtype_1), + } + } + } + #[derive(Clone, Debug)] pub struct People { score: Result, String>, @@ -7463,6 +7887,33 @@ impl Client { builder::GetCityCensus::new(self) } + ///Create census information for a specific city + /// + ///Create census information for a specific city. + /// + ///Sends a `POST` request to `/cities/{country}/{region}/{name}/census` + /// + ///Arguments: + /// - `country`: Country name + /// - `region`: Region name. A region can be a state, a province, a + /// community, or something similar depending on the country. If a country + /// does not have this concept, then the country name is used. + /// + /// - `name`: City name + /// - `body`: Create the census information for a specific city. + ///```ignore + /// let response = client.patch_city_census() + /// .country(country) + /// .region(region) + /// .name(name) + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn patch_city_census(&self) -> builder::PatchCityCensus { + builder::PatchCityCensus::new(self) + } + ///Enqueue a city to process /// ///Enqueue a city to process. @@ -7480,6 +7931,39 @@ impl Client { pub fn post_rating_enqueue(&self) -> builder::PostRatingEnqueue { builder::PostRatingEnqueue::new(self) } + + ///Get all the AWS Fargate prices used to compute analysis costs + /// + ///Get all the AWS Fargate prices used to compute analysis costs. + /// + ///Sends a `GET` request to `/price/fargate` + /// + ///```ignore + /// let response = client.get_prices_fargate() + /// .send() + /// .await; + /// ``` + pub fn get_prices_fargate(&self) -> builder::GetPricesFargate { + builder::GetPricesFargate::new(self) + } + + ///Get a AWS Fargate price used to compute the cost of an analysis + /// + ///Get a AWS Fargate price used to compute the cost of analysis cost. + /// + ///Sends a `GET` request to `/price/fargate/{fargate_price_id}` + /// + ///Arguments: + /// - `fargate_price_id`: Identifier of a Fargate price + ///```ignore + /// let response = client.get_price_fargate() + /// .fargate_price_id(fargate_price_id) + /// .send() + /// .await; + /// ``` + pub fn get_price_fargate(&self) -> builder::GetPriceFargate { + builder::GetPriceFargate::new(self) + } } /// Types for composing operation parameters. @@ -8856,6 +9340,129 @@ pub mod builder { } } + ///Builder for [`Client::patch_city_census`] + /// + ///[`Client::patch_city_census`]: super::Client::patch_city_census + #[derive(Debug, Clone)] + pub struct PatchCityCensus<'a> { + client: &'a super::Client, + country: Result, + region: Result, + name: Result, + body: Result, + } + + impl<'a> PatchCityCensus<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + country: Err("country was not initialized".to_string()), + region: Err("region was not initialized".to_string()), + name: Err("name was not initialized".to_string()), + body: Ok(types::builder::CensusPost::default()), + } + } + + pub fn country(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.country = value + .try_into() + .map_err(|_| "conversion to `Country` for country failed".to_string()); + self + } + + pub fn region(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.region = value + .try_into() + .map_err(|_| "conversion to `String` for region failed".to_string()); + self + } + + pub fn name(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.name = value + .try_into() + .map_err(|_| "conversion to `String` for name failed".to_string()); + self + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + >::Error: std::fmt::Display, + { + self.body = value + .try_into() + .map(From::from) + .map_err(|s| format!("conversion to `CensusPost` for body failed: {}", s)); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce(types::builder::CensusPost) -> types::builder::CensusPost, + { + self.body = self.body.map(f); + self + } + + ///Sends a `POST` request to `/cities/{country}/{region}/{name}/census` + pub async fn send( + self, + ) -> Result>, Error> + { + let Self { + client, + country, + region, + name, + body, + } = self; + let country = country.map_err(Error::InvalidRequest)?; + let region = region.map_err(Error::InvalidRequest)?; + let name = name.map_err(Error::InvalidRequest)?; + let body = body + .and_then(|v| types::CensusPost::try_from(v).map_err(|e| e.to_string())) + .map_err(Error::InvalidRequest)?; + let url = format!( + "{}/cities/{}/{}/{}/census", + client.baseurl, + encode_path(&country.to_string()), + encode_path(®ion.to_string()), + encode_path(&name.to_string()), + ); + #[allow(unused_mut)] + let mut request = client + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 404u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + ///Builder for [`Client::post_rating_enqueue`] /// ///[`Client::post_rating_enqueue`]: super::Client::post_rating_enqueue @@ -8924,6 +9531,103 @@ pub mod builder { } } } + + ///Builder for [`Client::get_prices_fargate`] + /// + ///[`Client::get_prices_fargate`]: super::Client::get_prices_fargate + #[derive(Debug, Clone)] + pub struct GetPricesFargate<'a> { + client: &'a super::Client, + } + + impl<'a> GetPricesFargate<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { client: client } + } + + ///Sends a `GET` request to `/price/fargate` + pub async fn send(self) -> Result>, Error<()>> { + let Self { client } = self; + let url = format!("{}/price/fargate", client.baseurl,); + #[allow(unused_mut)] + let mut request = client + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + ///Builder for [`Client::get_price_fargate`] + /// + ///[`Client::get_price_fargate`]: super::Client::get_price_fargate + #[derive(Debug, Clone)] + pub struct GetPriceFargate<'a> { + client: &'a super::Client, + fargate_price_id: Result, + } + + impl<'a> GetPriceFargate<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + fargate_price_id: Err("fargate_price_id was not initialized".to_string()), + } + } + + pub fn fargate_price_id(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.fargate_price_id = value + .try_into() + .map_err(|_| "conversion to `i64` for fargate_price_id failed".to_string()); + self + } + + ///Sends a `GET` request to `/price/fargate/{fargate_price_id}` + pub async fn send( + self, + ) -> Result, Error> { + let Self { + client, + fargate_price_id, + } = self; + let fargate_price_id = fargate_price_id.map_err(Error::InvalidRequest)?; + let url = format!( + "{}/price/fargate/{}", + client.baseurl, + encode_path(&fargate_price_id.to_string()), + ); + #[allow(unused_mut)] + let mut request = client + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 404u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } } /// Items consumers will typically use such as the Client. diff --git a/entity/src/wrappers/census.rs b/entity/src/wrappers/census.rs index c3a6117..4da7958 100644 --- a/entity/src/wrappers/census.rs +++ b/entity/src/wrappers/census.rs @@ -1,5 +1,6 @@ use crate::census; use sea_orm::{prelude::Uuid, ActiveValue, IntoActiveModel}; +use serde::Deserialize; pub struct CensusPost { pub city_id: Uuid, @@ -21,6 +22,26 @@ impl IntoActiveModel for CensusPost { } } +#[derive(Deserialize)] +pub struct CensusFromCityPost { + pub fips_code: String, + pub pop_size: i32, + pub population: i32, +} + +impl IntoActiveModel for CensusFromCityPost { + fn into_active_model(self) -> census::ActiveModel { + census::ActiveModel { + id: ActiveValue::NotSet, + city_id: ActiveValue::NotSet, + created_at: ActiveValue::NotSet, + fips_code: ActiveValue::Set(self.fips_code), + pop_size: ActiveValue::Set(self.pop_size), + population: ActiveValue::Set(self.population), + } + } +} + pub struct CensusPatch { pub city_id: Option, pub fips_code: Option, diff --git a/lambdas/src/core/resource/cities/adaptor.rs b/lambdas/src/core/resource/cities/adaptor.rs index 5e9cddf..76d7273 100644 --- a/lambdas/src/core/resource/cities/adaptor.rs +++ b/lambdas/src/core/resource/cities/adaptor.rs @@ -4,6 +4,7 @@ use super::db::{ }; use crate::{database_connect, Context, ExecutionError, PageFlow, Paginatron}; use entity::wrappers::{ + census::CensusFromCityPost, city::CityPost, submission::{SubmissionPatch, SubmissionPost}, }; @@ -74,6 +75,37 @@ pub async fn get_cities_censuses_adaptor( )) } +pub async fn post_cities_census_adaptor( + country: &str, + region: &str, + name: &str, + census: CensusFromCityPost, +) -> Result { + // Set the database connection. + let db = database_connect(Some("DATABASE_URL_SECRET_ID")).await?; + + // Fetch the city. + let city = fetch_city(&db, country, region, name).await?; + if let Some(city) = city { + // Turn the post model into an active model. + let mut active_model: entity::census::ActiveModel = census.into_active_model(); + + // Update the active model. + active_model.city_id = ActiveValue::Set(city.id); + + // And insert a new entry. + let model = active_model.insert(&db).await?; + let value = json!(model); + Ok(value) + } else { + Err(ExecutionError::NotFound( + Some(country.to_string()), + region.to_string(), + name.to_string(), + )) + } +} + pub async fn get_cities_ratings_adaptor( country: &str, region: &str, diff --git a/lambdas/src/core/resource/cities/db.rs b/lambdas/src/core/resource/cities/db.rs index 045036e..5f77873 100644 --- a/lambdas/src/core/resource/cities/db.rs +++ b/lambdas/src/core/resource/cities/db.rs @@ -1,6 +1,6 @@ use entity::{census, city, country, state_region_crosswalk, submission, summary}; use sea_orm::{ - ColumnTrait, Condition, DatabaseConnection, EntityTrait, PaginatorTrait, QueryFilter, + ColumnTrait, Condition, DatabaseConnection, DbErr, EntityTrait, PaginatorTrait, QueryFilter, QuerySelect, }; @@ -9,7 +9,7 @@ pub async fn fetch_city( country: &str, region: &str, name: &str, -) -> Result, sea_orm::DbErr> { +) -> Result, DbErr> { city::Entity::find_by_id((country.to_string(), region.to_string(), name.to_string())) .one(db) .await @@ -19,7 +19,7 @@ pub async fn fetch_cities( db: &DatabaseConnection, page: u64, page_size: u64, -) -> Result<(u64, Vec), sea_orm::DbErr> { +) -> Result<(u64, Vec), DbErr> { let select = city::Entity::find(); let count = select .clone() @@ -38,7 +38,7 @@ pub async fn fetch_cities_censuses( name: &str, page: u64, page_size: u64, -) -> Result<(u64, Vec<(city::Model, Option)>), sea_orm::DbErr> { +) -> Result<(u64, Vec<(city::Model, Option)>), DbErr> { let select = city::Entity::find_by_id((country.to_string(), region.to_string(), name.to_string())) .find_also_related(census::Entity); @@ -58,7 +58,7 @@ pub async fn fetch_cities_ratings( name: &str, page: u64, page_size: u64, -) -> Result<(u64, Vec<(city::Model, Option)>), sea_orm::DbErr> { +) -> Result<(u64, Vec<(city::Model, Option)>), DbErr> { let select = city::Entity::find_by_id((country.to_string(), region.to_string(), name.to_string())) .find_also_related(summary::Entity); @@ -74,7 +74,7 @@ pub async fn fetch_cities_ratings( pub async fn fetch_country( db: &DatabaseConnection, country: &str, -) -> Result, sea_orm::DbErr> { +) -> Result, DbErr> { country::Entity::find() .filter(country::Column::Name.eq(country)) .one(db) @@ -84,7 +84,7 @@ pub async fn fetch_country( pub async fn fetch_state_region_crosswalk( db: &DatabaseConnection, state: &str, -) -> Result, sea_orm::DbErr> { +) -> Result, DbErr> { state_region_crosswalk::Entity::find() .filter(state_region_crosswalk::Column::State.eq(state)) .one(db) @@ -95,7 +95,7 @@ pub async fn fetch_cities_submission( db: &DatabaseConnection, submission_id: i32, status: Option, -) -> Result, sea_orm::DbErr> { +) -> Result, DbErr> { // Filter the query if needed. let mut conditions = Condition::all(); if let Some(status) = status { @@ -114,7 +114,7 @@ pub async fn fetch_cities_submissions( status: Option, page: u64, page_size: u64, -) -> Result<(u64, Vec), sea_orm::DbErr> { +) -> Result<(u64, Vec), DbErr> { // Filter the query if needed. let mut conditions = Condition::all(); if let Some(status) = status { diff --git a/lambdas/src/core/resource/cities/endpoint.rs b/lambdas/src/core/resource/cities/endpoint.rs index f1d5d17..620f24d 100644 --- a/lambdas/src/core/resource/cities/endpoint.rs +++ b/lambdas/src/core/resource/cities/endpoint.rs @@ -1,7 +1,8 @@ use super::adaptor::{ get_cities_adaptor, get_cities_censuses_adaptor, get_cities_ratings_adaptor, get_cities_submission_adaptor, get_cities_submissions_adaptor, get_city_adaptor, - patch_cities_submission_adaptor, post_cities_adaptor, post_cities_submission_adaptor, + patch_cities_submission_adaptor, post_cities_adaptor, post_cities_census_adaptor, + post_cities_submission_adaptor, }; use crate::{core::resource::cities::CitiesPathParameters, Context, ExecutionError}; use axum::{ @@ -15,6 +16,7 @@ use axum::{ use axum_extra::extract::OptionalQuery; use effortless::api::PaginationParameters; use entity::wrappers::{ + census::CensusFromCityPost, city::CityPost, submission::{SubmissionPatch, SubmissionPost}, }; @@ -27,7 +29,7 @@ pub fn routes() -> Router { .route("/cities/:country/:region/:name", get(get_city)) .route( "/cities/:country/:region/:name/census", - get(get_city_censuses), + get(get_city_censuses).post(post_city_census), ) .route( "/cities/:country/:region/:name/ratings", @@ -141,6 +143,16 @@ async fn post_cities_submissions( .map(|v| (StatusCode::CREATED, Json(v))) } +#[axum::debug_handler] +async fn post_city_census( + Path(params): Path, + Json(census): Json, +) -> Result<(StatusCode, Json), ExecutionError> { + post_cities_census_adaptor(¶ms.country, ¶ms.region, ¶ms.name, census) + .await + .map(|v| (StatusCode::CREATED, Json(v))) +} + #[cfg(test)] mod tests { use axum::extract::Query; diff --git a/openapi.yaml b/openapi.yaml index 1b952cf..c3788d9 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -10,10 +10,11 @@ servers: - url: "https://api.peopleforbikes.xyz" description: Staging API - url: "https://localhost:3000" - description: Locol API + description: Local development API tags: - - name: rating - name: city + - name: price + - name: rating paths: /ratings: get: @@ -327,6 +328,27 @@ paths: $ref: "#/components/responses/bad_request" 404: $ref: "#/components/responses/not_found" + post: + operationId: patchCityCensus + summary: Create census information for a specific city. + description: Create census information for a specific city. + tags: + - city + requestBody: + description: Create the census information for a specific city. + content: + application/json: + schema: + $ref: "#/components/schemas/census_post" + responses: + 201: + $ref: "#/components/responses/city_with_census" + 400: + $ref: "#/components/responses/bad_request" + 404: + $ref: "#/components/responses/not_found" + security: + - Authorize: [] parameters: - $ref: "#/components/parameters/country" - $ref: "#/components/parameters/region" @@ -353,6 +375,30 @@ paths: $ref: "#/components/responses/forbidden" security: - Authorize: [] + /price/fargate: + get: + operationId: getPricesFargate + summary: "Get all the AWS Fargate prices used to compute analysis costs." + description: "Get all the AWS Fargate prices used to compute analysis costs." + tags: + - price + responses: + 200: + $ref: "#/components/responses/fargate_prices" + /price/fargate/{fargate_price_id}: + get: + operationId: getPriceFargate + summary: "Get a AWS Fargate price used to compute the cost of an analysis." + description: "Get a AWS Fargate price used to compute the cost of analysis cost." + tags: + - price + parameters: + - $ref: "#/components/parameters/fargate_price_id" + responses: + 200: + $ref: "#/components/responses/fargate_price" + 404: + $ref: "#/components/responses/not_found" components: schemas: analysis: @@ -756,6 +802,28 @@ components: type: integer description: "City population" example: 907779 + census_post: + type: object + properties: + fips_code: + type: string + description: > + Numerical city identifier given by the U.S. census, or 0 for non-US cities + example: "4805000" + pop_size: + type: integer + description: > + City population size category (small (0), medium (1), large (2)) + + enum: + - 0 + - 1 + - 2 + example: 2 + population: + type: integer + description: "City population" + example: 907779 city: type: object properties: @@ -1291,6 +1359,23 @@ components: properties: score: type: number + fargate_price: + type: object + properties: + fargate_price_id: + type: number + description: > + Identifier of the Fargate Price rate used to compute the cost of the pipeline + run. + example: 1 + per_second: + type: number + description: Cost to run Fargate for 1 second + example: 0.0023 + created_at: + type: string + format: date-time + description: "Date and time" parameters: rating_id: @@ -1389,6 +1474,13 @@ components: type: integer default: 25 example: 25 + fargate_price_id: + name: fargate_price_id + in: path + required: true + description: "Identifier of a Fargate price" + schema: + type: integer responses: analyses: description: "A collection of BNA analysis" @@ -1522,6 +1614,21 @@ components: type: array items: $ref: "#/components/schemas/submission" + fargate_price: + description: "Fargate price used to estimate the cost of an analysis." + content: + application/json: + schema: + $ref: "#/components/schemas/fargate_price" + fargate_prices: + description: "A collection of Fargate prices" + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/fargate_price" + securitySchemes: Authorize: description: |