package main import ( "fmt" "log" "sort" "strconv" "strings" "unsafe" "github.com/pocketbase/pocketbase/core" ) type CacheBrawlsetListItem struct { Name string SanitizedName string IconsSvgUri []string } type CacheCarteListItem struct { Name string Url string Layout string SmallImage string SmallImageBack string NormalImage string NormalImageBack string Price float64 CardmarketUri string NumberOfDecks int NumberOfPossibleDecks int PercentageOfDecks float64 Synergy float64 Banned bool } type CacheCarteListItemWithSynergy struct { MainCard CacheCarteListItem CanBeCommander bool Cards map[string][]CacheCarteListItem } type CacheCarteSearch struct { Name string Url string SetName string } type CacheBrawlsetData struct { Name string SanitizedName string Cards map[string][]CacheCarteListItem } func (app *application) SetupCache() { app.pb.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error { if err := e.Next(); err != nil { return err } GenerateCache(e.App) return nil }) } func GenerateCache(pb core.App) { log.Println("Creating json data cache...") defer timer("Generating cache")() var totalSize int64 brawlsets := []Brawlset{} mtgSetsQuery := []MtgSet{} mtgSets := map[string]MtgSet{} cardsQuery := []Carte{} cards := map[string]Carte{} cacheCardsSearch := []CacheCarteSearch{} cardsBySet := map[string][]Carte{} decks := []Deck{} decksByBset := map[string][]Deck{} err := pb.DB(). NewQuery("SELECT * FROM brawlset"). All(&brawlsets) if err != nil { log.Println(fmt.Sprintf("[ERROR] %v", err)) } err = pb.DB(). NewQuery("SELECT * FROM mtg_set"). All(&mtgSetsQuery) if err != nil { log.Println(fmt.Sprintf("[ERROR] %v", err)) } err = pb.DB(). NewQuery("SELECT * FROM deck"). All(&decks) if err != nil { log.Println(fmt.Sprintf("[ERROR] %v", err)) } for _, v := range decks { decksByBset[v.Brawlset] = append(decksByBset[v.Brawlset], v) } err = pb.DB(). NewQuery("SELECT * FROM carte"). All(&cardsQuery) if err != nil { log.Println(fmt.Sprintf("[ERROR] %v", err)) } for _, v := range mtgSetsQuery { mtgSets[v.ID] = v } for _, v := range cardsQuery { cards[v.ID] = v cardsBySet[v.MtgSet] = append(cardsBySet[v.MtgSet], v) cacheCardsSearch = append(cacheCardsSearch, CacheCarteSearch{ Name: v.Name, Url: fmt.Sprintf("/card/%s-%s", v.SetCode,v.SanitizedName), SetName: mtgSets[v.MtgSet].Name}) } totalSize += int64(unsafe.Sizeof(cacheCardsSearch)) pb.Store().Set("searchData", cacheCardsSearch) cacheColorCommanderTop := map[string][]CacheCarteListItem{} cacheBrawlsetList := []CacheBrawlsetListItem{} for _, v := range brawlsets { setIconList := []string{} numberOfDecksPerColorIdentity := map[string]int{} cacheBrawlsetData := CacheBrawlsetData{Name: v.Name, SanitizedName: v.SanitizedName, Cards: map[string][]CacheCarteListItem{}} numberOfDecksPerCard := map[string]int{} synergyPerCards := map[string]map[string]int{} for _, d := range decksByBset[v.ID] { possibleColorIdentities := GetAllColorCombination(d.ColorIdentity) for _, color := range possibleColorIdentities { if _, ok := numberOfDecksPerColorIdentity[color]; ok { numberOfDecksPerColorIdentity[color]++ } else { numberOfDecksPerColorIdentity[color] += 1 } } // Increment number of deck for commander if _, ok := numberOfDecksPerCard[fmt.Sprintf("c-%s",d.Commander)]; ok { numberOfDecksPerCard[fmt.Sprintf("c-%s",d.Commander)]++ } else { numberOfDecksPerCard[fmt.Sprintf("c-%s",d.Commander)] = 1 } // Increment number of deck for each card for _, c := range d.Cards { if _, ok := synergyPerCards[fmt.Sprintf("c-%s",d.Commander)]; !ok { synergyPerCards[fmt.Sprintf("c-%s", d.Commander)] = map[string]int{} } if _, ok := synergyPerCards[fmt.Sprintf("c-%s",d.Commander)][c.ID]; ok { synergyPerCards[fmt.Sprintf("c-%s",d.Commander)][c.ID]++ } else { synergyPerCards[fmt.Sprintf("c-%s",d.Commander)][c.ID] = 1 } // Add number of decks per card if _, ok := numberOfDecksPerCard[c.ID]; ok { numberOfDecksPerCard[c.ID]++ } else { numberOfDecksPerCard[c.ID] = 1 } // Add number of decks with the two same cards for synergy for _, tc := range d.Cards { if _, ok := synergyPerCards[c.ID]; !ok { synergyPerCards[c.ID] = map[string]int{} } if _, ok := synergyPerCards[c.ID][tc.ID]; ok { synergyPerCards[c.ID][tc.ID]++ } else { synergyPerCards[c.ID][tc.ID] = 1 } } } } for _, s := range v.Sets { setIconList = append(setIconList, mtgSets[s].IconUri) for _, c := range cardsBySet[s] { obj := CreateCardData(c, decksByBset, numberOfDecksPerColorIdentity, numberOfDecksPerCard, v.ID) cacheBrawlsetData.Cards[c.CardType] = append(cacheBrawlsetData.Cards[c.CardType], obj) if c.CanBeCommander { obj.NumberOfDecks = numberOfDecksPerCard[fmt.Sprintf("c-%s",c.ID)] obj.Url = fmt.Sprintf("/commander/%s-%s", c.SetCode,c.SanitizedName) colorIdentity := "" if len(c.ColorIdentity) == 0 { colorIdentity = "colorless" } else { colorIdentity = strings.Join(c.ColorIdentity, "") } cacheColorCommanderTop[colorIdentity] = append(cacheColorCommanderTop[colorIdentity], obj) cacheColorCommanderTop["all"] = append(cacheColorCommanderTop["all"], obj) cacheBrawlsetData.Cards["commander"] = append(cacheBrawlsetData.Cards["commander"], obj) detailsObj := CacheCarteListItemWithSynergy{ MainCard: obj, CanBeCommander: true, Cards: map[string][]CacheCarteListItem{}, } // Add each card that already appeared in a deck for cardId, numDecksWithCommander := range synergyPerCards[fmt.Sprintf("c-%s",c.ID)] { synergyObj := CreateCardData(cards[cardId], decksByBset, numberOfDecksPerColorIdentity, numberOfDecksPerCard, v.ID) synergy := 0 - synergyObj.PercentageOfDecks if numberOfDecksPerCard[fmt.Sprintf("c-%s",c.ID)] != 0 { synergy = (float64(synergyPerCards[fmt.Sprintf("c-%s",c.ID)][cardId]) / float64(numberOfDecksPerCard[fmt.Sprintf("c-%s",c.ID)])) - synergyObj.PercentageOfDecks } synergyObj.Synergy = synergy synergyObj.NumberOfDecks = numDecksWithCommander synergyObj.NumberOfPossibleDecks = numberOfDecksPerCard[fmt.Sprintf("c-%s",c.ID)] synergyObj.PercentageOfDecks = float64(synergyObj.NumberOfDecks) / float64(synergyObj.NumberOfPossibleDecks) detailsObj.Cards[cards[cardId].CardType] = append(detailsObj.Cards[cards[cardId].CardType], synergyObj) } for k := range detailsObj.Cards { sort.Slice(detailsObj.Cards[k], func(i, j int) bool { if detailsObj.Cards[k][i].Synergy == detailsObj.Cards[k][j].Synergy { return detailsObj.Cards[k][i].PercentageOfDecks > detailsObj.Cards[k][j].PercentageOfDecks } return detailsObj.Cards[k][i].Synergy > detailsObj.Cards[k][j].Synergy }) } totalSize += int64(unsafe.Sizeof(detailsObj)) pb.Store().Set(fmt.Sprintf("json/commander/%s-%s", c.SetCode, c.SanitizedName), detailsObj) } } } for k := range cacheBrawlsetData.Cards { if k == "commander" { sort.Slice(cacheBrawlsetData.Cards[k], func(i, j int) bool { return cacheBrawlsetData.Cards[k][i].NumberOfDecks > cacheBrawlsetData.Cards[k][j].NumberOfDecks}) } else { sort.Slice(cacheBrawlsetData.Cards[k], func(i, j int) bool { if cacheBrawlsetData.Cards[k][i].PercentageOfDecks == cacheBrawlsetData.Cards[k][j].PercentageOfDecks { return cacheBrawlsetData.Cards[k][i].NumberOfDecks > cacheBrawlsetData.Cards[k][j].NumberOfDecks } return cacheBrawlsetData.Cards[k][i].PercentageOfDecks > cacheBrawlsetData.Cards[k][j].PercentageOfDecks }) } } cacheBrawlsetList = append(cacheBrawlsetList, CacheBrawlsetListItem{Name: v.Name, SanitizedName: v.SanitizedName, IconsSvgUri: setIconList}) totalSize += int64(unsafe.Sizeof(cacheBrawlsetData)) pb.Store().Set("json/brawlset/" + v.SanitizedName, cacheBrawlsetData) } totalSize += int64(unsafe.Sizeof(cacheColorCommanderTop)) for k := range cacheColorCommanderTop { sort.Slice(cacheColorCommanderTop[k], func(i, j int) bool { return cacheColorCommanderTop[k][i].NumberOfDecks > cacheColorCommanderTop[k][j].NumberOfDecks}) pb.Store().Set(fmt.Sprintf("json/top/commander/%s", k),cacheColorCommanderTop[k]) } totalSize += int64(unsafe.Sizeof(cacheBrawlsetList)) pb.Store().Set("json/misc/brawlsets", cacheBrawlsetList) log.Println(fmt.Sprintf("Total cache size : %d", totalSize)) } func GetAllColorCombination(s []string) []string { res := []string{strings.Join(s,"")} for i := 1; i < len(s); i++ { combinations := CreateCombination(s, i) res = append(res, combinations...) } return res } func CreateCardData(c Carte, decksByBset map[string][]Deck, numberOfDecksPerColorIdentity map[string]int, numberOfDecksPerCard map[string]int, bsetID string) CacheCarteListItem { price, err := strconv.ParseFloat(c.Price, 64) if err != nil { price = 0.0 } numberOfPossibleDecks := 0 if len(c.ColorIdentity) == 0 { numberOfPossibleDecks = len(decksByBset[bsetID]) } else { numberOfPossibleDecks = numberOfDecksPerColorIdentity[strings.Join(c.ColorIdentity,"")] } percentageOfDecks := 0.0 if numberOfPossibleDecks != 0 { percentageOfDecks = float64(numberOfDecksPerCard[c.ID]) / float64(numberOfPossibleDecks) } return CacheCarteListItem{ Name: c.Name, Url: fmt.Sprintf("/card/%s-%s", c.SetCode,c.SanitizedName), Layout: c.Layout, SmallImage: c.SmallImage, SmallImageBack: c.SmallImageBack, NormalImage: c.NormalImage, NormalImageBack: c.NormalImageBack, Price: price, CardmarketUri: c.CardmarketUri, NumberOfDecks: numberOfDecksPerCard[c.ID], NumberOfPossibleDecks: numberOfPossibleDecks, PercentageOfDecks: percentageOfDecks, Banned: c.Banned, } }