diff --git a/postgres/000-schema.sql b/postgres/000-schema.sql index dda128c..cf7b0c1 100644 --- a/postgres/000-schema.sql +++ b/postgres/000-schema.sql @@ -149,8 +149,17 @@ SELECT "oracle"."oracle_id" , "scryfall"."collector_number" , "scryfall"."release_date" , "scryfall"."rarity" + , "sets"."name" AS "set_name" + , "card_prices"."usd" AS "price_usd" + , "card_prices"."usd_foil" AS "price_usd_foil" + , "card_prices"."eur" AS "price_eur" + , "card_prices"."eur_foil" AS "price_eur_foil" + , "card_prices"."tix" AS "price_tix" FROM "oracle" -JOIN "scryfall" USING ("oracle_id"); +JOIN "scryfall" USING ("oracle_id") +JOIN "card_prices" ON ("scryfall"."scryfall_id" = "card_prices"."scryfall_id" + AND "card_prices"."date" = (SELECT VALUE FROM "vars" WHERE "key" = 'last_update')) +JOIN "sets" USING ("set_code"); DROP MATERIALIZED VIEW IF EXISTS "oracle_latest"; CREATE MATERIALIZED VIEW "oracle_latest" AS @@ -167,9 +176,18 @@ SELECT "oracle"."oracle_id" , "scryfall"."collector_number" , "scryfall"."release_date" , "scryfall"."rarity" + , "sets"."name" AS "set_name" + , "card_prices"."usd" AS "price_usd" + , "card_prices"."usd_foil" AS "price_usd_foil" + , "card_prices"."eur" AS "price_eur" + , "card_prices"."eur_foil" AS "price_eur_foil" + , "card_prices"."tix" AS "price_tix" FROM "oracle" JOIN ( SELECT DISTINCT ON ("oracle_id") * FROM "scryfall" ORDER BY "oracle_id", "release_date" DESC -) "scryfall" USING ("oracle_id"); +) "scryfall" USING ("oracle_id") +JOIN "card_prices" ON ("scryfall"."scryfall_id" = "card_prices"."scryfall_id" + AND "card_prices"."date" = (SELECT VALUE FROM "vars" WHERE "key" = 'last_update')) +JOIN "sets" USING ("set_code"); diff --git a/tutor/database.py b/tutor/database.py index c8103d6..a8c4618 100644 --- a/tutor/database.py +++ b/tutor/database.py @@ -16,6 +16,13 @@ import tutor.search logger = logging.getLogger(__name__) +def convert_price(price: typing.Optional[str]) -> typing.Optional[decimal.Decimal]: + if price: + return decimal.Decimal(price) + else: + return None + + async def search( db: psycopg.Cursor, name: typing.Optional[str] = None, @@ -171,10 +178,6 @@ async def advanced_search( else: joins.append("LEFT JOIN copies ON (cards.scryfall_id = copies.scryfall_id)") joins.append("JOIN sets ON (cards.set_code = sets.set_code)") - joins.append( - "JOIN card_prices ON (cards.scryfall_id = card_prices.scryfall_id " - "AND card_prices.date = (select value from vars where key = %(last_update_key)s))" - ) orderings = [ "cards.rarity DESC", "length(cards.color_identity) DESC", @@ -185,15 +188,15 @@ async def advanced_search( ] if sort_by == "price": orderings = [ - 'CAST(COALESCE(CASE WHEN "copies"."isFoil" THEN card_prices.usd_foil ELSE card_prices.usd END, 0) as decimal) DESC', + 'CAST(COALESCE(CASE WHEN "copies"."isFoil" THEN cards.price_usd_foil ELSE cards.price_usd END, 0) as decimal) DESC', *orderings, ] params["last_update_key"] = "last_update" query = " ".join( [ - "SELECT cards.*, card_prices.*, copies.*", - ', CASE WHEN "copies"."isFoil" THEN card_prices.usd_foil', - " ELSE card_prices.usd END AS usd", + "SELECT cards.*, copies.*", + ', CASE WHEN "copies"."isFoil" THEN cards.price_usd_foil', + " ELSE cards.price_usd END AS usd", "FROM cards", " ".join(joins), "WHERE" if constraints else "", @@ -207,12 +210,6 @@ async def advanced_search( await db.execute(query, params) rows = await db.fetchall() - def convert_price(price: typing.Optional[str]) -> typing.Optional[decimal.Decimal]: - if price: - return decimal.Decimal(price) - else: - return None - return [ tutor.models.CardCopy( card=tutor.models.Card( @@ -220,6 +217,7 @@ async def advanced_search( oracle_id=row["oracle_id"], name=row["name"], set_code=row["set_code"], + set_name=row["set_name"], collector_number=row["collector_number"], rarity=tutor.models.Rarity.from_string(row["rarity"]), color_identity=tutor.models.Color.from_string(row["color_identity"]), @@ -231,11 +229,11 @@ async def advanced_search( legalities={}, edhrec_rank=row["edhrec_rank"], oracle_text=row["oracle_text"], - price_usd=convert_price(row["usd"]), - price_usd_foil=convert_price(row["usd_foil"]), - price_eur=convert_price(row["eur"]), - price_eur_foil=convert_price(row["eur_foil"]), - price_tix=convert_price(row["tix"]), + price_usd=convert_price(row["price_usd"]), + price_usd_foil=convert_price(row["price_usd_foil"]), + price_eur=convert_price(row["price_eur"]), + price_eur_foil=convert_price(row["price_eur_foil"]), + price_tix=convert_price(row["price_tix"]), ), foil=row["isFoil"] if row["isFoil"] is not None else False, collection=row["collection"] or "Default", @@ -380,9 +378,15 @@ async def get_decks( 'oracle_text', "oracle_latest"."oracle_text", 'scryfall_id', "oracle_latest"."scryfall_id", 'set_code', "oracle_latest"."set_code", + 'set_name', "oracle_latest"."set_name", 'collector_number', "oracle_latest"."collector_number", 'rarity', "oracle_latest"."rarity", 'release_date', "oracle_latest"."release_date", + 'price_usd', "oracle_latest"."price_usd", + 'price_usd_foil', "oracle_latest"."price_usd_foil", + 'price_eur', "oracle_latest"."price_eur", + 'price_eur_foil', "oracle_latest"."price_eur_foil", + 'price_tix', "oracle_latest"."price_tix", 'quantity', "deck_list"."quantity" ))) AS "cards" FROM "decks" @@ -417,9 +421,15 @@ async def get_decks( oracle_text=card.get("oracle_text"), scryfall_id=card["scryfall_id"], set_code=card["set_code"], + set_name=card["set_name"], collector_number=card["collector_number"], rarity=card["rarity"], release_date=card["release_date"], + price_usd=convert_price(card.get("price_usd")), + price_usd_foil=convert_price(card.get("price_usd_foil")), + price_eur=convert_price(card.get("price_eur")), + price_eur_foil=convert_price(card.get("price_eur_foil")), + price_tix=convert_price(card.get("price_tix")), ), quantity=card["quantity"], ) @@ -450,9 +460,15 @@ async def get_deck( 'oracle_text', "oracle_latest"."oracle_text", 'scryfall_id', "oracle_latest"."scryfall_id", 'set_code', "oracle_latest"."set_code", + 'set_name', "oracle_latest"."set_name", 'collector_number', "oracle_latest"."collector_number", 'rarity', "oracle_latest"."rarity", 'release_date', "oracle_latest"."release_date", + 'price_usd', "oracle_latest"."price_usd", + 'price_usd_foil', "oracle_latest"."price_usd_foil", + 'price_eur', "oracle_latest"."price_eur", + 'price_eur_foil', "oracle_latest"."price_eur_foil", + 'price_tix', "oracle_latest"."price_tix", 'quantity', "deck_list"."quantity" ))) AS "cards" FROM "decks" @@ -486,9 +502,15 @@ async def get_deck( oracle_text=card.get("oracle_text"), scryfall_id=card["scryfall_id"], set_code=card["set_code"], + set_name=card["set_name"], collector_number=card["collector_number"], rarity=card["rarity"], release_date=card["release_date"], + price_usd=convert_price(card.get("price_usd")), + price_usd_foil=convert_price(card.get("price_usd_foil")), + price_eur=convert_price(card.get("price_eur")), + price_eur_foil=convert_price(card.get("price_eur_foil")), + price_tix=convert_price(card.get("price_tix")), ), quantity=card["quantity"], ) @@ -517,16 +539,13 @@ async def collection_stats(db: psycopg.Cursor) -> dict: SELECT COUNT("copies"."id") AS cards , SUM( CASE WHEN "copies"."isFoil" - THEN "card_prices"."usd_foil" - ELSE "card_prices"."usd" + THEN "cards"."price_usd_foil" + ELSE "cards"."price_usd" END ) AS value , COUNT(DISTINCT cards.set_code) AS sets FROM "copies" JOIN "cards" USING ("scryfall_id") - LEFT JOIN "card_prices" USING ("scryfall_id") - WHERE "card_prices"."date" = - (SELECT "value" FROM "vars" where "key" = 'last_update') """ ) return await db.fetchone() diff --git a/tutor/models.py b/tutor/models.py index 4211d7a..9236f27 100644 --- a/tutor/models.py +++ b/tutor/models.py @@ -82,6 +82,7 @@ class Card: oracle_id: uuid.UUID name: str set_code: str + set_name: str collector_number: str rarity: str color_identity: typing.List[Color] diff --git a/tutor/server.py b/tutor/server.py index 8121516..7ad90f2 100644 --- a/tutor/server.py +++ b/tutor/server.py @@ -65,9 +65,9 @@ class JSONEncoder(json.JSONEncoder): } def _card(self, card: tutor.models.Card) -> dict: - def price(amount: typing.Optional[decimal.Decimal]) -> typing.Optional[str]: + def price(amount: typing.Optional[decimal.Decimal]) -> typing.Optional[float]: if amount is not None: - return str(amount) + return float(amount) else: return None @@ -75,6 +75,7 @@ class JSONEncoder(json.JSONEncoder): "scryfall_id": str(card.scryfall_id), "name": card.name, "set_code": card.set_code, + "set_name": card.set_name, "collector_number": card.collector_number, "rarity": str(card.rarity), "color_identity": tutor.models.Color.to_string(card.color_identity), diff --git a/www/elm.json b/www/elm.json index 12009ab..f580794 100644 --- a/www/elm.json +++ b/www/elm.json @@ -18,7 +18,8 @@ "elm/regex": "1.0.0", "elm/url": "1.0.0", "elm-community/maybe-extra": "5.2.0", - "mdgriffith/elm-ui": "1.1.8" + "mdgriffith/elm-ui": "1.1.8", + "myrho/elm-round": "1.0.5" }, "indirect": { "elm/bytes": "1.0.8", diff --git a/www/src/Card.elm b/www/src/Card.elm index 501f072..95a18bd 100644 --- a/www/src/Card.elm +++ b/www/src/Card.elm @@ -5,11 +5,11 @@ import Json.Decode.Pipeline as JDP type alias Prices = - { usd : Maybe String - , usd_foil : Maybe String - , eur : Maybe String - , eur_foil : Maybe String - , tix : Maybe String + { usd : Maybe Float + , usd_foil : Maybe Float + , eur : Maybe Float + , eur_foil : Maybe Float + , tix : Maybe Float } @@ -91,8 +91,8 @@ decodeCopy = decodePrices : Json.Decode.Decoder Prices decodePrices = Json.Decode.succeed Prices - |> JDP.required "usd" (Json.Decode.nullable Json.Decode.string) - |> JDP.required "usd_foil" (Json.Decode.nullable Json.Decode.string) - |> JDP.required "eur" (Json.Decode.nullable Json.Decode.string) - |> JDP.required "eur_foil" (Json.Decode.nullable Json.Decode.string) - |> JDP.required "tix" (Json.Decode.nullable Json.Decode.string) + |> JDP.required "usd" (Json.Decode.nullable Json.Decode.float) + |> JDP.required "usd_foil" (Json.Decode.nullable Json.Decode.float) + |> JDP.required "eur" (Json.Decode.nullable Json.Decode.float) + |> JDP.required "eur_foil" (Json.Decode.nullable Json.Decode.float) + |> JDP.required "tix" (Json.Decode.nullable Json.Decode.float) diff --git a/www/src/Pages/Collection.elm b/www/src/Pages/Collection.elm index 57944ce..7fcb243 100644 --- a/www/src/Pages/Collection.elm +++ b/www/src/Pages/Collection.elm @@ -495,7 +495,13 @@ viewCardBrowser model = [] ) in - UI.cardRow { foil = copy.foil, subtitle = copy.collection } cardAttrs model.symbols copy.card + UI.cardRow + { foil = copy.foil + , subtitle = String.join " " [ copy.collection ] + } + cardAttrs + model.symbols + copy.card in E.column attrs [ E.row diff --git a/www/src/UI.elm b/www/src/UI.elm index 109bfa0..cb8c724 100644 --- a/www/src/UI.elm +++ b/www/src/UI.elm @@ -20,6 +20,7 @@ import Element.Background as Background import Element.Border as Border import Element.Font as Font import Maybe.Extra +import Round import Spinner import Symbol import Task @@ -183,7 +184,7 @@ subtitle string = E.el [ Font.size 16, Font.italic, Font.color colors.subtitle ] <| E.text string -priceBadge : { currency : String, amount : String } -> E.Element msg +priceBadge : { currency : String, amount : Float } -> E.Element msg priceBadge { currency, amount } = E.el [ Border.rounded 5 @@ -196,7 +197,7 @@ priceBadge { currency, amount } = <| E.row [ E.width E.fill ] [ E.el [ E.width <| E.fillPortion 1 ] <| E.text <| String.toUpper currency - , E.el [ E.width <| E.fillPortion 2, Font.alignRight ] <| E.text amount + , E.el [ E.width <| E.fillPortion 2, Font.alignRight ] <| E.text <| Round.round 2 amount ]