Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
964ab26007 | |||
6b07ec7d07 | |||
a64caa4081 | |||
80345c3a6f | |||
55c68fee2c | |||
2f95fab825 | |||
81840b3d34 | |||
0adbf7c5c0 | |||
b214786415 | |||
d9a891c6b5 | |||
50184da1f6 | |||
0bc94e4b7d | |||
a1107b7100 | |||
9949c537a0 | |||
d277b5c5cd |
333
Cargo.lock
generated
333
Cargo.lock
generated
@ -146,6 +146,7 @@ dependencies = [
|
|||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-appender",
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
@ -236,6 +237,12 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
|
checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byte-tools"
|
name = "byte-tools"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -322,22 +329,6 @@ dependencies = [
|
|||||||
"proc-macro-hack",
|
"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]]
|
[[package]]
|
||||||
name = "cpuid-bool"
|
name = "cpuid-bool"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@ -467,21 +458,6 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
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]]
|
[[package]]
|
||||||
name = "fuchsia-cprng"
|
name = "fuchsia-cprng"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -863,6 +839,15 @@ version = "0.4.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kernel32-sys"
|
name = "kernel32-sys"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@ -1067,24 +1052,6 @@ dependencies = [
|
|||||||
"twoway",
|
"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]]
|
[[package]]
|
||||||
name = "net2"
|
name = "net2"
|
||||||
version = "0.2.34"
|
version = "0.2.34"
|
||||||
@ -1143,39 +1110,12 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
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]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
|
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]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -1240,12 +1180,6 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkg-config"
|
|
||||||
version = "0.3.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
@ -1487,6 +1421,34 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.16.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"spin",
|
||||||
|
"untrusted",
|
||||||
|
"web-sys",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.12.3",
|
||||||
|
"log",
|
||||||
|
"ring",
|
||||||
|
"sct",
|
||||||
|
"webpki",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@ -1499,16 +1461,6 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
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]]
|
[[package]]
|
||||||
name = "scoped-tls"
|
name = "scoped-tls"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -1521,35 +1473,22 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sct"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "seahash"
|
name = "seahash"
|
||||||
version = "4.0.1"
|
version = "4.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39ee459cae272d224928ca09a1df5406da984f263dc544f9f8bde92a8c3dc916"
|
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]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.114"
|
version = "1.0.114"
|
||||||
@ -1665,6 +1604,12 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlformat"
|
name = "sqlformat"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1678,8 +1623,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx"
|
name = "sqlx"
|
||||||
version = "0.4.0-beta.1"
|
version = "0.4.1"
|
||||||
source = "git+https://github.com/launchbadge/sqlx?branch=master#12b4250454b13fa2699dee9a4c761154ae60ddb6"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1f8eb788e1733bdbf69a8f97087213ebdebd253d4782c686d3cfd586b0a9453"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"sqlx-macros",
|
"sqlx-macros",
|
||||||
@ -1687,8 +1633,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-core"
|
name = "sqlx-core"
|
||||||
version = "0.4.0-beta.1"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/launchbadge/sqlx?branch=master#12b4250454b13fa2699dee9a4c761154ae60ddb6"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e647268dc1239dd9db2d3103fefd61151971a2214882cff9efea6f60cf50840"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.5.8",
|
"ahash 0.5.8",
|
||||||
"atoi",
|
"atoi",
|
||||||
@ -1718,6 +1665,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha-1 0.9.1",
|
"sha-1 0.9.1",
|
||||||
@ -1729,13 +1677,16 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
"uuid 0.8.1",
|
"uuid 0.8.1",
|
||||||
|
"webpki",
|
||||||
|
"webpki-roots",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-macros"
|
name = "sqlx-macros"
|
||||||
version = "0.4.0-beta.1"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/launchbadge/sqlx?branch=master#12b4250454b13fa2699dee9a4c761154ae60ddb6"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7acd32cba35531345f8a94a038874baf00efd0b701c913f5b00d2870b474b64"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"either",
|
"either",
|
||||||
@ -1755,13 +1706,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-rt"
|
name = "sqlx-rt"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/launchbadge/sqlx?branch=master#12b4250454b13fa2699dee9a4c761154ae60ddb6"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63fc5454c9dd7aaea3a0eeeb65ca40d06d0d8e7413a8184f7c3a3ffa5056190b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"native-tls",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1883,13 +1834,15 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-rustls"
|
||||||
version = "0.1.0"
|
version = "0.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd608593a919a8e05a7d1fc6df885e40f6a88d3a70a3a7eff23ff27964eda069"
|
checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"native-tls",
|
"futures-core",
|
||||||
|
"rustls",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1938,10 +1891,21 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-appender"
|
||||||
version = "0.1.9"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0693bf8d6f2bf22c690fc61a9d21ac69efdbb894a17ed596b9af0f01e64b84b"
|
checksum = "7aa52d56cc0d79ab604e8a022a1cebc4de33cf09dc9933c94353bea2e00d6e88"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -1980,9 +1944,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-serde"
|
name = "tracing-serde"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6ccba2f8f16e0ed268fc765d9b7ff22e965e7185d32f8f1ec8294fe17d86e79"
|
checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
@ -1990,9 +1954,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.2.10"
|
version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7b33f8b2ef2ab0c3778c12646d9c42a24f7772bee4cdafc72199644a9f58fdc"
|
checksum = "abd165311cc4d7a555ad11cc77a37756df836182db0d81aac908c8184c584f40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -2003,6 +1967,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
"tracing-serde",
|
"tracing-serde",
|
||||||
@ -2087,6 +2052,12 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@ -2129,12 +2100,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
@ -2173,6 +2138,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -2186,6 +2152,89 @@ version = "0.9.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.68"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.68"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.68"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.68"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.68"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki"
|
||||||
|
version = "0.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f"
|
||||||
|
dependencies = [
|
||||||
|
"webpki",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whoami"
|
name = "whoami"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -18,8 +18,8 @@ listenfd = "0.3"
|
|||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
openssl-probe = "0.1"
|
openssl-probe = "0.1"
|
||||||
tokio = { version = "0.2", features = ["macros", "rt-threaded", "sync"] }
|
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"] }
|
warp = { version = "0.2", features = ["compression", "tls"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||||
@ -28,7 +28,11 @@ url = "2.1"
|
|||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
seahash = "4.0"
|
seahash = "4.0"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
tracing-appender = "0.1"
|
||||||
tracing-subscriber = "0.2"
|
tracing-subscriber = "0.2"
|
||||||
tracing-futures = "0.2"
|
tracing-futures = "0.2"
|
||||||
lru = "0.5"
|
lru = "0.5"
|
||||||
http = "0.2"
|
http = "0.2"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
101
README.md
101
README.md
@ -16,16 +16,31 @@ are (all prefixed under `/v1`, the API version):
|
|||||||
- `/merchandise_lists`: Lists of in-game Forms that are in the merchant chest
|
- `/merchandise_lists`: Lists of in-game Forms that are in the merchant chest
|
||||||
of individual shops. When a user visits a shop, these forms are loaded
|
of individual shops. When a user visits a shop, these forms are loaded
|
||||||
onto the shop's shelves and are purchasable.
|
onto the shop's shelves and are purchasable.
|
||||||
|
- `/transactions`: Allows posting a new buy or sell between an owner and a
|
||||||
|
shop's merchandise.
|
||||||
|
|
||||||
Bazaar Realm was designed to allow users to change the API they are using the
|
Bazaar Realm was designed to allow users to change the API they are using the
|
||||||
mod under, if they wish. The API can run on a small server with minimal
|
mod under, if they wish. The API can run on a small server with minimal
|
||||||
resources, which should be suitable for a small group of friends to share
|
resources, which should be suitable for a group of friends to share shops
|
||||||
shops with each other.
|
with each other.
|
||||||
|
|
||||||
It uses the [`warp`](https://crates.io/crates/warp) web server framework and
|
It uses the [`warp`](https://crates.io/crates/warp) web server framework and
|
||||||
[`sqlx`](https://crates.io/crates/sqlx) for database queries to a [PostgreSQL
|
[`sqlx`](https://crates.io/crates/sqlx) for database queries to a [PostgreSQL
|
||||||
database](https://www.postgresql.org).
|
database](https://www.postgresql.org).
|
||||||
|
|
||||||
|
The API was designed with performance as a high priority. When it serves a
|
||||||
|
response, it also caches that response so future queries for the same data
|
||||||
|
can be returned in less than 1ms. To reduce data sent over the network,
|
||||||
|
clients can use the
|
||||||
|
[ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag)
|
||||||
|
headers to indicate to the server what version of the data they have cached
|
||||||
|
so the server can send a 304 response with no data if the resource hasn't
|
||||||
|
changed since the client last requested. Using the
|
||||||
|
[Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept)
|
||||||
|
header, clients can also opt for the more space-efficient and faster to
|
||||||
|
deserialize [bincode](https://github.com/servo/bincode) format instead of the
|
||||||
|
JSON default.
|
||||||
|
|
||||||
Related projects:
|
Related projects:
|
||||||
|
|
||||||
- [`BazaarRealmClient`](https://github.com/thallada/BazaarRealmClient): DLL that
|
- [`BazaarRealmClient`](https://github.com/thallada/BazaarRealmClient): DLL that
|
||||||
@ -40,6 +55,29 @@ Related projects:
|
|||||||
|
|
||||||
The easiest way to get the server up and running is using Docker.
|
The easiest way to get the server up and running is using Docker.
|
||||||
|
|
||||||
|
1. Download and install [Docker Desktop](https://www.docker.com/get-started)
|
||||||
|
2. In PowerShell, cmd.exe, or a terminal run `docker pull postgres:alpine` then `docker pull thallada/bazaarrealm:latest`
|
||||||
|
3. Run (replacing `<password>` with a secure generated password):
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -d --name postgres --network=bazaarrealm --network-alias=db --env POSTGRES_DB=bazaarrealm --env POSTGRES_USER=bazaarrealm --env POSTGRES_PASSWORD=<password> postgres:alpine
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run (replacing `<password>` with what you generated in previous step):
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -d --name bazaarrealm -p 3030:3030 --network=bazaarrealm --network-alias=api --env DATABASE_URL=postgresql://bazaarrealm:<password>@db/bazaarrealm --env HOST=http://localhost:3030 thallada/bazaarrealm:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
5. The server should now be available at `http://localhost:3030`.
|
||||||
|
|
||||||
|
## Docker-Compose Setup
|
||||||
|
|
||||||
|
An alternative way to set up the API, is to use `docker-compose` which can
|
||||||
|
orchestrate setting up the database and web server containers for you. This
|
||||||
|
method is more useful if you would like to make changes to the API code and
|
||||||
|
test them out.
|
||||||
|
|
||||||
1. Download and install [Docker Desktop](https://www.docker.com/get-started)
|
1. Download and install [Docker Desktop](https://www.docker.com/get-started)
|
||||||
2. Git clone this repo into a folder of your choosing: `git clone https://github.com/thallada/BazaarRealmAPI.git`
|
2. Git clone this repo into a folder of your choosing: `git clone https://github.com/thallada/BazaarRealmAPI.git`
|
||||||
3. Create a new file `.env.docker` in the checked out `bazaar_realm_api`
|
3. Create a new file `.env.docker` in the checked out `bazaar_realm_api`
|
||||||
@ -50,6 +88,7 @@ The easiest way to get the server up and running is using Docker.
|
|||||||
DATABASE_URL="postgresql://bazaarrealm:<password>@db/bazaarrealm"
|
DATABASE_URL="postgresql://bazaarrealm:<password>@db/bazaarrealm"
|
||||||
RUST_LOG="bazaar_realm_api=debug,warp=info"
|
RUST_LOG="bazaar_realm_api=debug,warp=info"
|
||||||
HOST="http://localhost:3030"
|
HOST="http://localhost:3030"
|
||||||
|
PORT=3030
|
||||||
POSTGRES_DB=bazaarrealm
|
POSTGRES_DB=bazaarrealm
|
||||||
POSTGRES_USER=bazaarrealm
|
POSTGRES_USER=bazaarrealm
|
||||||
POSTGRES_PASSWORD=<password>
|
POSTGRES_PASSWORD=<password>
|
||||||
@ -92,16 +131,36 @@ postgres=# CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|||||||
DATABASE_URL=postgresql://bazaarrealm:<password>@localhost/bazaarrealm
|
DATABASE_URL=postgresql://bazaarrealm:<password>@localhost/bazaarrealm
|
||||||
RUST_LOG="bazaar_realm_api=debug"
|
RUST_LOG="bazaar_realm_api=debug"
|
||||||
HOST="http://localhost:3030"
|
HOST="http://localhost:3030"
|
||||||
|
PORT=3030
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Install
|
4. Install
|
||||||
[`sqlx_cli`](https://github.com/launchbadge/sqlx/tree/master/sqlx-cli) with
|
[`sqlx_cli`](https://github.com/launchbadge/sqlx/tree/master/sqlx-cli) with
|
||||||
`cargo install --version=0.1.0-beta.1 sqlx-cli --no-default-features --features postgres`
|
`cargo install --version=0.1.0-beta.1 sqlx-cli --no-default-features --features postgres`
|
||||||
5. `cd db` to enter the `db` sub-directory of this repo.
|
5. Run `sqlx migrate --source db/migrations run` which will run all the database
|
||||||
6. Run `sqlx migrate run` which will run all the database migrations.
|
migrations.
|
||||||
7. `cd ..` to return to the top-level directory of this repo.
|
6. Run `./devserver.sh` to run the dev server (by default it listens at
|
||||||
8. Run `./devserver.sh` to run the dev server (by default it listens at
|
`127.0.0.1:3030`). Note that this runs the server in debug mode and shouldn't
|
||||||
`127.0.0.1:3030`).
|
be used to serve requests from the mod. You can build the release version of
|
||||||
|
the server with `cargo build --release`.
|
||||||
|
|
||||||
|
## TLS setup
|
||||||
|
|
||||||
|
If you would like to access the server over HTTPS, you can use [Let's
|
||||||
|
Encrypt](https://letsencrypt.org/) to generate a SSL certificate and key and
|
||||||
|
provide it to the API. Once you use [certbot](https://certbot.eff.org/) to
|
||||||
|
generate the certificate and key for your domain in
|
||||||
|
`/etc/letsencrypt/live/<domain>/`, run the api server with:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -d --name bazaarrealm --network=host --env DATABASE_URL=postgresql://bazaarrealm:<password>@localhost/bazaarrealm --env PORT=443 --HOST=https://<domain> --env TLS_CERT=/etc/letsencrypt/live/<domain>/fullchain.pem --env TLS_KEY=/etc/letsencrypt/live/<domain>/privkey.pem -v /etc/letsencrypt/:/etc/letsencrypt/ thallada/bazaarrealm:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
This command assumes that you are on Linux and you have a running postgres
|
||||||
|
database already set up outside of docker. See Manual Development Setup for
|
||||||
|
database setup instructions.
|
||||||
|
|
||||||
|
The server should be accessible at your domain: `https://<domain>`.
|
||||||
|
|
||||||
## Testing Data
|
## Testing Data
|
||||||
|
|
||||||
@ -124,6 +183,20 @@ http GET "http://localhost:3030/v1/interior_ref_lists"
|
|||||||
http GET "http://localhost:3030/v1/merchandise_lists"
|
http GET "http://localhost:3030/v1/merchandise_lists"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Database Migrations
|
||||||
|
|
||||||
|
Migrations are handled by `sqlx`. When the server initially starts, it will
|
||||||
|
connect to the database and check if there are any migrations in
|
||||||
|
`db/migrations` that have not yet been applied. It will apply any at that
|
||||||
|
time and then continue starting the server.
|
||||||
|
|
||||||
|
A new migration can be created by running: `sqlx migrate add <name>`.
|
||||||
|
|
||||||
|
To allow the docker container for the API to get built in CI without a
|
||||||
|
database, the `sqlx-data.json` file needs to be re-generated every time the
|
||||||
|
database schema changes or any query is updated. It can be generated with `cargo
|
||||||
|
sqlx prepare`.
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
I don't want to require users of Bazaar Realm to have to remember a password,
|
I don't want to require users of Bazaar Realm to have to remember a password,
|
||||||
@ -133,14 +206,6 @@ unique UUID identifier instead. This is the api key that the
|
|||||||
The api key is stored in the save game files for the player character and is
|
The api key is stored in the save game files for the player character and is
|
||||||
required to be sent with any API request that modifies data.
|
required to be sent with any API request that modifies data.
|
||||||
|
|
||||||
Yes, it's not most secure solution, but I'm not convinced security is a huge
|
Yes, it's not the most secure solution, but I'm not convinced security is a
|
||||||
concern here. As long as users don't share their API key or the save game
|
huge concern here. As long as users don't share their API key or the save
|
||||||
files that contain it, their data should be secure.
|
game files that contain it, their data should be secure.
|
||||||
|
|
||||||
## Todo
|
|
||||||
|
|
||||||
- Add update endpoints.
|
|
||||||
- Add endpoints for the other models.
|
|
||||||
- Make self-contained docker container that can run the app without any setup.
|
|
||||||
- Add rate-limiting per IP address. The `tower` crate has a service that might
|
|
||||||
be useful for this.
|
|
||||||
|
@ -13,6 +13,11 @@ CREATE TABLE "shops" (
|
|||||||
"name" VARCHAR(255) NOT NULL,
|
"name" VARCHAR(255) NOT NULL,
|
||||||
"owner_id" INTEGER REFERENCES "owners"(id) NOT NULL,
|
"owner_id" INTEGER REFERENCES "owners"(id) NOT NULL,
|
||||||
"description" TEXT,
|
"description" TEXT,
|
||||||
|
"gold" INTEGER NOT NULL DEFAULT 0
|
||||||
|
CONSTRAINT "shop_gold_gt_zero" CHECK (gold >= 0),
|
||||||
|
"shop_type" VARCHAR(255) NOT NULL DEFAULT 'general_store',
|
||||||
|
"vendor_keywords" TEXT[] NOT NULL DEFAULT '{"VendorItemKey", "VendorNoSale"}',
|
||||||
|
"vendor_keywords_exclude" BOOLEAN NOT NULL DEFAULT true,
|
||||||
"created_at" timestamp(3) NOT NULL,
|
"created_at" timestamp(3) NOT NULL,
|
||||||
"updated_at" timestamp(3) NOT NULL
|
"updated_at" timestamp(3) NOT NULL
|
||||||
);
|
);
|
||||||
@ -22,6 +27,7 @@ CREATE TABLE "interior_ref_lists" (
|
|||||||
"shop_id" INTEGER REFERENCES "shops"(id) NOT NULL UNIQUE,
|
"shop_id" INTEGER REFERENCES "shops"(id) NOT NULL UNIQUE,
|
||||||
"owner_id" INTEGER REFERENCES "owners"(id) NOT NULL,
|
"owner_id" INTEGER REFERENCES "owners"(id) NOT NULL,
|
||||||
"ref_list" jsonb NOT NULL,
|
"ref_list" jsonb NOT NULL,
|
||||||
|
"shelves" jsonb NOT NULL,
|
||||||
"created_at" timestamp(3) NOT NULL,
|
"created_at" timestamp(3) NOT NULL,
|
||||||
"updated_at" timestamp(3) NOT NULL
|
"updated_at" timestamp(3) NOT NULL
|
||||||
);
|
);
|
||||||
@ -35,6 +41,14 @@ CREATE TABLE "merchandise_lists" (
|
|||||||
"updated_at" timestamp(3) NOT NULL
|
"updated_at" timestamp(3) NOT NULL
|
||||||
);
|
);
|
||||||
CREATE INDEX "merchandise_lists_mod_name_and_local_form_id" ON "merchandise_lists" USING GIN (form_list jsonb_path_ops);
|
CREATE INDEX "merchandise_lists_mod_name_and_local_form_id" ON "merchandise_lists" USING GIN (form_list jsonb_path_ops);
|
||||||
|
CREATE TABLE "vendors" (
|
||||||
|
"id" SERIAL PRIMARY KEY NOT NULL,
|
||||||
|
"shop_id" INTEGER REFERENCES "shops"(id) NOT NULL UNIQUE,
|
||||||
|
"owner_id" INTEGER REFERENCES "owners"(id) NOT NULL,
|
||||||
|
"name" VARCHAR(255) NOT NULL,
|
||||||
|
"body_preset" INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX "vendors_unique_name_and_owner_id" ON "vendors" ("name", "owner_id", "shop_id");
|
||||||
CREATE TABLE "transactions" (
|
CREATE TABLE "transactions" (
|
||||||
"id" SERIAL PRIMARY KEY NOT NULL,
|
"id" SERIAL PRIMARY KEY NOT NULL,
|
||||||
"shop_id" INTEGER REFERENCES "shops"(id) NOT NULL,
|
"shop_id" INTEGER REFERENCES "shops"(id) NOT NULL,
|
||||||
@ -48,6 +62,7 @@ CREATE TABLE "transactions" (
|
|||||||
"is_sell" BOOLEAN NOT NULL,
|
"is_sell" BOOLEAN NOT NULL,
|
||||||
"quantity" INTEGER NOT NULL,
|
"quantity" INTEGER NOT NULL,
|
||||||
"amount" INTEGER NOT NULL,
|
"amount" INTEGER NOT NULL,
|
||||||
|
"keywords" TEXT[] NOT NULL DEFAULT '{}',
|
||||||
"created_at" timestamp(3) NOT NULL,
|
"created_at" timestamp(3) NOT NULL,
|
||||||
"updated_at" timestamp(3) NOT NULL
|
"updated_at" timestamp(3) NOT NULL
|
||||||
);
|
);
|
||||||
|
@ -3,4 +3,5 @@ DROP TABLE shops CASCADE;
|
|||||||
DROP TABLE interior_ref_lists CASCADE;
|
DROP TABLE interior_ref_lists CASCADE;
|
||||||
DROP TABLE merchandise_lists CASCADE;
|
DROP TABLE merchandise_lists CASCADE;
|
||||||
DROP TABLE transactions CASCADE;
|
DROP TABLE transactions CASCADE;
|
||||||
DROP TABLE refinery_schema_history CASCADE;
|
DROP TABLE vendors CASCADE;
|
||||||
|
DROP TABLE _sqlx_migrations CASCADE;
|
||||||
|
1075
sqlx-data.json
1075
sqlx-data.json
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,19 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
use hyper::body::Bytes;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use warp::reply::{with_header, with_status};
|
use warp::reply::{with_header, with_status};
|
||||||
use warp::{Rejection, Reply};
|
use warp::{Rejection, Reply};
|
||||||
|
|
||||||
use crate::caches::CACHES;
|
use crate::caches::{CachedResponse, CACHES};
|
||||||
use crate::models::{InteriorRefList, ListParams, PostedInteriorRefList, UnsavedInteriorRefList};
|
use crate::models::{InteriorRefList, ListParams, PostedInteriorRefList};
|
||||||
use crate::problem::reject_anyhow;
|
use crate::problem::reject_anyhow;
|
||||||
use crate::Environment;
|
use crate::Environment;
|
||||||
|
|
||||||
use super::{
|
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(
|
pub async fn get(
|
||||||
@ -20,12 +22,14 @@ pub async fn get(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => {
|
content_type,
|
||||||
(ContentType::Bincode, &CACHES.interior_ref_list_bin)
|
cache,
|
||||||
}
|
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||||
_ => (ContentType::Json, &CACHES.interior_ref_list),
|
accept,
|
||||||
};
|
&CACHES.interior_ref_list_bin,
|
||||||
|
&CACHES.interior_ref_list,
|
||||||
|
);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(id, || async {
|
.get_response(id, || async {
|
||||||
let interior_ref_list = InteriorRefList::get(&env.db, id).await?;
|
let interior_ref_list = InteriorRefList::get(&env.db, id).await?;
|
||||||
@ -50,13 +54,14 @@ pub async fn get_by_shop_id(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => (
|
content_type,
|
||||||
ContentType::Bincode,
|
cache,
|
||||||
|
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||||
|
accept,
|
||||||
&CACHES.interior_ref_list_by_shop_id_bin,
|
&CACHES.interior_ref_list_by_shop_id_bin,
|
||||||
),
|
&CACHES.interior_ref_list_by_shop_id,
|
||||||
_ => (ContentType::Json, &CACHES.interior_ref_list_by_shop_id),
|
);
|
||||||
};
|
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(shop_id, || async {
|
.get_response(shop_id, || async {
|
||||||
let interior_ref_list = InteriorRefList::get_by_shop_id(&env.db, shop_id).await?;
|
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>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => {
|
content_type,
|
||||||
(ContentType::Bincode, &CACHES.list_interior_ref_lists_bin)
|
cache,
|
||||||
}
|
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||||
_ => (ContentType::Json, &CACHES.list_interior_ref_lists),
|
accept,
|
||||||
};
|
&CACHES.list_interior_ref_lists_bin,
|
||||||
|
&CACHES.list_interior_ref_lists,
|
||||||
|
);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(list_params.clone(), || async {
|
.get_response(list_params.clone(), || async {
|
||||||
let interior_ref_lists = InteriorRefList::list(&env.db, &list_params).await?;
|
let interior_ref_lists = InteriorRefList::list(&env.db, &list_params).await?;
|
||||||
@ -107,24 +114,19 @@ pub async fn list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
interior_ref_list: PostedInteriorRefList,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: mut interior_ref_list,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedInteriorRefList>::from_bytes(bytes, content_type)
|
||||||
_ => ContentType::Json,
|
.map_err(reject_anyhow)?;
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
let unsaved_interior_ref_list = UnsavedInteriorRefList {
|
interior_ref_list.owner_id = Some(owner_id);
|
||||||
owner_id,
|
let saved_interior_ref_list = InteriorRefList::create(interior_ref_list, &env.db)
|
||||||
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)
|
|
||||||
.await
|
.await
|
||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
let url = saved_interior_ref_list
|
let url = saved_interior_ref_list
|
||||||
@ -159,17 +161,16 @@ pub async fn create(
|
|||||||
|
|
||||||
pub async fn update(
|
pub async fn update(
|
||||||
id: i32,
|
id: i32,
|
||||||
interior_ref_list: PostedInteriorRefList,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: interior_ref_list,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedInteriorRefList>::from_bytes(bytes, content_type)
|
||||||
_ => ContentType::Json,
|
.map_err(reject_anyhow)?;
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
let updated_interior_ref_list =
|
let updated_interior_ref_list =
|
||||||
InteriorRefList::update(interior_ref_list, &env.db, owner_id, id)
|
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(
|
pub async fn update_by_shop_id(
|
||||||
shop_id: i32,
|
shop_id: i32,
|
||||||
interior_ref_list: PostedInteriorRefList,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: interior_ref_list,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedInteriorRefList>::from_bytes(bytes, content_type)
|
||||||
_ => ContentType::Json,
|
.map_err(reject_anyhow)?;
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
let updated_interior_ref_list =
|
let updated_interior_ref_list =
|
||||||
InteriorRefList::update_by_shop_id(interior_ref_list, &env.db, owner_id, shop_id)
|
InteriorRefList::update_by_shop_id(interior_ref_list, &env.db, owner_id, shop_id)
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
use hyper::body::Bytes;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use warp::reply::{with_header, with_status};
|
use warp::reply::{with_header, with_status};
|
||||||
use warp::{Rejection, Reply};
|
use warp::{Rejection, Reply};
|
||||||
|
|
||||||
use crate::caches::CACHES;
|
use crate::caches::{CachedResponse, CACHES};
|
||||||
use crate::models::{ListParams, MerchandiseList, PostedMerchandiseList, UnsavedMerchandiseList};
|
use crate::models::{ListParams, MerchandiseList, PostedMerchandiseList};
|
||||||
use crate::problem::reject_anyhow;
|
use crate::problem::reject_anyhow;
|
||||||
use crate::Environment;
|
use crate::Environment;
|
||||||
|
|
||||||
use super::{
|
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(
|
pub async fn get(
|
||||||
@ -20,12 +22,14 @@ pub async fn get(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => {
|
content_type,
|
||||||
(ContentType::Bincode, &CACHES.merchandise_list_bin)
|
cache,
|
||||||
}
|
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||||
_ => (ContentType::Json, &CACHES.merchandise_list),
|
accept,
|
||||||
};
|
&CACHES.merchandise_list_bin,
|
||||||
|
&CACHES.merchandise_list,
|
||||||
|
);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(id, || async {
|
.get_response(id, || async {
|
||||||
let merchandise_list = MerchandiseList::get(&env.db, id).await?;
|
let merchandise_list = MerchandiseList::get(&env.db, id).await?;
|
||||||
@ -50,12 +54,14 @@ pub async fn get_by_shop_id(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => {
|
content_type,
|
||||||
(ContentType::Bincode, &CACHES.merchandise_list_bin)
|
cache,
|
||||||
}
|
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||||
_ => (ContentType::Json, &CACHES.merchandise_list),
|
accept,
|
||||||
};
|
&CACHES.merchandise_list_by_shop_id_bin,
|
||||||
|
&CACHES.merchandise_list_by_shop_id,
|
||||||
|
);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(shop_id, || async {
|
.get_response(shop_id, || async {
|
||||||
let merchandise_list = MerchandiseList::get_by_shop_id(&env.db, shop_id).await?;
|
let merchandise_list = MerchandiseList::get_by_shop_id(&env.db, shop_id).await?;
|
||||||
@ -80,12 +86,14 @@ pub async fn list(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => {
|
content_type,
|
||||||
(ContentType::Bincode, &CACHES.list_merchandise_lists_bin)
|
cache,
|
||||||
}
|
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||||
_ => (ContentType::Json, &CACHES.list_merchandise_lists),
|
accept,
|
||||||
};
|
&CACHES.list_merchandise_lists_bin,
|
||||||
|
&CACHES.list_merchandise_lists,
|
||||||
|
);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(list_params.clone(), || async {
|
.get_response(list_params.clone(), || async {
|
||||||
let merchandise_lists = MerchandiseList::list(&env.db, &list_params).await?;
|
let merchandise_lists = MerchandiseList::list(&env.db, &list_params).await?;
|
||||||
@ -105,24 +113,19 @@ pub async fn list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
merchandise_list: PostedMerchandiseList,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: mut merchandise_list,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedMerchandiseList>::from_bytes(bytes, content_type)
|
||||||
_ => ContentType::Json,
|
.map_err(reject_anyhow)?;
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
let unsaved_merchandise_list = UnsavedMerchandiseList {
|
merchandise_list.owner_id = Some(owner_id);
|
||||||
owner_id,
|
let saved_merchandise_list = MerchandiseList::create(merchandise_list, &env.db)
|
||||||
shop_id: merchandise_list.shop_id,
|
|
||||||
form_list: merchandise_list.form_list,
|
|
||||||
};
|
|
||||||
let saved_merchandise_list = MerchandiseList::create(unsaved_merchandise_list, &env.db)
|
|
||||||
.await
|
.await
|
||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
let url = saved_merchandise_list
|
let url = saved_merchandise_list
|
||||||
@ -156,17 +159,16 @@ pub async fn create(
|
|||||||
|
|
||||||
pub async fn update(
|
pub async fn update(
|
||||||
id: i32,
|
id: i32,
|
||||||
merchandise_list: PostedMerchandiseList,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: merchandise_list,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedMerchandiseList>::from_bytes(bytes, content_type)
|
||||||
_ => ContentType::Json,
|
.map_err(reject_anyhow)?;
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.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)
|
let updated_merchandise_list = MerchandiseList::update(merchandise_list, &env.db, owner_id, id)
|
||||||
.await
|
.await
|
||||||
@ -205,17 +207,16 @@ pub async fn update(
|
|||||||
|
|
||||||
pub async fn update_by_shop_id(
|
pub async fn update_by_shop_id(
|
||||||
shop_id: i32,
|
shop_id: i32,
|
||||||
merchandise_list: PostedMerchandiseList,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: merchandise_list,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedMerchandiseList>::from_bytes(bytes, content_type)
|
||||||
_ => ContentType::Json,
|
.map_err(reject_anyhow)?;
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
let updated_merchandise_list =
|
let updated_merchandise_list =
|
||||||
MerchandiseList::update_by_shop_id(merchandise_list, &env.db, owner_id, shop_id)
|
MerchandiseList::update_by_shop_id(merchandise_list, &env.db, owner_id, shop_id)
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
|
use std::fmt::Debug;
|
||||||
|
use std::hash::Hash;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use http::header::{HeaderValue, CONTENT_TYPE, ETAG};
|
use http::header::{HeaderValue, CONTENT_TYPE, ETAG, SERVER};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use http_api_problem::HttpApiProblem;
|
use http_api_problem::HttpApiProblem;
|
||||||
|
use hyper::body::Bytes;
|
||||||
use mime::{FromStrError, Mime};
|
use mime::{FromStrError, Mime};
|
||||||
use seahash::hash;
|
use seahash::hash;
|
||||||
use serde::Serialize;
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use tracing::{error, instrument, warn};
|
use tracing::{debug, error, instrument, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use warp::reply::Response;
|
use warp::reply::Response;
|
||||||
use warp::Reply;
|
use warp::Reply;
|
||||||
@ -19,10 +22,12 @@ pub mod owner;
|
|||||||
pub mod shop;
|
pub mod shop;
|
||||||
pub mod transaction;
|
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::problem::{unauthorized_no_api_key, unauthorized_no_owner};
|
||||||
use super::Environment;
|
use super::Environment;
|
||||||
|
|
||||||
|
pub static SERVER_STRING: &str = "BazaarRealmAPI/0.1.0";
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(env, api_key))]
|
#[instrument(level = "debug", skip(env, api_key))]
|
||||||
pub async fn authenticate(env: &Environment, api_key: Option<Uuid>) -> Result<i32> {
|
pub async fn authenticate(env: &Environment, api_key: Option<Uuid>) -> Result<i32> {
|
||||||
if let Some(api_key) = api_key {
|
if let Some(api_key) = api_key {
|
||||||
@ -75,6 +80,8 @@ impl Reply for ETagReply<Json> {
|
|||||||
let mut res = Response::new(self.body.into());
|
let mut res = Response::new(self.body.into());
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||||
|
res.headers_mut()
|
||||||
|
.insert(SERVER, HeaderValue::from_static(SERVER_STRING));
|
||||||
if let Ok(val) = HeaderValue::from_str(&self.etag) {
|
if let Ok(val) = HeaderValue::from_str(&self.etag) {
|
||||||
res.headers_mut().insert(ETAG, val);
|
res.headers_mut().insert(ETAG, val);
|
||||||
} else {
|
} else {
|
||||||
@ -113,6 +120,8 @@ impl Reply for ETagReply<Bincode> {
|
|||||||
CONTENT_TYPE,
|
CONTENT_TYPE,
|
||||||
HeaderValue::from_static("application/octet-stream"),
|
HeaderValue::from_static("application/octet-stream"),
|
||||||
);
|
);
|
||||||
|
res.headers_mut()
|
||||||
|
.insert(SERVER, HeaderValue::from_static(SERVER_STRING));
|
||||||
if let Ok(val) = HeaderValue::from_str(&self.etag) {
|
if let Ok(val) = HeaderValue::from_str(&self.etag) {
|
||||||
res.headers_mut().insert(ETAG, val);
|
res.headers_mut().insert(ETAG, val);
|
||||||
} else {
|
} else {
|
||||||
@ -178,3 +187,76 @@ impl AcceptHeader {
|
|||||||
self.mimes.contains(&mime::APPLICATION_OCTET_STREAM)
|
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 anyhow::Result;
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
use hyper::body::Bytes;
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
@ -7,13 +8,14 @@ use uuid::Uuid;
|
|||||||
use warp::reply::{with_header, with_status};
|
use warp::reply::{with_header, with_status};
|
||||||
use warp::{Rejection, Reply};
|
use warp::{Rejection, Reply};
|
||||||
|
|
||||||
use crate::caches::CACHES;
|
use crate::caches::{CachedResponse, CACHES};
|
||||||
use crate::models::{ListParams, Owner, PostedOwner, UnsavedOwner};
|
use crate::models::{FullPostedOwner, ListParams, Owner, PostedOwner};
|
||||||
use crate::problem::{reject_anyhow, unauthorized_no_api_key};
|
use crate::problem::{reject_anyhow, unauthorized_no_api_key};
|
||||||
use crate::Environment;
|
use crate::Environment;
|
||||||
|
|
||||||
use super::{
|
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(
|
pub async fn get(
|
||||||
@ -22,10 +24,10 @@ pub async fn get(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.owner_bin),
|
content_type,
|
||||||
_ => (ContentType::Json, &CACHES.owner),
|
cache,
|
||||||
};
|
} = TypedCache::<i32, CachedResponse>::pick_cache(accept, &CACHES.owner_bin, &CACHES.owner);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(id, || async {
|
.get_response(id, || async {
|
||||||
let owner = Owner::get(&env.db, id).await?;
|
let owner = Owner::get(&env.db, id).await?;
|
||||||
@ -46,10 +48,14 @@ pub async fn list(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.list_owners_bin),
|
content_type,
|
||||||
_ => (ContentType::Json, &CACHES.list_owners),
|
cache,
|
||||||
};
|
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||||
|
accept,
|
||||||
|
&CACHES.list_owners_bin,
|
||||||
|
&CACHES.list_owners,
|
||||||
|
);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(list_params.clone(), || async {
|
.get_response(list_params.clone(), || async {
|
||||||
let owners = Owner::list(&env.db, &list_params).await?;
|
let owners = Owner::list(&env.db, &list_params).await?;
|
||||||
@ -65,7 +71,7 @@ pub async fn list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
owner: PostedOwner,
|
bytes: Bytes,
|
||||||
remote_addr: Option<SocketAddr>,
|
remote_addr: Option<SocketAddr>,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
real_ip: Option<IpNetwork>,
|
real_ip: Option<IpNetwork>,
|
||||||
@ -73,24 +79,21 @@ pub async fn create(
|
|||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
if let Some(api_key) = api_key {
|
if let Some(api_key) = api_key {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: owner,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedOwner>::from_bytes(bytes, content_type)
|
||||||
_ => ContentType::Json,
|
.map_err(reject_anyhow)?;
|
||||||
};
|
let owner = FullPostedOwner {
|
||||||
let unsaved_owner = UnsavedOwner {
|
name: owner.name,
|
||||||
|
mod_version: owner.mod_version,
|
||||||
api_key,
|
api_key,
|
||||||
ip_address: match remote_addr {
|
ip_address: match remote_addr {
|
||||||
Some(addr) => Some(IpNetwork::from(addr.ip())),
|
Some(addr) => Some(IpNetwork::from(addr.ip())),
|
||||||
None => real_ip,
|
None => real_ip,
|
||||||
},
|
},
|
||||||
name: owner.name,
|
|
||||||
mod_version: owner.mod_version,
|
|
||||||
};
|
};
|
||||||
let saved_owner = Owner::create(unsaved_owner, &env.db)
|
let saved_owner = Owner::create(owner, &env.db).await.map_err(reject_anyhow)?;
|
||||||
.await
|
|
||||||
.map_err(reject_anyhow)?;
|
|
||||||
let url = saved_owner.url(&env.api_url).map_err(reject_anyhow)?;
|
let url = saved_owner.url(&env.api_url).map_err(reject_anyhow)?;
|
||||||
let reply: Box<dyn Reply> = match content_type {
|
let reply: Box<dyn Reply> = match content_type {
|
||||||
ContentType::Bincode => Box::new(
|
ContentType::Bincode => Box::new(
|
||||||
@ -114,17 +117,15 @@ pub async fn create(
|
|||||||
|
|
||||||
pub async fn update(
|
pub async fn update(
|
||||||
id: i32,
|
id: i32,
|
||||||
owner: PostedOwner,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: owner,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedOwner>::from_bytes(bytes, content_type).map_err(reject_anyhow)?;
|
||||||
_ => ContentType::Json,
|
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.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)
|
let updated_owner = Owner::update(owner, &env.db, owner_id, id)
|
||||||
.await
|
.await
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
use hyper::body::Bytes;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use warp::reply::{with_header, with_status};
|
use warp::reply::{with_header, with_status};
|
||||||
use warp::{Rejection, Reply};
|
use warp::{Rejection, Reply};
|
||||||
|
|
||||||
use crate::caches::CACHES;
|
use crate::caches::{CachedResponse, CACHES};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
InteriorRefList, ListParams, MerchandiseList, PostedShop, Shop, UnsavedInteriorRefList,
|
InteriorRefList, ListParams, MerchandiseList, PostedInteriorRefList, PostedMerchandiseList,
|
||||||
UnsavedMerchandiseList, UnsavedShop,
|
PostedShop, Shop,
|
||||||
};
|
};
|
||||||
use crate::problem::reject_anyhow;
|
use crate::problem::reject_anyhow;
|
||||||
use crate::Environment;
|
use crate::Environment;
|
||||||
|
|
||||||
use super::{
|
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(
|
pub async fn get(
|
||||||
@ -23,10 +25,10 @@ pub async fn get(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.shop_bin),
|
content_type,
|
||||||
_ => (ContentType::Json, &CACHES.shop),
|
cache,
|
||||||
};
|
} = TypedCache::<i32, CachedResponse>::pick_cache(accept, &CACHES.shop_bin, &CACHES.shop);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(id, || async {
|
.get_response(id, || async {
|
||||||
let shop = Shop::get(&env.db, id).await?;
|
let shop = Shop::get(&env.db, id).await?;
|
||||||
@ -47,10 +49,14 @@ pub async fn list(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.list_shops_bin),
|
content_type,
|
||||||
_ => (ContentType::Json, &CACHES.list_shops),
|
cache,
|
||||||
};
|
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||||
|
accept,
|
||||||
|
&CACHES.list_shops_bin,
|
||||||
|
&CACHES.list_shops,
|
||||||
|
);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(list_params.clone(), || async {
|
.get_response(list_params.clone(), || async {
|
||||||
let shops = Shop::list(&env.db, &list_params).await?;
|
let shops = Shop::list(&env.db, &list_params).await?;
|
||||||
@ -66,44 +72,37 @@ pub async fn list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
shop: PostedShop,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: mut shop,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedShop>::from_bytes(bytes, content_type).map_err(reject_anyhow)?;
|
||||||
_ => ContentType::Json,
|
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
let unsaved_shop = UnsavedShop {
|
shop.owner_id = Some(owner_id);
|
||||||
name: shop.name,
|
|
||||||
description: shop.description,
|
|
||||||
owner_id,
|
|
||||||
};
|
|
||||||
let mut tx = env
|
let mut tx = env
|
||||||
.db
|
.db
|
||||||
.begin()
|
.begin()
|
||||||
.await
|
.await
|
||||||
.map_err(|error| reject_anyhow(anyhow!(error)))?;
|
.map_err(|error| reject_anyhow(anyhow!(error)))?;
|
||||||
let saved_shop = Shop::create(unsaved_shop, &mut tx)
|
let saved_shop = Shop::create(shop, &mut tx).await.map_err(reject_anyhow)?;
|
||||||
.await
|
|
||||||
.map_err(reject_anyhow)?;
|
|
||||||
|
|
||||||
// also save empty interior_ref_list and merchandise_list rows
|
// 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,
|
shop_id: saved_shop.id,
|
||||||
owner_id,
|
owner_id: Some(owner_id),
|
||||||
ref_list: sqlx::types::Json::default(),
|
ref_list: sqlx::types::Json::default(),
|
||||||
|
shelves: sqlx::types::Json::default(),
|
||||||
};
|
};
|
||||||
InteriorRefList::create(interior_ref_list, &mut tx)
|
InteriorRefList::create(interior_ref_list, &mut tx)
|
||||||
.await
|
.await
|
||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
let merchandise_list = UnsavedMerchandiseList {
|
let merchandise_list = PostedMerchandiseList {
|
||||||
shop_id: saved_shop.id,
|
shop_id: saved_shop.id,
|
||||||
owner_id,
|
owner_id: Some(owner_id),
|
||||||
form_list: sqlx::types::Json::default(),
|
form_list: sqlx::types::Json::default(),
|
||||||
};
|
};
|
||||||
MerchandiseList::create(merchandise_list, &mut tx)
|
MerchandiseList::create(merchandise_list, &mut tx)
|
||||||
@ -133,27 +132,22 @@ pub async fn create(
|
|||||||
|
|
||||||
pub async fn update(
|
pub async fn update(
|
||||||
id: i32,
|
id: i32,
|
||||||
shop: PostedShop,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: mut shop,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedShop>::from_bytes(bytes, content_type).map_err(reject_anyhow)?;
|
||||||
_ => ContentType::Json,
|
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
let posted_shop = PostedShop {
|
shop.owner_id = match shop.owner_id {
|
||||||
owner_id: match shop.owner_id {
|
|
||||||
// allows an owner to transfer ownership of shop to another owner
|
// allows an owner to transfer ownership of shop to another owner
|
||||||
Some(posted_owner_id) => Some(posted_owner_id),
|
Some(posted_owner_id) => Some(posted_owner_id),
|
||||||
None => Some(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
|
.await
|
||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
let url = updated_shop.url(&env.api_url).map_err(reject_anyhow)?;
|
let url = updated_shop.url(&env.api_url).map_err(reject_anyhow)?;
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
use http_api_problem::HttpApiProblem;
|
||||||
|
use hyper::body::Bytes;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use warp::reply::{with_header, with_status};
|
use warp::reply::{with_header, with_status};
|
||||||
use warp::{Rejection, Reply};
|
use warp::{reject, Rejection, Reply};
|
||||||
|
|
||||||
use crate::caches::CACHES;
|
use crate::caches::{CachedResponse, CACHES};
|
||||||
use crate::models::{
|
use crate::models::{ListParams, MerchandiseList, PostedTransaction, Shop, Transaction};
|
||||||
ListParams, MerchandiseList, PostedTransaction, Transaction, UnsavedTransaction,
|
|
||||||
};
|
|
||||||
use crate::problem::reject_anyhow;
|
use crate::problem::reject_anyhow;
|
||||||
use crate::Environment;
|
use crate::Environment;
|
||||||
|
|
||||||
use super::{
|
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(
|
pub async fn get(
|
||||||
@ -22,10 +23,14 @@ pub async fn get(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => (ContentType::Bincode, &CACHES.transaction_bin),
|
content_type,
|
||||||
_ => (ContentType::Json, &CACHES.transaction),
|
cache,
|
||||||
};
|
} = TypedCache::<i32, CachedResponse>::pick_cache(
|
||||||
|
accept,
|
||||||
|
&CACHES.transaction_bin,
|
||||||
|
&CACHES.transaction,
|
||||||
|
);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(id, || async {
|
.get_response(id, || async {
|
||||||
let transaction = Transaction::get(&env.db, id).await?;
|
let transaction = Transaction::get(&env.db, id).await?;
|
||||||
@ -48,12 +53,14 @@ pub async fn list(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => {
|
content_type,
|
||||||
(ContentType::Bincode, &CACHES.list_transactions_bin)
|
cache,
|
||||||
}
|
} = TypedCache::<ListParams, CachedResponse>::pick_cache(
|
||||||
_ => (ContentType::Json, &CACHES.list_transactions),
|
accept,
|
||||||
};
|
&CACHES.list_transactions_bin,
|
||||||
|
&CACHES.list_transactions,
|
||||||
|
);
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response(list_params.clone(), || async {
|
.get_response(list_params.clone(), || async {
|
||||||
let transactions = Transaction::list(&env.db, &list_params).await?;
|
let transactions = Transaction::list(&env.db, &list_params).await?;
|
||||||
@ -77,13 +84,14 @@ pub async fn list_by_shop_id(
|
|||||||
accept: Option<AcceptHeader>,
|
accept: Option<AcceptHeader>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let (content_type, cache) = match accept {
|
let TypedCache {
|
||||||
Some(accept) if accept.accepts_bincode() => (
|
content_type,
|
||||||
ContentType::Bincode,
|
cache,
|
||||||
|
} = TypedCache::<(i32, ListParams), CachedResponse>::pick_cache(
|
||||||
|
accept,
|
||||||
&CACHES.list_transactions_by_shop_id_bin,
|
&CACHES.list_transactions_by_shop_id_bin,
|
||||||
),
|
&CACHES.list_transactions_by_shop_id,
|
||||||
_ => (ContentType::Json, &CACHES.list_transactions_by_shop_id),
|
);
|
||||||
};
|
|
||||||
let response = cache
|
let response = cache
|
||||||
.get_response((shop_id, list_params.clone()), || async {
|
.get_response((shop_id, list_params.clone()), || async {
|
||||||
let transactions = Transaction::list_by_shop_id(&env.db, shop_id, &list_params).await?;
|
let transactions = Transaction::list_by_shop_id(&env.db, shop_id, &list_params).await?;
|
||||||
@ -101,42 +109,43 @@ pub async fn list_by_shop_id(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
transaction: PostedTransaction,
|
bytes: Bytes,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
content_type: Option<Mime>,
|
content_type: Option<Mime>,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let content_type = match content_type {
|
let DeserializedBody {
|
||||||
Some(content_type) if content_type == mime::APPLICATION_OCTET_STREAM => {
|
body: mut transaction,
|
||||||
ContentType::Bincode
|
content_type,
|
||||||
}
|
} = DeserializedBody::<PostedTransaction>::from_bytes(bytes, content_type)
|
||||||
_ => ContentType::Json,
|
.map_err(reject_anyhow)?;
|
||||||
};
|
|
||||||
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
let owner_id = authenticate(&env, api_key).await.map_err(reject_anyhow)?;
|
||||||
let unsaved_transaction = UnsavedTransaction {
|
transaction.owner_id = Some(owner_id);
|
||||||
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
|
let mut tx = env
|
||||||
.db
|
.db
|
||||||
.begin()
|
.begin()
|
||||||
.await
|
.await
|
||||||
.map_err(|error| reject_anyhow(anyhow!(error)))?;
|
.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
|
.await
|
||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
let quantity_delta = match transaction.is_sell {
|
if !Shop::accepts_keywords(
|
||||||
true => transaction.quantity,
|
&mut tx,
|
||||||
false => transaction.quantity * -1,
|
saved_transaction.shop_id,
|
||||||
|
&saved_transaction.keywords,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(reject_anyhow)?
|
||||||
|
{
|
||||||
|
return Err(reject::custom(
|
||||||
|
HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
|
||||||
|
.set_title("Unacceptable Merchandise Type")
|
||||||
|
.set_detail("Shop does not accept that kind of merchandise"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let (quantity_delta, shop_gold_delta) = match saved_transaction.is_sell {
|
||||||
|
true => (saved_transaction.quantity, saved_transaction.price * -1),
|
||||||
|
false => (saved_transaction.quantity * -1, saved_transaction.price),
|
||||||
};
|
};
|
||||||
let updated_merchandise_list = MerchandiseList::update_merchandise_quantity(
|
let updated_merchandise_list = MerchandiseList::update_merchandise_quantity(
|
||||||
&mut tx,
|
&mut tx,
|
||||||
@ -148,9 +157,13 @@ pub async fn create(
|
|||||||
saved_transaction.is_food,
|
saved_transaction.is_food,
|
||||||
saved_transaction.price,
|
saved_transaction.price,
|
||||||
quantity_delta,
|
quantity_delta,
|
||||||
|
&saved_transaction.keywords,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(reject_anyhow)?;
|
.map_err(reject_anyhow)?;
|
||||||
|
Shop::update_gold(&mut tx, saved_transaction.shop_id, shop_gold_delta)
|
||||||
|
.await
|
||||||
|
.map_err(reject_anyhow)?;
|
||||||
tx.commit()
|
tx.commit()
|
||||||
.await
|
.await
|
||||||
.map_err(|error| reject_anyhow(anyhow!(error)))?;
|
.map_err(|error| reject_anyhow(anyhow!(error)))?;
|
||||||
@ -189,10 +202,21 @@ pub async fn create(
|
|||||||
CACHES.list_transactions_by_shop_id_bin.clear().await;
|
CACHES.list_transactions_by_shop_id_bin.clear().await;
|
||||||
CACHES.list_merchandise_lists.clear().await;
|
CACHES.list_merchandise_lists.clear().await;
|
||||||
CACHES.list_merchandise_lists_bin.clear().await;
|
CACHES.list_merchandise_lists_bin.clear().await;
|
||||||
|
CACHES
|
||||||
|
.shop
|
||||||
|
.delete_response(updated_merchandise_list.shop_id)
|
||||||
|
.await;
|
||||||
|
CACHES
|
||||||
|
.shop_bin
|
||||||
|
.delete_response(updated_merchandise_list.shop_id)
|
||||||
|
.await;
|
||||||
|
CACHES.list_shops.clear().await;
|
||||||
|
CACHES.list_shops_bin.clear().await;
|
||||||
});
|
});
|
||||||
Ok(reply)
|
Ok(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Does NOT reverse the transaction side-effects!
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
id: i32,
|
id: i32,
|
||||||
api_key: Option<Uuid>,
|
api_key: Option<Uuid>,
|
||||||
|
73
src/main.rs
73
src/main.rs
@ -3,16 +3,16 @@ extern crate lazy_static;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use http::StatusCode;
|
use http::header::SERVER;
|
||||||
use hyper::server::Server;
|
use hyper::{body::Bytes, server::Server};
|
||||||
use listenfd::ListenFd;
|
use listenfd::ListenFd;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use sqlx::{migrate, Pool, Postgres};
|
use sqlx::{migrate, Pool, Postgres};
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::env;
|
use std::env;
|
||||||
use tracing_subscriber::fmt::format::FmtSpan;
|
use tracing_subscriber::fmt::format::FmtSpan;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use warp::http::Response;
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
|
||||||
mod caches;
|
mod caches;
|
||||||
@ -22,10 +22,8 @@ mod macros;
|
|||||||
mod models;
|
mod models;
|
||||||
mod problem;
|
mod problem;
|
||||||
|
|
||||||
use models::{
|
use handlers::SERVER_STRING;
|
||||||
ListParams, PostedInteriorRefList, PostedMerchandiseList, PostedOwner, PostedShop,
|
use models::ListParams;
|
||||||
PostedTransaction,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
@ -45,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 {
|
fn with_env(env: Environment) -> impl Filter<Extract = (Environment,), Error = Infallible> + Clone {
|
||||||
warp::any().map(move || env.clone())
|
warp::any().map(move || env.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_body<T>() -> impl Filter<Extract = (T,), Error = warp::Rejection> + Clone
|
fn extract_body_bytes() -> impl Filter<Extract = (Bytes,), Error = warp::Rejection> + Clone {
|
||||||
where
|
warp::body::content_length_limit(1024 * 1024).and(warp::body::bytes())
|
||||||
T: Send + DeserializeOwned,
|
|
||||||
{
|
|
||||||
warp::body::content_length_limit(1024 * 1024).and(warp::body::json())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -69,10 +58,11 @@ async fn main() -> Result<()> {
|
|||||||
let env_log_filter =
|
let env_log_filter =
|
||||||
env::var("RUST_LOG").unwrap_or_else(|_| "warp=info,bazaar_realm_api=info".to_owned());
|
env::var("RUST_LOG").unwrap_or_else(|_| "warp=info,bazaar_realm_api=info".to_owned());
|
||||||
|
|
||||||
|
let (non_blocking_writer, _guard) = tracing_appender::non_blocking(std::io::stdout());
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_env_filter(env_log_filter)
|
.with_env_filter(env_log_filter)
|
||||||
.with_span_events(FmtSpan::CLOSE)
|
.with_span_events(FmtSpan::CLOSE)
|
||||||
.with_writer(std::io::stdout)
|
.with_writer(non_blocking_writer)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let host = env::var("HOST").expect("`HOST` environment variable not defined");
|
let host = env::var("HOST").expect("`HOST` environment variable not defined");
|
||||||
@ -85,7 +75,7 @@ async fn main() -> Result<()> {
|
|||||||
let status_handler = warp::path::path("status")
|
let status_handler = warp::path::path("status")
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
.map(|| StatusCode::OK); // TODO: return what api versions this server supports instead
|
.map(|| Response::builder().header(SERVER, SERVER_STRING).body("Ok"));
|
||||||
let get_owner_handler = warp::path("owners").and(
|
let get_owner_handler = warp::path("owners").and(
|
||||||
warp::path::param()
|
warp::path::param()
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
@ -98,7 +88,7 @@ async fn main() -> Result<()> {
|
|||||||
let create_owner_handler = warp::path("owners").and(
|
let create_owner_handler = warp::path("owners").and(
|
||||||
warp::path::end()
|
warp::path::end()
|
||||||
.and(warp::post())
|
.and(warp::post())
|
||||||
.and(json_body::<PostedOwner>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::addr::remote())
|
.and(warp::addr::remote())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("x-real-ip"))
|
.and(warp::header::optional("x-real-ip"))
|
||||||
@ -118,7 +108,7 @@ async fn main() -> Result<()> {
|
|||||||
warp::path::param()
|
warp::path::param()
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::patch())
|
.and(warp::patch())
|
||||||
.and(json_body::<PostedOwner>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -145,7 +135,7 @@ async fn main() -> Result<()> {
|
|||||||
let create_shop_handler = warp::path("shops").and(
|
let create_shop_handler = warp::path("shops").and(
|
||||||
warp::path::end()
|
warp::path::end()
|
||||||
.and(warp::post())
|
.and(warp::post())
|
||||||
.and(json_body::<PostedShop>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -163,7 +153,7 @@ async fn main() -> Result<()> {
|
|||||||
warp::path::param()
|
warp::path::param()
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::patch())
|
.and(warp::patch())
|
||||||
.and(json_body::<PostedShop>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -190,7 +180,7 @@ async fn main() -> Result<()> {
|
|||||||
let create_interior_ref_list_handler = warp::path("interior_ref_lists").and(
|
let create_interior_ref_list_handler = warp::path("interior_ref_lists").and(
|
||||||
warp::path::end()
|
warp::path::end()
|
||||||
.and(warp::post())
|
.and(warp::post())
|
||||||
.and(json_body::<PostedInteriorRefList>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -208,7 +198,7 @@ async fn main() -> Result<()> {
|
|||||||
warp::path::param()
|
warp::path::param()
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::patch())
|
.and(warp::patch())
|
||||||
.and(json_body::<PostedInteriorRefList>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -219,7 +209,7 @@ async fn main() -> Result<()> {
|
|||||||
.and(warp::path("interior_ref_list"))
|
.and(warp::path("interior_ref_list"))
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::patch())
|
.and(warp::patch())
|
||||||
.and(json_body::<PostedInteriorRefList>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -256,7 +246,7 @@ async fn main() -> Result<()> {
|
|||||||
let create_merchandise_list_handler = warp::path("merchandise_lists").and(
|
let create_merchandise_list_handler = warp::path("merchandise_lists").and(
|
||||||
warp::path::end()
|
warp::path::end()
|
||||||
.and(warp::post())
|
.and(warp::post())
|
||||||
.and(json_body::<PostedMerchandiseList>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -274,7 +264,7 @@ async fn main() -> Result<()> {
|
|||||||
warp::path::param()
|
warp::path::param()
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::patch())
|
.and(warp::patch())
|
||||||
.and(json_body::<PostedMerchandiseList>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -285,7 +275,7 @@ async fn main() -> Result<()> {
|
|||||||
.and(warp::path("merchandise_list"))
|
.and(warp::path("merchandise_list"))
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::patch())
|
.and(warp::patch())
|
||||||
.and(json_body::<PostedMerchandiseList>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -322,7 +312,7 @@ async fn main() -> Result<()> {
|
|||||||
let create_transaction_handler = warp::path("transactions").and(
|
let create_transaction_handler = warp::path("transactions").and(
|
||||||
warp::path::end()
|
warp::path::end()
|
||||||
.and(warp::post())
|
.and(warp::post())
|
||||||
.and(json_body::<PostedTransaction>())
|
.and(extract_body_bytes())
|
||||||
.and(warp::header::optional("api-key"))
|
.and(warp::header::optional("api-key"))
|
||||||
.and(warp::header::optional("content-type"))
|
.and(warp::header::optional("content-type"))
|
||||||
.and(with_env(env.clone()))
|
.and(with_env(env.clone()))
|
||||||
@ -395,6 +385,21 @@ async fn main() -> Result<()> {
|
|||||||
.with(warp::compression::gzip())
|
.with(warp::compression::gzip())
|
||||||
.with(warp::trace::request());
|
.with(warp::trace::request());
|
||||||
|
|
||||||
|
if let Ok(tls_cert) = env::var("TLS_CERT") {
|
||||||
|
if let Ok(tls_key) = env::var("TLS_KEY") {
|
||||||
|
let port = env::var("PORT")
|
||||||
|
.unwrap_or_else(|_| "443".to_owned())
|
||||||
|
.parse()?;
|
||||||
|
warp::serve(routes)
|
||||||
|
.tls()
|
||||||
|
.cert_path(tls_cert)
|
||||||
|
.key_path(tls_key)
|
||||||
|
.run(([0, 0, 0, 0], port))
|
||||||
|
.await;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let svc = warp::service(routes);
|
let svc = warp::service(routes);
|
||||||
let make_svc = hyper::service::make_service_fn(|_: _| {
|
let make_svc = hyper::service::make_service_fn(|_: _| {
|
||||||
let svc = svc.clone();
|
let svc = svc.clone();
|
||||||
@ -405,10 +410,12 @@ async fn main() -> Result<()> {
|
|||||||
let server = if let Some(l) = listenfd.take_tcp_listener(0)? {
|
let server = if let Some(l) = listenfd.take_tcp_listener(0)? {
|
||||||
Server::from_tcp(l)?
|
Server::from_tcp(l)?
|
||||||
} else {
|
} else {
|
||||||
Server::bind(&([0, 0, 0, 0], 3030).into())
|
let port = env::var("PORT")
|
||||||
|
.unwrap_or_else(|_| "3030".to_owned())
|
||||||
|
.parse()?;
|
||||||
|
Server::bind(&([0, 0, 0, 0], port).into())
|
||||||
};
|
};
|
||||||
|
|
||||||
// warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
|
|
||||||
server.serve(make_svc).await?;
|
server.serve(make_svc).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,12 @@ use url::Url;
|
|||||||
use super::ListParams;
|
use super::ListParams;
|
||||||
use crate::problem::forbidden_permission;
|
use crate::problem::forbidden_permission;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(sqlx::FromRow, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct InteriorRef {
|
pub struct InteriorRef {
|
||||||
pub base_mod_name: String,
|
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_mod_name: Option<String>,
|
||||||
pub ref_local_form_id: i32,
|
pub ref_local_form_id: u32,
|
||||||
pub position_x: f32,
|
pub position_x: f32,
|
||||||
pub position_y: f32,
|
pub position_y: f32,
|
||||||
pub position_z: f32,
|
pub position_z: f32,
|
||||||
@ -24,28 +24,41 @@ pub struct InteriorRef {
|
|||||||
pub scale: u16,
|
pub scale: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(sqlx::FromRow, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Shelf {
|
||||||
|
pub shelf_type: u32,
|
||||||
|
pub position_x: f32,
|
||||||
|
pub position_y: f32,
|
||||||
|
pub position_z: f32,
|
||||||
|
pub angle_x: f32,
|
||||||
|
pub angle_y: f32,
|
||||||
|
pub angle_z: f32,
|
||||||
|
pub scale: u16,
|
||||||
|
pub page: u32,
|
||||||
|
pub filter_form_type: Option<u32>,
|
||||||
|
pub filter_is_food: bool,
|
||||||
|
pub search: Option<String>,
|
||||||
|
pub sort_on: Option<String>,
|
||||||
|
pub sort_asc: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct InteriorRefList {
|
pub struct InteriorRefList {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub shop_id: i32,
|
pub shop_id: i32,
|
||||||
pub owner_id: i32,
|
pub owner_id: i32,
|
||||||
pub ref_list: serde_json::Value,
|
pub ref_list: Json<Vec<InteriorRef>>,
|
||||||
|
pub shelves: Json<Vec<Shelf>>,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_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)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct PostedInteriorRefList {
|
pub struct PostedInteriorRefList {
|
||||||
pub shop_id: i32,
|
pub shop_id: i32,
|
||||||
pub owner_id: Option<i32>,
|
pub owner_id: Option<i32>,
|
||||||
pub ref_list: Json<Vec<InteriorRef>>,
|
pub ref_list: Json<Vec<InteriorRef>>,
|
||||||
|
pub shelves: Json<Vec<Shelf>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InteriorRefList {
|
impl InteriorRefList {
|
||||||
@ -64,7 +77,14 @@ impl InteriorRefList {
|
|||||||
// TODO: this model will probably never need to be accessed through it's ID, should these methods be removed/unimplemented?
|
// 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))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
pub async fn get(db: impl Executor<'_, Database = Postgres>, id: i32) -> Result<Self> {
|
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>>",
|
||||||
|
shelves as "shelves: Json<Vec<Shelf>>"
|
||||||
|
FROM interior_ref_lists WHERE id = $1"#,
|
||||||
|
id
|
||||||
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await
|
.await
|
||||||
.map_err(Error::new)
|
.map_err(Error::new)
|
||||||
@ -72,18 +92,21 @@ impl InteriorRefList {
|
|||||||
|
|
||||||
#[instrument(level = "debug", skip(interior_ref_list, db))]
|
#[instrument(level = "debug", skip(interior_ref_list, db))]
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
interior_ref_list: UnsavedInteriorRefList,
|
interior_ref_list: PostedInteriorRefList,
|
||||||
db: impl Executor<'_, Database = Postgres>,
|
db: impl Executor<'_, Database = Postgres>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"INSERT INTO interior_ref_lists
|
r#"INSERT INTO interior_ref_lists
|
||||||
(shop_id, owner_id, ref_list, created_at, updated_at)
|
(shop_id, owner_id, ref_list, shelves, created_at, updated_at)
|
||||||
VALUES ($1, $2, $3, now(), now())
|
VALUES ($1, $2, $3, $4, now(), now())
|
||||||
RETURNING *",
|
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||||
|
ref_list as "ref_list: Json<Vec<InteriorRef>>",
|
||||||
|
shelves as "shelves: Json<Vec<Shelf>>""#,
|
||||||
interior_ref_list.shop_id,
|
interior_ref_list.shop_id,
|
||||||
interior_ref_list.owner_id,
|
interior_ref_list.owner_id,
|
||||||
serde_json::json!(interior_ref_list.ref_list),
|
serde_json::json!(interior_ref_list.ref_list),
|
||||||
|
serde_json::json!(interior_ref_list.shelves),
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?)
|
.await?)
|
||||||
@ -119,10 +142,12 @@ impl InteriorRefList {
|
|||||||
let result = if let Some(order_by) = list_params.get_order_by() {
|
let result = if let Some(order_by) = list_params.get_order_by() {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
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>>",
|
||||||
|
shelves as "shelves: Json<Vec<Shelf>>" FROM interior_ref_lists
|
||||||
ORDER BY $1
|
ORDER BY $1
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
OFFSET $3",
|
OFFSET $3"#,
|
||||||
order_by,
|
order_by,
|
||||||
list_params.limit.unwrap_or(10),
|
list_params.limit.unwrap_or(10),
|
||||||
list_params.offset.unwrap_or(0),
|
list_params.offset.unwrap_or(0),
|
||||||
@ -132,9 +157,11 @@ impl InteriorRefList {
|
|||||||
} else {
|
} else {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
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>>",
|
||||||
|
shelves as "shelves: Json<Vec<Shelf>>" FROM interior_ref_lists
|
||||||
LIMIT $1
|
LIMIT $1
|
||||||
OFFSET $2",
|
OFFSET $2"#,
|
||||||
list_params.limit.unwrap_or(10),
|
list_params.limit.unwrap_or(10),
|
||||||
list_params.offset.unwrap_or(0),
|
list_params.offset.unwrap_or(0),
|
||||||
)
|
)
|
||||||
@ -158,13 +185,17 @@ impl InteriorRefList {
|
|||||||
if existing_interior_ref_list.owner_id == owner_id {
|
if existing_interior_ref_list.owner_id == owner_id {
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"UPDATE interior_ref_lists SET
|
r#"UPDATE interior_ref_lists SET
|
||||||
ref_list = $2,
|
ref_list = $2,
|
||||||
|
shelves = $3,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING *",
|
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||||
|
ref_list as "ref_list: Json<Vec<InteriorRef>>",
|
||||||
|
shelves as "shelves: Json<Vec<Shelf>>""#,
|
||||||
id,
|
id,
|
||||||
serde_json::json!(interior_ref_list.ref_list),
|
serde_json::json!(interior_ref_list.ref_list),
|
||||||
|
serde_json::json!(interior_ref_list.shelves),
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?)
|
.await?)
|
||||||
@ -180,8 +211,10 @@ impl InteriorRefList {
|
|||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"SELECT * FROM interior_ref_lists
|
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||||
WHERE shop_id = $1",
|
ref_list as "ref_list: Json<Vec<InteriorRef>>",
|
||||||
|
shelves as "shelves: Json<Vec<Shelf>>" FROM interior_ref_lists
|
||||||
|
WHERE shop_id = $1"#,
|
||||||
shop_id,
|
shop_id,
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
@ -205,13 +238,17 @@ impl InteriorRefList {
|
|||||||
if existing_interior_ref_list.owner_id == owner_id {
|
if existing_interior_ref_list.owner_id == owner_id {
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"UPDATE interior_ref_lists SET
|
r#"UPDATE interior_ref_lists SET
|
||||||
ref_list = $2,
|
ref_list = $2,
|
||||||
|
shelves = $3,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE shop_id = $1
|
WHERE shop_id = $1
|
||||||
RETURNING *",
|
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||||
|
ref_list as "ref_list: Json<Vec<InteriorRef>>",
|
||||||
|
shelves as "shelves: Json<Vec<Shelf>>""#,
|
||||||
shop_id,
|
shop_id,
|
||||||
serde_json::json!(interior_ref_list.ref_list),
|
serde_json::json!(interior_ref_list.ref_list),
|
||||||
|
serde_json::json!(interior_ref_list.shelves),
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?)
|
.await?)
|
||||||
|
@ -15,12 +15,13 @@ use crate::problem::forbidden_permission;
|
|||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Merchandise {
|
pub struct Merchandise {
|
||||||
pub mod_name: String,
|
pub mod_name: String,
|
||||||
pub local_form_id: i32,
|
pub local_form_id: u32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub quantity: i32,
|
pub quantity: u32,
|
||||||
pub form_type: i32,
|
pub form_type: u32,
|
||||||
pub is_food: bool,
|
pub is_food: bool,
|
||||||
pub price: i32,
|
pub price: u32,
|
||||||
|
pub keywords: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
@ -28,18 +29,11 @@ pub struct MerchandiseList {
|
|||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub shop_id: i32,
|
pub shop_id: i32,
|
||||||
pub owner_id: i32,
|
pub owner_id: i32,
|
||||||
pub form_list: serde_json::Value,
|
pub form_list: Json<Vec<Merchandise>>,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_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)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct PostedMerchandiseList {
|
pub struct PostedMerchandiseList {
|
||||||
pub shop_id: i32,
|
pub shop_id: i32,
|
||||||
@ -63,7 +57,14 @@ impl MerchandiseList {
|
|||||||
// TODO: this model will probably never need to be accessed through it's ID, should these methods be removed/unimplemented?
|
// 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))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
pub async fn get(db: impl Executor<'_, Database = Postgres>, id: i32) -> Result<Self> {
|
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)
|
.fetch_one(db)
|
||||||
.await
|
.await
|
||||||
.map_err(Error::new)
|
.map_err(Error::new)
|
||||||
@ -71,15 +72,16 @@ impl MerchandiseList {
|
|||||||
|
|
||||||
#[instrument(level = "debug", skip(merchandise_list, db))]
|
#[instrument(level = "debug", skip(merchandise_list, db))]
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
merchandise_list: UnsavedMerchandiseList,
|
merchandise_list: PostedMerchandiseList,
|
||||||
db: impl Executor<'_, Database = Postgres>,
|
db: impl Executor<'_, Database = Postgres>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"INSERT INTO merchandise_lists
|
r#"INSERT INTO merchandise_lists
|
||||||
(shop_id, owner_id, form_list, created_at, updated_at)
|
(shop_id, owner_id, form_list, created_at, updated_at)
|
||||||
VALUES ($1, $2, $3, now(), now())
|
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.shop_id,
|
||||||
merchandise_list.owner_id,
|
merchandise_list.owner_id,
|
||||||
serde_json::json!(merchandise_list.form_list),
|
serde_json::json!(merchandise_list.form_list),
|
||||||
@ -118,10 +120,12 @@ impl MerchandiseList {
|
|||||||
let result = if let Some(order_by) = list_params.get_order_by() {
|
let result = if let Some(order_by) = list_params.get_order_by() {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
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
|
ORDER BY $1
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
OFFSET $3",
|
OFFSET $3"#,
|
||||||
order_by,
|
order_by,
|
||||||
list_params.limit.unwrap_or(10),
|
list_params.limit.unwrap_or(10),
|
||||||
list_params.offset.unwrap_or(0),
|
list_params.offset.unwrap_or(0),
|
||||||
@ -131,9 +135,11 @@ impl MerchandiseList {
|
|||||||
} else {
|
} else {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
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
|
LIMIT $1
|
||||||
OFFSET $2",
|
OFFSET $2"#,
|
||||||
list_params.limit.unwrap_or(10),
|
list_params.limit.unwrap_or(10),
|
||||||
list_params.offset.unwrap_or(0),
|
list_params.offset.unwrap_or(0),
|
||||||
)
|
)
|
||||||
@ -157,11 +163,12 @@ impl MerchandiseList {
|
|||||||
if existing_merchandise_list.owner_id == owner_id {
|
if existing_merchandise_list.owner_id == owner_id {
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"UPDATE merchandise_lists SET
|
r#"UPDATE merchandise_lists SET
|
||||||
form_list = $2,
|
form_list = $2,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING *",
|
RETURNING id, shop_id, owner_id, created_at, updated_at,
|
||||||
|
form_list as "form_list: Json<Vec<Merchandise>>""#,
|
||||||
id,
|
id,
|
||||||
serde_json::json!(merchandise_list.form_list),
|
serde_json::json!(merchandise_list.form_list),
|
||||||
)
|
)
|
||||||
@ -179,8 +186,10 @@ impl MerchandiseList {
|
|||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"SELECT * FROM merchandise_lists
|
r#"SELECT id, shop_id, owner_id, created_at, updated_at,
|
||||||
WHERE shop_id = $1",
|
form_list as "form_list: Json<Vec<Merchandise>>"
|
||||||
|
FROM merchandise_lists
|
||||||
|
WHERE shop_id = $1"#,
|
||||||
shop_id,
|
shop_id,
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
@ -204,11 +213,12 @@ impl MerchandiseList {
|
|||||||
if existing_merchandise_list.owner_id == owner_id {
|
if existing_merchandise_list.owner_id == owner_id {
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"UPDATE merchandise_lists SET
|
r#"UPDATE merchandise_lists SET
|
||||||
form_list = $2,
|
form_list = $2,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE shop_id = $1
|
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,
|
shop_id,
|
||||||
serde_json::json!(merchandise_list.form_list),
|
serde_json::json!(merchandise_list.form_list),
|
||||||
)
|
)
|
||||||
@ -230,6 +240,7 @@ impl MerchandiseList {
|
|||||||
is_food: bool,
|
is_food: bool,
|
||||||
price: i32,
|
price: i32,
|
||||||
quantity_delta: i32,
|
quantity_delta: i32,
|
||||||
|
keywords: &[String],
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let add_item = json!([{
|
let add_item = json!([{
|
||||||
"mod_name": mod_name,
|
"mod_name": mod_name,
|
||||||
@ -239,10 +250,11 @@ impl MerchandiseList {
|
|||||||
"form_type": form_type,
|
"form_type": form_type,
|
||||||
"is_food": is_food,
|
"is_food": is_food,
|
||||||
"price": price,
|
"price": price,
|
||||||
|
"keywords": keywords,
|
||||||
}]);
|
}]);
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"UPDATE
|
r#"UPDATE
|
||||||
merchandise_lists
|
merchandise_lists
|
||||||
SET
|
SET
|
||||||
form_list = CASE
|
form_list = CASE
|
||||||
@ -277,7 +289,13 @@ impl MerchandiseList {
|
|||||||
) sub
|
) sub
|
||||||
WHERE
|
WHERE
|
||||||
shop_id = $1
|
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,
|
shop_id,
|
||||||
mod_name,
|
mod_name,
|
||||||
&local_form_id.to_string(),
|
&local_form_id.to_string(),
|
||||||
|
@ -9,12 +9,12 @@ pub mod owner;
|
|||||||
pub mod shop;
|
pub mod shop;
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
|
|
||||||
pub use interior_ref_list::{InteriorRefList, PostedInteriorRefList, UnsavedInteriorRefList};
|
pub use interior_ref_list::{InteriorRefList, PostedInteriorRefList};
|
||||||
pub use merchandise_list::{MerchandiseList, PostedMerchandiseList, UnsavedMerchandiseList};
|
pub use merchandise_list::{MerchandiseList, PostedMerchandiseList};
|
||||||
pub use model::{Model, UpdateableModel};
|
pub use model::{Model, UpdateableModel};
|
||||||
pub use owner::{Owner, PostedOwner, UnsavedOwner};
|
pub use owner::{FullPostedOwner, Owner, PostedOwner};
|
||||||
pub use shop::{PostedShop, Shop, UnsavedShop};
|
pub use shop::{PostedShop, Shop};
|
||||||
pub use transaction::{PostedTransaction, Transaction, UnsavedTransaction};
|
pub use transaction::{PostedTransaction, Transaction};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Deserialize)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Deserialize)]
|
||||||
pub enum Order {
|
pub enum Order {
|
||||||
|
@ -24,18 +24,16 @@ pub struct Owner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct UnsavedOwner {
|
pub struct PostedOwner {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub api_key: Uuid,
|
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub ip_address: Option<IpNetwork>,
|
|
||||||
pub mod_version: i32,
|
pub mod_version: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct PostedOwner {
|
pub struct FullPostedOwner {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub api_key: Uuid,
|
||||||
|
pub ip_address: Option<IpNetwork>,
|
||||||
pub mod_version: i32,
|
pub mod_version: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +60,7 @@ impl Owner {
|
|||||||
|
|
||||||
#[instrument(level = "debug", skip(owner, db))]
|
#[instrument(level = "debug", skip(owner, db))]
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
owner: UnsavedOwner,
|
owner: FullPostedOwner,
|
||||||
db: impl Executor<'_, Database = Postgres>,
|
db: impl Executor<'_, Database = Postgres>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
|
@ -14,22 +14,23 @@ pub struct Shop {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub owner_id: i32,
|
pub owner_id: i32,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
pub gold: i32,
|
||||||
|
pub shop_type: String,
|
||||||
|
pub vendor_keywords: Vec<String>,
|
||||||
|
pub vendor_keywords_exclude: bool,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_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)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct PostedShop {
|
pub struct PostedShop {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub owner_id: Option<i32>,
|
pub owner_id: Option<i32>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
pub gold: Option<i32>,
|
||||||
|
pub shop_type: Option<String>,
|
||||||
|
pub vendor_keywords: Option<Vec<String>>,
|
||||||
|
pub vendor_keywords_exclude: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shop {
|
impl Shop {
|
||||||
@ -55,18 +56,25 @@ impl Shop {
|
|||||||
|
|
||||||
#[instrument(level = "debug", skip(shop, db))]
|
#[instrument(level = "debug", skip(shop, db))]
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
shop: UnsavedShop,
|
shop: PostedShop,
|
||||||
db: impl Executor<'_, Database = Postgres>,
|
db: impl Executor<'_, Database = Postgres>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"INSERT INTO shops
|
"INSERT INTO shops
|
||||||
(name, owner_id, description, created_at, updated_at)
|
(name, owner_id, description, gold, shop_type, vendor_keywords,
|
||||||
VALUES ($1, $2, $3, now(), now())
|
vendor_keywords_exclude, created_at, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, now(), now())
|
||||||
RETURNING *",
|
RETURNING *",
|
||||||
shop.name,
|
shop.name,
|
||||||
shop.owner_id,
|
shop.owner_id,
|
||||||
shop.description,
|
shop.description,
|
||||||
|
shop.gold.unwrap_or(0),
|
||||||
|
shop.shop_type.unwrap_or("general_store".to_string()),
|
||||||
|
&shop
|
||||||
|
.vendor_keywords
|
||||||
|
.unwrap_or_else(|| vec!["VendorItemKey".to_string(), "VendorNoSale".to_string()]),
|
||||||
|
shop.vendor_keywords_exclude.unwrap_or(true),
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?)
|
.await?)
|
||||||
@ -141,6 +149,10 @@ impl Shop {
|
|||||||
name = $2,
|
name = $2,
|
||||||
owner_id = $3,
|
owner_id = $3,
|
||||||
description = $4,
|
description = $4,
|
||||||
|
gold = $5,
|
||||||
|
shop_type = $6,
|
||||||
|
vendor_keywords = $7,
|
||||||
|
vendor_keywords_exclude = $8,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING *",
|
RETURNING *",
|
||||||
@ -148,6 +160,10 @@ impl Shop {
|
|||||||
shop.name,
|
shop.name,
|
||||||
shop.owner_id,
|
shop.owner_id,
|
||||||
shop.description,
|
shop.description,
|
||||||
|
shop.gold,
|
||||||
|
shop.shop_type,
|
||||||
|
&shop.vendor_keywords.unwrap_or_else(|| vec![]),
|
||||||
|
shop.vendor_keywords_exclude,
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?)
|
.await?)
|
||||||
@ -155,4 +171,48 @@ impl Shop {
|
|||||||
return Err(forbidden_permission());
|
return Err(forbidden_permission());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip(db))]
|
||||||
|
pub async fn accepts_keywords(
|
||||||
|
db: impl Executor<'_, Database = Postgres>,
|
||||||
|
id: i32,
|
||||||
|
keywords: &[String],
|
||||||
|
) -> Result<bool> {
|
||||||
|
// Macro not available, see: https://github.com/launchbadge/sqlx/issues/428
|
||||||
|
Ok(sqlx::query_scalar(
|
||||||
|
"SELECT EXISTS (
|
||||||
|
SELECT 1 FROM shops
|
||||||
|
WHERE id = $1
|
||||||
|
AND ((
|
||||||
|
vendor_keywords_exclude = true AND
|
||||||
|
NOT vendor_keywords && $2
|
||||||
|
) OR (
|
||||||
|
vendor_keywords_exclude = false AND
|
||||||
|
vendor_keywords && $2
|
||||||
|
))
|
||||||
|
)",
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.bind(keywords)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip(db))]
|
||||||
|
pub async fn update_gold(
|
||||||
|
db: impl Executor<'_, Database = Postgres>,
|
||||||
|
id: i32,
|
||||||
|
gold_delta: i32,
|
||||||
|
) -> Result<()> {
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE shops SET
|
||||||
|
gold = gold + $2
|
||||||
|
WHERE id = $1",
|
||||||
|
id,
|
||||||
|
gold_delta,
|
||||||
|
)
|
||||||
|
.execute(db)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,25 +22,11 @@ pub struct Transaction {
|
|||||||
pub is_sell: bool,
|
pub is_sell: bool,
|
||||||
pub quantity: i32,
|
pub quantity: i32,
|
||||||
pub amount: i32,
|
pub amount: i32,
|
||||||
|
pub keywords: Vec<String>,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_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)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct PostedTransaction {
|
pub struct PostedTransaction {
|
||||||
pub shop_id: i32,
|
pub shop_id: i32,
|
||||||
@ -54,6 +40,7 @@ pub struct PostedTransaction {
|
|||||||
pub is_sell: bool,
|
pub is_sell: bool,
|
||||||
pub quantity: i32,
|
pub quantity: i32,
|
||||||
pub amount: i32,
|
pub amount: i32,
|
||||||
|
pub keywords: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
@ -79,15 +66,15 @@ impl Transaction {
|
|||||||
|
|
||||||
#[instrument(level = "debug", skip(db))]
|
#[instrument(level = "debug", skip(db))]
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
transaction: UnsavedTransaction,
|
transaction: PostedTransaction,
|
||||||
db: impl Executor<'_, Database = Postgres>,
|
db: impl Executor<'_, Database = Postgres>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
Self,
|
Self,
|
||||||
"INSERT INTO transactions
|
"INSERT INTO transactions
|
||||||
(shop_id, owner_id, mod_name, local_form_id, name, form_type, is_food, price,
|
(shop_id, owner_id, mod_name, local_form_id, name, form_type, is_food, price,
|
||||||
is_sell, quantity, amount, created_at, updated_at)
|
is_sell, quantity, amount, keywords, created_at, updated_at)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, now(), now())
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now(), now())
|
||||||
RETURNING *",
|
RETURNING *",
|
||||||
transaction.shop_id,
|
transaction.shop_id,
|
||||||
transaction.owner_id,
|
transaction.owner_id,
|
||||||
@ -100,6 +87,7 @@ impl Transaction {
|
|||||||
transaction.is_sell,
|
transaction.is_sell,
|
||||||
transaction.quantity,
|
transaction.quantity,
|
||||||
transaction.amount,
|
transaction.amount,
|
||||||
|
&transaction.keywords,
|
||||||
)
|
)
|
||||||
.fetch_one(db)
|
.fetch_one(db)
|
||||||
.await?)
|
.await?)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use http_api_problem::HttpApiProblem;
|
use http_api_problem::HttpApiProblem;
|
||||||
@ -31,16 +33,14 @@ pub fn from_anyhow(error: anyhow::Error) -> HttpApiProblem {
|
|||||||
Err(error) => error,
|
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>() {
|
if let Some(sqlx_error) = error.downcast_ref::<sqlx::error::Error>() {
|
||||||
match sqlx_error {
|
match sqlx_error {
|
||||||
sqlx::error::Error::RowNotFound => {
|
sqlx::error::Error::RowNotFound => {
|
||||||
return HttpApiProblem::with_title_and_type_from_status(StatusCode::NOT_FOUND)
|
return HttpApiProblem::with_title_and_type_from_status(StatusCode::NOT_FOUND)
|
||||||
}
|
}
|
||||||
_ => {}
|
sqlx::error::Error::Database(db_error) => {
|
||||||
}
|
let pg_error = db_error.downcast_ref::<sqlx::postgres::PgDatabaseError>();
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(pg_error) = error.downcast_ref::<sqlx::postgres::PgDatabaseError>() {
|
|
||||||
error!(
|
error!(
|
||||||
"Database error: {}. {}",
|
"Database error: {}. {}",
|
||||||
pg_error.message(),
|
pg_error.message(),
|
||||||
@ -51,36 +51,122 @@ pub fn from_anyhow(error: anyhow::Error) -> HttpApiProblem {
|
|||||||
dbg!(&code);
|
dbg!(&code);
|
||||||
if let Some(constraint) = pg_error.constraint() {
|
if let Some(constraint) = pg_error.constraint() {
|
||||||
dbg!(&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
|
// 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,
|
||||||
|
)
|
||||||
|
// TODO: better message when this is triggered by a non-cascading DELETE
|
||||||
.set_detail("Owner does not exist");
|
.set_detail("Owner does not exist");
|
||||||
|
} else if code == "23503"
|
||||||
|
&& (constraint == "interior_ref_lists_shop_id_fkey"
|
||||||
|
|| constraint == "merchandise_lists_shop_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" {
|
} else if code == "23505" && constraint == "owners_api_key_key" {
|
||||||
// unique_violation
|
// 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");
|
.set_detail("Owner with Api-Key already exists");
|
||||||
} else if code == "23505" && constraint == "owners_unique_name_and_api_key" {
|
} else if code == "23505" && constraint == "owners_unique_name_and_api_key" {
|
||||||
// unique_violation
|
// 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");
|
.set_detail("Duplicate owner with same name and Api-Key exists");
|
||||||
} else if code == "23505" && constraint == "shops_unique_name_and_owner_id" {
|
} else if code == "23505" && constraint == "shops_unique_name_and_owner_id" {
|
||||||
// unique_violation
|
// 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");
|
.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" {
|
} 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");
|
.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);
|
error!("Recovering unhandled error: {:?}", error);
|
||||||
// TODO: this leaks internal info, should not stringify error
|
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
HttpApiProblem::new(format!("Internal Server Error: {:?}", error))
|
|
||||||
.set_status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn unpack_problem(rejection: Rejection) -> Result<impl Reply, Rejection> {
|
pub async fn unpack_problem(rejection: Rejection) -> Result<impl Reply, Rejection> {
|
||||||
|
if rejection.is_not_found() {
|
||||||
|
let reply = warp::reply::json(&HttpApiProblem::with_title_and_type_from_status(
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
));
|
||||||
|
let reply = warp::reply::with_status(reply, StatusCode::NOT_FOUND);
|
||||||
|
let reply = warp::reply::with_header(
|
||||||
|
reply,
|
||||||
|
warp::http::header::CONTENT_TYPE,
|
||||||
|
http_api_problem::PROBLEM_JSON_MEDIA_TYPE,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(reply);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(problem) = rejection.find::<HttpApiProblem>() {
|
if let Some(problem) = rejection.find::<HttpApiProblem>() {
|
||||||
let code = problem.status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
|
let code = problem.status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
|
@ -2602,5 +2602,23 @@
|
|||||||
"angle_z": 1.006,
|
"angle_z": 1.006,
|
||||||
"scale": 1
|
"scale": 1
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"shelves": [
|
||||||
|
{
|
||||||
|
"shelf_type": 1,
|
||||||
|
"position_x": 1.001,
|
||||||
|
"position_y": 1.002,
|
||||||
|
"position_z": 1.003,
|
||||||
|
"angle_x": 1.004,
|
||||||
|
"angle_y": 1.005,
|
||||||
|
"angle_z": 1.006,
|
||||||
|
"scale": 1,
|
||||||
|
"page": 1,
|
||||||
|
"filter_form_type": null,
|
||||||
|
"filter_is_food": false,
|
||||||
|
"search": null,
|
||||||
|
"sort_on": null,
|
||||||
|
"sort_asc": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@
|
|||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"form_type": 32,
|
"form_type": 32,
|
||||||
"is_food": false,
|
"is_food": false,
|
||||||
"price": 1
|
"price": 1,
|
||||||
|
"keywords": ["VendorItemMisc"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mod_name": "Skyrim.esm",
|
"mod_name": "Skyrim.esm",
|
||||||
@ -18,7 +19,8 @@
|
|||||||
"quantity": 2,
|
"quantity": 2,
|
||||||
"form_type": 23,
|
"form_type": 23,
|
||||||
"is_food": false,
|
"is_food": false,
|
||||||
"price": 2
|
"price": 2,
|
||||||
|
"keywords": ["VendorItemScroll"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mod_name": "Skyrim.esm",
|
"mod_name": "Skyrim.esm",
|
||||||
@ -27,7 +29,8 @@
|
|||||||
"quantity": 3,
|
"quantity": 3,
|
||||||
"form_type": 46,
|
"form_type": 46,
|
||||||
"is_food": true,
|
"is_food": true,
|
||||||
"price": 3
|
"price": 3,
|
||||||
|
"keywords": ["VendorItemIngredient"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mod_name": "Skyrim.esm",
|
"mod_name": "Skyrim.esm",
|
||||||
@ -36,7 +39,8 @@
|
|||||||
"quantity": 4,
|
"quantity": 4,
|
||||||
"form_type": 41,
|
"form_type": 41,
|
||||||
"is_food": false,
|
"is_food": false,
|
||||||
"price": 4
|
"price": 4,
|
||||||
|
"keywords": ["VendorItemWeapon"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -8,5 +8,6 @@
|
|||||||
"price": 100,
|
"price": 100,
|
||||||
"is_sell": false,
|
"is_sell": false,
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"amount": 100
|
"amount": 100,
|
||||||
|
"keywords": ["VendorItemMisc"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user