Fixed bincode responses, refactored content_type handling, better error reporting
Fixed issues with Bincode responses not actually being readable, oops. Also fix handling Bincode requests. Added `TypedCache` for DRYing up GET request content-type handling. Added `DeserializedBody` for DRYing up POST/PATCH request conent-type handling. Removed "Unsaved" structs since I could just mutate Posted structs instead. Added improved error reporting and stopped sending unfiltered interal error data. Upgraded sqlx to proper 0.4.1 release.
This commit is contained in:
parent
0bc94e4b7d
commit
50184da1f6
166
Cargo.lock
generated
166
Cargo.lock
generated
@ -328,22 +328,6 @@ dependencies = [
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
||||
|
||||
[[package]]
|
||||
name = "cpuid-bool"
|
||||
version = "0.1.2"
|
||||
@ -473,21 +457,6 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
@ -1082,24 +1051,6 @@ dependencies = [
|
||||
"twoway",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net2"
|
||||
version = "0.2.34"
|
||||
@ -1158,39 +1109,12 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if 0.1.10",
|
||||
"foreign-types",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.0"
|
||||
@ -1255,12 +1179,6 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.8"
|
||||
@ -1542,16 +1460,6 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.0"
|
||||
@ -1580,29 +1488,6 @@ version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39ee459cae272d224928ca09a1df5406da984f263dc544f9f8bde92a8c3dc916"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.114"
|
||||
@ -1737,8 +1622,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.4.0-beta.1"
|
||||
source = "git+https://github.com/launchbadge/sqlx?branch=master#12b4250454b13fa2699dee9a4c761154ae60ddb6"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1f8eb788e1733bdbf69a8f97087213ebdebd253d4782c686d3cfd586b0a9453"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
@ -1746,8 +1632,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.4.0-beta.1"
|
||||
source = "git+https://github.com/launchbadge/sqlx?branch=master#12b4250454b13fa2699dee9a4c761154ae60ddb6"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e647268dc1239dd9db2d3103fefd61151971a2214882cff9efea6f60cf50840"
|
||||
dependencies = [
|
||||
"ahash 0.5.8",
|
||||
"atoi",
|
||||
@ -1777,6 +1664,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"rand 0.7.3",
|
||||
"rustls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha-1 0.9.1",
|
||||
@ -1788,13 +1676,16 @@ dependencies = [
|
||||
"thiserror",
|
||||
"url",
|
||||
"uuid 0.8.1",
|
||||
"webpki",
|
||||
"webpki-roots",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.4.0-beta.1"
|
||||
source = "git+https://github.com/launchbadge/sqlx?branch=master#12b4250454b13fa2699dee9a4c761154ae60ddb6"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7acd32cba35531345f8a94a038874baf00efd0b701c913f5b00d2870b474b64"
|
||||
dependencies = [
|
||||
"dotenv",
|
||||
"either",
|
||||
@ -1814,13 +1705,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-rt"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/launchbadge/sqlx?branch=master#12b4250454b13fa2699dee9a4c761154ae60ddb6"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63fc5454c9dd7aaea3a0eeeb65ca40d06d0d8e7413a8184f7c3a3ffa5056190b"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1941,16 +1832,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd608593a919a8e05a7d1fc6df885e40f6a88d3a70a3a7eff23ff27964eda069"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.14.1"
|
||||
@ -2207,12 +2088,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.2"
|
||||
@ -2339,6 +2214,15 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "0.9.0"
|
||||
|
@ -18,7 +18,7 @@ listenfd = "0.3"
|
||||
mime = "0.3"
|
||||
openssl-probe = "0.1"
|
||||
tokio = { version = "0.2", features = ["macros", "rt-threaded", "sync"] }
|
||||
sqlx = { git = "https://github.com/launchbadge/sqlx", branch = "master", default-features = false, features = [ "runtime-tokio", "macros", "postgres", "chrono", "uuid", "ipnetwork", "json", "migrate", "offline" ] }
|
||||
sqlx = { version = "0.4.1", default-features = false, features = [ "runtime-tokio-rustls", "macros", "postgres", "chrono", "uuid", "ipnetwork", "json", "migrate", "offline" ] }
|
||||
warp = { version = "0.2", features = ["compression", "tls"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
@ -1,17 +1,19 @@
|
||||
use anyhow::Result;
|
||||
use http::StatusCode;
|
||||
use hyper::body::Bytes;
|
||||
use mime::Mime;
|
||||
use uuid::Uuid;
|
||||
use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::models::{InteriorRefList, ListParams, PostedInteriorRefList, UnsavedInteriorRefList};
|
||||
use crate::caches::{CachedResponse, CACHES};
|
||||
use crate::models::{InteriorRefList, ListParams, PostedInteriorRefList};
|
||||
use crate::problem::reject_anyhow;
|
||||
use crate::Environment;
|
||||
|
||||
use super::{
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, ETagReply, Json,
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, DeserializedBody,
|
||||
ETagReply, Json, TypedCache,
|
||||
};
|
||||
|
||||
pub async fn get(
|
||||
@ -20,12 +22,14 @@ pub async fn get(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => {
|
||||
(ContentType::Bincode, &CACHES.interior_ref_list_bin)
|
||||
}
|
||||
_ => (ContentType::Json, &CACHES.interior_ref_list),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.interior_ref_list_bin,
|
||||
&CACHES.interior_ref_list,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(id, || async {
|
||||
let interior_ref_list = InteriorRefList::get(&env.db, id).await?;
|
||||
@ -50,13 +54,14 @@ pub async fn get_by_shop_id(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => (
|
||||
ContentType::Bincode,
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.interior_ref_list_by_shop_id_bin,
|
||||
),
|
||||
_ => (ContentType::Json, &CACHES.interior_ref_list_by_shop_id),
|
||||
};
|
||||
&CACHES.interior_ref_list_by_shop_id,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(shop_id, || async {
|
||||
let interior_ref_list = InteriorRefList::get_by_shop_id(&env.db, shop_id).await?;
|
||||
@ -81,12 +86,14 @@ pub async fn list(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => {
|
||||
(ContentType::Bincode, &CACHES.list_interior_ref_lists_bin)
|
||||
}
|
||||
_ => (ContentType::Json, &CACHES.list_interior_ref_lists),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.list_interior_ref_lists_bin,
|
||||
&CACHES.list_interior_ref_lists,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(list_params.clone(), || async {
|
||||
let interior_ref_lists = InteriorRefList::list(&env.db, &list_params).await?;
|
||||
@ -107,24 +114,19 @@ pub async fn list(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
interior_ref_list: PostedInteriorRefList,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: mut interior_ref_list,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedInteriorRefList>::from_bytes(bytes, content_type)
|
||||
.map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
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 = InteriorRefList::create(unsaved_interior_ref_list, &env.db)
|
||||
interior_ref_list.owner_id = Some(owner_id);
|
||||
let saved_interior_ref_list = InteriorRefList::create(interior_ref_list, &env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = saved_interior_ref_list
|
||||
@ -159,17 +161,16 @@ pub async fn create(
|
||||
|
||||
pub async fn update(
|
||||
id: i32,
|
||||
interior_ref_list: PostedInteriorRefList,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: interior_ref_list,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedInteriorRefList>::from_bytes(bytes, content_type)
|
||||
.map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let updated_interior_ref_list =
|
||||
InteriorRefList::update(interior_ref_list, &env.db, owner_id, id)
|
||||
@ -209,17 +210,16 @@ pub async fn update(
|
||||
|
||||
pub async fn update_by_shop_id(
|
||||
shop_id: i32,
|
||||
interior_ref_list: PostedInteriorRefList,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: interior_ref_list,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedInteriorRefList>::from_bytes(bytes, content_type)
|
||||
.map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let updated_interior_ref_list =
|
||||
InteriorRefList::update_by_shop_id(interior_ref_list, &env.db, owner_id, shop_id)
|
||||
|
@ -1,17 +1,19 @@
|
||||
use anyhow::Result;
|
||||
use http::StatusCode;
|
||||
use hyper::body::Bytes;
|
||||
use mime::Mime;
|
||||
use uuid::Uuid;
|
||||
use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::models::{ListParams, MerchandiseList, PostedMerchandiseList, UnsavedMerchandiseList};
|
||||
use crate::caches::{CachedResponse, CACHES};
|
||||
use crate::models::{ListParams, MerchandiseList, PostedMerchandiseList};
|
||||
use crate::problem::reject_anyhow;
|
||||
use crate::Environment;
|
||||
|
||||
use super::{
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, ETagReply, Json,
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, DeserializedBody,
|
||||
ETagReply, Json, TypedCache,
|
||||
};
|
||||
|
||||
pub async fn get(
|
||||
@ -20,12 +22,14 @@ pub async fn get(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => {
|
||||
(ContentType::Bincode, &CACHES.merchandise_list_bin)
|
||||
}
|
||||
_ => (ContentType::Json, &CACHES.merchandise_list),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.merchandise_list_bin,
|
||||
&CACHES.merchandise_list,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(id, || async {
|
||||
let merchandise_list = MerchandiseList::get(&env.db, id).await?;
|
||||
@ -50,12 +54,14 @@ pub async fn get_by_shop_id(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => {
|
||||
(ContentType::Bincode, &CACHES.merchandise_list_bin)
|
||||
}
|
||||
_ => (ContentType::Json, &CACHES.merchandise_list),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.merchandise_list_by_shop_id_bin,
|
||||
&CACHES.merchandise_list_by_shop_id,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(shop_id, || async {
|
||||
let merchandise_list = MerchandiseList::get_by_shop_id(&env.db, shop_id).await?;
|
||||
@ -80,12 +86,14 @@ pub async fn list(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => {
|
||||
(ContentType::Bincode, &CACHES.list_merchandise_lists_bin)
|
||||
}
|
||||
_ => (ContentType::Json, &CACHES.list_merchandise_lists),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.list_merchandise_lists_bin,
|
||||
&CACHES.list_merchandise_lists,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(list_params.clone(), || async {
|
||||
let merchandise_lists = MerchandiseList::list(&env.db, &list_params).await?;
|
||||
@ -105,24 +113,19 @@ pub async fn list(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
merchandise_list: PostedMerchandiseList,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: mut merchandise_list,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedMerchandiseList>::from_bytes(bytes, content_type)
|
||||
.map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let unsaved_merchandise_list = UnsavedMerchandiseList {
|
||||
owner_id,
|
||||
shop_id: merchandise_list.shop_id,
|
||||
form_list: merchandise_list.form_list,
|
||||
};
|
||||
let saved_merchandise_list = MerchandiseList::create(unsaved_merchandise_list, &env.db)
|
||||
merchandise_list.owner_id = Some(owner_id);
|
||||
let saved_merchandise_list = MerchandiseList::create(merchandise_list, &env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = saved_merchandise_list
|
||||
@ -156,17 +159,16 @@ pub async fn create(
|
||||
|
||||
pub async fn update(
|
||||
id: i32,
|
||||
merchandise_list: PostedMerchandiseList,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: merchandise_list,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedMerchandiseList>::from_bytes(bytes, content_type)
|
||||
.map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let updated_merchandise_list = MerchandiseList::update(merchandise_list, &env.db, owner_id, id)
|
||||
.await
|
||||
@ -205,17 +207,16 @@ pub async fn update(
|
||||
|
||||
pub async fn update_by_shop_id(
|
||||
shop_id: i32,
|
||||
merchandise_list: PostedMerchandiseList,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: merchandise_list,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedMerchandiseList>::from_bytes(bytes, content_type)
|
||||
.map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let updated_merchandise_list =
|
||||
MerchandiseList::update_by_shop_id(merchandise_list, &env.db, owner_id, shop_id)
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::str::FromStr;
|
||||
|
||||
@ -5,10 +7,11 @@ use anyhow::{anyhow, Error, Result};
|
||||
use http::header::{HeaderValue, CONTENT_TYPE, ETAG, SERVER};
|
||||
use http::StatusCode;
|
||||
use http_api_problem::HttpApiProblem;
|
||||
use hyper::body::Bytes;
|
||||
use mime::{FromStrError, Mime};
|
||||
use seahash::hash;
|
||||
use serde::Serialize;
|
||||
use tracing::{error, instrument, warn};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use tracing::{debug, error, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
use warp::reply::Response;
|
||||
use warp::Reply;
|
||||
@ -19,7 +22,7 @@ pub mod owner;
|
||||
pub mod shop;
|
||||
pub mod transaction;
|
||||
|
||||
use super::caches::{CachedResponse, CACHES};
|
||||
use super::caches::{Cache, CachedResponse, CACHES};
|
||||
use super::problem::{unauthorized_no_api_key, unauthorized_no_owner};
|
||||
use super::Environment;
|
||||
|
||||
@ -184,3 +187,76 @@ impl AcceptHeader {
|
||||
self.mimes.contains(&mime::APPLICATION_OCTET_STREAM)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeserializedBody<T> {
|
||||
body: T,
|
||||
content_type: ContentType,
|
||||
}
|
||||
|
||||
impl<T: DeserializeOwned> DeserializedBody<T> {
|
||||
pub fn from_bytes(bytes: Bytes, content_type: Option<Mime>) -> Result<Self> {
|
||||
match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
debug!(
|
||||
content_type = ?ContentType::Bincode,
|
||||
"deserializing body as bincode"
|
||||
);
|
||||
Ok(Self {
|
||||
content_type: ContentType::Bincode,
|
||||
body: bincode::deserialize(&bytes)?,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
debug!(
|
||||
content_type = ?ContentType::Json,
|
||||
"deserializing body as json"
|
||||
);
|
||||
Ok(Self {
|
||||
content_type: ContentType::Json,
|
||||
body: serde_json::from_slice(&bytes)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TypedCache<'a, K, V>
|
||||
where
|
||||
K: Eq + Hash + Debug,
|
||||
V: Clone,
|
||||
{
|
||||
cache: &'a Cache<K, V>,
|
||||
content_type: ContentType,
|
||||
}
|
||||
|
||||
impl<'a, K, V> TypedCache<'a, K, V>
|
||||
where
|
||||
K: Eq + Hash + Debug,
|
||||
V: Clone,
|
||||
{
|
||||
pub fn pick_cache(
|
||||
accept: Option<AcceptHeader>,
|
||||
bincode_cache: &'a Cache<K, V>,
|
||||
json_cache: &'a Cache<K, V>,
|
||||
) -> Self {
|
||||
match accept {
|
||||
Some(accept) if accept.accepts_bincode() => {
|
||||
debug!(
|
||||
content_type = ?ContentType::Bincode,
|
||||
"serializing body as bincode"
|
||||
);
|
||||
Self {
|
||||
content_type: ContentType::Bincode,
|
||||
cache: bincode_cache,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!(content_type = ?ContentType::Json, "serializing body as json");
|
||||
Self {
|
||||
content_type: ContentType::Json,
|
||||
cache: json_cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use http::StatusCode;
|
||||
use hyper::body::Bytes;
|
||||
use ipnetwork::IpNetwork;
|
||||
use mime::Mime;
|
||||
use std::net::SocketAddr;
|
||||
@ -7,13 +8,14 @@ use uuid::Uuid;
|
||||
use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::models::{ListParams, Owner, PostedOwner, UnsavedOwner};
|
||||
use crate::caches::{CachedResponse, CACHES};
|
||||
use crate::models::{FullPostedOwner, ListParams, Owner, PostedOwner};
|
||||
use crate::problem::{reject_anyhow, unauthorized_no_api_key};
|
||||
use crate::Environment;
|
||||
|
||||
use super::{
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, ETagReply, Json,
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, DeserializedBody,
|
||||
ETagReply, Json, TypedCache,
|
||||
};
|
||||
|
||||
pub async fn get(
|
||||
@ -22,10 +24,10 @@ pub async fn get(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.owner_bin),
|
||||
_ => (ContentType::Json, &CACHES.owner),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<i32, CachedResponse>::pick_cache(accept, &CACHES.owner_bin, &CACHES.owner);
|
||||
let response = cache
|
||||
.get_response(id, || async {
|
||||
let owner = Owner::get(&env.db, id).await?;
|
||||
@ -46,10 +48,14 @@ pub async fn list(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.list_owners_bin),
|
||||
_ => (ContentType::Json, &CACHES.list_owners),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.list_owners_bin,
|
||||
&CACHES.list_owners,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(list_params.clone(), || async {
|
||||
let owners = Owner::list(&env.db, &list_params).await?;
|
||||
@ -65,7 +71,7 @@ pub async fn list(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
owner: PostedOwner,
|
||||
bytes: Bytes,
|
||||
remote_addr: Option<SocketAddr>,
|
||||
api_key: Option<Uuid>,
|
||||
real_ip: Option<IpNetwork>,
|
||||
@ -73,24 +79,21 @@ pub async fn create(
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
if let Some(api_key) = api_key {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let unsaved_owner = UnsavedOwner {
|
||||
let DeserializedBody {
|
||||
body: owner,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedOwner>::from_bytes(bytes, content_type)
|
||||
.map_err(reject_anyhow)?;
|
||||
let owner = FullPostedOwner {
|
||||
name: owner.name,
|
||||
mod_version: owner.mod_version,
|
||||
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::create(unsaved_owner, &env.db)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let saved_owner = Owner::create(owner, &env.db).await.map_err(reject_anyhow)?;
|
||||
let url = saved_owner.url(&env.api_url).map_err(reject_anyhow)?;
|
||||
let reply: Box<dyn Reply> = match content_type {
|
||||
ContentType::Bincode => Box::new(
|
||||
@ -114,17 +117,15 @@ pub async fn create(
|
||||
|
||||
pub async fn update(
|
||||
id: i32,
|
||||
owner: PostedOwner,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: owner,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedOwner>::from_bytes(bytes, content_type).map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let updated_owner = Owner::update(owner, &env.db, owner_id, id)
|
||||
.await
|
||||
|
@ -1,20 +1,22 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use http::StatusCode;
|
||||
use hyper::body::Bytes;
|
||||
use mime::Mime;
|
||||
use uuid::Uuid;
|
||||
use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::caches::{CachedResponse, CACHES};
|
||||
use crate::models::{
|
||||
InteriorRefList, ListParams, MerchandiseList, PostedShop, Shop, UnsavedInteriorRefList,
|
||||
UnsavedMerchandiseList, UnsavedShop,
|
||||
InteriorRefList, ListParams, MerchandiseList, PostedInteriorRefList, PostedMerchandiseList,
|
||||
PostedShop, Shop,
|
||||
};
|
||||
use crate::problem::reject_anyhow;
|
||||
use crate::Environment;
|
||||
|
||||
use super::{
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, ETagReply, Json,
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, DeserializedBody,
|
||||
ETagReply, Json, TypedCache,
|
||||
};
|
||||
|
||||
pub async fn get(
|
||||
@ -23,10 +25,10 @@ pub async fn get(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.shop_bin),
|
||||
_ => (ContentType::Json, &CACHES.shop),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<i32, CachedResponse>::pick_cache(accept, &CACHES.shop_bin, &CACHES.shop);
|
||||
let response = cache
|
||||
.get_response(id, || async {
|
||||
let shop = Shop::get(&env.db, id).await?;
|
||||
@ -47,10 +49,14 @@ pub async fn list(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.list_shops_bin),
|
||||
_ => (ContentType::Json, &CACHES.list_shops),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.list_shops_bin,
|
||||
&CACHES.list_shops,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(list_params.clone(), || async {
|
||||
let shops = Shop::list(&env.db, &list_params).await?;
|
||||
@ -66,44 +72,36 @@ pub async fn list(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
shop: PostedShop,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: mut shop,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedShop>::from_bytes(bytes, content_type).map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let unsaved_shop = UnsavedShop {
|
||||
name: shop.name,
|
||||
description: shop.description,
|
||||
owner_id,
|
||||
};
|
||||
shop.owner_id = Some(owner_id);
|
||||
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)?;
|
||||
let saved_shop = Shop::create(shop, &mut tx).await.map_err(reject_anyhow)?;
|
||||
|
||||
// also save empty interior_ref_list and merchandise_list rows
|
||||
let interior_ref_list = UnsavedInteriorRefList {
|
||||
let interior_ref_list = PostedInteriorRefList {
|
||||
shop_id: saved_shop.id,
|
||||
owner_id,
|
||||
owner_id: Some(owner_id),
|
||||
ref_list: sqlx::types::Json::default(),
|
||||
};
|
||||
InteriorRefList::create(interior_ref_list, &mut tx)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let merchandise_list = UnsavedMerchandiseList {
|
||||
let merchandise_list = PostedMerchandiseList {
|
||||
shop_id: saved_shop.id,
|
||||
owner_id,
|
||||
owner_id: Some(owner_id),
|
||||
form_list: sqlx::types::Json::default(),
|
||||
};
|
||||
MerchandiseList::create(merchandise_list, &mut tx)
|
||||
@ -133,27 +131,22 @@ pub async fn create(
|
||||
|
||||
pub async fn update(
|
||||
id: i32,
|
||||
shop: PostedShop,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: mut shop,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedShop>::from_bytes(bytes, content_type).map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
let posted_shop = PostedShop {
|
||||
owner_id: match shop.owner_id {
|
||||
shop.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::update(posted_shop, &env.db, owner_id, id)
|
||||
let updated_shop = Shop::update(shop, &env.db, owner_id, id)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let url = updated_shop.url(&env.api_url).map_err(reject_anyhow)?;
|
||||
|
@ -1,19 +1,19 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use http::StatusCode;
|
||||
use hyper::body::Bytes;
|
||||
use mime::Mime;
|
||||
use uuid::Uuid;
|
||||
use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
|
||||
use crate::caches::CACHES;
|
||||
use crate::models::{
|
||||
ListParams, MerchandiseList, PostedTransaction, Transaction, UnsavedTransaction,
|
||||
};
|
||||
use crate::caches::{CachedResponse, CACHES};
|
||||
use crate::models::{ListParams, MerchandiseList, PostedTransaction, Transaction};
|
||||
use crate::problem::reject_anyhow;
|
||||
use crate::Environment;
|
||||
|
||||
use super::{
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, ETagReply, Json,
|
||||
authenticate, check_etag, AcceptHeader, Bincode, ContentType, DataReply, DeserializedBody,
|
||||
ETagReply, Json, TypedCache,
|
||||
};
|
||||
|
||||
pub async fn get(
|
||||
@ -22,10 +22,14 @@ pub async fn get(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.transaction_bin),
|
||||
_ => (ContentType::Json, &CACHES.transaction),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.transaction_bin,
|
||||
&CACHES.transaction,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(id, || async {
|
||||
let transaction = Transaction::get(&env.db, id).await?;
|
||||
@ -48,12 +52,14 @@ pub async fn list(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => {
|
||||
(ContentType::Bincode, &CACHES.list_transactions_bin)
|
||||
}
|
||||
_ => (ContentType::Json, &CACHES.list_transactions),
|
||||
};
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.list_transactions_bin,
|
||||
&CACHES.list_transactions,
|
||||
);
|
||||
let response = cache
|
||||
.get_response(list_params.clone(), || async {
|
||||
let transactions = Transaction::list(&env.db, &list_params).await?;
|
||||
@ -77,13 +83,14 @@ pub async fn list_by_shop_id(
|
||||
accept: Option<AcceptHeader>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let (content_type, cache) = match accept {
|
||||
Some(accept) if accept.accepts_bincode() => (
|
||||
ContentType::Bincode,
|
||||
let TypedCache {
|
||||
content_type,
|
||||
cache,
|
||||
} = TypedCache::<(i32, ListParams), CachedResponse>::pick_cache(
|
||||
accept,
|
||||
&CACHES.list_transactions_by_shop_id_bin,
|
||||
),
|
||||
_ => (ContentType::Json, &CACHES.list_transactions_by_shop_id),
|
||||
};
|
||||
&CACHES.list_transactions_by_shop_id,
|
||||
);
|
||||
let response = cache
|
||||
.get_response((shop_id, list_params.clone()), || async {
|
||||
let transactions = Transaction::list_by_shop_id(&env.db, shop_id, &list_params).await?;
|
||||
@ -101,42 +108,29 @@ pub async fn list_by_shop_id(
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
transaction: PostedTransaction,
|
||||
bytes: Bytes,
|
||||
api_key: Option<Uuid>,
|
||||
content_type: Option<Mime>,
|
||||
env: Environment,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let content_type = match content_type {
|
||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
||||
ContentType::Bincode
|
||||
}
|
||||
_ => ContentType::Json,
|
||||
};
|
||||
let DeserializedBody {
|
||||
body: mut transaction,
|
||||
content_type,
|
||||
} = DeserializedBody::<PostedTransaction>::from_bytes(bytes, content_type)
|
||||
.map_err(reject_anyhow)?;
|
||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||
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,
|
||||
};
|
||||
transaction.owner_id = Some(owner_id);
|
||||
let mut tx = env
|
||||
.db
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|error| reject_anyhow(anyhow!(error)))?;
|
||||
let saved_transaction = Transaction::create(unsaved_transaction, &mut tx)
|
||||
let saved_transaction = Transaction::create(transaction, &mut tx)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let quantity_delta = match transaction.is_sell {
|
||||
true => transaction.quantity,
|
||||
false => transaction.quantity * -1,
|
||||
let quantity_delta = match saved_transaction.is_sell {
|
||||
true => saved_transaction.quantity,
|
||||
false => saved_transaction.quantity * -1,
|
||||
};
|
||||
let updated_merchandise_list = MerchandiseList::update_merchandise_quantity(
|
||||
&mut tx,
|
||||
|
43
src/main.rs
43
src/main.rs
@ -4,9 +4,8 @@ extern crate lazy_static;
|
||||
use anyhow::Result;
|
||||
use dotenv::dotenv;
|
||||
use http::header::SERVER;
|
||||
use hyper::server::Server;
|
||||
use hyper::{body::Bytes, server::Server};
|
||||
use listenfd::ListenFd;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::{migrate, Pool, Postgres};
|
||||
use std::convert::Infallible;
|
||||
@ -24,10 +23,7 @@ mod models;
|
||||
mod problem;
|
||||
|
||||
use handlers::SERVER_STRING;
|
||||
use models::{
|
||||
ListParams, PostedInteriorRefList, PostedMerchandiseList, PostedOwner, PostedShop,
|
||||
PostedTransaction,
|
||||
};
|
||||
use models::ListParams;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Environment {
|
||||
@ -47,21 +43,12 @@ impl Environment {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ErrorMessage {
|
||||
code: u16,
|
||||
message: String,
|
||||
}
|
||||
|
||||
fn with_env(env: Environment) -> impl Filter<Extract = (Environment,), Error = Infallible> + Clone {
|
||||
warp::any().map(move || env.clone())
|
||||
}
|
||||
|
||||
fn json_body<T>() -> impl Filter<Extract = (T,), Error = warp::Rejection> + Clone
|
||||
where
|
||||
T: Send + DeserializeOwned,
|
||||
{
|
||||
warp::body::content_length_limit(1024 * 1024).and(warp::body::json())
|
||||
fn extract_body_bytes() -> impl Filter<Extract = (Bytes,), Error = warp::Rejection> + Clone {
|
||||
warp::body::content_length_limit(1024 * 1024).and(warp::body::bytes())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@ -100,7 +87,7 @@ async fn main() -> Result<()> {
|
||||
let create_owner_handler = warp::path("owners").and(
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(json_body::<PostedOwner>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::addr::remote())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("x-real-ip"))
|
||||
@ -120,7 +107,7 @@ async fn main() -> Result<()> {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<PostedOwner>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@ -147,7 +134,7 @@ async fn main() -> Result<()> {
|
||||
let create_shop_handler = warp::path("shops").and(
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(json_body::<PostedShop>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@ -165,7 +152,7 @@ async fn main() -> Result<()> {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<PostedShop>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@ -192,7 +179,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::<PostedInteriorRefList>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@ -210,7 +197,7 @@ async fn main() -> Result<()> {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<PostedInteriorRefList>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@ -221,7 +208,7 @@ async fn main() -> Result<()> {
|
||||
.and(warp::path("interior_ref_list"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<PostedInteriorRefList>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@ -258,7 +245,7 @@ async fn main() -> Result<()> {
|
||||
let create_merchandise_list_handler = warp::path("merchandise_lists").and(
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(json_body::<PostedMerchandiseList>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@ -276,7 +263,7 @@ async fn main() -> Result<()> {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<PostedMerchandiseList>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@ -287,7 +274,7 @@ async fn main() -> Result<()> {
|
||||
.and(warp::path("merchandise_list"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::patch())
|
||||
.and(json_body::<PostedMerchandiseList>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
@ -324,7 +311,7 @@ async fn main() -> Result<()> {
|
||||
let create_transaction_handler = warp::path("transactions").and(
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(json_body::<PostedTransaction>())
|
||||
.and(extract_body_bytes())
|
||||
.and(warp::header::optional("api-key"))
|
||||
.and(warp::header::optional("content-type"))
|
||||
.and(with_env(env.clone()))
|
||||
|
@ -9,12 +9,12 @@ use url::Url;
|
||||
use super::ListParams;
|
||||
use crate::problem::forbidden_permission;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[derive(sqlx::FromRow, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct InteriorRef {
|
||||
pub base_mod_name: String,
|
||||
pub base_local_form_id: i32,
|
||||
pub base_local_form_id: u32,
|
||||
pub ref_mod_name: Option<String>,
|
||||
pub ref_local_form_id: i32,
|
||||
pub ref_local_form_id: u32,
|
||||
pub position_x: f32,
|
||||
pub position_y: f32,
|
||||
pub position_z: f32,
|
||||
@ -24,23 +24,16 @@ pub struct InteriorRef {
|
||||
pub scale: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[derive(sqlx::FromRow, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct InteriorRefList {
|
||||
pub id: i32,
|
||||
pub shop_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub ref_list: serde_json::Value,
|
||||
pub ref_list: Json<Vec<InteriorRef>>,
|
||||
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,
|
||||
@ -64,7 +57,13 @@ impl InteriorRefList {
|
||||
// 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: impl Executor<'_, Database = Postgres>, id: i32) -> Result<Self> {
|
||||
sqlx::query_as!(Self, "SELECT * FROM interior_ref_lists WHERE id = $1", id)
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||
ref_list as "ref_list: Json<Vec<InteriorRef>>"
|
||||
FROM interior_ref_lists WHERE id = $1"#,
|
||||
id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
@ -72,15 +71,16 @@ impl InteriorRefList {
|
||||
|
||||
#[instrument(level = "debug", skip(interior_ref_list, db))]
|
||||
pub async fn create(
|
||||
interior_ref_list: UnsavedInteriorRefList,
|
||||
interior_ref_list: PostedInteriorRefList,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"INSERT INTO interior_ref_lists
|
||||
r#"INSERT INTO interior_ref_lists
|
||||
(shop_id, owner_id, ref_list, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, now(), now())
|
||||
RETURNING *",
|
||||
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||
ref_list as "ref_list: Json<Vec<InteriorRef>>""#,
|
||||
interior_ref_list.shop_id,
|
||||
interior_ref_list.owner_id,
|
||||
serde_json::json!(interior_ref_list.ref_list),
|
||||
@ -119,10 +119,11 @@ impl InteriorRefList {
|
||||
let result = if let Some(order_by) = list_params.get_order_by() {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM interior_ref_lists
|
||||
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||
ref_list as "ref_list: Json<Vec<InteriorRef>>" FROM interior_ref_lists
|
||||
ORDER BY $1
|
||||
LIMIT $2
|
||||
OFFSET $3",
|
||||
OFFSET $3"#,
|
||||
order_by,
|
||||
list_params.limit.unwrap_or(10),
|
||||
list_params.offset.unwrap_or(0),
|
||||
@ -132,9 +133,10 @@ impl InteriorRefList {
|
||||
} else {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM interior_ref_lists
|
||||
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||
ref_list as "ref_list: Json<Vec<InteriorRef>>" FROM interior_ref_lists
|
||||
LIMIT $1
|
||||
OFFSET $2",
|
||||
OFFSET $2"#,
|
||||
list_params.limit.unwrap_or(10),
|
||||
list_params.offset.unwrap_or(0),
|
||||
)
|
||||
@ -158,11 +160,12 @@ impl InteriorRefList {
|
||||
if existing_interior_ref_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE interior_ref_lists SET
|
||||
r#"UPDATE interior_ref_lists SET
|
||||
ref_list = $2,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING *",
|
||||
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||
ref_list as "ref_list: Json<Vec<InteriorRef>>""#,
|
||||
id,
|
||||
serde_json::json!(interior_ref_list.ref_list),
|
||||
)
|
||||
@ -180,8 +183,9 @@ impl InteriorRefList {
|
||||
) -> Result<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM interior_ref_lists
|
||||
WHERE shop_id = $1",
|
||||
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||
ref_list as "ref_list: Json<Vec<InteriorRef>>" FROM interior_ref_lists
|
||||
WHERE shop_id = $1"#,
|
||||
shop_id,
|
||||
)
|
||||
.fetch_one(db)
|
||||
@ -205,11 +209,12 @@ impl InteriorRefList {
|
||||
if existing_interior_ref_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE interior_ref_lists SET
|
||||
r#"UPDATE interior_ref_lists SET
|
||||
ref_list = $2,
|
||||
updated_at = now()
|
||||
WHERE shop_id = $1
|
||||
RETURNING *",
|
||||
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||
ref_list as "ref_list: Json<Vec<InteriorRef>>""#,
|
||||
shop_id,
|
||||
serde_json::json!(interior_ref_list.ref_list),
|
||||
)
|
||||
|
@ -15,12 +15,12 @@ use crate::problem::forbidden_permission;
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Merchandise {
|
||||
pub mod_name: String,
|
||||
pub local_form_id: i32,
|
||||
pub local_form_id: u32,
|
||||
pub name: String,
|
||||
pub quantity: i32,
|
||||
pub form_type: i32,
|
||||
pub quantity: u32,
|
||||
pub form_type: u32,
|
||||
pub is_food: bool,
|
||||
pub price: i32,
|
||||
pub price: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@ -28,18 +28,11 @@ pub struct MerchandiseList {
|
||||
pub id: i32,
|
||||
pub shop_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub form_list: serde_json::Value,
|
||||
pub form_list: Json<Vec<Merchandise>>,
|
||||
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,
|
||||
@ -63,7 +56,14 @@ impl MerchandiseList {
|
||||
// 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: impl Executor<'_, Database = Postgres>, id: i32) -> Result<Self> {
|
||||
sqlx::query_as!(Self, "SELECT * FROM merchandise_lists WHERE id = $1", id)
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||
form_list as "form_list: Json<Vec<Merchandise>>"
|
||||
FROM merchandise_lists
|
||||
WHERE id = $1"#,
|
||||
id,
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
@ -71,15 +71,16 @@ impl MerchandiseList {
|
||||
|
||||
#[instrument(level = "debug", skip(merchandise_list, db))]
|
||||
pub async fn create(
|
||||
merchandise_list: UnsavedMerchandiseList,
|
||||
merchandise_list: PostedMerchandiseList,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"INSERT INTO merchandise_lists
|
||||
r#"INSERT INTO merchandise_lists
|
||||
(shop_id, owner_id, form_list, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, now(), now())
|
||||
RETURNING *",
|
||||
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||
form_list as "form_list: Json<Vec<Merchandise>>""#,
|
||||
merchandise_list.shop_id,
|
||||
merchandise_list.owner_id,
|
||||
serde_json::json!(merchandise_list.form_list),
|
||||
@ -118,10 +119,12 @@ impl MerchandiseList {
|
||||
let result = if let Some(order_by) = list_params.get_order_by() {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM merchandise_lists
|
||||
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||
form_list as "form_list: Json<Vec<Merchandise>>"
|
||||
FROM merchandise_lists
|
||||
ORDER BY $1
|
||||
LIMIT $2
|
||||
OFFSET $3",
|
||||
OFFSET $3"#,
|
||||
order_by,
|
||||
list_params.limit.unwrap_or(10),
|
||||
list_params.offset.unwrap_or(0),
|
||||
@ -131,9 +134,11 @@ impl MerchandiseList {
|
||||
} else {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM merchandise_lists
|
||||
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||
form_list as "form_list: Json<Vec<Merchandise>>"
|
||||
FROM merchandise_lists
|
||||
LIMIT $1
|
||||
OFFSET $2",
|
||||
OFFSET $2"#,
|
||||
list_params.limit.unwrap_or(10),
|
||||
list_params.offset.unwrap_or(0),
|
||||
)
|
||||
@ -157,11 +162,12 @@ impl MerchandiseList {
|
||||
if existing_merchandise_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE merchandise_lists SET
|
||||
r#"UPDATE merchandise_lists SET
|
||||
form_list = $2,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING *",
|
||||
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||
form_list as "form_list: Json<Vec<Merchandise>>""#,
|
||||
id,
|
||||
serde_json::json!(merchandise_list.form_list),
|
||||
)
|
||||
@ -179,8 +185,10 @@ impl MerchandiseList {
|
||||
) -> Result<Self> {
|
||||
sqlx::query_as!(
|
||||
Self,
|
||||
"SELECT * FROM merchandise_lists
|
||||
WHERE shop_id = $1",
|
||||
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||
form_list as "form_list: Json<Vec<Merchandise>>"
|
||||
FROM merchandise_lists
|
||||
WHERE shop_id = $1"#,
|
||||
shop_id,
|
||||
)
|
||||
.fetch_one(db)
|
||||
@ -204,11 +212,12 @@ impl MerchandiseList {
|
||||
if existing_merchandise_list.owner_id == owner_id {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE merchandise_lists SET
|
||||
r#"UPDATE merchandise_lists SET
|
||||
form_list = $2,
|
||||
updated_at = now()
|
||||
WHERE shop_id = $1
|
||||
RETURNING *",
|
||||
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||
form_list as "form_list: Json<Vec<Merchandise>>""#,
|
||||
shop_id,
|
||||
serde_json::json!(merchandise_list.form_list),
|
||||
)
|
||||
@ -242,7 +251,7 @@ impl MerchandiseList {
|
||||
}]);
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"UPDATE
|
||||
r#"UPDATE
|
||||
merchandise_lists
|
||||
SET
|
||||
form_list = CASE
|
||||
@ -277,7 +286,13 @@ impl MerchandiseList {
|
||||
) sub
|
||||
WHERE
|
||||
shop_id = $1
|
||||
RETURNING merchandise_lists.*",
|
||||
RETURNING
|
||||
merchandise_lists.id,
|
||||
merchandise_lists.shop_id,
|
||||
merchandise_lists.owner_id,
|
||||
merchandise_lists.created_at,
|
||||
merchandise_lists.updated_at,
|
||||
merchandise_lists.form_list as "form_list: Json<Vec<Merchandise>>""#,
|
||||
shop_id,
|
||||
mod_name,
|
||||
&local_form_id.to_string(),
|
||||
|
@ -9,12 +9,12 @@ pub mod owner;
|
||||
pub mod shop;
|
||||
pub mod transaction;
|
||||
|
||||
pub use interior_ref_list::{InteriorRefList, PostedInteriorRefList, UnsavedInteriorRefList};
|
||||
pub use merchandise_list::{MerchandiseList, PostedMerchandiseList, UnsavedMerchandiseList};
|
||||
pub use interior_ref_list::{InteriorRefList, PostedInteriorRefList};
|
||||
pub use merchandise_list::{MerchandiseList, PostedMerchandiseList};
|
||||
pub use model::{Model, UpdateableModel};
|
||||
pub use owner::{Owner, PostedOwner, UnsavedOwner};
|
||||
pub use shop::{PostedShop, Shop, UnsavedShop};
|
||||
pub use transaction::{PostedTransaction, Transaction, UnsavedTransaction};
|
||||
pub use owner::{FullPostedOwner, Owner, PostedOwner};
|
||||
pub use shop::{PostedShop, Shop};
|
||||
pub use transaction::{PostedTransaction, Transaction};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Deserialize)]
|
||||
pub enum Order {
|
||||
|
@ -24,18 +24,16 @@ pub struct Owner {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct UnsavedOwner {
|
||||
pub struct PostedOwner {
|
||||
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 struct FullPostedOwner {
|
||||
pub name: String,
|
||||
pub api_key: Uuid,
|
||||
pub ip_address: Option<IpNetwork>,
|
||||
pub mod_version: i32,
|
||||
}
|
||||
|
||||
@ -62,7 +60,7 @@ impl Owner {
|
||||
|
||||
#[instrument(level = "debug", skip(owner, db))]
|
||||
pub async fn create(
|
||||
owner: UnsavedOwner,
|
||||
owner: FullPostedOwner,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
|
@ -18,13 +18,6 @@ pub struct Shop {
|
||||
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,
|
||||
@ -55,7 +48,7 @@ impl Shop {
|
||||
|
||||
#[instrument(level = "debug", skip(shop, db))]
|
||||
pub async fn create(
|
||||
shop: UnsavedShop,
|
||||
shop: PostedShop,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
|
@ -26,21 +26,6 @@ pub struct Transaction {
|
||||
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,
|
||||
@ -79,7 +64,7 @@ impl Transaction {
|
||||
|
||||
#[instrument(level = "debug", skip(db))]
|
||||
pub async fn create(
|
||||
transaction: UnsavedTransaction,
|
||||
transaction: PostedTransaction,
|
||||
db: impl Executor<'_, Database = Postgres>,
|
||||
) -> Result<Self> {
|
||||
Ok(sqlx::query_as!(
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use http::StatusCode;
|
||||
use http_api_problem::HttpApiProblem;
|
||||
@ -31,16 +33,14 @@ pub fn from_anyhow(error: anyhow::Error) -> HttpApiProblem {
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
// TODO: should probably decentralize all this error handling to the places where they are relevant
|
||||
if let Some(sqlx_error) = error.downcast_ref::<sqlx::error::Error>() {
|
||||
match sqlx_error {
|
||||
sqlx::error::Error::RowNotFound => {
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::NOT_FOUND)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pg_error) = error.downcast_ref::<sqlx::postgres::PgDatabaseError>() {
|
||||
sqlx::error::Error::Database(db_error) => {
|
||||
let pg_error = db_error.downcast_ref::<sqlx::postgres::PgDatabaseError>();
|
||||
error!(
|
||||
"Database error: {}. {}",
|
||||
pg_error.message(),
|
||||
@ -51,33 +51,104 @@ pub fn from_anyhow(error: anyhow::Error) -> HttpApiProblem {
|
||||
dbg!(&code);
|
||||
if let Some(constraint) = pg_error.constraint() {
|
||||
dbg!(&constraint);
|
||||
if code == "23503" && constraint == "shops_owner_id_fkey" {
|
||||
if code == "23503"
|
||||
&& (constraint == "shops_owner_id_fkey"
|
||||
|| constraint == "interior_ref_lists_owner_id_fkey"
|
||||
|| constraint == "merchandise_lists_owner_id_fkey"
|
||||
|| constraint == "transactions_owner_id_fkey")
|
||||
{
|
||||
// foreign_key_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Owner does not exist");
|
||||
} else if code == "23503"
|
||||
&& (constraint == "interior_ref_lists_shop_id_fkey"
|
||||
|| constraint == "merchandise_lists_owner_id_fkey"
|
||||
|| constraint == "transactions_shop_id_fkey")
|
||||
{
|
||||
// foreign_key_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Shop 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)
|
||||
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)
|
||||
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)
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Owner already has a shop with that name");
|
||||
} else if code == "23505" && constraint == "interior_ref_lists_shop_id_key" {
|
||||
// unique_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Interior ref list already exists for that shop");
|
||||
} else if code == "23505" && constraint == "merchandise_lists_shop_id_key" {
|
||||
// unique_violation
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Merchandise list already exists for that shop");
|
||||
} else if code == "23514" && constraint == "merchandise_quantity_gt_zero" {
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
return HttpApiProblem::with_title_and_type_from_status(
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.set_detail("Quantity of merchandise must be greater than zero");
|
||||
}
|
||||
}
|
||||
// Might possibly link sensitive info:
|
||||
// let mut problem = HttpApiProblem::with_title_and_type_from_status(
|
||||
// StatusCode::INTERNAL_SERVER_ERROR,
|
||||
// )
|
||||
// .set_title("Database Error")
|
||||
// .set_detail(format!(
|
||||
// "{}. {}",
|
||||
// pg_error.message(),
|
||||
// pg_error.detail().unwrap_or("")
|
||||
// ));
|
||||
// problem
|
||||
// .set_value("code".to_string(), &code.to_string())
|
||||
// .unwrap();
|
||||
// return problem;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(json_error) = error.downcast_ref::<serde_json::Error>() {
|
||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
.set_title("Json Body Deserialization Error")
|
||||
.set_detail(format!("{}", json_error));
|
||||
}
|
||||
|
||||
if let Some(bincode_error) = error.downcast_ref::<bincode::Error>() {
|
||||
return match bincode_error.borrow() {
|
||||
bincode::ErrorKind::Io(io_error) => {
|
||||
HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
.set_title("Bincode Body Deserialization Error")
|
||||
.set_detail(format!("io error ({:?}): {}", io_error.kind(), io_error))
|
||||
}
|
||||
error => HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||
.set_title("Bincode Body Deserialization Error")
|
||||
.set_detail(format!("{}", error)),
|
||||
};
|
||||
}
|
||||
|
||||
error!("Recovering unhandled error: {:?}", error);
|
||||
// TODO: this leaks internal info, should not stringify error
|
||||
HttpApiProblem::new(format!("Internal Server Error: {:?}", error))
|
||||
.set_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
pub async fn unpack_problem(rejection: Rejection) -> Result<impl Reply, Rejection> {
|
||||
|
Loading…
Reference in New Issue
Block a user