one interior_ref_list and merchandise_list per shop

still some bugs with routing I need to figure out
This commit is contained in:
Tyler Hallada 2020-10-24 03:01:38 -04:00
parent 3f124ce439
commit df48b64ffd
6 changed files with 210 additions and 25 deletions

View File

@ -20,7 +20,7 @@ pub struct Caches {
pub list_owners: Cache<ListParams, CachedResponse>,
pub list_interior_ref_lists: Cache<ListParams, CachedResponse>,
pub list_merchandise_lists: Cache<ListParams, CachedResponse>,
pub latest_interior_ref_list_by_shop_id: Cache<i32, CachedResponse>,
pub interior_ref_list_by_shop_id: Cache<i32, CachedResponse>,
}
impl Caches {
@ -35,10 +35,7 @@ impl Caches {
list_owners: Cache::new("list_owners", 100),
list_interior_ref_lists: Cache::new("list_interior_ref_lists", 100),
list_merchandise_lists: Cache::new("list_merchandise_lists", 100),
latest_interior_ref_list_by_shop_id: Cache::new(
"latest_interior_ref_list_by_shop_id",
100,
),
interior_ref_list_by_shop_id: Cache::new("interior_ref_list_by_shop_id", 100),
}
}
}

View File

@ -37,7 +37,10 @@ pub fn migration() -> String {
m.create_table("merchandise_lists", |t| {
t.add_column("id", types::primary().indexed(true));
t.add_column("shop_id", types::foreign("shops", "id").indexed(true));
t.add_column(
"shop_id",
types::foreign("shops", "id").indexed(true).unique(true),
);
t.add_column("owner_id", types::foreign("owners", "id").indexed(true));
t.add_column("form_list", types::custom("jsonb"));
t.add_column("created_at", types::custom("timestamp(3)"));
@ -59,7 +62,10 @@ pub fn migration() -> String {
m.create_table("interior_ref_lists", |t| {
t.add_column("id", types::primary().indexed(true));
t.add_column("shop_id", types::foreign("shops", "id").indexed(true));
t.add_column(
"shop_id",
types::foreign("shops", "id").indexed(true).unique(true),
);
t.add_column("owner_id", types::foreign("owners", "id").indexed(true));
t.add_column("ref_list", types::custom("jsonb"));
t.add_column("created_at", types::custom("timestamp(3)"));

View File

@ -1,6 +1,7 @@
use anyhow::{anyhow, Result};
use http::StatusCode;
use ipnetwork::IpNetwork;
use sqlx::types::Json;
use std::net::SocketAddr;
use tracing::instrument;
use uuid::Uuid;
@ -79,6 +80,35 @@ pub async fn create_shop(
.create(&env.db)
.await
.map_err(reject_anyhow)?;
// also save empty interior_ref_list and merchandise_list rows
if let Some(shop_id) = saved_shop.id {
let interior_ref_list = InteriorRefList {
id: None,
shop_id,
owner_id: Some(owner_id),
ref_list: Json::default(),
created_at: None,
updated_at: None,
};
interior_ref_list
.create(&env.db)
.await
.map_err(reject_anyhow)?;
let merchandise_list = MerchandiseList {
id: None,
shop_id,
owner_id: Some(owner_id),
form_list: Json::default(),
created_at: None,
updated_at: None,
};
merchandise_list
.create(&env.db)
.await
.map_err(reject_anyhow)?;
}
let url = saved_shop.url(&env.api_url).map_err(reject_anyhow)?;
let reply = json(&saved_shop);
let reply = with_header(reply, "Location", url.as_str());
@ -139,10 +169,7 @@ pub async fn delete_shop(
.await
.map_err(reject_anyhow)?;
env.caches.list_shops.clear().await;
env.caches
.latest_interior_ref_list_by_shop_id
.delete(id)
.await;
env.caches.interior_ref_list_by_shop_id.delete(id).await;
Ok(StatusCode::NO_CONTENT)
}
@ -307,12 +334,55 @@ pub async fn create_interior_ref_list(
let reply = with_status(reply, StatusCode::CREATED);
env.caches.list_interior_ref_lists.clear().await;
env.caches
.latest_interior_ref_list_by_shop_id
.interior_ref_list_by_shop_id
.delete(saved_interior_ref_list.shop_id)
.await;
Ok(reply)
}
pub async fn update_interior_ref_list(
id: i32,
interior_ref_list: InteriorRefList,
api_key: Option<Uuid>,
env: Environment,
) -> Result<impl Reply, Rejection> {
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
let interior_ref_list_with_id_and_owner_id = if interior_ref_list.owner_id.is_some() {
InteriorRefList {
id: Some(id),
..interior_ref_list
}
} else {
InteriorRefList {
id: Some(id),
owner_id: Some(owner_id),
..interior_ref_list
}
};
let updated_interior_ref_list = interior_ref_list_with_id_and_owner_id
.update(&env.db, owner_id, id)
.await
.map_err(reject_anyhow)?;
let url = updated_interior_ref_list
.url(&env.api_url)
.map_err(reject_anyhow)?;
let reply = json(&updated_interior_ref_list);
let reply = with_header(reply, "Location", url.as_str());
let reply = with_status(reply, StatusCode::CREATED);
env.caches
.interior_ref_list
.delete_response(id)
.await
.map_err(reject_anyhow)?;
env.caches
.interior_ref_list_by_shop_id
.delete_response(updated_interior_ref_list.shop_id)
.await
.map_err(reject_anyhow)?;
env.caches.list_interior_ref_lists.clear().await;
Ok(reply)
}
pub async fn delete_interior_ref_list(
id: i32,
api_key: Option<Uuid>,
@ -332,21 +402,20 @@ pub async fn delete_interior_ref_list(
.map_err(reject_anyhow)?;
env.caches.list_interior_ref_lists.clear().await;
env.caches
.latest_interior_ref_list_by_shop_id
.interior_ref_list_by_shop_id
.delete(interior_ref_list.shop_id)
.await;
Ok(StatusCode::NO_CONTENT)
}
pub async fn get_latest_interior_ref_list_by_shop_id(
pub async fn get_interior_ref_list_by_shop_id(
shop_id: i32,
env: Environment,
) -> Result<impl Reply, Rejection> {
env.caches
.latest_interior_ref_list_by_shop_id
.interior_ref_list_by_shop_id
.get_response(shop_id, || async {
let interior_ref_list =
InteriorRefList::get_latest_by_shop_id(&env.db, shop_id).await?;
let interior_ref_list = InteriorRefList::get_by_shop_id(&env.db, shop_id).await?;
let reply = json(&interior_ref_list);
let reply = with_status(reply, StatusCode::OK);
Ok(reply)
@ -406,6 +475,44 @@ pub async fn create_merchandise_list(
Ok(reply)
}
pub async fn update_merchandise_list(
id: i32,
merchandise_list: MerchandiseList,
api_key: Option<Uuid>,
env: Environment,
) -> Result<impl Reply, Rejection> {
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
let merchandise_list_with_id_and_owner_id = if merchandise_list.owner_id.is_some() {
MerchandiseList {
id: Some(id),
..merchandise_list
}
} else {
MerchandiseList {
id: Some(id),
owner_id: Some(owner_id),
..merchandise_list
}
};
let updated_merchandise_list = merchandise_list_with_id_and_owner_id
.update(&env.db, owner_id, id)
.await
.map_err(reject_anyhow)?;
let url = updated_merchandise_list
.url(&env.api_url)
.map_err(reject_anyhow)?;
let reply = json(&updated_merchandise_list);
let reply = with_header(reply, "Location", url.as_str());
let reply = with_status(reply, StatusCode::CREATED);
env.caches
.merchandise_list
.delete_response(id)
.await
.map_err(reject_anyhow)?;
env.caches.list_merchandise_lists.clear().await;
Ok(reply)
}
pub async fn delete_merchandise_list(
id: i32,
api_key: Option<Uuid>,

View File

@ -71,6 +71,7 @@ where
{
warp::body::content_length_limit(1024 * 64).and(warp::body::json())
}
#[tokio::main]
async fn main() -> Result<()> {
dotenv().ok();
@ -199,6 +200,15 @@ async fn main() -> Result<()> {
.and(with_env(env.clone()))
.and_then(handlers::delete_interior_ref_list),
);
let update_interior_ref_list_handler = warp::path("interior_ref_lists").and(
warp::path::param()
.and(warp::path::end())
.and(warp::patch())
.and(json_body::<InteriorRefList>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::update_interior_ref_list),
);
let list_interior_ref_lists_handler = warp::path("interior_ref_lists").and(
warp::path::end()
.and(warp::get())
@ -206,13 +216,13 @@ async fn main() -> Result<()> {
.and(with_env(env.clone()))
.and_then(handlers::list_interior_ref_lists),
);
let get_latest_interior_ref_list_by_shop_id_handler = warp::path("shops").and(
let get_interior_ref_list_by_shop_id_handler = warp::path("shops").and(
warp::path::param()
.and(warp::path("latest_interior_ref_list"))
.and(warp::path("interior_ref_list"))
.and(warp::path::end())
.and(warp::get())
.and(with_env(env.clone()))
.and_then(handlers::get_latest_interior_ref_list_by_shop_id),
.and_then(handlers::get_interior_ref_list_by_shop_id),
);
let get_merchandise_list_handler = warp::path("merchandise_lists").and(
warp::path::param()
@ -237,6 +247,15 @@ async fn main() -> Result<()> {
.and(with_env(env.clone()))
.and_then(handlers::delete_merchandise_list),
);
let update_merchandise_list_handler = warp::path("merchandise_lists").and(
warp::path::param()
.and(warp::path::end())
.and(warp::patch())
.and(json_body::<MerchandiseList>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::update_merchandise_list),
);
let list_merchandise_lists_handler = warp::path("merchandise_lists").and(
warp::path::end()
.and(warp::get())
@ -258,15 +277,18 @@ async fn main() -> Result<()> {
update_shop_handler,
create_shop_handler,
list_shops_handler,
get_latest_interior_ref_list_by_shop_id_handler,
get_interior_ref_list_by_shop_id_handler,
get_interior_ref_list_handler,
delete_interior_ref_list_handler,
update_interior_ref_list_handler,
create_interior_ref_list_handler,
list_interior_ref_lists_handler,
get_merchandise_list_handler,
delete_merchandise_list_handler,
update_merchandise_list_handler,
create_merchandise_list_handler,
list_merchandise_lists_handler,
// warp::any().map(|| StatusCode::NOT_FOUND),
))
.recover(problem::unpack_problem)
.with(warp::compression::gzip())

View File

@ -7,7 +7,7 @@ use sqlx::types::Json;
use tracing::instrument;
use super::ListParams;
use super::Model;
use super::{Model, UpdateableModel};
use crate::problem::forbidden_permission;
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
@ -126,15 +126,41 @@ impl Model for InteriorRefList {
}
}
#[async_trait]
impl UpdateableModel for InteriorRefList {
#[instrument(level = "debug", skip(self, db))]
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
let interior_ref_list =
sqlx::query!("SELECT owner_id FROM interior_ref_lists WHERE id = $1", id)
.fetch_one(db)
.await?;
if interior_ref_list.owner_id == owner_id {
Ok(sqlx::query_as_unchecked!(
Self,
"UPDATE interior_ref_lists SET
ref_list = $2,
updated_at = now()
WHERE id = $1
RETURNING *",
id,
self.ref_list,
)
.fetch_one(db)
.await?)
} else {
return Err(forbidden_permission());
}
}
}
impl InteriorRefList {
#[instrument(level = "debug", skip(db))]
pub async fn get_latest_by_shop_id(db: &PgPool, shop_id: i32) -> Result<Self> {
pub async fn get_by_shop_id(db: &PgPool, shop_id: i32) -> Result<Self> {
sqlx::query_as_unchecked!(
Self,
"SELECT interior_ref_lists.* FROM interior_ref_lists
INNER JOIN shops ON (interior_ref_lists.shop_id = shops.id)
WHERE shops.id = $1
ORDER BY interior_ref_lists.created_at DESC
LIMIT 1",
shop_id,
)

View File

@ -7,7 +7,7 @@ use sqlx::types::Json;
use tracing::instrument;
use super::ListParams;
use super::Model;
use super::{Model, UpdateableModel};
use crate::problem::forbidden_permission;
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
@ -117,3 +117,30 @@ impl Model for MerchandiseList {
Ok(result)
}
}
#[async_trait]
impl UpdateableModel for MerchandiseList {
#[instrument(level = "debug", skip(self, db))]
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
let merchandise_list =
sqlx::query!("SELECT owner_id FROM merchandise_lists WHERE id = $1", id)
.fetch_one(db)
.await?;
if merchandise_list.owner_id == owner_id {
Ok(sqlx::query_as_unchecked!(
Self,
"UPDATE merchandise_lists SET
form_list = $2,
updated_at = now()
WHERE id = $1
RETURNING *",
id,
self.form_list,
)
.fetch_one(db)
.await?)
} else {
return Err(forbidden_permission());
}
}
}