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,
|
||||
"owner_id" INTEGER REFERENCES "owners"(id) NOT NULL,
|
||||
"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,
|
||||
"updated_at" timestamp(3) NOT NULL
|
||||
);
|
||||
@ -36,6 +41,14 @@ CREATE TABLE "merchandise_lists" (
|
||||
"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 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" (
|
||||
"id" SERIAL PRIMARY KEY 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 merchandise_lists CASCADE;
|
||||
DROP TABLE transactions CASCADE;
|
||||
DROP TABLE vendors CASCADE;
|
||||
DROP TABLE _sqlx_migrations CASCADE;
|
||||
|
@ -1,13 +1,14 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use http::StatusCode;
|
||||
use http_api_problem::HttpApiProblem;
|
||||
use hyper::body::Bytes;
|
||||
use mime::Mime;
|
||||
use uuid::Uuid;
|
||||
use warp::reply::{with_header, with_status};
|
||||
use warp::{Rejection, Reply};
|
||||
use warp::{reject, Rejection, Reply};
|
||||
|
||||
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::Environment;
|
||||
|
||||
@ -128,9 +129,22 @@ pub async fn create(
|
||||
let saved_transaction = Transaction::create(transaction, &mut tx)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
let quantity_delta = match saved_transaction.is_sell {
|
||||
true => saved_transaction.quantity,
|
||||
false => saved_transaction.quantity * -1,
|
||||
if !Shop::accepts_keywords(
|
||||
&mut tx,
|
||||
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(
|
||||
&mut tx,
|
||||
@ -146,6 +160,9 @@ pub async fn create(
|
||||
)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
Shop::update_gold(&mut tx, saved_transaction.shop_id, shop_gold_delta)
|
||||
.await
|
||||
.map_err(reject_anyhow)?;
|
||||
tx.commit()
|
||||
.await
|
||||
.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_merchandise_lists.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)
|
||||
}
|
||||
|
||||
// Does NOT reverse the transaction side-effects!
|
||||
pub async fn delete(
|
||||
id: i32,
|
||||
api_key: Option<Uuid>,
|
||||
|
@ -14,6 +14,10 @@ pub struct Shop {
|
||||
pub name: String,
|
||||
pub owner_id: i32,
|
||||
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 updated_at: NaiveDateTime,
|
||||
}
|
||||
@ -23,6 +27,10 @@ pub struct PostedShop {
|
||||
pub name: String,
|
||||
pub owner_id: Option<i32>,
|
||||
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 {
|
||||
@ -54,12 +62,19 @@ impl Shop {
|
||||
Ok(sqlx::query_as!(
|
||||
Self,
|
||||
"INSERT INTO shops
|
||||
(name, owner_id, description, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, now(), now())
|
||||
(name, owner_id, description, gold, shop_type, vendor_keywords,
|
||||
vendor_keywords_exclude, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, now(), now())
|
||||
RETURNING *",
|
||||
shop.name,
|
||||
shop.owner_id,
|
||||
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)
|
||||
.await?)
|
||||
@ -134,6 +149,10 @@ impl Shop {
|
||||
name = $2,
|
||||
owner_id = $3,
|
||||
description = $4,
|
||||
gold = $5,
|
||||
shop_type = $6,
|
||||
vendor_keywords = $7,
|
||||
vendor_keywords_exclude = $8,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING *",
|
||||
@ -141,6 +160,10 @@ impl Shop {
|
||||
shop.name,
|
||||
shop.owner_id,
|
||||
shop.description,
|
||||
shop.gold,
|
||||
shop.shop_type,
|
||||
&shop.vendor_keywords.unwrap_or_else(|| vec![]),
|
||||
shop.vendor_keywords_exclude,
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await?)
|
||||
@ -148,4 +171,48 @@ impl Shop {
|
||||
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