Compare commits

...

23 Commits

Author SHA1 Message Date
The Magician 99ba02ef05 Rename dev and prod databases 2025-03-25 12:10:39 +00:00
The Magician eed44deddc Remove hardcoded path 2025-03-25 11:58:59 +00:00
The Magician 556ef2dd78 Rename commands and clean up filename code 2025-03-25 11:58:37 +00:00
The Magician 0a52dde4f6 Fix migrations for scan placeholder 2025-01-16 15:16:05 +00:00
The Magician f0758a2cce Add code to retrieve card images 2025-01-03 16:33:30 +00:00
The Magician 9076edb6f6 Replace CardLocation table with ProductLocation 2025-01-03 14:21:16 +00:00
The Magician 540a1df24e Remove foreign key constraint 2024-12-17 15:00:03 +00:00
The Magician 50e8f0c692 Don't abort update if Cardtrader API throws an error 2024-12-17 14:59:29 +00:00
The Magician de1b7e15b2 Fix no update condition 2024-12-17 14:59:15 +00:00
The Magician 94b8dfb6b9 Fix SQL command to insert CardtraderBlueprint 2024-12-17 14:58:16 +00:00
The Magician f32f64b6b3 Add build target to Makefile 2024-12-17 14:53:55 +00:00
The Magician c2380b51cb Add missing goose commands to Makefile 2024-12-17 14:50:30 +00:00
The Magician 5021ac3835 Insert Scryfall ID when inserting Blueprints 2024-12-14 20:35:36 +00:00
The Magician 9a1f306aec Fix down migrations 2024-12-13 18:10:43 +00:00
The Magician fd078f09a5 Update ExpansionSet to ScryfallSet 2024-12-13 18:10:33 +00:00
The Magician d107a92b54 Update CardPrinting to ScryfallCard 2024-12-13 18:09:15 +00:00
The Magician ec6e6b7f6a Move update logic to update package 2024-12-13 17:52:01 +00:00
The Magician 6814b23018 Remove unused code 2024-12-13 17:13:25 +00:00
The Magician 8c683002be Remove debug print 2024-12-13 17:10:53 +00:00
The Magician 96b9e0ad71 Update database migrations 2024-12-13 17:08:26 +00:00
The Magician ceceb4722f Add Cardtrader data during update 2024-12-10 19:39:25 +00:00
The Magician 848754b509 Remove CardtraderCategory table 2024-12-08 17:06:27 +00:00
The Magician 3e5f0082f9 Remove CardtraderGame table 2024-12-08 16:52:04 +00:00
34 changed files with 551 additions and 1240 deletions

View File

@ -1,17 +1,24 @@
build:
go build
connect: connect:
mysql --user=root --password=$(shell pass show sevenkeys/mysql) mysql --user=root --password=$(shell pass show sevenkeys/mysql)
dump: dump:
mysqldump --user=root --password=$(shell pass show sevenkeys/mysql) sevenkeys >sevenkeys.sql mysqldump --user=root --password=$(shell pass show sevenkeys/mysql) sevenkeys >sevenkeys.sql
dev_create: dev_up:
goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_development?parseTime=true&multiStatements=true" up goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_dev?parseTime=true&multiStatements=true" up
dev_rollback: dev_down:
goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_dev?parseTime=true&multiStatements=true" down
dev_reset:
rm -rf cache/ rm -rf cache/
goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_development?parseTime=true&multiStatements=true" reset goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_dev?parseTime=true&multiStatements=true" reset
prod_create: prod_up:
goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys?parseTime=true&multiStatements=true" up goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_prod?parseTime=true&multiStatements=true" up
prod_rollback: prod_down:
goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_prod?parseTime=true&multiStatements=true" down
prod_reset:
rm -rf cache/ rm -rf cache/
goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys?parseTime=true&multiStatements=true" reset goose -dir database/migrations/ mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_prod?parseTime=true&multiStatements=true" reset

11
sevenkeys/config.dev.json Normal file
View File

@ -0,0 +1,11 @@
{
"DatabaseConfig": {
"User": "root",
"Passwd": "o7MS6CIn660jIApSP",
"Net": "tcp",
"Addr": "127.0.0.1:3306",
"DBName": "sevenkeys_dev",
"AllowNativePasswords": true
},
"CardtraderToken": "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJjYXJkdHJhZGVyLXByb2R1Y3Rpb24iLCJzdWIiOiJhcHA6MTI4NzciLCJhdWQiOiJhcHA6MTI4NzciLCJleHAiOjQ4ODkzNDkwOTYsImp0aSI6IjIyZTdmOGVjLWMzYWQtNDUzYi1iMWIxLWQ0Y2M5ZTFjNmExNiIsImlhdCI6MTczMzY3NTQ5NiwibmFtZSI6IlRoZVdoZWVsT2ZGb3J0dW5lIEFwcCAyMDI0MTEyNTIwMTQzNCJ9.rDAdlgPPcyisLdXHu1suyqcha6SzKamYBg3pfqThTAVGVY0kL3I_mytiID1UX4zqy_7uSODmtVX3w7gcJDtZ3wEt2QQVOmDGPWjOQcXblSDTku7T6xz7_J03MEdrmyXag7EQ9iGW3JUEa6mjy2y4Cznr8AJ1i4_66ui_luinOmjnLbXrjNAjqtSkAOfDO8BL07MxR4bkwaRBED5GGh14NlWeeDttZfAuTWXXfZ0da9OUelps09woztUGPwjguCx1Mu1fXZhC1k68K-Lm0rizkhKAzGoTm3OB1WOKGD9G4deENj_vnsrT-3EbwGjBZNB8eHWtCKliybiwowONaJYMuQ"
}

