Add card USD pricing and a deck maybeboard
Prices come from Scryfall's prices.usd on printings, shown as a hover badge on card tiles. The price is picked independently of whichever printing supplies the display image (the cheapest known price across all printings), since the display printing is often a promo/variant with no listed price. The maybeboard lets a deck track cards under consideration without counting them in deck analysis stats or library assignment totals. Cards move between mainboard and maybeboard via a per-card toggle; maybeboarding the commander clears commander status. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,8 +11,8 @@ use crate::Catalog;
|
||||
|
||||
/// Map a row shaped `(oracle_id, quantity_owned, location, name, mana_value,
|
||||
/// colors, color_identity, type_line, image_uri, back_image_uri, produced_mana,
|
||||
/// produces_colorless)` into its library parts. Shared by [`Catalog::list_library`]
|
||||
/// and the criteria search.
|
||||
/// produces_colorless, price_usd)` into its library parts. Shared by
|
||||
/// [`Catalog::list_library`] and the criteria search.
|
||||
pub(crate) fn row_to_library_parts(
|
||||
r: &rusqlite::Row,
|
||||
) -> rusqlite::Result<(String, i64, String, CardRef)> {
|
||||
@@ -32,6 +32,7 @@ pub(crate) fn row_to_library_parts(
|
||||
back_image_uri: r.get::<_, Option<String>>(9)?.unwrap_or_default(),
|
||||
produced_mana: ColorSet(r.get::<_, i64>(10)? as u8),
|
||||
produces_colorless: r.get::<_, i64>(11)? != 0,
|
||||
price_usd: r.get::<_, Option<f64>>(12)?.unwrap_or_default(),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -74,13 +75,14 @@ impl Catalog {
|
||||
let mut stmt = self.conn.prepare(&format!(
|
||||
"SELECT lc.oracle_id, lc.quantity_owned, lc.location,
|
||||
oc.name, oc.mana_value, oc.colors, oc.color_identity, oc.type_line, {image}, {back},
|
||||
oc.produced_mana, oc.produces_colorless
|
||||
oc.produced_mana, oc.produces_colorless, {price}
|
||||
FROM library_cards lc
|
||||
JOIN {REF_DB}.oracle_cards oc ON oc.oracle_id = lc.oracle_id
|
||||
WHERE lc.quantity_owned > 0
|
||||
ORDER BY oc.name COLLATE NOCASE",
|
||||
image = crate::db::image_subquery("image_uri"),
|
||||
back = crate::db::image_subquery("back_image_uri")
|
||||
back = crate::db::image_subquery("back_image_uri"),
|
||||
price = crate::db::price_subquery()
|
||||
))?;
|
||||
let rows = stmt
|
||||
.query_map([], row_to_library_parts)?
|
||||
@@ -199,9 +201,12 @@ impl Catalog {
|
||||
}
|
||||
|
||||
pub(crate) fn assigned_totals_by_oracle(&self) -> Result<HashMap<String, i64>> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT oracle_id, SUM(quantity) FROM deck_cards GROUP BY oracle_id")?;
|
||||
// Maybeboard cards aren't actually committed to a deck, so they
|
||||
// shouldn't count against a card's owned copies.
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT oracle_id, SUM(quantity) FROM deck_cards
|
||||
WHERE is_maybeboard = 0 GROUP BY oracle_id",
|
||||
)?;
|
||||
let mut map = HashMap::new();
|
||||
let rows = stmt.query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, i64>(1)?)))?;
|
||||
for row in rows {
|
||||
@@ -292,6 +297,54 @@ mod tests {
|
||||
assert_eq!(elves.card.back_image_uri, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn price_usd_is_plumbed_through() {
|
||||
let fx = test_support::make();
|
||||
let cat = &fx.catalog;
|
||||
cat.connection()
|
||||
.execute(
|
||||
"UPDATE ref.printings SET price_usd = 12.34 WHERE oracle_id = 'sol-ring'",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
cat.add_owned("sol-ring", 1).unwrap();
|
||||
cat.add_owned("llanowar-elves", 1).unwrap();
|
||||
|
||||
let lib = cat.list_library().unwrap();
|
||||
let sol = lib.iter().find(|c| c.card.oracle_id == "sol-ring").unwrap();
|
||||
assert_eq!(sol.card.price_usd, 12.34);
|
||||
|
||||
// Fixture cards default to no known price.
|
||||
let elves = lib.iter().find(|c| c.card.oracle_id == "llanowar-elves").unwrap();
|
||||
assert_eq!(elves.card.price_usd, 0.0);
|
||||
}
|
||||
|
||||
/// Regression test: a card whose *displayed* printing (the one supplying
|
||||
/// `image_uri`) has no known price shouldn't show as priceless if a
|
||||
/// different printing does — e.g. a promo/showcase variant with no
|
||||
/// listed USD price, while the regular printing has one.
|
||||
#[test]
|
||||
fn price_uses_cheapest_available_printing_even_if_display_printing_has_none() {
|
||||
let fx = test_support::make();
|
||||
let cat = &fx.catalog;
|
||||
// The fixture's only printing ("print-sol-ring") supplies the image
|
||||
// and has no price. Add a second, imageless printing with a price.
|
||||
cat.connection()
|
||||
.execute(
|
||||
"INSERT INTO ref.printings (scryfall_id, oracle_id, price_usd)
|
||||
VALUES ('print-sol-ring-promo', 'sol-ring', 5.67)",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
cat.add_owned("sol-ring", 1).unwrap();
|
||||
|
||||
let lib = cat.list_library().unwrap();
|
||||
let sol = lib.iter().find(|c| c.card.oracle_id == "sol-ring").unwrap();
|
||||
assert_eq!(sol.card.price_usd, 5.67);
|
||||
// Image still comes from the original (imaged) printing.
|
||||
assert_eq!(sol.card.image_uri, "http://img/sol-ring");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn location_is_stored_and_listed() {
|
||||
let fx = test_support::make();
|
||||
|
||||
Reference in New Issue
Block a user