Add update shop & owner endpoints

This commit is contained in:
Tyler Hallada 2020-10-18 02:31:17 -04:00
parent 441bc979f2
commit 97a2ac52dc
8 changed files with 169 additions and 13 deletions

View File

@ -17,6 +17,7 @@ pub fn shops(env: Environment) -> impl Filter<Extract = impl Reply, Error = Reje
warp::path("shops").and( warp::path("shops").and(
get_shop(env.clone()) get_shop(env.clone())
.or(delete_shop(env.clone())) .or(delete_shop(env.clone()))
.or(update_shop(env.clone()))
.or(create_shop(env.clone())) .or(create_shop(env.clone()))
.or(list_shops(env)), .or(list_shops(env)),
) )
@ -26,6 +27,7 @@ pub fn owners(env: Environment) -> impl Filter<Extract = impl Reply, Error = Rej
warp::path("owners").and( warp::path("owners").and(
get_owner(env.clone()) get_owner(env.clone())
.or(delete_owner(env.clone())) .or(delete_owner(env.clone()))
.or(update_owner(env.clone()))
.or(create_owner(env.clone())) .or(create_owner(env.clone()))
.or(list_owners(env)), .or(list_owners(env)),
) )
@ -81,6 +83,17 @@ pub fn delete_shop(
.and_then(handlers::delete_shop) .and_then(handlers::delete_shop)
} }
pub fn update_shop(
env: Environment,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::path::param()
.and(warp::patch())
.and(json_body::<Shop>())
.and(warp::header::optional("api-key"))
.and(with_env(env))
.and_then(handlers::update_shop)
}
pub fn list_shops( pub fn list_shops(
env: Environment, env: Environment,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
@ -121,6 +134,17 @@ pub fn delete_owner(
.and_then(handlers::delete_owner) .and_then(handlers::delete_owner)
} }
pub fn update_owner(
env: Environment,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::path::param()
.and(warp::patch())
.and(json_body::<Owner>())
.and(warp::header::optional("api-key"))
.and(with_env(env))
.and_then(handlers::update_owner)
}
pub fn list_owners( pub fn list_owners(
env: Environment, env: Environment,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {

View File

@ -7,7 +7,7 @@ use uuid::Uuid;
use warp::reply::{json, with_header, with_status}; use warp::reply::{json, with_header, with_status};
use warp::{Rejection, Reply}; use warp::{Rejection, Reply};
use super::models::{InteriorRefList, ListParams, Model, Owner, Shop, MerchandiseList}; use super::models::{InteriorRefList, ListParams, Model, UpdateableModel, Owner, Shop, MerchandiseList};
use super::problem::{reject_anyhow, unauthorized_no_api_key, unauthorized_no_owner}; use super::problem::{reject_anyhow, unauthorized_no_api_key, unauthorized_no_owner};
use super::Environment; use super::Environment;
@ -74,7 +74,7 @@ pub async fn create_shop(
..shop ..shop
}; };
let saved_shop = shop_with_owner_id let saved_shop = shop_with_owner_id
.save(&env.db) .create(&env.db)
.await .await
.map_err(reject_anyhow)?; .map_err(reject_anyhow)?;
let url = saved_shop.url(&env.api_url).map_err(reject_anyhow)?; let url = saved_shop.url(&env.api_url).map_err(reject_anyhow)?;
@ -85,6 +85,43 @@ pub async fn create_shop(
Ok(reply) Ok(reply)
} }
pub async fn update_shop(
id: i32,
shop: Shop,
api_key: Option<Uuid>,
env: Environment,
) -> Result<impl Reply, Rejection> {
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
let shop_with_id_and_owner_id = if shop.owner_id.is_some() {
// allows an owner to transfer ownership of shop to another owner
Shop {
id: Some(id),
..shop
}
} else {
Shop {
id: Some(id),
owner_id: Some(owner_id),
..shop
}
};
let updated_shop = shop_with_id_and_owner_id
.update(&env.db, owner_id, id)
.await
.map_err(reject_anyhow)?;
let url = updated_shop.url(&env.api_url).map_err(reject_anyhow)?;
let reply = json(&updated_shop);
let reply = with_header(reply, "Location", url.as_str());
let reply = with_status(reply, StatusCode::CREATED);
env.caches
.shop
.delete_response(id)
.await
.map_err(reject_anyhow)?;
env.caches.list_shops.clear().await;
Ok(reply)
}
pub async fn delete_shop( pub async fn delete_shop(
id: i32, id: i32,
api_key: Option<Uuid>, api_key: Option<Uuid>,
@ -151,7 +188,7 @@ pub async fn create_owner(
}, },
}; };
let saved_owner = owner_with_ip_and_key let saved_owner = owner_with_ip_and_key
.save(&env.db) .create(&env.db)
.await .await
.map_err(reject_anyhow)?; .map_err(reject_anyhow)?;
let url = saved_owner.url(&env.api_url).map_err(reject_anyhow)?; let url = saved_owner.url(&env.api_url).map_err(reject_anyhow)?;
@ -165,6 +202,34 @@ pub async fn create_owner(
} }
} }
pub async fn update_owner(
id: i32,
owner: Owner,
api_key: Option<Uuid>,
env: Environment,
) -> Result<impl Reply, Rejection> {
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
let owner_with_id = Owner {
id: Some(id),
..owner
};
let updated_owner = owner_with_id
.update(&env.db, owner_id, id)
.await
.map_err(reject_anyhow)?;
let url = updated_owner.url(&env.api_url).map_err(reject_anyhow)?;
let reply = json(&updated_owner);
let reply = with_header(reply, "Location", url.as_str());
let reply = with_status(reply, StatusCode::CREATED);
env.caches
.owner
.delete_response(id)
.await
.map_err(reject_anyhow)?;
env.caches.list_owners.clear().await;
Ok(reply)
}
pub async fn delete_owner( pub async fn delete_owner(
id: i32, id: i32,
api_key: Option<Uuid>, api_key: Option<Uuid>,
@ -227,7 +292,7 @@ pub async fn create_interior_ref_list(
..interior_ref_list ..interior_ref_list
}; };
let saved_interior_ref_list = ref_list_with_owner_id let saved_interior_ref_list = ref_list_with_owner_id
.save(&env.db) .create(&env.db)
.await .await
.map_err(reject_anyhow)?; .map_err(reject_anyhow)?;
let url = saved_interior_ref_list let url = saved_interior_ref_list
@ -297,7 +362,7 @@ pub async fn create_merchandise_list(
..merchandise_list ..merchandise_list
}; };
let saved_merchandise_list = ref_list_with_owner_id let saved_merchandise_list = ref_list_with_owner_id
.save(&env.db) .create(&env.db)
.await .await
.map_err(reject_anyhow)?; .map_err(reject_anyhow)?;
let url = saved_merchandise_list let url = saved_merchandise_list