View File

@ -1,37 +0,0 @@
package database
import (
"database/sql"
"time"
)
const CacheTypeAllCardsBulkData = "AllCardsBulkData"
func GetCacheTimestampByType(db *sql.DB, cacheType string) (string, error) {
var timestamp string
query := "SELECT Stamp FROM CacheTimestamp WHERE CacheType = ?;"
err := db.QueryRow(query, cacheType).Scan(&timestamp)
return timestamp, err
}
func InsertOrUpdateCacheTimestampByType(db *sql.DB, cacheType string, stamp time.Time) error {
query := `INSERT INTO CacheTimestamp (CacheType, Stamp)
VALUES (?, ?)
ON DUPLICATE KEY
UPDATE Stamp = ?;`
insertOrUpdate, err := db.Prepare(query)
defer insertOrUpdate.Close()
if err != nil {
return err
}
_, err = insertOrUpdate.Exec(cacheType, stamp, stamp)
if err != nil {
return err
}
return nil
}

View File

@ -1,65 +0,0 @@
package database
import (
"database/sql"
)
type CardPrinting struct {
Id string
Name string
SetCode string
IsFoil bool
IsPromo bool
CollectorNumber string
ImageUrl string
Language string
}
func InsertCardPrinting(db *sql.DB, cardPrinting CardPrinting) error {
query := `INSERT INTO CardPrinting (
Id,
Name,
SetCode,
IsFoil,
IsPromo,
CollectorNumber,
ImageUrl,
Language)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`
insert, err := db.Prepare(query)
defer insert.Close()
if err != nil {
return err
}
_, err = insert.Exec(cardPrinting.Id, cardPrinting.Name, cardPrinting.SetCode, cardPrinting.IsFoil, cardPrinting.IsPromo, cardPrinting.CollectorNumber, cardPrinting.ImageUrl, cardPrinting.Language)
if err != nil {
return err
}
return nil
}
func GetAllCardPrintings(db *sql.DB) ([]CardPrinting, error) {
var cardPrintings []CardPrinting
query := `SELECT Id, Name, SetCode, IsFoil, IsPromo, CollectorNumber, Language FROM CardPrinting;`
rows, err := db.Query(query)
defer rows.Close()
if err != nil {
return cardPrintings, err
}
var printing CardPrinting
for rows.Next() {
err := rows.Scan(&printing.Id, &printing.Name, &printing.SetCode, &printing.IsFoil, &printing.IsPromo, &printing.CollectorNumber, &printing.Language)
if err != nil {
return cardPrintings, err
}
cardPrintings = append(cardPrintings, printing)
}
return cardPrintings, nil
}

View File

@ -0,0 +1,74 @@
package database
import (
"database/sql"
"github.com/mtgban/go-mtgban/cardtrader"
)
func InsertCardtraderBlueprint(db *sql.DB, blueprint cardtrader.Blueprint) error {
query := `INSERT INTO CardtraderBlueprint (
Id,
ScryfallCardId,
CardtraderCategoryId,
CardtraderExpansionId,
Name,
CollectorNumber
) VALUES (
?, ?, ?, ?, ?, ?
)
ON DUPLICATE KEY UPDATE
Id = ?,
ScryfallCardId = ?,
CardtraderCategoryId = ?,
CardtraderExpansionId = ?,
Name = ?,
CollectorNumber = ?;`
insert, err := db.Prepare(query)
defer insert.Close()
if err != nil {
return err
}
_, err = insert.Exec(blueprint.Id,
blueprint.ScryfallId,
blueprint.CategoryId,
blueprint.ExpansionId,
blueprint.Name,
blueprint.FixedProperties.Number,
blueprint.Id,
blueprint.ScryfallId,
blueprint.CategoryId,
blueprint.ExpansionId,
blueprint.Name,
blueprint.FixedProperties.Number)
if err != nil {
return err
}
return nil
}
func GetAllCardtraderBlueprints(db *sql.DB) ([]cardtrader.Blueprint, error) {
var blueprints []cardtrader.Blueprint
query := `SELECT * FROM CardtraderBlueprint;`
rows, err := db.Query(query)
defer rows.Close()
if err != nil {
return blueprints, err
}
var blueprint cardtrader.Blueprint
for rows.Next() {
err := rows.Scan(&blueprint.Id, &blueprint.CategoryId, &blueprint.ExpansionId, &blueprint.Name, &blueprint.FixedProperties.Number)
if err != nil {
return blueprints, err
}
blueprints = append(blueprints, blueprint)
}
return blueprints, nil
}

