Check keywords on transaction, update shop gold
Shops now have a "type" and keywords of items they only deal with. Also add vendors table in preparation for implementing the endpoint.
This commit is contained in:
parent
a64caa4081
commit
6b07ec7d07
@ -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
|
||||||
);
|
);
|
||||||
@ -36,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,
|
||||||
|
@ -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 vendors CASCADE;
|
||||||
DROP TABLE _sqlx_migrations CASCADE;
|
DROP TABLE _sqlx_migrations CASCADE;
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
use http_api_problem::HttpApiProblem;
|
||||||
use hyper::body::Bytes;
|
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::{CachedResponse, CACHES};
|
use crate::caches::{CachedResponse, CACHES};
|
||||||
use crate::models::{ListParams, MerchandiseList, PostedTransaction, Transaction};
|
use crate::models::{ListParams, MerchandiseList, PostedTransaction, Shop, Transaction};
|
||||||
use crate::problem::reject_anyhow;
|
use crate::problem::reject_anyhow;
|
||||||
use crate::Environment;
|
use crate::Environment;
|
||||||
|
|
||||||
@ -128,9 +129,22 @@ pub async fn create(
|
|||||||
let saved_transaction = Transaction::create(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 saved_transaction.is_sell {
|
if !Shop::accepts_keywords(
|
||||||
true => saved_transaction.quantity,
|
&mut tx,
|
||||||
false => saved_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_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,
|
||||||
@ -146,6 +160,9 @@ pub async fn create(
|
|||||||
)
|
)
|
||||||
.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)))?;
|
||||||
@ -184,10 +201,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>,
|
||||||
|
@ -14,6 +14,10 @@ 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,
|
||||||
}
|
}
|
||||||
@ -23,6 +27,10 @@ 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 {
|
||||||
@ -54,12 +62,19 @@ impl Shop {
|
|||||||
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?)
|
||||||
@ -134,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 *",
|
||||||
@ -141,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?)
|
||||||
@ -148,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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user