Attempt to migrate to sqlx 0.4.0-beta.1

There's still a ton of stuff broken. It's honestly too much to deal with right now so I'm abandoning it until it's released officially.
This commit is contained in:
Tyler Hallada 2020-10-28 12:25:36 -04:00
parent e0bba0254c
commit e73d5d5f88
11 changed files with 331 additions and 229 deletions

210
Cargo.lock generated
View File

@ -15,6 +15,12 @@ dependencies = [
"const-random",
]
[[package]]
name = "ahash"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
[[package]]
name = "aho-corasick"
version = "0.7.13"
@ -74,39 +80,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-native-tls"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33"
dependencies = [
"native-tls",
"thiserror",
"tokio",
"url",
]
[[package]]
name = "async-stream"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5"
dependencies = [
"async-stream-impl",
"futures-core",
]
[[package]]
name = "async-stream-impl"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-trait"
version = "0.1.36"
@ -118,6 +91,15 @@ dependencies = [
"syn",
]
[[package]]
name = "atoi"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c897df197d57c25b37df9d8fa2f93ddbfeee9ebd2264350ac79c8ec4b795885"
dependencies = [
"num-traits",
]
[[package]]
name = "atty"
version = "0.2.14"
@ -392,6 +374,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
dependencies = [
"crossbeam-utils",
"maybe-uninit",
]
[[package]]
name = "crossbeam-queue"
version = "0.2.3"
@ -414,16 +406,6 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "crypto-mac"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
dependencies = [
"generic-array 0.12.3",
"subtle 1.0.0",
]
[[package]]
name = "crypto-mac"
version = "0.8.0"
@ -431,7 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
dependencies = [
"generic-array 0.14.3",
"subtle 2.2.3",
"subtle",
]
[[package]]
@ -464,6 +446,12 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "fake-simd"
version = "0.1.2"
@ -681,7 +669,7 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6073d0ca812575946eb5f35ff68dbe519907b25c42530389ff946dc84c6ead"
dependencies = [
"ahash",
"ahash 0.2.18",
"autocfg 0.1.7",
]
@ -691,6 +679,7 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34f595585f103464d8d2f6e9864682d74c1601fed5e07d62b1c9058dba8246fb"
dependencies = [
"ahash 0.3.8",
"autocfg 1.0.0",
]
@ -743,23 +732,13 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]]
name = "hmac"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
dependencies = [
"crypto-mac 0.7.0",
"digest 0.8.1",
]
[[package]]
name = "hmac"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
dependencies = [
"crypto-mac 0.8.0",
"crypto-mac",
"digest 0.9.0",
]
@ -873,9 +852,9 @@ dependencies = [
[[package]]
name = "ipnetwork"
version = "0.16.0"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8eca9f51da27bc908ef3dd85c21e1bbba794edaf94d7841e37356275b82d31e"
checksum = "02c3eaab3ac0ede60ffa41add21970a7df7d91772c03383aac6c2c3d53cc716b"
dependencies = [
"serde",
]
@ -908,6 +887,12 @@ version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
[[package]]
name = "linked-hash-map"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]]
name = "listenfd"
version = "0.3.3"
@ -946,6 +931,15 @@ dependencies = [
"hashbrown 0.6.3",
]
[[package]]
name = "lru-cache"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "maplit"
version = "1.0.2"
@ -975,13 +969,13 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "md-5"
version = "0.8.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8"
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"opaque-debug 0.2.3",
"block-buffer 0.9.0",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
@ -1316,11 +1310,11 @@ dependencies = [
"byteorder",
"bytes",
"fallible-iterator",
"hmac 0.8.1",
"hmac",
"md5",
"memchr",
"rand 0.7.3",
"sha2 0.9.1",
"sha2",
"stringprep",
]
@ -1738,6 +1732,7 @@ version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
@ -1780,18 +1775,6 @@ dependencies = [
"opaque-debug 0.3.0",
]
[[package]]
name = "sha2"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
dependencies = [
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha2"
version = "0.9.1"
@ -1867,9 +1850,9 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.3.5"
version = "0.4.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8974cacd80085fbe49e778708d660dec6fb351604dc34c3905b26efb2803b038"
checksum = "8cb7b012f28c74075d6b11172ba1874f4376a255509462eaf2ef25068b31729f"
dependencies = [
"sqlx-core",
"sqlx-macros",
@ -1877,59 +1860,82 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.3.5"
version = "0.4.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ac5a436f941c42eac509471a730df5c3c58e1450e68cd39afedbd948206273"
checksum = "fe2857d90b39b8528948109abc1b8d4c1905d184c87deaf06055f0b21050f13e"
dependencies = [
"async-native-tls",
"async-stream",
"atoi",
"base64",
"bitflags",
"byteorder",
"bytes",
"chrono",
"crossbeam-channel",
"crossbeam-queue",
"crossbeam-utils",
"either",
"futures-channel",
"futures-core",
"futures-util",
"hashbrown 0.8.1",
"hex",
"hmac 0.7.1",
"hmac",
"ipnetwork",
"itoa",
"libc",
"log",
"lru-cache",
"md-5",
"memchr",
"once_cell",
"parking_lot",
"percent-encoding",
"rand 0.7.3",
"serde",
"serde_json",
"sha-1 0.8.2",
"sha2 0.8.2",
"sha-1 0.9.1",
"sha2",
"smallvec",
"sqlformat",
"tokio",
"sqlx-rt",
"stringprep",
"thiserror",
"url",
"uuid 0.8.1",
"whoami",
]
[[package]]
name = "sqlx-macros"
version = "0.3.5"
version = "0.4.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de2ae78b783af5922d811b14665a5a3755e531c3087bb805cf24cf71f15e6780"
checksum = "6d1bc862e5f4484965156c224f7e4c139f2d3c65b6aa0e3ae8c63461831b8da9"
dependencies = [
"dotenv",
"either",
"futures",
"heck",
"lazy_static",
"proc-macro2",
"quote",
"serde_json",
"sqlx-core",
"sqlx-rt",
"syn",
"tokio",
"url",
]
[[package]]
name = "sqlx-rt"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23f9104f6116b568358f315e9839ae66c4ebbc0e974db5580105f0acfeb4863f"
dependencies = [
"native-tls",
"once_cell",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "stringprep"
version = "0.1.2"
@ -1946,12 +1952,6 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
[[package]]
name = "subtle"
version = "2.2.3"
@ -2092,6 +2092,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd608593a919a8e05a7d1fc6df885e40f6a88d3a70a3a7eff23ff27964eda069"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-postgres"
version = "0.5.5"
@ -2440,6 +2450,12 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "whoami"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7884773ab69074615cb8f8425d0e53f11710786158704fca70f53e71b0e05504"
[[package]]
name = "winapi"
version = "0.2.8"

View File

@ -14,7 +14,7 @@ http-api-problem = { version = "0.17", features = ["with-warp"] }
hyper = "0.13"
listenfd = "0.3"
tokio = { version = "0.2", features = ["macros", "rt-threaded", "sync"] }
sqlx = { version = "0.3", default-features = false, features = [ "runtime-tokio", "macros", "postgres", "chrono", "uuid", "ipnetwork", "json" ] }
sqlx = { version = "0.4.0-beta.1", default-features = false, features = [ "runtime-tokio", "macros", "postgres", "chrono", "uuid", "ipnetwork", "json" ] }
warp = { version = "0.2", features = ["compression"] }
refinery = { version = "0.3.0", features = [ "tokio-postgres", "tokio" ] }
barrel = { version = "0.6.5", features = [ "pg" ] }
@ -22,7 +22,7 @@ clap = "3.0.0-beta.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "0.8", features = ["serde", "v4"] }
ipnetwork = "0.16"
ipnetwork = "0.17"
url = "2.1"
async-trait = "0.1"
tracing = "0.1"

View File

@ -0,0 +1,35 @@
CREATE TABLE IF NOT EXISTS "owners" (
"id" BIGSERIAL PRIMARY KEY NOT NULL,
"name" VARCHAR(255) NOT NULL,
"api_key" UUID NOT NULL UNIQUE,
"ip_address" inet,
"mod_version" INTEGER NOT NULL,
"created_at" timestamp(3) NOT NULL,
"updated_at" timestamp(3) NOT NULL
);
CREATE UNIQUE INDEX "owners_unique_name_and_api_key" ON "owners" ("name", "api_key");
CREATE TABLE "shops" (
"id" BIGSERIAL PRIMARY KEY NOT NULL,
"name" VARCHAR(255) NOT NULL,
"owner_id" INTEGER REFERENCES "owners"(id) NOT NULL,
"description" TEXT,
"created_at" timestamp(3) NOT NULL,
"updated_at" timestamp(3) NOT NULL
);
CREATE UNIQUE INDEX "shops_unique_name_and_owner_id" ON "shops" ("name", "owner_id");
CREATE TABLE "interior_ref_lists" (
"id" BIGSERIAL PRIMARY KEY NOT NULL,
"shop_id" INTEGER REFERENCES "shops"(id) NOT NULL UNIQUE,
"owner_id" INTEGER REFERENCES "owners"(id) NOT NULL,
"ref_list" jsonb NOT NULL,
"created_at" timestamp(3) NOT NULL,
"updated_at" timestamp(3) NOT NULL
);
CREATE TABLE "merchandise_lists" (
"id" BIGSERIAL PRIMARY KEY NOT NULL,
"shop_id" INTEGER REFERENCES "shops"(id) NOT NULL UNIQUE,
"owner_id" INTEGER REFERENCES "owners"(id) NOT NULL,
"form_list" jsonb NOT NULL,
"created_at" timestamp(3) NOT NULL,
"updated_at" timestamp(3) NOT NULL
);

View File

@ -22,10 +22,10 @@ mod models;
mod problem;
use caches::Caches;
use models::interior_ref_list::InteriorRefList;
use models::merchandise_list::{MerchandiseList, MerchandiseParams};
use models::owner::Owner;
use models::shop::Shop;
use models::interior_ref_list::PostedInteriorRefList;
use models::merchandise_list::{MerchandiseParams, PostedMerchandiseList};
use models::owner::PostedOwner;
use models::shop::PostedShop;
use models::ListParams;
#[derive(Clap)]
@ -45,10 +45,7 @@ pub struct Environment {
impl Environment {
async fn new(api_url: Url) -> Result<Environment> {
Ok(Environment {
db: PgPool::builder()
.max_size(5)
.build(&env::var("DATABASE_URL")?)
.await?,
db: PgPool::connect(&env::var("DATABASE_URL")?).await?,
caches: Caches::initialize(),
api_url,
})
@ -108,7 +105,7 @@ async fn main() -> Result<()> {
let create_owner_handler = warp::path("owners").and(
warp::path::end()
.and(warp::post())
.and(json_body::<Owner>())
.and(json_body::<PostedOwner>())
.and(warp::addr::remote())
.and(warp::header::optional("api-key"))
.and(warp::header::optional("x-real-ip"))
@ -127,7 +124,7 @@ async fn main() -> Result<()> {
warp::path::param()
.and(warp::path::end())
.and(warp::patch())
.and(json_body::<Owner>())
.and(json_body::<PostedOwner>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::update_owner),
@ -149,7 +146,7 @@ async fn main() -> Result<()> {
let create_shop_handler = warp::path("shops").and(
warp::path::end()
.and(warp::post())
.and(json_body::<Shop>())
.and(json_body::<PostedShop>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::create_shop),
@ -166,7 +163,7 @@ async fn main() -> Result<()> {
warp::path::param()
.and(warp::path::end())
.and(warp::patch())
.and(json_body::<Shop>())
.and(json_body::<PostedShop>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::update_shop),
@ -188,7 +185,7 @@ async fn main() -> Result<()> {
let create_interior_ref_list_handler = warp::path("interior_ref_lists").and(
warp::path::end()
.and(warp::post())
.and(json_body::<InteriorRefList>())
.and(json_body::<PostedInteriorRefList>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::create_interior_ref_list),
@ -205,7 +202,7 @@ async fn main() -> Result<()> {
warp::path::param()
.and(warp::path::end())
.and(warp::patch())
.and(json_body::<InteriorRefList>())
.and(json_body::<PostedInteriorRefList>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::update_interior_ref_list),
@ -215,7 +212,7 @@ async fn main() -> Result<()> {
.and(warp::path("interior_ref_list"))
.and(warp::path::end())
.and(warp::patch())
.and(json_body::<InteriorRefList>())
.and(json_body::<PostedInteriorRefList>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::update_interior_ref_list_by_shop_id),
@ -245,7 +242,7 @@ async fn main() -> Result<()> {
let create_merchandise_list_handler = warp::path("merchandise_lists").and(
warp::path::end()
.and(warp::post())
.and(json_body::<MerchandiseList>())
.and(json_body::<PostedMerchandiseList>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::create_merchandise_list),
@ -262,7 +259,7 @@ async fn main() -> Result<()> {
warp::path::param()
.and(warp::path::end())
.and(warp::patch())
.and(json_body::<MerchandiseList>())
.and(json_body::<PostedMerchandiseList>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::update_merchandise_list),
@ -272,7 +269,7 @@ async fn main() -> Result<()> {
.and(warp::path("merchandise_list"))
.and(warp::path::end())
.and(warp::patch())
.and(json_body::<MerchandiseList>())
.and(json_body::<PostedMerchandiseList>())
.and(warp::header::optional("api-key"))
.and(with_env(env.clone()))
.and_then(handlers::update_merchandise_list_by_shop_id),

View File

@ -7,7 +7,7 @@ use sqlx::types::Json;
use tracing::instrument;
use super::ListParams;
use super::{Model, UpdateableModel};
use super::{Model, PostedModel, UpdateableModel};
use crate::problem::forbidden_permission;
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
@ -32,21 +32,30 @@ pub struct InteriorRef {
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct InteriorRefList {
pub id: Option<i32>,
pub id: i32,
pub shop_id: i32,
pub owner_id: i32,
pub ref_list: Json<Vec<InteriorRef>>,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PostedInteriorRefList {
pub shop_id: i32,
pub owner_id: Option<i32>,
pub ref_list: Json<Vec<InteriorRef>>,
pub created_at: Option<NaiveDateTime>,
pub updated_at: Option<NaiveDateTime>,
}
impl PostedModel for PostedInteriorRefList {}
#[async_trait]
impl Model for InteriorRefList {
fn resource_name() -> &'static str {
"interior_ref_list"
}
fn pk(&self) -> Option<i32> {
fn pk(&self) -> i32 {
self.id
}
@ -59,8 +68,8 @@ impl Model for InteriorRefList {
.map_err(Error::new)
}
#[instrument(level = "debug", skip(self, db))]
async fn create(self, db: &PgPool) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
async fn create(posted: PostedInteriorRefList, db: &PgPool) -> Result<Self> {
// TODO:
// * Decide if I'll need to make the same changes to merchandise and transactions
// - answer depends on how many rows of each I expect to insert in one go
@ -71,9 +80,9 @@ impl Model for InteriorRefList {
(shop_id, owner_id, ref_list, created_at, updated_at)
VALUES ($1, $2, $3, now(), now())
RETURNING *",
self.shop_id,
self.owner_id,
self.ref_list,
posted.shop_id,
posted.owner_id,
posted.ref_list,
)
.fetch_one(db)
.await?)
@ -129,8 +138,13 @@ impl Model for InteriorRefList {
#[async_trait]
impl UpdateableModel for InteriorRefList {
#[instrument(level = "debug", skip(self, db))]
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
async fn update(
posted: PostedInteriorRefList,
db: &PgPool,
owner_id: i32,
id: i32,
) -> Result<Self> {
let interior_ref_list =
sqlx::query!("SELECT owner_id FROM interior_ref_lists WHERE id = $1", id)
.fetch_one(db)
@ -144,7 +158,7 @@ impl UpdateableModel for InteriorRefList {
WHERE id = $1
RETURNING *",
id,
self.ref_list,
posted.ref_list,
)
.fetch_one(db)
.await?)
@ -168,8 +182,13 @@ impl InteriorRefList {
.map_err(Error::new)
}
#[instrument(level = "debug", skip(self, db))]
pub async fn update_by_shop_id(self, db: &PgPool, owner_id: i32, shop_id: i32) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
pub async fn update_by_shop_id(
posted: PostedInteriorRefList,
db: &PgPool,
owner_id: i32,
shop_id: i32,
) -> Result<Self> {
let interior_ref_list = sqlx::query!(
"SELECT owner_id FROM interior_ref_lists WHERE shop_id = $1",
shop_id
@ -185,7 +204,7 @@ impl InteriorRefList {
WHERE shop_id = $1
RETURNING *",
shop_id,
self.ref_list,
posted.ref_list,
)
.fetch_one(db)
.await?)

View File

@ -8,7 +8,7 @@ use sqlx::types::Json;
use tracing::instrument;
use super::ListParams;
use super::{Model, UpdateableModel};
use super::{Model, PostedModel, UpdateableModel};
use crate::problem::forbidden_permission;
// sqlx queries for this model need to be `query_as_unchecked!` because `query_as!` does not
@ -29,14 +29,23 @@ pub struct Merchandise {
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MerchandiseList {
pub id: Option<i32>,
pub id: i32,
pub shop_id: i32,
pub owner_id: i32,
pub form_list: Json<Vec<Merchandise>>,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PostedMerchandiseList {
pub shop_id: i32,
pub owner_id: Option<i32>,
pub form_list: Json<Vec<Merchandise>>,
pub created_at: Option<NaiveDateTime>,
pub updated_at: Option<NaiveDateTime>,
}
impl PostedModel for PostedMerchandiseList {}
#[derive(Debug, Eq, PartialEq, Hash, Clone, Deserialize)]
pub struct MerchandiseParams {
pub mod_name: String,
@ -50,7 +59,7 @@ impl Model for MerchandiseList {
"merchandise_list"
}
fn pk(&self) -> Option<i32> {
fn pk(&self) -> i32 {
self.id
}
@ -63,17 +72,17 @@ impl Model for MerchandiseList {
.map_err(Error::new)
}
#[instrument(level = "debug", skip(self, db))]
async fn create(self, db: &PgPool) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
async fn create(posted: PostedMerchandiseList, db: &PgPool) -> Result<Self> {
Ok(sqlx::query_as_unchecked!(
Self,
"INSERT INTO merchandise_lists
(shop_id, owner_id, form_list, created_at, updated_at)
VALUES ($1, $2, $3, now(), now())
RETURNING *",
self.shop_id,
self.owner_id,
self.form_list,
posted.shop_id,
posted.owner_id,
posted.form_list,
)
.fetch_one(db)
.await?)
@ -129,8 +138,13 @@ impl Model for MerchandiseList {
#[async_trait]
impl UpdateableModel for MerchandiseList {
#[instrument(level = "debug", skip(self, db))]
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
async fn update(
posted: PostedMerchandiseList,
db: &PgPool,
owner_id: i32,
id: i32,
) -> Result<Self> {
let merchandise_list =
sqlx::query!("SELECT owner_id FROM merchandise_lists WHERE id = $1", id)
.fetch_one(db)
@ -144,7 +158,7 @@ impl UpdateableModel for MerchandiseList {
WHERE id = $1
RETURNING *",
id,
self.form_list,
posted.form_list,
)
.fetch_one(db)
.await?)
@ -168,8 +182,13 @@ impl MerchandiseList {
.map_err(Error::new)
}
#[instrument(level = "debug", skip(db))]
pub async fn update_by_shop_id(self, db: &PgPool, owner_id: i32, shop_id: i32) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
pub async fn update_by_shop_id(
posted: PostedMerchandiseList,
db: &PgPool,
owner_id: i32,
shop_id: i32,
) -> Result<Self> {
let merchandise_list = sqlx::query!(
"SELECT owner_id FROM merchandise_lists WHERE shop_id = $1",
shop_id
@ -185,7 +204,7 @@ impl MerchandiseList {
WHERE shop_id = $1
RETURNING *",
shop_id,
self.form_list,
posted.form_list,
)
.fetch_one(db)
.await?)

View File

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

View File

@ -5,25 +5,20 @@ use url::Url;
use super::ListParams;
pub trait PostedModel {}
#[async_trait]
pub trait Model
where
Self: std::marker::Sized,
{
fn resource_name() -> &'static str;
fn pk(&self) -> Option<i32>;
fn pk(&self) -> i32;
fn url(&self, api_url: &Url) -> Result<Url> {
if let Some(pk) = self.pk() {
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), pk))?)
} else {
Err(anyhow!(
"Cannot get URL for {} with no primary key",
Self::resource_name()
))
}
Ok(api_url.join(&format!("{}s/{}", Self::resource_name(), self.pk()))?)
}
async fn get(db: &PgPool, id: i32) -> Result<Self>;
async fn create(self, db: &PgPool) -> Result<Self>;
async fn create(posted: dyn PostedModel, db: &PgPool) -> Result<Self>;
async fn delete(db: &PgPool, owner_id: i32, id: i32) -> Result<u64>;
async fn list(db: &PgPool, list_params: &ListParams) -> Result<Vec<Self>>;
}
@ -33,5 +28,5 @@ pub trait UpdateableModel
where
Self: std::marker::Sized,
{
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self>;
}
async fn update(posted: dyn PostedModel, db: &PgPool, owner_id: i32, id: i32) -> Result<Self>;
}

View File

@ -8,29 +8,43 @@ use tracing::instrument;
use uuid::Uuid;
use super::ListParams;
use super::{Model, UpdateableModel};
use super::{Model, PostedModel, UpdateableModel};
use crate::problem::forbidden_permission;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Owner {
pub id: Option<i32>,
pub id: i32,
pub name: String,
#[serde(skip_serializing)]
pub api_key: Uuid,
#[serde(skip_serializing)]
pub ip_address: Option<IpNetwork>,
pub mod_version: i32,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PostedOwner {
pub name: String,
#[serde(skip_serializing)]
pub api_key: Option<Uuid>,
#[serde(skip_serializing)]
pub ip_address: Option<IpNetwork>,
pub mod_version: i32,
pub created_at: Option<NaiveDateTime>,
pub updated_at: Option<NaiveDateTime>,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
}
impl PostedModel for PostedOwner {}
#[async_trait]
impl Model for Owner {
fn resource_name() -> &'static str {
"owner"
}
fn pk(&self) -> Option<i32> {
fn pk(&self) -> i32 {
self.id
}
@ -42,18 +56,18 @@ impl Model for Owner {
.map_err(Error::new)
}
#[instrument(level = "debug", skip(self, db))]
async fn create(self, db: &PgPool) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
async fn create(posted: PostedOwner, db: &PgPool) -> Result<Self> {
Ok(sqlx::query_as!(
Self,
"INSERT INTO owners
(name, api_key, ip_address, mod_version, created_at, updated_at)
VALUES ($1, $2, $3, $4, now(), now())
RETURNING *",
self.name,
self.api_key,
self.ip_address,
self.mod_version,
posted.name,
posted.api_key,
posted.ip_address,
posted.mod_version,
)
.fetch_one(db)
.await?)
@ -106,8 +120,8 @@ impl Model for Owner {
#[async_trait]
impl UpdateableModel for Owner {
#[instrument(level = "debug", skip(self, db))]
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
async fn update(posted: PostedOwner, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
let owner = sqlx::query!("SELECT id FROM owners WHERE id = $1", id)
.fetch_one(db)
.await?;
@ -121,8 +135,8 @@ impl UpdateableModel for Owner {
WHERE id = $1
RETURNING *",
id,
self.name,
self.mod_version,
posted.name,
posted.mod_version,
)
.fetch_one(db)
.await?)
@ -130,4 +144,4 @@ impl UpdateableModel for Owner {
return Err(forbidden_permission());
}
}
}
}

View File

@ -6,31 +6,40 @@ use sqlx::postgres::PgPool;
use tracing::instrument;
use super::ListParams;
use super::{Model, UpdateableModel};
use super::{Model, PostedModel, UpdateableModel};
use crate::problem::forbidden_permission;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Shop {
pub id: Option<i32>,
pub id: i32,
pub name: String,
pub owner_id: Option<i32>,
pub description: String,
pub owner_id: i32,
pub description: Option<String>,
// removing these until I figure out the plan for buying and selling
// pub is_not_sell_buy: bool,
// pub sell_buy_list_id: i32,
// pub vendor_id: i32,
// pub vendor_gold: i32,
pub created_at: Option<NaiveDateTime>,
pub updated_at: Option<NaiveDateTime>,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PostedShop {
pub name: String,
pub owner_id: Option<i32>,
pub description: Option<String>,
}
impl PostedModel for PostedShop {}
#[async_trait]
impl Model for Shop {
fn resource_name() -> &'static str {
"shop"
}
fn pk(&self) -> Option<i32> {
fn pk(&self) -> i32 {
self.id
}
@ -42,17 +51,17 @@ impl Model for Shop {
.map_err(Error::new)
}
#[instrument(level = "debug", skip(self, db))]
async fn create(self, db: &PgPool) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
async fn create(posted: PostedShop, db: &PgPool) -> Result<Self> {
Ok(sqlx::query_as!(
Self,
"INSERT INTO shops
(name, owner_id, description, created_at, updated_at)
VALUES ($1, $2, $3, now(), now())
RETURNING *",
self.name,
self.owner_id,
self.description,
posted.name,
posted.owner_id,
posted.description,
)
.fetch_one(db)
.await?)
@ -105,8 +114,8 @@ impl Model for Shop {
#[async_trait]
impl UpdateableModel for Shop {
#[instrument(level = "debug", skip(self, db))]
async fn update(self, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
#[instrument(level = "debug", skip(posted, db))]
async fn update(posted: PostedShop, db: &PgPool, owner_id: i32, id: i32) -> Result<Self> {
let shop = sqlx::query!("SELECT owner_id FROM shops WHERE id = $1", id)
.fetch_one(db)
.await?;
@ -121,9 +130,9 @@ impl UpdateableModel for Shop {
WHERE id = $1
RETURNING *",
id,
self.name,
self.owner_id,
self.description,
posted.name,
posted.owner_id,
posted.description,
)
.fetch_one(db)
.await?)

View File

@ -37,43 +37,41 @@ pub fn from_anyhow(error: anyhow::Error) -> HttpApiProblem {
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>();
error!(
"Database error: {}. {}",
db_error.message(),
db_error.details().unwrap_or("")
pg_error.message(),
pg_error.detail().unwrap_or("")
);
dbg!(&db_error);
if let Some(code) = db_error.code() {
dbg!(&code);
if let Some(constraint) = db_error.constraint_name() {
dbg!(&constraint);
if code == "23503" && constraint == "shops_owner_id_fkey" {
// foreign_key_violation
return HttpApiProblem::with_title_and_type_from_status(
StatusCode::BAD_REQUEST,
)
.set_detail("Owner does not exist");
} else if code == "23505" && constraint == "owners_api_key_key" {
// unique_violation
return HttpApiProblem::with_title_and_type_from_status(
StatusCode::BAD_REQUEST,
)
.set_detail("Owner with Api-Key already exists");
} else if code == "23505" && constraint == "owners_unique_name_and_api_key"
{
// unique_violation
return HttpApiProblem::with_title_and_type_from_status(
StatusCode::BAD_REQUEST,
)
.set_detail("Duplicate owner with same name and Api-Key exists");
} else if code == "23505" && constraint == "shops_unique_name_and_owner_id"
{
// unique_violation
return HttpApiProblem::with_title_and_type_from_status(
StatusCode::BAD_REQUEST,
)
.set_detail("Owner already has a shop with that name");
}
dbg!(&pg_error);
let code = pg_error.code();
dbg!(&code);
if let Some(constraint) = pg_error.constraint() {
dbg!(&constraint);
if code == "23503" && constraint == "shops_owner_id_fkey" {
// foreign_key_violation
return HttpApiProblem::with_title_and_type_from_status(
StatusCode::BAD_REQUEST,
)
.set_detail("Owner does not exist");
} else if code == "23505" && constraint == "owners_api_key_key" {
// unique_violation
return HttpApiProblem::with_title_and_type_from_status(
StatusCode::BAD_REQUEST,
)
.set_detail("Owner with Api-Key already exists");
} else if code == "23505" && constraint == "owners_unique_name_and_api_key" {
// unique_violation
return HttpApiProblem::with_title_and_type_from_status(
StatusCode::BAD_REQUEST,
)
.set_detail("Duplicate owner with same name and Api-Key exists");
} else if code == "23505" && constraint == "shops_unique_name_and_owner_id" {
// unique_violation
return HttpApiProblem::with_title_and_type_from_status(
StatusCode::BAD_REQUEST,
)
.set_detail("Owner already has a shop with that name");
}
}
}