View File

@ -0,0 +1,25 @@
package database
import (
"database/sql"
"github.com/mtgban/go-mtgban/cardtrader"
)
func InsertCardtraderExpansion(db *sql.DB, expansion cardtrader.Expansion) error {
query := `INSERT INTO CardtraderExpansion (Id, Code, Name) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE Id = ?, Code = ?, Name = ?;`
insert, err := db.Prepare(query)
defer insert.Close()
if err != nil {
return err
}
_, err = insert.Exec(expansion.Id, expansion.Code, expansion.Name, expansion.Id, expansion.Code, expansion.Name)
if err != nil {
return err
}
return nil
}

View File

@ -1,12 +0,0 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS CacheTimestamp (
CacheType ENUM('AllCardsBulkData') PRIMARY KEY,
Stamp DATETIME NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS CacheTimestamp;
-- +goose StatementEnd

View File

@ -0,0 +1,25 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS CardtraderExpansion (
Id INT PRIMARY KEY,
Code VARCHAR(20) NOT NULL,
Name VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS CardtraderBlueprint (
Id INT PRIMARY KEY,
ScryfallCardId VARCHAR(100) NULL,
CardtraderCategoryId INT NOT NULL,
CardtraderExpansionId INT NOT NULL,
FOREIGN KEY (CardtraderExpansionId) REFERENCES CardtraderExpansion(Id),
Name VARCHAR(255) NOT NULL,
CollectorNumber VARCHAR(50) NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS CardtraderBlueprint;
DROP TABLE IF EXISTS CardtraderExpansion;
-- +goose StatementEnd

View File

@ -1,14 +0,0 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS ExpansionSet (
SetCode VARCHAR(6) PRIMARY KEY,
Name VARCHAR(60) NOT NULL,
CardCount INT NOT NULL,
IconSvgUri VARCHAR(60) NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS ExpansionSet;
-- +goose StatementEnd

View File

@ -3,7 +3,7 @@
CREATE TABLE IF NOT EXISTS StorageArea ( CREATE TABLE IF NOT EXISTS StorageArea (
Id INT AUTO_INCREMENT PRIMARY KEY, Id INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(100) NOT NULL, Name VARCHAR(100) NOT NULL,
StorageType ENUM('Binder', 'Box') StorageType ENUM('Binder', 'Box') NOT NULL
); );
-- +goose StatementEnd -- +goose StatementEnd

View File

@ -1,19 +0,0 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS CardPrinting (
Id VARCHAR(37) PRIMARY KEY, -- GUID, plus one character for foil/nonfoil
Name VARCHAR(150) NOT NULL,
SetCode VARCHAR(6) NOT NULL,
FOREIGN KEY (SetCode) REFERENCES ExpansionSet(SetCode),
IsFoil BOOLEAN NOT NULL,
IsPromo BOOLEAN NOT NULL,
CollectorNumber VARCHAR(10) NOT NULL,
ImageUrl VARCHAR(100) NOT NULL,
Language VARCHAR(3) NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS CardPrinting;
-- +goose StatementEnd

View File

@ -1,9 +1,9 @@
-- +goose Up -- +goose Up
-- +goose StatementBegin -- +goose StatementBegin
CREATE TABLE IF NOT EXISTS CardLocation ( CREATE TABLE IF NOT EXISTS ProductLocation (
Id INT AUTO_INCREMENT PRIMARY KEY, Id INT AUTO_INCREMENT PRIMARY KEY,
CardPrintingId VARCHAR(37) NULL, CardtraderBlueprintId INT NULL,
FOREIGN KEY (CardPrintingId) REFERENCES CardPrinting(Id), FOREIGN KEY (CardtraderBlueprintId) REFERENCES CardtraderBlueprint(Id),
StorageAreaId INT NOT NULL, StorageAreaId INT NOT NULL,
FOREIGN KEY (StorageAreaId) REFERENCES StorageArea(Id), FOREIGN KEY (StorageAreaId) REFERENCES StorageArea(Id),
Position INT NULL, Position INT NULL,
@ -13,5 +13,5 @@ CREATE TABLE IF NOT EXISTS CardLocation (
-- +goose Down -- +goose Down
-- +goose StatementBegin -- +goose StatementBegin
DROP TABLE IF EXISTS CardLocation; DROP TABLE IF EXISTS ProductLocation;
-- +goose StatementEnd -- +goose StatementEnd

View File

@ -0,0 +1,33 @@
-- +goose Up
-- +goose StatementBegin
INSERT INTO CardtraderExpansion (
Id,
Code,
Name
) VALUES (
-1,
'None',
'None'
);
INSERT INTO CardtraderBlueprint (
Id,
ScryfallCardId,
CardtraderCategoryId,
CardtraderExpansionId,
Name,
CollectorNumber
) VALUES (
-1,
'00000000-0000-0000-0000-000000000000',
1,
-1,
'Scanned Card Placeholder',
0
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DELETE FROM CardtraderBlueprint WHERE Id = -1;
-- +goose StatementEnd

View File

@ -1,39 +0,0 @@
-- +goose Up
-- +goose StatementBegin
INSERT INTO ExpansionSet (
SetCode,
Name,
CardCount,
IconSvgUri
) VALUES (
'null',
'None',
0,
''
);
INSERT INTO CardPrinting (
Id,
Name,
SetCode,
IsFoil,
IsPromo,
CollectorNumber,
ImageUrl,
Language
) VALUES (
'00000000-0000-0000-0000-0000000000000',
'Scanned Card Placeholder',
'null',
0,
0,
0,
'',
'en'
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DELETE FROM CardPrinting WHERE Id = '00000000-0000-0000-0000-0000000000000';
-- +goose StatementEnd

View File

@ -1,43 +0,0 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS CardtraderGame (
Id INT PRIMARY KEY AUTO_INCREMENT,
Name VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS CardtraderCategory (
Id INT PRIMARY KEY AUTO_INCREMENT,
CardtraderGameId INT NOT NULL,
FOREIGN KEY (CardtraderGameId) REFERENCES CardtraderGame(Id),
Name VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS CardtraderExpansion (
Id INT PRIMARY KEY AUTO_INCREMENT,
CardtraderGameId INT NOT NULL,
FOREIGN KEY (CardtraderGameId) REFERENCES CardtraderGame(Id)
);
CREATE TABLE IF NOT EXISTS CardtraderBlueprint (
Id INT PRIMARY KEY AUTO_INCREMENT,
CardtraderGameId INT NOT NULL,
FOREIGN KEY (CardtraderGameId) REFERENCES CardtraderGame(Id),
CardtraderCategoryId INT NOT NULL,
FOREIGN KEY (CardtraderCategoryId) REFERENCES CardtraderCategory(Id),
CardtraderExpansionId INT NOT NULL,
FOREIGN KEY (CardtraderExpansionId) REFERENCES CardtraderExpansion(Id),
Name VARCHAR(255) NOT NULL,
CollectorNumber VARCHAR(10) NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS CardtraderBlueprint;
DROP TABLE IF EXISTS CardtraderExpansion;
DROP TABLE IF EXISTS CardtraderCategory;
DROP TABLE IF EXISTS CardtraderGame;
-- +goose StatementEnd

View File

@ -1,23 +0,0 @@
package database
import (
"database/sql"
"sevenkeys/logic/scryfall"
)
func InsertSet(db *sql.DB, set scryfall.Set) error {
query := `INSERT INTO ExpansionSet (SetCode, Name, CardCount, IconSvgUri) VALUES (?, ?, ?, ?);`
insert, err := db.Prepare(query)
defer insert.Close()
if err != nil {
return err
}
_, err = insert.Exec(set.Code, set.Name, set.CardCount, set.IconSvgUri)
if err != nil {
return err
}
return nil
}

View File

@ -14,7 +14,7 @@ type DelverLensCard struct {
func ParseExportFile(filename string) ([]DelverLensCard, error) { func ParseExportFile(filename string) ([]DelverLensCard, error) {
var cards []DelverLensCard var cards []DelverLensCard
file, err := os.Open("/home/viciouscirce/dox/sevenkeys_imports/" + filename) file, err := os.Open(filename)
defer file.Close() defer file.Close()
if err != nil { if err != nil {
return cards, err return cards, err

View File

@ -1,22 +0,0 @@
package logic
import "os"
const CACHE_DIR string = "cache"
const SET_ICON_CACHE_DIR string = CACHE_DIR + "/seticons/"
const SET_ICON_FILE_EXTENSION string = ".svg"
const ALL_CARDS_CACHE_FILENAME = CACHE_DIR + "/all-cards.json"
func CreateCacheDirectories() error {
err := os.Mkdir(CACHE_DIR, os.ModePerm)
if err != nil && !os.IsExist(err) {
return err
}
err = os.Mkdir(SET_ICON_CACHE_DIR, os.ModePerm)
if err != nil && !os.IsExist(err) {
return err
}
return nil
}

View File

@ -1,36 +0,0 @@
package logic
import (
"sevenkeys/database"
"strings"
)
func filterPrinting(printing database.CardPrinting, searchCriteria SearchCriteria) bool {
if searchCriteria.SetCode != "" && !strings.Contains(printing.SetCode, searchCriteria.SetCode) {
return true
}
if searchCriteria.CollectorNumber != "" && !strings.Contains(printing.CollectorNumber, searchCriteria.CollectorNumber) {
return true
}
if searchCriteria.Foil == False && printing.IsFoil {
return true
}
if searchCriteria.Foil == True && !printing.IsFoil {
return true
}
if searchCriteria.Promo == False && printing.IsPromo {
return true
}
if searchCriteria.Promo == True && !printing.IsPromo {
return true
}
if searchCriteria.Language != "" && printing.Language != searchCriteria.Language {
return true
}
return false
}

View File

@ -1,426 +0,0 @@
package logic
import (
"sevenkeys/database"
"testing"
)
func TestFilterPrinting_ReturnsTrue_IfSetCodeDoesNotMatch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "otj",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsFalse_IfSetCodeDoesMatch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfSetCodeNotSet(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsTrue_IfFoilCardInNonFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: true,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsTrue_IfNonFoilCardInFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: True,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsFalse_IfNonFoilCardInNonFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfFoilCardInFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: true,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: True,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfFoilCardInEitherFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: true,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: Either,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfNonFoilCardInEitherFoilSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: Either,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsTrue_IfPromoCardInNonPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: true,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsTrue_IfNonPromoCardInPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: True,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsFalse_IfNonPromoCardInNonPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfPromoCardInPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: true,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: True,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfPromoCardInEitherPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: true,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: Either,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfNonPromoCardInEitherPromoSearch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: Either,
Language: "en",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsTrue_IfLanguageDoesNotMatch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "de",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}
func TestFilterPrinting_ReturnsFalse_IfLanguageDoesMatch(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "de",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "de",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsFalse_IfLanguageNotSet(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
Foil: False,
Promo: False,
Language: "",
}
filter := filterPrinting(printing, searchCriteria)
if filter != false {
t.Errorf("filter was true")
}
}
func TestFilterPrinting_ReturnsTrue_IfSetCodeMatchesButCollectorNumberDoesnt(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
CollectorNumber: "301",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
CollectorNumber: "299",
Foil: False,
Promo: False,
Language: "",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}

View File

@ -41,33 +41,6 @@ func GetCardAtLocation(db *sql.DB, cardLocationId int) (database.LocateCardResul
return result, nil return result, nil
} }
func LocateCards(db *sql.DB, cardNames []string, criteria SearchCriteria) ([]database.LocateCardResult, error) {
results, err := database.GetLocateResults(db, cardNames)
if err != nil {
return results, err
}
var filteredResults []database.LocateCardResult
for _, result := range results {
printing := database.CardPrinting{
SetCode: result.SetCode,
IsFoil: result.IsFoil,
IsPromo: result.IsPromo,
Language: result.Language,
}
filter := filterPrinting(printing, criteria)
if filter {
continue
}
filteredResults = append(filteredResults, result)
}
return filteredResults, nil
}
func GetLocationDescription(location database.LocateCardResult) string { func GetLocationDescription(location database.LocateCardResult) string {
var description string var description string
@ -77,9 +50,6 @@ func GetLocationDescription(location database.LocateCardResult) string {
location.CollectorNumber, location.CollectorNumber,
location.Language) location.Language)
if location.IsFoil {
description += " FOIL"
}
if location.IsPromo { if location.IsPromo {
description += " PROMO" description += " PROMO"
} }

View File

@ -1,63 +0,0 @@
package scryfall
import (
"encoding/json"
"errors"
"io"
"net/http"
"time"
)
type BulkData struct {
Id string `json:"id"`
Uri string `json:"uri"`
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
DownloadUri string `json:"download_uri"`
UpdatedAt string `json:"updated_at"`
UpdatedAtTime time.Time `json:"ignore"`
Size int `json:"size"`
ContentType string `json:"content_type"`
ContentEncoding string `json:"content_encoding"`
}
const BULK_DATA_URI = "https://api.scryfall.com/bulk-data"
const BulkDataTypeOracleCards string = "oracle_cards"
const BulkDataTypeUniqueArtwork string = "unique_artwork"
const BulkDataTypeDefaultCards string = "default_cards"
const BulkDataTypeAllCards string = "all_cards"
const BulkDataTypeRulings string = "rulings"
func GetBulkDataByType(bulkDataType string) (BulkData, error) {
response, err := http.Get(BULK_DATA_URI + "/" + bulkDataType)
if err != nil {
return BulkData{}, err
}
if response.StatusCode != http.StatusOK {
return BulkData{}, errors.New("HTTP request failed with code: " + string(response.StatusCode))
}
defer response.Body.Close()
bulkDataBytes, err := io.ReadAll(response.Body)
if err != nil {
return BulkData{}, err
}
var bulkData BulkData
err = json.Unmarshal(bulkDataBytes, &bulkData)
if err != nil {
return BulkData{}, err
}
bulkData.UpdatedAtTime, err = time.Parse(ScryfallTimestampFormat, bulkData.UpdatedAt)
if err != nil {
return BulkData{}, err
}
// Round to the nearest second; this is so that comparison with the timestamp stored in the database works as intended
bulkData.UpdatedAtTime = bulkData.UpdatedAtTime.Truncate(time.Second)
return bulkData, nil
}

View File

@ -1,5 +1,16 @@
package scryfall package scryfall
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)
const SCRYFALL_API_CARDS = "/cards/"
const CARD_IMAGEURIS_KEY_PNG = "png"
type Card struct { type Card struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -12,3 +23,38 @@ type Card struct {
ImageUris map[string]string `json:"image_uris"` ImageUris map[string]string `json:"image_uris"`
Language string `json:"lang"` 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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -1,49 +0,0 @@
package scryfall
import (
"encoding/json"
"errors"
"io"
"net/http"
)
type SetList struct {
Object string `json:"object"`
HasMore bool `json:"has_more"`
Data []Set `json:"data"`
}
type Set struct {
Code string `json:"code"`
Name string `json:"name"`
CardCount int `json:"card_count"`
IconSvgUri string `json:"icon_svg_uri"`
Digital bool `json:"digital"`
}
const SETS_API_URL string = "https://api.scryfall.com/sets"
func GetAllSets() ([]Set, error) {
response, err := http.Get(SETS_API_URL)
if err != nil {
return []Set{}, nil
}
if response.StatusCode != http.StatusOK {
return []Set{}, errors.New("HTTP request failed with code: " + string(response.StatusCode))
}
defer response.Body.Close()
setsBytes, err := io.ReadAll(response.Body)
if err != nil {
return []Set{}, err
}
var setList SetList
err = json.Unmarshal(setsBytes, &setList)
if err != nil {
return []Set{}, err
}
return setList.Data, nil
}

View File

@ -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"
}
}

View File

@ -1,3 +0,0 @@
package scryfall
const ScryfallTimestampFormat = "2006-01-02T15:04:05.999-07:00"

View File

@ -2,7 +2,6 @@ package logic
import ( import (
"database/sql" "database/sql"
"fmt"
"io" "io"
"os" "os"
"os/exec" "os/exec"
@ -21,45 +20,12 @@ const (
type SearchCriteria struct { type SearchCriteria struct {
SetCode string SetCode string
CollectorNumber string CollectorNumber string
Foil Triadic
Promo Triadic Promo Triadic
Language string Language string
} }
type CardPrintingSearchOptions map[string]string type CardPrintingSearchOptions map[string]string
func GetAllCardPrintingSearchOptions(db *sql.DB, searchCriteria SearchCriteria) (CardPrintingSearchOptions, error) {
var searchOptions CardPrintingSearchOptions = make(CardPrintingSearchOptions)
cardPrintings, err := database.GetAllCardPrintings(db)
if err != nil {
return searchOptions, err
}
for _, printing := range cardPrintings {
// Filter based on search criteria
filter := filterPrinting(printing, searchCriteria)
if filter {
continue
}
// Construct search option string
searchString := fmt.Sprintf("%s (%s %s) [%s]", printing.Name, printing.SetCode, printing.CollectorNumber, printing.Language)
if printing.IsFoil {
searchString += " FOIL"
}
if printing.IsPromo {
searchString += " PROMO"
}
searchOptions[searchString] = printing.Id
}
return searchOptions, err
}
type StorageSearchOptions map[string]int type StorageSearchOptions map[string]int
func GetAllStorageSearchOptions(db *sql.DB) (StorageSearchOptions, error) { func GetAllStorageSearchOptions(db *sql.DB) (StorageSearchOptions, error) {

View File

@ -1,197 +0,0 @@
package logic
import (
"database/sql"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"sevenkeys/database"
"sevenkeys/logic/scryfall"
"time"
sqlerr "github.com/go-mysql/errors"
)
const GAME_PAPER = "paper"
func CheckForUpdates(db *sql.DB, bulkData scryfall.BulkData) (bool, error) {
// TODO: We also want to update if:
// - there is no cached version of the all-cards.json file or it is empty
// - The set icon files are missing
cachedFileTimestampStr, err := database.GetCacheTimestampByType(db, database.CacheTypeAllCardsBulkData)
if err == sql.ErrNoRows {
return true, nil
} else if err != nil {
return false, err
}
cachedFileTimestamp, err := time.Parse("2006-01-02 15:04:05", cachedFileTimestampStr)
return bulkData.UpdatedAtTime.After(cachedFileTimestamp), nil
}
func UpdateSets(db *sql.DB) error {
sets, err := scryfall.GetAllSets()
if err != nil {
return err
}
for _, set := range sets {
// We're only interested in paper cards, so skip importing
// any sets that were only released in a video game
if set.Digital {
continue
}
err := database.InsertSet(db, set)
// If we already have this set in the database, then we can just skip it
if ok, insertErr := sqlerr.Error(err); ok {
if insertErr == sqlerr.ErrDupeKey {
continue
}
}
if err != nil {
return err
}
response, err := http.Get(set.IconSvgUri)
defer response.Body.Close()
if err != nil {
return nil
}
iconFilename := SET_ICON_CACHE_DIR + set.Code + SET_ICON_FILE_EXTENSION
iconFile, err := os.Create(iconFilename)
if err != nil {
return err
}
io.Copy(iconFile, response.Body)
}
fmt.Println("Sets updated.")
return nil
}
func cacheBulkCardsFile(db *sql.DB, bulkData scryfall.BulkData) error {
bulkCardsResponse, err := http.Get(bulkData.DownloadUri)
defer bulkCardsResponse.Body.Close()
if err != nil {
return err
}
cacheFile, err := os.Create(ALL_CARDS_CACHE_FILENAME)
defer cacheFile.Close()
if err != nil {
return err
}
io.Copy(cacheFile, bulkCardsResponse.Body)
err = database.InsertOrUpdateCacheTimestampByType(db, database.CacheTypeAllCardsBulkData, bulkData.UpdatedAtTime)
if err != nil {
return err
}
return nil
}
func isPaper(card scryfall.Card) bool {
var paper bool = false
for _, game := range card.Games {
if game == GAME_PAPER {
paper = true
}
}
return paper
}
func getCardPrintings(card scryfall.Card) []database.CardPrinting {
var printings []database.CardPrinting
if card.Foil {
printings = append(printings, database.CardPrinting{
Id: card.Id + "f",
Name: card.Name,
SetCode: card.Set,
IsFoil: true,
IsPromo: card.Promo,
CollectorNumber: card.CollectorNumber,
ImageUrl: card.ImageUris["png"],
Language: card.Language,
})
}
if card.NonFoil {
printings = append(printings, database.CardPrinting{
Id: card.Id + "n",
Name: card.Name,
SetCode: card.Set,
IsFoil: false,
IsPromo: card.Promo,
CollectorNumber: card.CollectorNumber,
Language: card.Language,
})
}
return printings
}
func UpdateCards(db *sql.DB, bulkData scryfall.BulkData) error {
log.Println("Caching bulk cards file")
err := cacheBulkCardsFile(db, bulkData)
if err != nil {
return err
}
log.Println("Cached bulk cards file")
log.Println("Reading cached file")
cardsBytes, err := ioutil.ReadFile(ALL_CARDS_CACHE_FILENAME)
if err != nil {
return err
}
log.Println("Read cached file")
log.Println("Unmarshaling JSON")
var cards []scryfall.Card
err = json.Unmarshal(cardsBytes, &cards)
if err != nil {
return err
}
log.Println("Unmarshaled JSON")
log.Println("INSERTing cards")
for _, card := range cards {
// We're only interested in paper cards, so skip cards or printings of a card which
// aren't available in paper
if !isPaper(card) {
continue
}
cardPrintings := getCardPrintings(card)
for _, cardPrinting := range cardPrintings {
err := database.InsertCardPrinting(db, cardPrinting)
// If we already have this card in the database, then we can just skip it
if ok, insertErr := sqlerr.Error(err); ok {
if insertErr == sqlerr.ErrDupeKey {
continue
}
}
}
if err != nil {
return err
}
}
log.Println("INSERTed cards")
return nil
}

View File

@ -1,11 +1,9 @@
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os" "os"
"sevenkeys/config" "sevenkeys/config"
@ -13,13 +11,11 @@ import (
"sevenkeys/delverlens" "sevenkeys/delverlens"
"sevenkeys/logic" "sevenkeys/logic"
"sevenkeys/update" "sevenkeys/update"
"github.com/mtgban/go-mtgban/cardtrader"
) )
const ( const (
UpdateSubcommand string = "update" UpdateSubcommand string = "update"
CreateStorageAreaSubcommand string = "createstorage" CreateStorageAreaSubcommand string = "create-storage"
StoreSubcommand string = "store" StoreSubcommand string = "store"
ImportSubcommand string = "import" ImportSubcommand string = "import"
SearchPrintingsSubcommand string = "search-printings" SearchPrintingsSubcommand string = "search-printings"
@ -27,9 +23,6 @@ const (
AddSubcommand string = "add" AddSubcommand string = "add"
RemoveSubcommand string = "remove" RemoveSubcommand string = "remove"
ReplaceSubcommand string = "replace" ReplaceSubcommand string = "replace"
DeckSubcommand string = "deck"
GetProductIdSubcommand string = "products"
) )
func main() { func main() {
@ -48,8 +41,8 @@ func main() {
switch flag.Args()[0] { switch flag.Args()[0] {
case UpdateSubcommand: case UpdateSubcommand:
update.UpdateScryfallData(db) err := update.UpdateCardtraderData(db, config.CardtraderToken)
//update.UpdateCardtraderData(config.CardtraderToken) logic.Check(err)
break break
case CreateStorageAreaSubcommand: case CreateStorageAreaSubcommand:
createStorageCmd := flag.NewFlagSet(CreateStorageAreaSubcommand, flag.ExitOnError) createStorageCmd := flag.NewFlagSet(CreateStorageAreaSubcommand, flag.ExitOnError)
@ -88,9 +81,10 @@ func main() {
fmt.Printf("%d\n", cardLocationId) fmt.Printf("%d\n", cardLocationId)
break break
case ImportSubcommand: case ImportSubcommand:
importCmd := flag.NewFlagSet(ImportSubcommand, flag.ExitOnError) importCmd := flag.NewFlagSet(ImportSubcommand, flag.ExitOnError)
storageArea := importCmd.String("storagearea", "", storageArea := importCmd.String("storage-area", "",
"The name of the StorageArea where cards should be imported.") "The name of the StorageArea where cards should be imported.")
importCmd.Parse(flag.Args()[1:]) importCmd.Parse(flag.Args()[1:])
@ -102,18 +96,22 @@ func main() {
} }
logic.Check(err) logic.Check(err)
delverLensCards, err := delverlens.ParseExportFile(importCmd.Args()[0]) filename := importCmd.Args()[0]
delverLensCards, err := delverlens.ParseExportFile(filename)
logic.Check(err) logic.Check(err)
err = logic.ImportDelverLensCards(db, delverLensCards, storageAreaId) err = logic.ImportDelverLensCards(db, delverLensCards, storageAreaId)
logic.Check(err) logic.Check(err)
break break
/*
TODO: Rewrite this to search Blueprints
case SearchPrintingsSubcommand: case SearchPrintingsSubcommand:
searchPrintingsCmd := flag.NewFlagSet(SearchPrintingsSubcommand, flag.ExitOnError) searchPrintingsCmd := flag.NewFlagSet(SearchPrintingsSubcommand, flag.ExitOnError)
setCode := searchPrintingsCmd.String("set-code", "", "The code for the set the card we're searching for belongs to.") setCode := searchPrintingsCmd.String("set-code", "", "The code for the set the card we're searching for belongs to.")
collectorNumber := searchPrintingsCmd.String("collector-number", "", "The collector number of the card we're searching for.") collectorNumber := searchPrintingsCmd.String("collector-number", "", "The collector number of the card we're searching for.")
foil := searchPrintingsCmd.String("foil", "E", "Whether the card we're searching for is foil.") //foil := searchPrintingsCmd.String("foil", "E", "Whether the card we're searching for is foil.")
searchPrintingsCmd.Parse(flag.Args()[1:]) searchPrintingsCmd.Parse(flag.Args()[1:])
@ -138,6 +136,7 @@ func main() {
logic.Check(err) logic.Check(err)
fmt.Println(id) fmt.Println(id)
break break
*/
case SearchStorageSubcommand: case SearchStorageSubcommand:
searchOptions, err := logic.GetAllStorageSearchOptions(db) searchOptions, err := logic.GetAllStorageSearchOptions(db)
logic.Check(err) logic.Check(err)
@ -203,51 +202,6 @@ func main() {
err := logic.Replace(db, *cardLocationId, *cardPrintingId) err := logic.Replace(db, *cardLocationId, *cardPrintingId)
logic.Check(err) logic.Check(err)
break break
case DeckSubcommand:
deckCmd := flag.NewFlagSet(DeckSubcommand, flag.ExitOnError)
deckCmd.Parse(flag.Args()[1:])
//filename := deckCmd.Args()[0]
break
case GetProductIdSubcommand:
blbBlueprintsBytes, err := ioutil.ReadFile("blb_blueprints.json")
logic.Check(err)
var blbBlueprints []cardtrader.Blueprint
err = json.Unmarshal(blbBlueprintsBytes, &blbBlueprints)
logic.Check(err)
productsBytes, err := ioutil.ReadFile("products.json")
logic.Check(err)
var products []cardtrader.Product
err = json.Unmarshal(productsBytes, &products)
logic.Check(err)
for _, product := range products {
var productBlueprint cardtrader.Blueprint
for _, blueprint := range blbBlueprints {
if blueprint.Id == product.BlueprintId {
productBlueprint = blueprint
break
}
}
fmt.Printf("%s %s %d ",
productBlueprint.Name,
product.Properties.Number,
product.Id,
)
if product.Properties.MTGFoil {
fmt.Printf("FOIL ")
} else {
fmt.Printf("NONFOIL ")
}
fmt.Printf("x%d\n", product.Quantity)
}
break
default: default:
fmt.Fprintf(os.Stderr, "Unrecognized subcommand: %s\n", os.Args[1]) fmt.Fprintf(os.Stderr, "Unrecognized subcommand: %s\n", os.Args[1])
break break

View File

@ -0,0 +1,44 @@
package update
import (
"database/sql"
"log"
"sevenkeys/database"
"github.com/mtgban/go-mtgban/cardtrader"
)
func UpdateCardtraderData(db *sql.DB, token string) error {
client := cardtrader.NewCTAuthClient(token)
expansions, err := client.Expansions()
if err != nil {
return err
}
for _, expansion := range expansions {
if expansion.GameId != cardtrader.GameIdMagic {
continue
}
err = database.InsertCardtraderExpansion(db, expansion)
if err != nil {
return err
}
blueprints, err := client.Blueprints(expansion.Id)
if err != nil {
log.Printf("cardtrader: Error getting blueprints for Expansion ID %d: %v\n", expansion.Id, err)
continue
}
for _, blueprint := range blueprints {
err = database.InsertCardtraderBlueprint(db, blueprint)
if err != nil {
return err
}
}
}
return nil
}

View File

@ -1,32 +0,0 @@
package update
import (
"database/sql"
"fmt"
"sevenkeys/logic"
"sevenkeys/logic/scryfall"
)
func UpdateScryfallData(db *sql.DB) {
fmt.Println("Checking for updates...")
bulkData, err := scryfall.GetBulkDataByType(scryfall.BulkDataTypeAllCards)
logic.Check(err)
needsUpdate, err := logic.CheckForUpdates(db, bulkData)
logic.Check(err)
if !needsUpdate {
fmt.Println("No update required.")
return
}
logic.CreateCacheDirectories()
err = logic.UpdateSets(db)
logic.Check(err)
err = logic.UpdateCards(db, bulkData)
logic.Check(err)
fmt.Println("Update finished.")
}