serra/pkg/serra/update.go
2025-12-03 10:15:03 +01:00

162 lines
5.2 KiB
Go

package serra
import (
"fmt"
"time"
"github.com/mitchellh/mapstructure"
"github.com/schollz/progressbar/v3"
"github.com/spf13/cobra"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
func init() {
rootCmd.AddCommand(updateCmd)
}
var updateCmd = &cobra.Command{
Aliases: []string{"u"},
Use: "update",
Short: "update card values from scryfall",
Long: `the update mechanism iterates over each card in your collection and fetches its price. after all cards you own in a set are updated, the set value will update. after all sets are updated, the whole collection value is updated.`,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
client := storageConnect()
l := Logger()
defer storageDisconnect(client)
// update sets
setscoll := &Collection{client.Database("serra").Collection("sets")}
coll := &Collection{client.Database("serra").Collection("cards")}
totalcoll := &Collection{client.Database("serra").Collection("total")}
// predefine query for set analysis. used for total stats later
projectStage := bson.D{{"$project",
bson.D{
{"serra_count", true},
{"serra_count_foil", true},
{"set", true},
{"last_price", bson.D{{"$arrayElemAt", bson.A{"$serra_prices", -1}}}}}}}
groupStage := bson.D{
{"$group", bson.D{
{"_id", ""},
{"eur", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.eur", "$serra_count"}}}}}},
{"eurfoil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.eur_foil", "$serra_count_foil"}}}}}},
{"usd", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.usd", "$serra_count"}}}}}},
{"usdfoil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.usd_foil", "$serra_count_foil"}}}}}},
}}}
l.Info("Fetching bulk data from scryfall...")
downloadURL, err := fetchBulkDownloadURL()
if err != nil {
l.Error("Could not extract bulk download URL:", err)
return err
}
l.Infof("Found latest bulkfile url: %s", downloadURL)
l.Info("Downloading bulk data file...")
bulkFilePath, err := downloadBulkData(downloadURL)
if err != nil {
l.Error("Could not fetch bulk json from scryfall", err)
return err
}
l.Info("Loading bulk data file...")
updatedCards, err := loadBulkFile(bulkFilePath)
if err != nil {
l.Error("Could not load bulk file:", err)
return err
}
l.Infof("Successfully loaded %d cards. Starting Update.", len(updatedCards))
sets, _ := fetchSets()
for _, set := range sets.Data {
// When downloading new sets, PriceList needs to be initialized
// This query silently fails if set was already downloaded. Not nice but ok for now.
// TODO: make this not fail silently
set.SerraPrices = []PriceEntry{}
setscoll.storageAddSet(&set)
cards, _ := coll.storageFind(bson.D{{"set", set.Code}}, bson.D{{"_id", 1}}, 0, 0)
// if no cards in collection for this set, skip it
if len(cards) == 0 {
continue
}
bar := progressbar.NewOptions(len(cards),
progressbar.OptionSetWidth(50),
progressbar.OptionSetDescription(fmt.Sprintf("%s, %s%s%s\t", set.ReleasedAt[0:4], Yellow, set.Code, Reset)),
progressbar.OptionEnableColorCodes(true),
progressbar.OptionShowCount(),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerHead: "[green]>[reset]",
SaucerPadding: " ",
BarStart: "|",
BarEnd: "| " + set.Name,
}),
)
for _, card := range cards {
bar.Add(1)
updatedCard, err := getCardFromBulk(updatedCards, card.Set, card.CollectorNumber)
if err != nil {
l.Error(err)
continue
}
updatedCard.Prices.Date = primitive.NewDateTimeFromTime(time.Now())
update := bson.M{
"$set": bson.M{"serra_updated": primitive.NewDateTimeFromTime(time.Now()), "prices": updatedCard.Prices, "cmc": updatedCard.Cmc, "cardmarketid": updatedCard.CardmarketID, "tcgplayerid": updatedCard.TCGPlayerID},
"$push": bson.M{"serra_prices": updatedCard.Prices},
}
coll.storageUpdate(bson.M{"_id": bson.M{"$eq": card.ID}}, update)
}
fmt.Println()
// update set value sum
// calculate value summary
matchStage := bson.D{{"$match", bson.D{{"set", set.Code}}}}
setValue, _ := coll.storageAggregate(mongo.Pipeline{matchStage, projectStage, groupStage})
p := PriceEntry{}
s := setValue[0]
p.Date = primitive.NewDateTimeFromTime(time.Now())
// fill struct PriceEntry with map from mongoresult
mapstructure.Decode(s, &p)
// do the update
setUpdate := bson.M{
"$set": bson.M{"serra_updated": p.Date, "cardcount": set.CardCount},
"$push": bson.M{"serra_prices": p},
}
setscoll.storageUpdate(bson.M{"code": bson.M{"$eq": set.Code}}, setUpdate)
}
totalValue, _ := coll.storageAggregate(mongo.Pipeline{projectStage, groupStage})
t := PriceEntry{}
t.Date = primitive.NewDateTimeFromTime(time.Now())
mapstructure.Decode(totalValue[0], &t)
// This is here to be able to fetch currency from
// constructed new priceentry
tmpCard := Card{}
tmpCard.Prices = t
l.Infof("\nUpdating total value of collection to: %s%.02f%s%s\n", Yellow, tmpCard.getValue(false)+tmpCard.getValue(true), getCurrency(), Reset)
totalcoll.storageAddTotal(t)
return nil
},
}