diff --git a/sevenkeys/logic/scryfall/card.go b/sevenkeys/logic/scryfall/card.go index 9707423..6d819bf 100644 --- a/sevenkeys/logic/scryfall/card.go +++ b/sevenkeys/logic/scryfall/card.go @@ -1,5 +1,16 @@ package scryfall +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" +) + +const SCRYFALL_API_CARDS = "/cards/" +const CARD_IMAGEURIS_KEY_PNG = "png" + type Card struct { Id string `json:"id"` Name string `json:"name"` @@ -12,3 +23,38 @@ type Card struct { ImageUris map[string]string `json:"image_uris"` Language string `json:"lang"` } + +func (c *ScryfallClient) GetCardById(id string) (Card, error) { + var card Card + + response, err := c.httpClient.Get(c.baseURL + SCRYFALL_API_CARDS + id) + if err != nil { + return card, err + } + + if response.StatusCode != http.StatusOK { + return card, errors.New("HTTP request failed with code: " + fmt.Sprint(response.StatusCode)) + } + + defer response.Body.Close() + cardBytes, err := io.ReadAll(response.Body) + if err != nil { + return card, err + } + + err = json.Unmarshal(cardBytes, &card) + if err != nil { + return card, err + } + + return card, nil +} + +func (c *ScryfallClient) GetCardImageUrlById(id string) (string, error) { + card, err := c.GetCardById(id) + if err != nil { + return "", err + } + + return card.ImageUris[CARD_IMAGEURIS_KEY_PNG], nil +} diff --git a/sevenkeys/logic/scryfall/card_test.go b/sevenkeys/logic/scryfall/card_test.go new file mode 100644 index 0000000..a44e86a --- /dev/null +++ b/sevenkeys/logic/scryfall/card_test.go @@ -0,0 +1,59 @@ +package scryfall + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" +) + +const TEST_ID = "56ebc372-aabd-4174-a943-c7bf59e5028d" +const TEST_ENDPOINT = "/cards/" + TEST_ID +const TESTDATA_DIRECTORY = "testdata/" +const TEST_GOOD_CARD_DATA_FILENAME = "card.json" + +var ( + mux *http.ServeMux + server *httptest.Server + client *ScryfallClient +) + +func setup() func() { + mux = http.NewServeMux() + server = httptest.NewServer(mux) + + client, _ = NewScryfallClient(BaseURL(server.URL)) + + return func() { + server.Close() + } +} + +func fixture(filename string) string { + bytes, err := ioutil.ReadFile(TESTDATA_DIRECTORY + filename) + if err != nil { + panic(err) + } + + return string(bytes) +} + +func Test_GetCardImageUrlById_ReturnsUrl_ForSuccessfulRequest(t *testing.T) { + teardown := setup() + defer teardown() + + mux.HandleFunc(TEST_ENDPOINT, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, fixture(TEST_GOOD_CARD_DATA_FILENAME)) + }) + + url, err := client.GetCardImageUrlById(TEST_ID) + if err != nil { + t.Fatal(err) + } + + if url != "https://cards.scryfall.io/png/front/5/6/56ebc372-aabd-4174-a943-c7bf59e5028d.png?1562629953" { + t.Fatal(err) + } +} diff --git a/sevenkeys/logic/scryfall/scryfallclient.go b/sevenkeys/logic/scryfall/scryfallclient.go new file mode 100644 index 0000000..c1e19e3 --- /dev/null +++ b/sevenkeys/logic/scryfall/scryfallclient.go @@ -0,0 +1,47 @@ +package scryfall + +import ( + "net/http" + "time" +) + +const SCRYFALL_API_URL string = "https://api.scryfall.com/" + +type ScryfallClient struct { + baseURL string + httpClient *http.Client +} +type ScryfallClientOption func(*ScryfallClient) error + +func NewScryfallClient(options ...ScryfallClientOption) (*ScryfallClient, error) { + client := &ScryfallClient{ + baseURL: SCRYFALL_API_URL, + httpClient: &http.Client{ + Timeout: time.Second * 30, + }, + } + + if err := client.parseOptions(options...); err != nil { + return nil, err + } + + return client, nil +} + +func BaseURL(baseURL string) ScryfallClientOption { + return func(c *ScryfallClient) error { + c.baseURL = baseURL + return nil + } +} + +func (c *ScryfallClient) parseOptions(options ...ScryfallClientOption) error { + for _, option := range options { + err := option(c) + if err != nil { + return err + } + } + + return nil +} diff --git a/sevenkeys/logic/scryfall/testdata/card.json b/sevenkeys/logic/scryfall/testdata/card.json new file mode 100644 index 0000000..8db287f --- /dev/null +++ b/sevenkeys/logic/scryfall/testdata/card.json @@ -0,0 +1,130 @@ + +{ + "object": "card", + "id": "56ebc372-aabd-4174-a943-c7bf59e5028d", + "oracle_id": "e43e06fb-52b7-4f38-8fac-f31973b043f7", + "multiverse_ids": [ + 37113 + ], + "mtgo_id": 17622, + "mtgo_foil_id": 17623, + "tcgplayer_id": 10190, + "cardmarket_id": 2266, + "name": "Phantom Nishoba", + "lang": "en", + "released_at": "2002-05-27", + "uri": "https://api.scryfall.com/cards/56ebc372-aabd-4174-a943-c7bf59e5028d", + "scryfall_uri": "https://scryfall.com/card/jud/140/phantom-nishoba?utm_source=api", + "layout": "normal", + "highres_image": true, + "image_status": "highres_scan", + "image_uris": { + "small": "https://cards.scryfall.io/small/front/5/6/56ebc372-aabd-4174-a943-c7bf59e5028d.jpg?1562629953", + "normal": "https://cards.scryfall.io/normal/front/5/6/56ebc372-aabd-4174-a943-c7bf59e5028d.jpg?1562629953", + "large": "https://cards.scryfall.io/large/front/5/6/56ebc372-aabd-4174-a943-c7bf59e5028d.jpg?1562629953", + "png": "https://cards.scryfall.io/png/front/5/6/56ebc372-aabd-4174-a943-c7bf59e5028d.png?1562629953", + "art_crop": "https://cards.scryfall.io/art_crop/front/5/6/56ebc372-aabd-4174-a943-c7bf59e5028d.jpg?1562629953", + "border_crop": "https://cards.scryfall.io/border_crop/front/5/6/56ebc372-aabd-4174-a943-c7bf59e5028d.jpg?1562629953" + }, + "mana_cost": "{5}{G}{W}", + "cmc": 7, + "type_line": "Creature — Cat Beast Spirit", + "oracle_text": "Trample\nPhantom Nishoba enters with seven +1/+1 counters on it.\nWhenever Phantom Nishoba deals damage, you gain that much life.\nIf damage would be dealt to Phantom Nishoba, prevent that damage. Remove a +1/+1 counter from Phantom Nishoba.", + "power": "0", + "toughness": "0", + "colors": [ + "G", + "W" + ], + "color_identity": [ + "G", + "W" + ], + "keywords": [ + "Trample" + ], + "legalities": { + "standard": "not_legal", + "future": "not_legal", + "historic": "not_legal", + "timeless": "not_legal", + "gladiator": "not_legal", + "pioneer": "not_legal", + "explorer": "not_legal", + "modern": "not_legal", + "legacy": "legal", + "pauper": "not_legal", + "vintage": "legal", + "penny": "not_legal", + "commander": "legal", + "oathbreaker": "legal", + "standardbrawl": "not_legal", + "brawl": "not_legal", + "alchemy": "not_legal", + "paupercommander": "not_legal", + "duel": "legal", + "oldschool": "not_legal", + "premodern": "legal", + "predh": "legal" + }, + "games": [ + "paper", + "mtgo" + ], + "reserved": false, + "foil": true, + "nonfoil": true, + "finishes": [ + "nonfoil", + "foil" + ], + "oversized": false, + "promo": false, + "reprint": false, + "variation": false, + "set_id": "cd82de1a-36fd-4618-bfe8-b45532a582d9", + "set": "jud", + "set_name": "Judgment", + "set_type": "expansion", + "set_uri": "https://api.scryfall.com/sets/cd82de1a-36fd-4618-bfe8-b45532a582d9", + "set_search_uri": "https://api.scryfall.com/cards/search?order=set&q=e%3Ajud&unique=prints", + "scryfall_set_uri": "https://scryfall.com/sets/jud?utm_source=api", + "rulings_uri": "https://api.scryfall.com/cards/56ebc372-aabd-4174-a943-c7bf59e5028d/rulings", + "prints_search_uri": "https://api.scryfall.com/cards/search?order=released&q=oracleid%3Ae43e06fb-52b7-4f38-8fac-f31973b043f7&unique=prints", + "collector_number": "140", + "digital": false, + "rarity": "rare", + "card_back_id": "0aeebaf5-8c7d-4636-9e82-8c27447861f7", + "artist": "Arnie Swekel", + "artist_ids": [ + "af10ecf2-eb82-4100-97b2-6c236b0fa644" + ], + "illustration_id": "2bdc53d2-1f4b-4f6d-b59d-985ff2a01268", + "border_color": "black", + "frame": "1997", + "full_art": false, + "textless": false, + "booster": true, + "story_spotlight": false, + "edhrec_rank": 10936, + "penny_rank": 4165, + "prices": { + "usd": "3.99", + "usd_foil": "29.06", + "usd_etched": null, + "eur": "4.24", + "eur_foil": "28.93", + "tix": "0.53" + }, + "related_uris": { + "gatherer": "https://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=37113&printed=false", + "tcgplayer_infinite_articles": "https://partner.tcgplayer.com/c/4931599/1830156/21018?subId1=api&trafcat=infinite&u=https%3A%2F%2Finfinite.tcgplayer.com%2Fsearch%3FcontentMode%3Darticle%26game%3Dmagic%26partner%3Dscryfall%26q%3DPhantom%2BNishoba", + "tcgplayer_infinite_decks": "https://partner.tcgplayer.com/c/4931599/1830156/21018?subId1=api&trafcat=infinite&u=https%3A%2F%2Finfinite.tcgplayer.com%2Fsearch%3FcontentMode%3Ddeck%26game%3Dmagic%26partner%3Dscryfall%26q%3DPhantom%2BNishoba", + "edhrec": "https://edhrec.com/route/?cc=Phantom+Nishoba" + }, + "purchase_uris": { + "tcgplayer": "https://partner.tcgplayer.com/c/4931599/1830156/21018?subId1=api&u=https%3A%2F%2Fwww.tcgplayer.com%2Fproduct%2F10190%3Fpage%3D1", + "cardmarket": "https://www.cardmarket.com/en/Magic/Products/Singles/Judgment/Phantom-Nishoba?referrer=scryfall&utm_campaign=card_prices&utm_medium=text&utm_source=scryfall", + "cardhoarder": "https://www.cardhoarder.com/cards/17622?affiliate_id=scryfall&ref=card-profile&utm_campaign=affiliate&utm_medium=card&utm_source=scryfall" + } +}