View File

@ -59,7 +59,7 @@ impl Model for InteriorRefList {
} }
#[instrument(level = "debug", skip(self, db))] #[instrument(level = "debug", skip(self, db))]
async fn save(self, db: &PgPool) -> Result<Self> { async fn create(self, db: &PgPool) -> Result<Self> {
// TODO: // TODO:
// * Decide if I'll need to make the same changes to merchandise and transactions // * Decide if I'll need to make the same changes to merchandise and transactions
// - answer depends on how many rows of each I expect to insert in one go // - answer depends on how many rows of each I expect to insert in one go

View File

@ -55,7 +55,7 @@ impl Model for MerchandiseList {
} }
#[instrument(level = "debug", skip(self, db))] #[instrument(level = "debug", skip(self, db))]
async fn save(self, db: &PgPool) -> Result<Self> { async fn create(self, db: &PgPool) -> Result<Self> {
Ok(sqlx::query_as_unchecked!( Ok(sqlx::query_as_unchecked!(
Self, Self,
"INSERT INTO merchandise_lists "INSERT INTO merchandise_lists

View File

@ -9,7 +9,7 @@ pub mod owner;
pub mod shop; pub mod shop;
pub use interior_ref_list::InteriorRefList; pub use interior_ref_list::InteriorRefList;
pub use model::Model; pub use model::{Model, UpdateableModel};
pub use owner::Owner; pub use owner::Owner;
pub use shop::Shop; pub use shop::Shop;
pub use merchandise_list::MerchandiseList; pub use merchandise_list::MerchandiseList;

View File

@ -23,7 +23,15 @@ where
} }
} }
async fn get(db: &PgPool, id: i32) -> Result<Self>; async fn get(db: &PgPool, id: i32) -> Result<Self>;
async fn save(self, db: &PgPool) -> Result<Self>; async fn create(self, db: &PgPool) -> Result<Self>;
async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64>; async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64>;
async fn list(db: &PgPool, list_params: &ListParams) -> Result<Vec<Self>>; async fn list(db: &PgPool, list_params: &ListParams) -> Result<Vec<Self>>;
} }
#[async_trait]
pub trait UpdateableModel
where
Self: std::marker::Sized,
{
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self>;
}

View File

@ -8,7 +8,7 @@ use tracing::instrument;
use uuid::Uuid; use uuid::Uuid;
use super::ListParams; use super::ListParams;
use super::Model; use super::{Model, UpdateableModel};
use crate::problem::forbidden_permission; use crate::problem::forbidden_permission;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -43,7 +43,7 @@ impl Model for Owner {
} }
#[instrument(level = "debug", skip(self, db))] #[instrument(level = "debug", skip(self, db))]
async fn save(self, db: &PgPool) -> Result<Self> { async fn create(self, db: &PgPool) -> Result<Self> {
Ok(sqlx::query_as!( Ok(sqlx::query_as!(
Self, Self,
"INSERT INTO owners "INSERT INTO owners
@ -103,3 +103,31 @@ impl Model for Owner {
Ok(result) Ok(result)
} }
} }
#[async_trait]
impl UpdateableModel for Owner {
#[instrument(level = "debug", skip(self, db))]
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
let owner = sqlx::query!("SELECT id FROM owners WHERE id = $1", id)
.fetch_one(db)
.await?;
if owner.id == owner_id {
Ok(sqlx::query_as!(
Self,
"UPDATE owners SET
name = $2,
mod_version = $3,
updated_at = now()
WHERE id = $1
RETURNING *",
id,
self.name,
self.mod_version,
)
.fetch_one(db)
.await?)
} else {
return Err(forbidden_permission());
}
}
}

View File

@ -6,7 +6,7 @@ use sqlx::postgres::PgPool;
use tracing::instrument; use tracing::instrument;
use super::ListParams; use super::ListParams;
use super::Model; use super::{Model, UpdateableModel};
use crate::problem::forbidden_permission; use crate::problem::forbidden_permission;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -43,7 +43,7 @@ impl Model for Shop {
} }
#[instrument(level = "debug", skip(self, db))] #[instrument(level = "debug", skip(self, db))]
async fn save(self, db: &PgPool) -> Result<Self> { async fn create(self, db: &PgPool) -> Result<Self> {
Ok(sqlx::query_as!( Ok(sqlx::query_as!(
Self, Self,
"INSERT INTO shops "INSERT INTO shops
@ -102,3 +102,34 @@ impl Model for Shop {
Ok(result) Ok(result)
} }
} }
#[async_trait]
impl UpdateableModel for Shop {
#[instrument(level = "debug", skip(self, db))]
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
let shop = sqlx::query!("SELECT owner_id FROM shops WHERE id = $1", id)
.fetch_one(db)
.await?;
if shop.owner_id == owner_id {
Ok(sqlx::query_as!(
Self,
"UPDATE shops SET
name = $2,
owner_id = $3,
description = $4,
updated_at = now()
WHERE id = $1
RETURNING *",
id,
self.name,
self.owner_id,
self.description,
)
.fetch_one(db)
.await?)
} else {
return Err(forbidden_permission());
}
}
}