Support collection and sorting query parameters

This commit is contained in:
Correl Roush 2022-03-26 17:11:24 -04:00
parent d032f9b294
commit 070487f924
2 changed files with 60 additions and 128 deletions

View file

@ -2,6 +2,7 @@
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeOperators #-}
module Lib
@ -15,6 +16,7 @@ import qualified Control.Lens as Database.SQLite
import Control.Monad.IO.Class
import Data.Aeson
import Data.Aeson.TH
import qualified Data.List as L
import Data.Maybe
import Data.OpenApi
( HasDescription (description),
@ -35,6 +37,7 @@ import Database.SQLite.Simple
import Database.SQLite.Simple.FromField
import Database.SQLite.Simple.Internal
import Database.SQLite.Simple.Ok
import Database.SQLite.Simple.QQ
import GHC.Generics
import Network.Wai
import Network.Wai.Handler.Warp
@ -251,6 +254,8 @@ type SearchCards =
\: `:` (matches)"
:> "search"
:> QueryParam' '[Description "Query string"] "q" T.Text
:> QueryParam' '[Description "Sorting method"] "sort_by" T.Text
:> QueryParam' '[Description "Search across collection or all cards"] "in_collection" T.Text
:> Get '[JSON] [Card]
type TutorAPI =
@ -307,86 +312,66 @@ status =
{ apiVersion = packageVersion
}
cards :: Maybe T.Text -> Handler [Card]
cards q =
return
[ Card
{ scryfall_id = fromJust $ fromText "f6cd7465-9dd0-473c-ac5e-dd9e2f22f5f6",
name = "Esika, God of the Tree // The Prismatic Bridge",
set_code = "KHM",
collector_number = "168",
rarity = "mythic",
color_identity = "WUBGR",
oracle_text = Nothing,
prices =
Prices
{ usd = Just "9.07",
usd_foil = Just "10.77",
eur = Just "7.79",
eur_foil = Just "12.27",
tix = Just "1.08"
}
},
Card
{ scryfall_id = fromJust $ fromText "d761ff73-0717-4ee4-996b-f5547bcf9b2f",
name = "Go-Shintai of Life's Origin",
set_code = "NEC",
collector_number = "66",
rarity = "mythic",
color_identity = "WUBGR",
oracle_text = Just "{W}{U}{B}{R}{G}, {T}: Return target enchantment card from your graveyard to the battlefield.\nWhenever Go-Shintai of Life's Origin or another nontoken Shrine enters the battlefield under your control, create a 1/1 colorless Shrine enchantment creature token.",
prices =
Prices
{ usd = Just "23.28",
usd_foil = Nothing,
eur = Just "23.05",
eur_foil = Nothing,
tix = Nothing
}
},
Card
{ scryfall_id = fromJust $ fromText "e2539ff7-2b7d-47e3-bd77-3138a6c42d2b",
name = "Godsire",
set_code = "ALA",
collector_number = "170",
rarity = "mythic",
color_identity = "WGR",
oracle_text = Just "Vigilance\n{T}: Create an 8/8 Beast creature token that's red, green, and white.",
prices =
Prices
{ usd = Just "7.22",
usd_foil = Just "16.56",
eur = Just "4.00",
eur_foil = Just "13.95",
tix = Just "0.02"
}
}
]
searchCards :: FilePath -> Maybe T.Text -> Handler [Card]
searchCards dbFile q =
searchCards :: FilePath -> Maybe T.Text -> Maybe T.Text -> Maybe T.Text -> Handler [Card]
searchCards dbFile q sortBy inCollection =
liftIO results
where
search = Search.parse $ fromMaybe "" q
defaultOrderings :: [Query]
defaultOrderings =
[ "rarities.rarity_ord DESC",
"length(cards.color_identity) DESC",
[sql|CASE WHEN length(cards.color_identity) > 0 THEN '0'
ELSE cards.color_identity END ASC|],
"cards.name ASC"
]
orderSql :: Query
orderSql =
"ORDER BY " <> case sortBy of
Just "price" -> sqlJoin "," $ "CAST(COALESCE(card_prices.usd, card_prices.usd_foil) as decimal) DESC" : defaultOrderings
_ -> sqlJoin "," defaultOrderings
joins :: [Query]
joins =
[ [sql|JOIN card_prices ON (cards.scryfall_id = card_prices.scryfall_id)
AND card_prices.date = (SELECT value FROM vars WHERE key = :last_update_key)|],
[sql|JOIN rarities ON (cards.rarity = rarities.rarity)|]
]
joinSql :: Query
joinSql = case inCollection of
Just "no" -> sqlJoin "" joins
_ -> sqlJoin "JOIN copies ON (cards.scryfall_id = copies.scryfall_id)" $ joins
results :: IO [Card]
results = withConnection dbFile $ \conn ->
queryNamed
conn
"SELECT cards.scryfall_id, \
\ cards.name, \
\ cards.set_code, \
\ cards.collector_number, \
\ cards.rarity, \
\ cards.color_identity, \
\ cards.oracle_text, \
\ card_prices.usd, \
\ card_prices.usd_foil, \
\ card_prices.eur, \
\ card_prices.eur_foil, \
\ card_prices.tix \
\FROM cards \
\JOIN card_prices ON (cards.scryfall_id = card_prices.scryfall_id) \
\ AND card_prices.date = (SELECT value FROM vars WHERE key = :last_update_key) \
\LIMIT 5"
( sqlJoin
" "
[ [sql|
SELECT cards.scryfall_id,
cards.name,
cards.set_code,
cards.collector_number,
cards.rarity,
cards.color_identity,
cards.oracle_text,
card_prices.usd,
card_prices.usd_foil,
card_prices.eur,
card_prices.eur_foil,
card_prices.tix
FROM cards|],
joinSql,
orderSql,
"LIMIT 10"
]
)
[":last_update_key" := ("last_update" :: T.Text)]
sqlJoin :: Query -> [Query] -> Query
sqlJoin joiner snippets =
mconcat $ L.intercalate [joiner] [[s] | s <- snippets]

View file

@ -12,60 +12,7 @@ main :: IO ()
main = hspec spec
spec :: Spec
spec = with (return app) $ do
spec = with (return $ app "tutor.db") $ do
describe "GET /search" $ do
it "responds with 200" $ do
get "/search" `shouldRespondWith` 200
it "responds with [Card]" $ do
let cards =
[json|[
{
"scryfall_id": "f6cd7465-9dd0-473c-ac5e-dd9e2f22f5f6",
"name": "Esika, God of the Tree // The Prismatic Bridge",
"set_code": "KHM",
"collector_number": "168",
"rarity": "mythic",
"color_identity": "WUBGR",
"oracle_text": null,
"prices": {
"usd": "9.07",
"usd_foil": "10.77",
"eur": "7.79",
"eur_foil": "12.27",
"tix": "1.08"
}
},
{
"scryfall_id": "d761ff73-0717-4ee4-996b-f5547bcf9b2f",
"name": "Go-Shintai of Life's Origin",
"set_code": "NEC",
"collector_number": "66",
"rarity": "mythic",
"color_identity": "WUBGR",
"oracle_text": "{W}{U}{B}{R}{G}, {T}: Return target enchantment card from your graveyard to the battlefield.\nWhenever Go-Shintai of Life's Origin or another nontoken Shrine enters the battlefield under your control, create a 1/1 colorless Shrine enchantment creature token.",
"prices": {
"usd": "23.28",
"usd_foil": null,
"eur": "23.05",
"eur_foil": null,
"tix": null
}
},
{
"scryfall_id": "e2539ff7-2b7d-47e3-bd77-3138a6c42d2b",
"name": "Godsire",
"set_code": "ALA",
"collector_number": "170",
"rarity": "mythic",
"color_identity": "WGR",
"oracle_text": "Vigilance\n{T}: Create an 8/8 Beast creature token that's red, green, and white.",
"prices": {
"usd": "7.22",
"usd_foil": "16.56",
"eur": "4.00",
"eur_foil": "13.95",
"tix": "0.02"
}
}
]|]
get "/search" `shouldRespondWith` cards