Upgrade to sqlx 0.4 beta master branch
This required a ton of changes including updating error handling and separating out the models into intermediate representations so that fields that are marked non-null in the database are not `Option` in the final model. The update allows using `query_as!` in `interior_ref_list` and `merchandise_list` and model functions to specify a generic `Executor` param that can take either a db pool connection, transaction, or plain db connection. This should allow me to impl my old `Model` trait again. Also compile times are magically 20x faster?!?
This commit is contained in:
@@ -6,7 +6,7 @@ use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::models::{InteriorRefList, ListParams};
|
||||
use crate::models::{InteriorRefList, ListParams, PostedInteriorRefList, UnsavedInteriorRefList};
|
||||
use crate::problem::reject_anyhow;
|
||||
use crate::Environment;
|
||||
|
||||
@@ -107,7 +107,7 @@ pub async fn list(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
interior_ref_list: InteriorRefList,
|
||||
interior_ref_list: PostedInteriorRefList,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -119,12 +119,12 @@ pub async fn create(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let ref_list_with_owner_id = InteriorRefList {
|
||||
owner_id: Some(owner_id),
|
||||
..interior_ref_list
|
||||
let unsaved_interior_ref_list = UnsavedInteriorRefList {
|
||||
owner_id,
|
||||
shop_id: interior_ref_list.shop_id,
|
||||
ref_list: interior_ref_list.ref_list,
|
||||
};
|
||||
let saved_interior_ref_list = ref_list_with_owner_id
|
||||
.create(&env.db)
|
||||
let saved_interior_ref_list = InteriorRefList::create(unsaved_interior_ref_list, &env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = saved_interior_ref_list
|
||||
@@ -159,7 +159,7 @@ pub async fn create(
|
||||
|
||||
pub async fn update(
|
||||
id: i32,
|
||||
interior_ref_list: InteriorRefList,
|
||||
interior_ref_list: PostedInteriorRefList,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -171,22 +171,10 @@ pub async fn update(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
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 updated_interior_ref_list =
|
||||
InteriorRefList::update(interior_ref_list, &env.db, owner_id, id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = updated_interior_ref_list
|
||||
.url(&env.api_url)
|
||||
.map_err(reject_anyhow)?;
|
||||
@@ -221,7 +209,7 @@ pub async fn update(
|
||||
|
||||
pub async fn update_by_shop_id(
|
||||
shop_id: i32,
|
||||
interior_ref_list: InteriorRefList,
|
||||
interior_ref_list: PostedInteriorRefList,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -233,14 +221,10 @@ pub async fn update_by_shop_id(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let interior_ref_list_with_owner_id = InteriorRefList {
|
||||
owner_id: Some(owner_id),
|
||||
..interior_ref_list
|
||||
};
|
||||
let updated_interior_ref_list = interior_ref_list_with_owner_id
|
||||
.update_by_shop_id(&env.db, owner_id, shop_id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let updated_interior_ref_list =
|
||||
InteriorRefList::update_by_shop_id(interior_ref_list, &env.db, owner_id, shop_id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = updated_interior_ref_list
|
||||
.url(&env.api_url)
|
||||
.map_err(reject_anyhow)?;
|
||||
@@ -257,11 +241,14 @@ pub async fn update_by_shop_id(
|
||||
let reply = with_header(reply, "Location", url.as_str());
|
||||
let reply = with_status(reply, StatusCode::CREATED);
|
||||
tokio::spawn(async move {
|
||||
let id = updated_interior_ref_list
|
||||
.id
|
||||
.expect("saved interior_ref_list has no id");
|
||||
CACHES.interior_ref_list.delete_response(id).await;
|
||||
CACHES.interior_ref_list_bin.delete_response(id).await;
|
||||
CACHES
|
||||
.interior_ref_list
|
||||
.delete_response(updated_interior_ref_list.id)
|
||||
.await;
|
||||
CACHES
|
||||
.interior_ref_list_bin
|
||||
.delete_response(updated_interior_ref_list.id)
|
||||
.await;
|
||||
CACHES
|
||||
.interior_ref_list_by_shop_id
|
||||
.delete_response(updated_interior_ref_list.shop_id)
|
||||
|
||||
@@ -6,7 +6,7 @@ use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::models::{ListParams, MerchandiseList};
|
||||
use crate::models::{ListParams, MerchandiseList, PostedMerchandiseList, UnsavedMerchandiseList};
|
||||
use crate::problem::reject_anyhow;
|
||||
use crate::Environment;
|
||||
|
||||
@@ -105,7 +105,7 @@ pub async fn list(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
merchandise_list: MerchandiseList,
|
||||
merchandise_list: PostedMerchandiseList,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -117,12 +117,12 @@ pub async fn create(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let ref_list_with_owner_id = MerchandiseList {
|
||||
owner_id: Some(owner_id),
|
||||
..merchandise_list
|
||||
let unsaved_merchandise_list = UnsavedMerchandiseList {
|
||||
owner_id,
|
||||
shop_id: merchandise_list.shop_id,
|
||||
form_list: merchandise_list.form_list,
|
||||
};
|
||||
let saved_merchandise_list = ref_list_with_owner_id
|
||||
.create(&env.db)
|
||||
let saved_merchandise_list = MerchandiseList::create(unsaved_merchandise_list, &env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = saved_merchandise_list
|
||||
@@ -156,7 +156,7 @@ pub async fn create(
|
||||
|
||||
pub async fn update(
|
||||
id: i32,
|
||||
merchandise_list: MerchandiseList,
|
||||
merchandise_list: PostedMerchandiseList,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -168,20 +168,7 @@ pub async fn update(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
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)
|
||||
let updated_merchandise_list = MerchandiseList::update(merchandise_list, &env.db, owner_id, id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = updated_merchandise_list
|
||||
@@ -218,7 +205,7 @@ pub async fn update(
|
||||
|
||||
pub async fn update_by_shop_id(
|
||||
shop_id: i32,
|
||||
merchandise_list: MerchandiseList,
|
||||
merchandise_list: PostedMerchandiseList,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -230,14 +217,10 @@ pub async fn update_by_shop_id(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let merchandise_list_with_owner_id = MerchandiseList {
|
||||
owner_id: Some(owner_id),
|
||||
..merchandise_list
|
||||
};
|
||||
let updated_merchandise_list = merchandise_list_with_owner_id
|
||||
.update_by_shop_id(&env.db, owner_id, shop_id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let updated_merchandise_list =
|
||||
MerchandiseList::update_by_shop_id(merchandise_list, &env.db, owner_id, shop_id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = updated_merchandise_list
|
||||
.url(&env.api_url)
|
||||
.map_err(reject_anyhow)?;
|
||||
@@ -254,11 +237,14 @@ pub async fn update_by_shop_id(
|
||||
let reply = with_header(reply, "Location", url.as_str());
|
||||
let reply = with_status(reply, StatusCode::CREATED);
|
||||
tokio::spawn(async move {
|
||||
let id = updated_merchandise_list
|
||||
.id
|
||||
.expect("saved merchandise_list has no id");
|
||||
CACHES.merchandise_list.delete_response(id).await;
|
||||
CACHES.merchandise_list_bin.delete_response(id).await;
|
||||
CACHES
|
||||
.merchandise_list
|
||||
.delete_response(updated_merchandise_list.id)
|
||||
.await;
|
||||
CACHES
|
||||
.merchandise_list_bin
|
||||
.delete_response(updated_merchandise_list.id)
|
||||
.await;
|
||||
CACHES
|
||||
.merchandise_list_by_shop_id
|
||||
.delete_response(updated_merchandise_list.shop_id)
|
||||
|
||||
@@ -8,7 +8,7 @@ use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::models::{ListParams, Owner};
|
||||
use crate::models::{ListParams, Owner, PostedOwner, UnsavedOwner};
|
||||
use crate::problem::{reject_anyhow, unauthorized_no_api_key};
|
||||
use crate::Environment;
|
||||
|
||||
@@ -65,7 +65,7 @@ pub async fn list(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
owner: Owner,
|
||||
owner: PostedOwner,
|
||||
remote_addr: Option<SocketAddr>,
|
||||
api_key: Option<Uuid>,
|
||||
real_ip: Option<IpNetwork>,
|
||||
@@ -79,20 +79,16 @@ pub async fn create(
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let owner_with_ip_and_key = match remote_addr {
|
||||
Some(addr) => Owner {
|
||||
api_key: Some(api_key),
|
||||
ip_address: Some(IpNetwork::from(addr.ip())),
|
||||
..owner
|
||||
},
|
||||
None => Owner {
|
||||
api_key: Some(api_key),
|
||||
ip_address: real_ip,
|
||||
..owner
|
||||
let unsaved_owner = UnsavedOwner {
|
||||
api_key,
|
||||
ip_address: match remote_addr {
|
||||
Some(addr) => Some(IpNetwork::from(addr.ip())),
|
||||
None => real_ip,
|
||||
},
|
||||
name: owner.name,
|
||||
mod_version: owner.mod_version,
|
||||
};
|
||||
let saved_owner = owner_with_ip_and_key
|
||||
.create(&env.db)
|
||||
let saved_owner = Owner::create(unsaved_owner, &env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = saved_owner.url(&env.api_url).map_err(reject_anyhow)?;
|
||||
@@ -118,7 +114,7 @@ pub async fn create(
|
||||
|
||||
pub async fn update(
|
||||
id: i32,
|
||||
owner: Owner,
|
||||
owner: PostedOwner,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -130,12 +126,7 @@ pub async fn update(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
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)
|
||||
let updated_owner = Owner::update(owner, &env.db, owner_id, id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = updated_owner.url(&env.api_url).map_err(reject_anyhow)?;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
use http::StatusCode;
|
||||
use mime::Mime;
|
||||
use uuid::Uuid;
|
||||
@@ -6,7 +6,10 @@ use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::models::{InteriorRefList, ListParams, MerchandiseList, Shop};
|
||||
use crate::models::{
|
||||
InteriorRefList, ListParams, MerchandiseList, PostedShop, Shop, UnsavedInteriorRefList,
|
||||
UnsavedMerchandiseList, UnsavedShop,
|
||||
};
|
||||
use crate::problem::reject_anyhow;
|
||||
use crate::Environment;
|
||||
|
||||
@@ -63,7 +66,7 @@ pub async fn list(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
shop: Shop,
|
||||
shop: PostedShop,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -75,43 +78,40 @@ pub async fn create(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let shop_with_owner_id = Shop {
|
||||
owner_id: Some(owner_id),
|
||||
..shop
|
||||
let unsaved_shop = UnsavedShop {
|
||||
name: shop.name,
|
||||
description: shop.description,
|
||||
owner_id,
|
||||
};
|
||||
let saved_shop = shop_with_owner_id
|
||||
.create(&env.db)
|
||||
let mut tx = env
|
||||
.db
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|error| reject_anyhow(anyhow!(error)))?;
|
||||
let saved_shop = Shop::create(unsaved_shop, &mut tx)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
|
||||
// also save empty interior_ref_list and merchandise_list rows
|
||||
// TODO: do this in a transaction with shop.create
|
||||
if let Some(shop_id) = saved_shop.id {
|
||||
let interior_ref_list = InteriorRefList {
|
||||
id: None,
|
||||
shop_id,
|
||||
owner_id: Some(owner_id),
|
||||
ref_list: sqlx::types::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: sqlx::types::Json::default(),
|
||||
created_at: None,
|
||||
updated_at: None,
|
||||
};
|
||||
merchandise_list
|
||||
.create(&env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
}
|
||||
let interior_ref_list = UnsavedInteriorRefList {
|
||||
shop_id: saved_shop.id,
|
||||
owner_id,
|
||||
ref_list: sqlx::types::Json::default(),
|
||||
};
|
||||
InteriorRefList::create(interior_ref_list, &mut tx)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let merchandise_list = UnsavedMerchandiseList {
|
||||
shop_id: saved_shop.id,
|
||||
owner_id,
|
||||
form_list: sqlx::types::Json::default(),
|
||||
};
|
||||
MerchandiseList::create(merchandise_list, &mut tx)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
tx.commit()
|
||||
.await
|
||||
.map_err(|error| reject_anyhow(anyhow!(error)))?;
|
||||
|
||||
let url = saved_shop.url(&env.api_url).map_err(reject_anyhow)?;
|
||||
let reply: Box<dyn Reply> = match content_type {
|
||||
@@ -133,7 +133,7 @@ pub async fn create(
|
||||
|
||||
pub async fn update(
|
||||
id: i32,
|
||||
shop: Shop,
|
||||
shop: PostedShop,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -145,21 +145,15 @@ pub async fn update(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
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 posted_shop = PostedShop {
|
||||
owner_id: match shop.owner_id {
|
||||
// allows an owner to transfer ownership of shop to another owner
|
||||
Some(posted_owner_id) => Some(posted_owner_id),
|
||||
None => Some(owner_id),
|
||||
},
|
||||
..shop
|
||||
};
|
||||
let updated_shop = shop_with_id_and_owner_id
|
||||
.update(&env.db, owner_id, id)
|
||||
let updated_shop = Shop::update(posted_shop, &env.db, owner_id, id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = updated_shop.url(&env.api_url).map_err(reject_anyhow)?;
|
||||
|
||||
@@ -6,7 +6,9 @@ use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::models::{ListParams, MerchandiseList, Transaction};
|
||||
use crate::models::{
|
||||
ListParams, MerchandiseList, PostedTransaction, Transaction, UnsavedTransaction,
|
||||
};
|
||||
use crate::problem::reject_anyhow;
|
||||
use crate::Environment;
|
||||
|
||||
@@ -99,7 +101,7 @@ pub async fn list_by_shop_id(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
transaction: Transaction,
|
||||
transaction: PostedTransaction,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
@@ -111,17 +113,25 @@ pub async fn create(
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let transaction_with_owner_id = Transaction {
|
||||
owner_id: Some(owner_id),
|
||||
..transaction
|
||||
let unsaved_transaction = UnsavedTransaction {
|
||||
shop_id: transaction.shop_id,
|
||||
owner_id,
|
||||
mod_name: transaction.mod_name,
|
||||
local_form_id: transaction.local_form_id,
|
||||
name: transaction.name,
|
||||
form_type: transaction.form_type,
|
||||
is_food: transaction.is_food,
|
||||
price: transaction.price,
|
||||
is_sell: transaction.is_sell,
|
||||
quantity: transaction.quantity,
|
||||
amount: transaction.amount,
|
||||
};
|
||||
let mut tx = env
|
||||
.db
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|error| reject_anyhow(anyhow!(error)))?;
|
||||
let saved_transaction = transaction_with_owner_id
|
||||
.create(&mut tx)
|
||||
let saved_transaction = Transaction::create(unsaved_transaction, &mut tx)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let quantity_delta = match transaction.is_sell {
|
||||
@@ -157,11 +167,14 @@ pub async fn create(
|
||||
let reply = with_status(reply, StatusCode::CREATED);
|
||||
tokio::spawn(async move {
|
||||
// TODO: will this make these caches effectively useless?
|
||||
let merch_id = updated_merchandise_list
|
||||
.id
|
||||
.expect("saved merchandise_list has no id");
|
||||
CACHES.merchandise_list.delete_response(merch_id).await;
|
||||
CACHES.merchandise_list_bin.delete_response(merch_id).await;
|
||||
CACHES
|
||||
.merchandise_list
|
||||
.delete_response(updated_merchandise_list.id)
|
||||
.await;
|
||||
CACHES
|
||||
.merchandise_list_bin
|
||||
.delete_response(updated_merchandise_list.id)
|
||||
.await;
|
||||
CACHES
|
||||
.merchandise_list_by_shop_id
|
||||
.delete_response(updated_merchandise_list.shop_id)
|
||||
|
||||
38
src/main.rs
38
src/main.rs
@@ -7,7 +7,8 @@ use http::StatusCode;
|
||||
use hyper::server::Server;
|
||||
use listenfd::ListenFd;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use std::convert::Infallible;
|
||||
use std::env;
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
@@ -21,20 +22,23 @@ mod macros;
|
||||
mod models;
|
||||
mod problem;
|
||||
|
||||
use models::{InteriorRefList, ListParams, MerchandiseList, Owner, Shop, Transaction};
|
||||
use models::{
|
||||
ListParams, PostedInteriorRefList, PostedMerchandiseList, PostedOwner, PostedShop,
|
||||
PostedTransaction,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Environment {
|
||||
pub db: PgPool,
|
||||
pub db: Pool<Postgres>,
|
||||
pub api_url: Url,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
async fn new(api_url: Url) -> Result<Environment> {
|
||||
Ok(Environment {
|
||||
db: PgPool::builder()
|
||||
.max_size(5)
|
||||
.build(&env::var("DATABASE_URL")?)
|
||||
db: PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&env::var("DATABASE_URL")?)
|
||||
.await?,
|
||||
api_url,
|
||||
})
|
||||
@@ -90,7 +94,7 @@ async fn main() -> Result<()> {
|
||||
let create_owner_handler = warp::path("owners").and(
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(json_body::<Owner>())
|
||||
.and(json_body::<PostedOwner>())
|
||||
.and(warp::addr::remote())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("x-real-ip"))
|
||||
@@ -110,7 +114,7 @@ async fn main() -> Result<()> {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<Owner>())
|
||||
.and(json_body::<PostedOwner>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@@ -137,7 +141,7 @@ async fn main() -> Result<()> {
|
||||
let create_shop_handler = warp::path("shops").and(
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(json_body::<Shop>())
|
||||
.and(json_body::<PostedShop>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@@ -155,7 +159,7 @@ async fn main() -> Result<()> {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<Shop>())
|
||||
.and(json_body::<PostedShop>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@@ -182,7 +186,7 @@ async fn main() -> Result<()> {
|
||||
let create_interior_ref_list_handler = warp::path("interior_ref_lists").and(
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(json_body::<InteriorRefList>())
|
||||
.and(json_body::<PostedInteriorRefList>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@@ -200,7 +204,7 @@ async fn main() -> Result<()> {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<InteriorRefList>())
|
||||
.and(json_body::<PostedInteriorRefList>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@@ -211,7 +215,7 @@ async fn main() -> Result<()> {
|
||||
.and(warp::path("interior_ref_list"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<InteriorRefList>())
|
||||
.and(json_body::<PostedInteriorRefList>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@@ -248,7 +252,7 @@ async fn main() -> Result<()> {
|
||||
let create_merchandise_list_handler = warp::path("merchandise_lists").and(
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(json_body::<MerchandiseList>())
|
||||
.and(json_body::<PostedMerchandiseList>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@@ -266,7 +270,7 @@ async fn main() -> Result<()> {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<MerchandiseList>())
|
||||
.and(json_body::<PostedMerchandiseList>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@@ -277,7 +281,7 @@ async fn main() -> Result<()> {
|
||||
.and(warp::path("merchandise_list"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<MerchandiseList>())
|
||||
.and(json_body::<PostedMerchandiseList>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@@ -314,7 +318,7 @@ async fn main() -> Result<()> {
|
||||
let create_transaction_handler = warp::path("transactions").and(
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(json_body::<Transaction>())
|
||||
.and(json_body::<PostedTransaction>())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use anyhow::{Error, Result};
|
||||
use chrono::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::types::Json;
|
||||
use sqlx::PgPool;
|
||||
use sqlx::{Done, Executor, Postgres};
|
||||
use tracing::instrument;
|
||||
use url::Url;
|
||||
|
||||
use super::ListParams;
|
||||
use crate::problem::forbidden_permission;
|
||||
|
||||
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
|
||||
// support user-defined types (`ref_list` Json field).
|
||||
// See for more info: https://github.com/thallada/rust_sqlx_bug/blob/master/src/main.rs
|
||||
// This may be fixed in sqlx 0.4.
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct InteriorRef {
|
||||
pub base_mod_name: String,
|
||||
@@ -31,12 +26,26 @@ pub struct InteriorRef {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct InteriorRefList {
|
||||
pub id: Option<i32>,
|
||||
pub id: i32,
|
||||
pub shop_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub ref_list: serde_json::Value,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UnsavedInteriorRefList {
|
||||
pub shop_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub ref_list: Json<Vec<InteriorRef>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PostedInteriorRefList {
|
||||
pub shop_id: i32,
|
||||
pub owner_id: Option<i32>,
|
||||
pub ref_list: Json<Vec<InteriorRef>>,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
impl InteriorRefList {
|
||||
@@ -44,52 +53,48 @@ impl InteriorRefList {
|
||||
"interior_ref_list"
|
||||
}
|
||||
|
||||
pub fn pk(&self) -> Option<i32> {
|
||||
pub fn pk(&self) -> i32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn url(&self, api_url: &Url) -> Result<Url> {
|
||||
if let Some(pk) = self.pk() {
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), pk))?)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Cannot get URL for {} with no primary key",
|
||||
Self::resource_name()
|
||||
))
|
||||
}
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), self.pk()))?)
|
||||
}
|
||||
|
||||
// TODO: this model will probably never need to be accessed through it's ID, should these methods be removed/unimplemented?
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn get(db: &PgPool, id: i32) -> Result<Self> {
|
||||
sqlx::query_as_unchecked!(Self, "SELECT * FROM interior_ref_lists WHERE id = $1", id)
|
||||
pub async fn get(db: impl Executor<'_, Database = Postgres>, id: i32) -> Result<Self> {
|
||||
sqlx::query_as!(Self, "SELECT * FROM interior_ref_lists WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub async fn create(self, db: &PgPool) -> Result<Self> {
|
||||
// TODO:
|
||||
// * 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
|
||||
// * should probably omit ref_list from response
|
||||
Ok(sqlx::query_as_unchecked!(
|
||||
#[instrument(level = "debug", skip(interior_ref_list, db))]
|
||||
pub async fn create(
|
||||
interior_ref_list: UnsavedInteriorRefList,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"INSERT INTO interior_ref_lists
|
||||
(shop_id, owner_id, ref_list, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, now(), now())
|
||||
RETURNING *",
|
||||
self.shop_id,
|
||||
self.owner_id,
|
||||
self.ref_list,
|
||||
interior_ref_list.shop_id,
|
||||
interior_ref_list.owner_id,
|
||||
serde_json::json!(interior_ref_list.ref_list),
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||
pub async fn delete(
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
id: i32,
|
||||
) -> Result<u64> {
|
||||
let interior_ref_list =
|
||||
sqlx::query!("SELECT owner_id FROM interior_ref_lists WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
@@ -98,7 +103,8 @@ impl InteriorRefList {
|
||||
return Ok(
|
||||
sqlx::query!("DELETE FROM interior_ref_lists WHERE id = $1", id)
|
||||
.execute(db)
|
||||
.await?,
|
||||
.await?
|
||||
.rows_affected(),
|
||||
);
|
||||
} else {
|
||||
return Err(forbidden_permission());
|
||||
@@ -106,9 +112,12 @@ impl InteriorRefList {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn list(db: &PgPool, list_params: &ListParams) -> Result<Vec<Self>> {
|
||||
pub async fn list(
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
list_params: &ListParams,
|
||||
) -> Result<Vec<Self>> {
|
||||
let result = if let Some(order_by) = list_params.get_order_by() {
|
||||
sqlx::query_as_unchecked!(
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM interior_ref_lists
|
||||
ORDER BY $1
|
||||
@@ -121,7 +130,7 @@ impl InteriorRefList {
|
||||
.fetch_all(db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as_unchecked!(
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM interior_ref_lists
|
||||
LIMIT $1
|
||||
@@ -135,14 +144,19 @@ impl InteriorRefList {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
|
||||
let interior_ref_list =
|
||||
#[instrument(level = "debug", skip(interior_ref_list, db))]
|
||||
pub async fn update(
|
||||
interior_ref_list: PostedInteriorRefList,
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
id: i32,
|
||||
) -> Result<Self> {
|
||||
let existing_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!(
|
||||
if existing_interior_ref_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE interior_ref_lists SET
|
||||
ref_list = $2,
|
||||
@@ -150,7 +164,7 @@ impl InteriorRefList {
|
||||
WHERE id = $1
|
||||
RETURNING *",
|
||||
id,
|
||||
self.ref_list,
|
||||
serde_json::json!(interior_ref_list.ref_list),
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
@@ -160,8 +174,11 @@ impl InteriorRefList {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn get_by_shop_id(db: &PgPool, shop_id: i32) -> Result<Self> {
|
||||
sqlx::query_as_unchecked!(
|
||||
pub async fn get_by_shop_id(
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
shop_id: i32,
|
||||
) -> Result<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM interior_ref_lists
|
||||
WHERE shop_id = $1",
|
||||
@@ -172,16 +189,21 @@ impl InteriorRefList {
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub async fn update_by_shop_id(self, db: &PgPool, owner_id: i32, shop_id: i32) -> Result<Self> {
|
||||
let interior_ref_list = sqlx::query!(
|
||||
#[instrument(level = "debug", skip(interior_ref_list, db))]
|
||||
pub async fn update_by_shop_id(
|
||||
interior_ref_list: PostedInteriorRefList,
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
shop_id: i32,
|
||||
) -> Result<Self> {
|
||||
let existing_interior_ref_list = sqlx::query!(
|
||||
"SELECT owner_id FROM interior_ref_lists WHERE shop_id = $1",
|
||||
shop_id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if interior_ref_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as_unchecked!(
|
||||
if existing_interior_ref_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE interior_ref_lists SET
|
||||
ref_list = $2,
|
||||
@@ -189,7 +211,7 @@ impl InteriorRefList {
|
||||
WHERE shop_id = $1
|
||||
RETURNING *",
|
||||
shop_id,
|
||||
self.ref_list,
|
||||
serde_json::json!(interior_ref_list.ref_list),
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
|
||||
@@ -4,20 +4,14 @@ use http::StatusCode;
|
||||
use http_api_problem::HttpApiProblem;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::types::Json;
|
||||
use sqlx::{PgConnection, PgPool, Transaction};
|
||||
use sqlx::{Done, Executor, Postgres};
|
||||
use tracing::instrument;
|
||||
use url::Url;
|
||||
|
||||
use super::ListParams;
|
||||
use crate::problem::forbidden_permission;
|
||||
|
||||
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
|
||||
// support user-defined types (`form_list` Json field).
|
||||
// See for more info: https://github.com/thallada/rust_sqlx_bug/blob/master/src/main.rs
|
||||
// This may be fixed in sqlx 0.4.
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Merchandise {
|
||||
pub mod_name: String,
|
||||
@@ -31,12 +25,26 @@ pub struct Merchandise {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct MerchandiseList {
|
||||
pub id: Option<i32>,
|
||||
pub id: i32,
|
||||
pub shop_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub form_list: serde_json::Value,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UnsavedMerchandiseList {
|
||||
pub shop_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub form_list: Json<Vec<Merchandise>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PostedMerchandiseList {
|
||||
pub shop_id: i32,
|
||||
pub owner_id: Option<i32>,
|
||||
pub form_list: Json<Vec<Merchandise>>,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
impl MerchandiseList {
|
||||
@@ -44,48 +52,48 @@ impl MerchandiseList {
|
||||
"merchandise_list"
|
||||
}
|
||||
|
||||
pub fn pk(&self) -> Option<i32> {
|
||||
pub fn pk(&self) -> i32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn url(&self, api_url: &Url) -> Result<Url> {
|
||||
if let Some(pk) = self.pk() {
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), pk))?)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Cannot get URL for {} with no primary key",
|
||||
Self::resource_name()
|
||||
))
|
||||
}
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), self.pk()))?)
|
||||
}
|
||||
|
||||
// TODO: this model will probably never need to be accessed through it's ID, should these methods be removed/unimplemented?
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn get(db: &PgPool, id: i32) -> Result<Self> {
|
||||
sqlx::query_as_unchecked!(Self, "SELECT * FROM merchandise_lists WHERE id = $1", id)
|
||||
pub async fn get(db: impl Executor<'_, Database = Postgres>, id: i32) -> Result<Self> {
|
||||
sqlx::query_as!(Self, "SELECT * FROM merchandise_lists WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub async fn create(self, db: &PgPool) -> Result<Self> {
|
||||
Ok(sqlx::query_as_unchecked!(
|
||||
#[instrument(level = "debug", skip(merchandise_list, db))]
|
||||
pub async fn create(
|
||||
merchandise_list: UnsavedMerchandiseList,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"INSERT INTO merchandise_lists
|
||||
(shop_id, owner_id, form_list, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, now(), now())
|
||||
RETURNING *",
|
||||
self.shop_id,
|
||||
self.owner_id,
|
||||
self.form_list,
|
||||
merchandise_list.shop_id,
|
||||
merchandise_list.owner_id,
|
||||
serde_json::json!(merchandise_list.form_list),
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||
pub async fn delete(
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
id: i32,
|
||||
) -> Result<u64> {
|
||||
let merchandise_list =
|
||||
sqlx::query!("SELECT owner_id FROM merchandise_lists WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
@@ -94,7 +102,8 @@ impl MerchandiseList {
|
||||
return Ok(
|
||||
sqlx::query!("DELETE FROM merchandise_lists WHERE id = $1", id)
|
||||
.execute(db)
|
||||
.await?,
|
||||
.await?
|
||||
.rows_affected(),
|
||||
);
|
||||
} else {
|
||||
return Err(forbidden_permission());
|
||||
@@ -102,9 +111,12 @@ impl MerchandiseList {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn list(db: &PgPool, list_params: &ListParams) -> Result<Vec<Self>> {
|
||||
pub async fn list(
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
list_params: &ListParams,
|
||||
) -> Result<Vec<Self>> {
|
||||
let result = if let Some(order_by) = list_params.get_order_by() {
|
||||
sqlx::query_as_unchecked!(
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM merchandise_lists
|
||||
ORDER BY $1
|
||||
@@ -117,7 +129,7 @@ impl MerchandiseList {
|
||||
.fetch_all(db)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as_unchecked!(
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM merchandise_lists
|
||||
LIMIT $1
|
||||
@@ -131,14 +143,19 @@ impl MerchandiseList {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
|
||||
let merchandise_list =
|
||||
#[instrument(level = "debug", skip(merchandise_list, db))]
|
||||
pub async fn update(
|
||||
merchandise_list: PostedMerchandiseList,
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
id: i32,
|
||||
) -> Result<Self> {
|
||||
let existing_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!(
|
||||
if existing_merchandise_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE merchandise_lists SET
|
||||
form_list = $2,
|
||||
@@ -146,7 +163,7 @@ impl MerchandiseList {
|
||||
WHERE id = $1
|
||||
RETURNING *",
|
||||
id,
|
||||
self.form_list,
|
||||
serde_json::json!(merchandise_list.form_list),
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
@@ -156,8 +173,11 @@ impl MerchandiseList {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn get_by_shop_id(db: &PgPool, shop_id: i32) -> Result<Self> {
|
||||
sqlx::query_as_unchecked!(
|
||||
pub async fn get_by_shop_id(
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
shop_id: i32,
|
||||
) -> Result<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM merchandise_lists
|
||||
WHERE shop_id = $1",
|
||||
@@ -168,16 +188,21 @@ impl MerchandiseList {
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub async fn update_by_shop_id(self, db: &PgPool, owner_id: i32, shop_id: i32) -> Result<Self> {
|
||||
let merchandise_list = sqlx::query!(
|
||||
#[instrument(level = "debug", skip(merchandise_list, db))]
|
||||
pub async fn update_by_shop_id(
|
||||
merchandise_list: PostedMerchandiseList,
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
shop_id: i32,
|
||||
) -> Result<Self> {
|
||||
let existing_merchandise_list = sqlx::query!(
|
||||
"SELECT owner_id FROM merchandise_lists WHERE shop_id = $1",
|
||||
shop_id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if merchandise_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as_unchecked!(
|
||||
if existing_merchandise_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE merchandise_lists SET
|
||||
form_list = $2,
|
||||
@@ -185,7 +210,7 @@ impl MerchandiseList {
|
||||
WHERE shop_id = $1
|
||||
RETURNING *",
|
||||
shop_id,
|
||||
self.form_list,
|
||||
serde_json::json!(merchandise_list.form_list),
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
@@ -196,7 +221,7 @@ impl MerchandiseList {
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn update_merchandise_quantity(
|
||||
db: &mut Transaction<PoolConnection<PgConnection>>,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
shop_id: i32,
|
||||
mod_name: &str,
|
||||
local_form_id: i32,
|
||||
@@ -215,7 +240,7 @@ impl MerchandiseList {
|
||||
"is_food": is_food,
|
||||
"price": price,
|
||||
}]);
|
||||
Ok(sqlx::query_as_unchecked!(
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE
|
||||
merchandise_lists
|
||||
@@ -255,7 +280,7 @@ impl MerchandiseList {
|
||||
RETURNING merchandise_lists.*",
|
||||
shop_id,
|
||||
mod_name,
|
||||
local_form_id,
|
||||
&local_form_id.to_string(),
|
||||
quantity_delta,
|
||||
add_item,
|
||||
)
|
||||
@@ -263,10 +288,10 @@ impl MerchandiseList {
|
||||
.await
|
||||
.map_err(|error| {
|
||||
let anyhow_error = anyhow!(error);
|
||||
if let Some(sqlx::error::Error::Database(db_error)) =
|
||||
anyhow_error.downcast_ref::<sqlx::error::Error>()
|
||||
if let Some(db_error) =
|
||||
anyhow_error.downcast_ref::<sqlx::postgres::PgDatabaseError>()
|
||||
{
|
||||
if db_error.code() == Some("23502") && db_error.column_name() == Some("form_list") {
|
||||
if db_error.code() == "23502" && db_error.column() == Some("form_list") {
|
||||
return anyhow!(HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::NOT_FOUND
|
||||
)
|
||||
|
||||
@@ -9,12 +9,12 @@ pub mod owner;
|
||||
pub mod shop;
|
||||
pub mod transaction;
|
||||
|
||||
pub use interior_ref_list::InteriorRefList;
|
||||
pub use merchandise_list::MerchandiseList;
|
||||
pub use interior_ref_list::{InteriorRefList, PostedInteriorRefList, UnsavedInteriorRefList};
|
||||
pub use merchandise_list::{MerchandiseList, PostedMerchandiseList, UnsavedMerchandiseList};
|
||||
pub use model::{Model, UpdateableModel};
|
||||
pub use owner::Owner;
|
||||
pub use shop::Shop;
|
||||
pub use transaction::Transaction;
|
||||
pub use owner::{Owner, PostedOwner, UnsavedOwner};
|
||||
pub use shop::{PostedShop, Shop, UnsavedShop};
|
||||
pub use transaction::{PostedTransaction, Transaction, UnsavedTransaction};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Deserialize)]
|
||||
pub enum Order {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use anyhow::{Error, Result};
|
||||
use chrono::prelude::*;
|
||||
use ipnetwork::IpNetwork;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use sqlx::{Done, Executor, Postgres};
|
||||
use tracing::instrument;
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
@@ -12,15 +12,31 @@ use crate::problem::forbidden_permission;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Owner {
|
||||
pub id: Option<i32>,
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
#[serde(skip_serializing)]
|
||||
pub api_key: Option<Uuid>,
|
||||
pub api_key: Uuid,
|
||||
#[serde(skip_serializing)]
|
||||
pub ip_address: Option<IpNetwork>,
|
||||
pub mod_version: i32,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UnsavedOwner {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing)]
|
||||
pub api_key: Uuid,
|
||||
#[serde(skip_serializing)]
|
||||
pub ip_address: Option<IpNetwork>,
|
||||
pub mod_version: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PostedOwner {
|
||||
pub name: String,
|
||||
pub mod_version: i32,
|
||||
}
|
||||
|
||||
impl Owner {
|
||||
@@ -28,62 +44,66 @@ impl Owner {
|
||||
"owner"
|
||||
}
|
||||
|
||||
pub fn pk(&self) -> Option<i32> {
|
||||
pub fn pk(&self) -> i32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn url(&self, api_url: &Url) -> Result<Url> {
|
||||
if let Some(pk) = self.pk() {
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), pk))?)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Cannot get URL for {} with no primary key",
|
||||
Self::resource_name()
|
||||
))
|
||||
}
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), self.pk()))?)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn get(db: &PgPool, id: i32) -> Result<Self> {
|
||||
pub async fn get(db: impl Executor<'_, Database = Postgres>, id: i32) -> Result<Self> {
|
||||
sqlx::query_as!(Self, "SELECT * FROM owners WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub async fn create(self, db: &PgPool) -> Result<Self> {
|
||||
#[instrument(level = "debug", skip(owner, db))]
|
||||
pub async fn create(
|
||||
owner: UnsavedOwner,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"INSERT INTO owners
|
||||
(name, api_key, ip_address, mod_version, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, now(), now())
|
||||
RETURNING *",
|
||||
self.name,
|
||||
self.api_key,
|
||||
self.ip_address,
|
||||
self.mod_version,
|
||||
owner.name,
|
||||
owner.api_key,
|
||||
owner.ip_address,
|
||||
owner.mod_version,
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||
pub async fn delete(
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
id: i32,
|
||||
) -> Result<u64> {
|
||||
let owner = sqlx::query!("SELECT id FROM owners WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if owner.id == owner_id {
|
||||
Ok(sqlx::query!("DELETE FROM owners WHERE id = $1", id)
|
||||
.execute(db)
|
||||
.await?)
|
||||
.await?
|
||||
.rows_affected())
|
||||
} else {
|
||||
return Err(forbidden_permission());
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn list(db: &PgPool, list_params: &ListParams) -> Result<Vec<Self>> {
|
||||
pub async fn list(
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
list_params: &ListParams,
|
||||
) -> Result<Vec<Self>> {
|
||||
let result = if let Some(order_by) = list_params.get_order_by() {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
@@ -112,12 +132,17 @@ impl Owner {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub 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)
|
||||
#[instrument(level = "debug", skip(owner, db))]
|
||||
pub async fn update(
|
||||
owner: PostedOwner,
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
id: i32,
|
||||
) -> Result<Self> {
|
||||
let existing_owner = sqlx::query!("SELECT id FROM owners WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if owner.id == owner_id {
|
||||
if existing_owner.id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE owners SET
|
||||
@@ -127,8 +152,8 @@ impl Owner {
|
||||
WHERE id = $1
|
||||
RETURNING *",
|
||||
id,
|
||||
self.name,
|
||||
self.mod_version,
|
||||
owner.name,
|
||||
owner.mod_version,
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use anyhow::{Error, Result};
|
||||
use chrono::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use sqlx::{Done, Executor, Postgres};
|
||||
use tracing::instrument;
|
||||
use url::Url;
|
||||
|
||||
@@ -10,17 +10,26 @@ use crate::problem::forbidden_permission;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Shop {
|
||||
pub id: Option<i32>,
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub owner_id: i32,
|
||||
pub description: Option<String>,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UnsavedShop {
|
||||
pub name: String,
|
||||
pub owner_id: i32,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PostedShop {
|
||||
pub name: String,
|
||||
pub owner_id: Option<i32>,
|
||||
pub description: String,
|
||||
// removing these until I figure out the plan for buying and selling
|
||||
// pub is_not_sell_buy: bool,
|
||||
// pub sell_buy_list_id: i32,
|
||||
// pub vendor_id: i32,
|
||||
// pub vendor_gold: i32,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
impl Shop {
|
||||
@@ -28,61 +37,65 @@ impl Shop {
|
||||
"shop"
|
||||
}
|
||||
|
||||
pub fn pk(&self) -> Option<i32> {
|
||||
pub fn pk(&self) -> i32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn url(&self, api_url: &Url) -> Result<Url> {
|
||||
if let Some(pk) = self.pk() {
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), pk))?)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Cannot get URL for {} with no primary key",
|
||||
Self::resource_name()
|
||||
))
|
||||
}
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), self.pk()))?)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn get(db: &PgPool, id: i32) -> Result<Self> {
|
||||
pub async fn get(db: impl Executor<'_, Database = Postgres>, id: i32) -> Result<Self> {
|
||||
sqlx::query_as!(Self, "SELECT * FROM shops WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub async fn create(self, db: &PgPool) -> Result<Self> {
|
||||
#[instrument(level = "debug", skip(shop, db))]
|
||||
pub async fn create(
|
||||
shop: UnsavedShop,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"INSERT INTO shops
|
||||
(name, owner_id, description, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, now(), now())
|
||||
RETURNING *",
|
||||
self.name,
|
||||
self.owner_id,
|
||||
self.description,
|
||||
shop.name,
|
||||
shop.owner_id,
|
||||
shop.description,
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||
pub async fn delete(
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
id: i32,
|
||||
) -> Result<u64> {
|
||||
let shop = sqlx::query!("SELECT owner_id FROM shops WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if shop.owner_id == owner_id {
|
||||
return Ok(sqlx::query!("DELETE FROM shops WHERE shops.id = $1", id)
|
||||
.execute(db)
|
||||
.await?);
|
||||
.await?
|
||||
.rows_affected());
|
||||
} else {
|
||||
return Err(forbidden_permission());
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn list(db: &PgPool, list_params: &ListParams) -> Result<Vec<Self>> {
|
||||
pub async fn list(
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
list_params: &ListParams,
|
||||
) -> Result<Vec<Self>> {
|
||||
let result = if let Some(order_by) = list_params.get_order_by() {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
@@ -111,12 +124,17 @@ impl Shop {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, db))]
|
||||
pub 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)
|
||||
#[instrument(level = "debug", skip(shop, db))]
|
||||
pub async fn update(
|
||||
shop: PostedShop,
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
id: i32,
|
||||
) -> Result<Self> {
|
||||
let existing_shop = sqlx::query!("SELECT owner_id FROM shops WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if shop.owner_id == owner_id {
|
||||
if existing_shop.owner_id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE shops SET
|
||||
@@ -127,9 +145,9 @@ impl Shop {
|
||||
WHERE id = $1
|
||||
RETURNING *",
|
||||
id,
|
||||
self.name,
|
||||
self.owner_id,
|
||||
self.description,
|
||||
shop.name,
|
||||
shop.owner_id,
|
||||
shop.description,
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use anyhow::{Error, Result};
|
||||
use chrono::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{PgConnection, PgPool};
|
||||
use sqlx::{Done, Executor, Postgres};
|
||||
use tracing::instrument;
|
||||
use url::Url;
|
||||
|
||||
@@ -11,7 +10,39 @@ use crate::problem::forbidden_permission;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Transaction {
|
||||
pub id: Option<i32>,
|
||||
pub id: i32,
|
||||
pub shop_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub mod_name: String,
|
||||
pub local_form_id: i32,
|
||||
pub name: String,
|
||||
pub form_type: i32,
|
||||
pub is_food: bool,
|
||||
pub price: i32,
|
||||
pub is_sell: bool,
|
||||
pub quantity: i32,
|
||||
pub amount: i32,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UnsavedTransaction {
|
||||
pub shop_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub mod_name: String,
|
||||
pub local_form_id: i32,
|
||||
pub name: String,
|
||||
pub form_type: i32,
|
||||
pub is_food: bool,
|
||||
pub price: i32,
|
||||
pub is_sell: bool,
|
||||
pub quantity: i32,
|
||||
pub amount: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PostedTransaction {
|
||||
pub shop_id: i32,
|
||||
pub owner_id: Option<i32>,
|
||||
pub mod_name: String,
|
||||
@@ -23,8 +54,6 @@ pub struct Transaction {
|
||||
pub is_sell: bool,
|
||||
pub quantity: i32,
|
||||
pub amount: i32,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
@@ -32,23 +61,16 @@ impl Transaction {
|
||||
"transaction"
|
||||
}
|
||||
|
||||
pub fn pk(&self) -> Option<i32> {
|
||||
pub fn pk(&self) -> i32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn url(&self, api_url: &Url) -> Result<Url> {
|
||||
if let Some(pk) = self.pk() {
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), pk))?)
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Cannot get URL for {} with no primary key",
|
||||
Self::resource_name()
|
||||
))
|
||||
}
|
||||
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), self.pk()))?)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn get(db: &PgPool, id: i32) -> Result<Self> {
|
||||
pub async fn get(db: impl Executor<'_, Database = Postgres>, id: i32) -> Result<Self> {
|
||||
sqlx::query_as!(Self, "SELECT * FROM transactions WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
@@ -57,8 +79,8 @@ impl Transaction {
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn create(
|
||||
self,
|
||||
db: &mut sqlx::Transaction<PoolConnection<PgConnection>>,
|
||||
transaction: UnsavedTransaction,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
@@ -67,38 +89,46 @@ impl Transaction {
|
||||
is_sell, quantity, amount, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now(), now())
|
||||
RETURNING *",
|
||||
self.shop_id,
|
||||
self.owner_id,
|
||||
self.mod_name,
|
||||
self.local_form_id,
|
||||
self.name,
|
||||
self.form_type,
|
||||
self.is_food,
|
||||
self.price,
|
||||
self.is_sell,
|
||||
self.quantity,
|
||||
self.amount,
|
||||
transaction.shop_id,
|
||||
transaction.owner_id,
|
||||
transaction.mod_name,
|
||||
transaction.local_form_id,
|
||||
transaction.name,
|
||||
transaction.form_type,
|
||||
transaction.is_food,
|
||||
transaction.price,
|
||||
transaction.is_sell,
|
||||
transaction.quantity,
|
||||
transaction.amount,
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64> {
|
||||
pub async fn delete(
|
||||
db: impl Executor<'_, Database = Postgres> + Copy,
|
||||
owner_id: i32,
|
||||
id: i32,
|
||||
) -> Result<u64> {
|
||||
let transaction = sqlx::query!("SELECT owner_id FROM transactions WHERE id = $1", id)
|
||||
.fetch_one(db)
|
||||
.await?;
|
||||
if transaction.owner_id == owner_id {
|
||||
return Ok(sqlx::query!("DELETE FROM transactions WHERE id = $1", id)
|
||||
.execute(db)
|
||||
.await?);
|
||||
.await?
|
||||
.rows_affected());
|
||||
} else {
|
||||
return Err(forbidden_permission());
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn list(db: &PgPool, list_params: &ListParams) -> Result<Vec<Self>> {
|
||||
pub async fn list(
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
list_params: &ListParams,
|
||||
) -> Result<Vec<Self>> {
|
||||
let result = if let Some(order_by) = list_params.get_order_by() {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
@@ -129,7 +159,7 @@ impl Transaction {
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn list_by_shop_id(
|
||||
db: &PgPool,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
shop_id: i32,
|
||||
list_params: &ListParams,
|
||||
) -> Result<Vec<Self>> {
|
||||
|
||||
@@ -36,56 +36,44 @@ pub fn from_anyhow(error: anyhow::Error) -> HttpApiProblem {
|
||||
sqlx::error::Error::RowNotFound => {
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::NOT_FOUND)
|
||||
}
|
||||
sqlx::error::Error::Database(db_error) => {
|
||||
error!(
|
||||
"Database error: {}. {}",
|
||||
db_error.message(),
|
||||
db_error.details().unwrap_or("")
|
||||
);
|
||||
dbg!(&db_error);
|
||||
if let Some(code) = db_error.code() {
|
||||
dbg!(&code);
|
||||
if let Some(constraint) = db_error.constraint_name() {
|
||||
dbg!(&constraint);
|
||||
if code == "23503" && constraint == "shops_owner_id_fkey" {
|
||||
// foreign_key_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Owner does not exist");
|
||||
} else if code == "23505" && constraint == "owners_api_key_key" {
|
||||
// unique_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Owner with Api-Key already exists");
|
||||
} else if code == "23505" && constraint == "owners_unique_name_and_api_key"
|
||||
{
|
||||
// unique_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Duplicate owner with same name and Api-Key exists");
|
||||
} else if code == "23505" && constraint == "shops_unique_name_and_owner_id"
|
||||
{
|
||||
// unique_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Owner already has a shop with that name");
|
||||
} else if code == "23514" && constraint == "merchandise_quantity_gt_zero" {
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Quantity of merchandise must be greater than zero");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pg_error) = error.downcast_ref::<sqlx::postgres::PgDatabaseError>() {
|
||||
error!(
|
||||
"Database error: {}. {}",
|
||||
pg_error.message(),
|
||||
pg_error.detail().unwrap_or("")
|
||||
);
|
||||
dbg!(&pg_error);
|
||||
let code = pg_error.code();
|
||||
dbg!(&code);
|
||||
if let Some(constraint) = pg_error.constraint() {
|
||||
dbg!(&constraint);
|
||||
if code == "23503" && constraint == "shops_owner_id_fkey" {
|
||||
// foreign_key_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
.set_detail("Owner does not exist");
|
||||
} else if code == "23505" && constraint == "owners_api_key_key" {
|
||||
// unique_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
.set_detail("Owner with Api-Key already exists");
|
||||
} else if code == "23505" && constraint == "owners_unique_name_and_api_key" {
|
||||
// unique_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
.set_detail("Duplicate owner with same name and Api-Key exists");
|
||||
} else if code == "23505" && constraint == "shops_unique_name_and_owner_id" {
|
||||
// unique_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
.set_detail("Owner already has a shop with that name");
|
||||
} else if code == "23514" && constraint == "merchandise_quantity_gt_zero" {
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
.set_detail("Quantity of merchandise must be greater than zero");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error!("Recovering unhandled error: {:?}", error);
|
||||
// TODO: this leaks internal info, should not stringify error
|
||||
HttpApiProblem::new(format!("Internal Server Error: {:?}", error))
|
||||
|
||||
Reference in New Issue
Block a user