New rewrite with svelte and pocketbase

This commit is contained in:
zuma 2025-04-12 16:08:00 +02:00
parent 72bfc2ed89
commit 160617af60
95 changed files with 4402 additions and 0 deletions

295
backend/cache.go Normal file
View file

@ -0,0 +1,295 @@
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
}
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)
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)
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 k := range synergyPerCards[fmt.Sprintf("c-%s",c.ID)] {
synergyObj := CreateCardData(cards[k], 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)][k]) / float64(numberOfDecksPerCard[fmt.Sprintf("c-%s",c.ID)])) - synergyObj.PercentageOfDecks
}
synergyObj.Synergy = synergy
detailsObj.Cards[cards[k].CardType] = append(detailsObj.Cards[cards[k].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(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,
}
}

70
backend/crons.go Normal file
View file

@ -0,0 +1,70 @@
package main
import (
"log"
"fmt"
)
func (app *application) SetupCrons() {
app.pb.Cron().MustAdd("update_sets_and_cards", "0 3 * * *", func() {
log.Println("Started updating sets and cards...")
scryfallSets := fetchApiSets()
insertQuery := CreateBulkSetsInsertQuery(scryfallSets)
_, err := app.pb.DB().
NewQuery(insertQuery).
Execute()
if err != nil {
log.Println(fmt.Sprintf("[ERROR] %v",err))
}
log.Println(fmt.Sprintf("Updated %d sets", len(scryfallSets)))
setsCodesQuery := []MtgSet{}
err = app.pb.DB().
NewQuery("SELECT id, code FROM mtg_set").
All(&setsCodesQuery)
if err != nil {
log.Println(fmt.Sprintf("[ERROR] %v",err))
}
setsCodes := map[string]string{}
for _,v := range setsCodesQuery {
setsCodes[v.ID] = v.Code
}
selectedSets := []Brawlset{}
selectedSetsCodes := []string{}
err = app.pb.DB().
NewQuery("SELECT sets FROM brawlset").
All(&selectedSets)
if err != nil {
log.Println(fmt.Sprintf("[ERROR] %v",err))
}
for _, v := range selectedSets {
for _, set := range v.Sets {
selectedSetsCodes = append(selectedSetsCodes, setsCodes[set])
}
}
log.Println(fmt.Sprintf("Fetching %d sets...", len(selectedSetsCodes)))
allCards := []ScryfallCard{}
for _, v := range selectedSetsCodes {
url := "https://api.scryfall.com/cards/search?q=(game%3Apaper)+set%3A" + v
allCards = fetchApiCards(url, allCards)
}
upsertQuery := CreateBulkCardsUpsertQuery(allCards)
_, err = app.pb.DB().
NewQuery(upsertQuery).
Execute()
if err != nil {
log.Println(fmt.Sprintf("[ERROR] %v",err))
}
log.Println(fmt.Sprintf("Updated %d cards", len(allCards)))
})
app.pb.Cron().MustAdd("regenerate_cache_json", "30 3 * * *", func() {
GenerateCache(app.pb.App)
})
}

BIN
backend/custom_pocketbase Executable file

Binary file not shown.

47
backend/go.mod Normal file
View file

@ -0,0 +1,47 @@
module brawlset
go 1.24.1
require (
github.com/labstack/echo v3.3.10+incompatible
github.com/pocketbase/pocketbase v0.26.6
)
require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/ganigeorgiev/fexpr v0.4.1 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.27 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pocketbase/dbx v1.11.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/image v0.25.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
modernc.org/libc v1.61.13 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.36.3 // indirect
)

164
backend/go.sum Normal file
View file

@ -0,0 +1,164 @@
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k=
github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/pocketbase v0.26.6 h1:ya+D2QK5DP3ynntCEJPj5Sc6hl9KZ+ZsfxVKp9UCB4o=
github.com/pocketbase/pocketbase v0.26.6/go.mod h1:Pd+NfdYGBHXJOi9OI5WHS/Shn7J0iDSv5rNcCZ93LJM=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.36.3 h1:qYMYlFR+rtLDUzuXoST1SDIdEPbX8xzuhdF90WsX1ss=
modernc.org/sqlite v1.36.3/go.mod h1:ADySlx7K4FdY5MaJcEv86hTJ0PjedAloTUuif0YS3ws=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

52
backend/main.go Normal file
View file

@ -0,0 +1,52 @@
package main
import (
"os"
"log"
"github.com/pocketbase/pocketbase"
)
// Copied from https://github.com/s-petr/longhabit/blob/main/backend/main.go
// application holds the core application state and configuration.
type application struct {
pb *pocketbase.PocketBase
config appConfig
}
// appConfig holds the application's configuration settings.
type appConfig struct {
dbDir string
}
// newApplication creates and initializes a new application instance.
func newApplication() *application {
dbDir := getEnvOrDefault("DB_DIR", "pb_data")
return &application{
pb: pocketbase.NewWithConfig(pocketbase.Config{DefaultDataDir: dbDir}),
config: appConfig{
dbDir: dbDir,
},
}
}
func main() {
app := newApplication()
app.SetupCache()
app.SetupCrons()
app.SetupRoutes()
log.Fatal(app.pb.Start())
}
// getEnvOrDefault retrieves the value of an environment variable by key.
// If the environment variable is empty or not set, it returns the defaultValue.
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}

View file

@ -0,0 +1,178 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3273110370",
"max": 0,
"min": 0,
"name": "sanitized_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3520360348",
"max": 0,
"min": 0,
"name": "released_at",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text976907234",
"max": 0,
"min": 0,
"name": "layout",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url1456686396",
"name": "small_image",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url2615033119",
"name": "small_image_back",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url2291853061",
"name": "normal_image",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url2961008824",
"name": "normal_image_back",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text2363381545",
"max": 0,
"min": 0,
"name": "type",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "select3054531206",
"maxSelect": 2,
"name": "color_identity",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"B",
"W",
"R",
"G",
"U"
]
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_1905410326",
"indexes": [],
"listRule": null,
"name": "carte",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326");
return app.delete(collection);
})

View file

@ -0,0 +1,101 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3273110370",
"max": 0,
"min": 0,
"name": "sanitized_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1997877400",
"max": 0,
"min": 0,
"name": "code",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "date3520360348",
"max": "",
"min": "",
"name": "released_at",
"presentable": false,
"required": false,
"system": false,
"type": "date"
},
{
"exceptDomains": null,
"hidden": false,
"id": "url659239397",
"name": "icon_uri",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}
],
"id": "pbc_3912384429",
"indexes": [],
"listRule": null,
"name": "set",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429");
return app.delete(collection);
})

View file

@ -0,0 +1,78 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text3273110370",
"max": 0,
"min": 0,
"name": "sanitized_name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "pbc_3912384429",
"hidden": false,
"id": "relation2492286417",
"maxSelect": 999,
"minSelect": 0,
"name": "sets",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}
],
"id": "pbc_749385185",
"indexes": [],
"listRule": null,
"name": "brawlset",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_749385185");
return app.delete(collection);
})

View file

@ -0,0 +1,45 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// remove field
collection.fields.removeById("text3520360348")
// add field
collection.fields.addAt(10, new Field({
"hidden": false,
"id": "date3520360348",
"max": "",
"min": "",
"name": "released_at",
"presentable": false,
"required": false,
"system": false,
"type": "date"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// add field
collection.fields.addAt(3, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3520360348",
"max": 0,
"min": 0,
"name": "released_at",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// remove field
collection.fields.removeById("date3520360348")
return app.save(collection)
})

View file

@ -0,0 +1,94 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// add field
collection.fields.addAt(11, new Field({
"cascadeDelete": false,
"collectionId": "pbc_3912384429",
"hidden": false,
"id": "relation3860080092",
"maxSelect": 1,
"minSelect": 0,
"name": "set",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
// add field
collection.fields.addAt(12, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3805467153",
"max": 0,
"min": 0,
"name": "set_code",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(13, new Field({
"hidden": false,
"id": "number3402113753",
"max": null,
"min": null,
"name": "price",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}))
// add field
collection.fields.addAt(14, new Field({
"exceptDomains": null,
"hidden": false,
"id": "url518645060",
"name": "cardmarket_url",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": false,
"type": "url"
}))
// add field
collection.fields.addAt(15, new Field({
"hidden": false,
"id": "bool1734659578",
"name": "can_be_commander",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// remove field
collection.fields.removeById("relation3860080092")
// remove field
collection.fields.removeById("text3805467153")
// remove field
collection.fields.removeById("number3402113753")
// remove field
collection.fields.removeById("url518645060")
// remove field
collection.fields.removeById("bool1734659578")
return app.save(collection)
})

View file

@ -0,0 +1,140 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "select3054531206",
"maxSelect": 2,
"name": "color_identity",
"presentable": false,
"required": false,
"system": false,
"type": "select",
"values": [
"B",
"R",
"G",
"U",
"W"
]
},
{
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "relation3479234172",
"maxSelect": 1,
"minSelect": 0,
"name": "owner",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1905410326",
"hidden": false,
"id": "relation3635975509",
"maxSelect": 999,
"minSelect": 0,
"name": "cartes",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"cascadeDelete": false,
"collectionId": "pbc_1905410326",
"hidden": false,
"id": "relation1121130682",
"maxSelect": 1,
"minSelect": 0,
"name": "commander",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"cascadeDelete": false,
"collectionId": "pbc_749385185",
"hidden": false,
"id": "relation1826942456",
"maxSelect": 1,
"minSelect": 0,
"name": "brawlset",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_1755402631",
"indexes": [],
"listRule": null,
"name": "deck",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1755402631");
return app.delete(collection);
})

View file

@ -0,0 +1,42 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_749385185")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": true,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_749385185")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
})

View file

@ -0,0 +1,74 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": true,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// update field
collection.fields.addAt(12, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3805467153",
"max": 0,
"min": 0,
"name": "set_code",
"pattern": "",
"presentable": true,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// update field
collection.fields.addAt(12, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text3805467153",
"max": 0,
"min": 0,
"name": "set_code",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
})

View file

@ -0,0 +1,42 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1755402631")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": true,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1755402631")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
})

View file

@ -0,0 +1,42 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": true,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update field
collection.fields.addAt(1, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
})

View file

@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update collection data
unmarshal({
"createRule": "",
"deleteRule": "",
"listRule": "",
"updateRule": "",
"viewRule": ""
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update collection data
unmarshal({
"createRule": null,
"deleteRule": null,
"listRule": null,
"updateRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

View file

@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update collection data
unmarshal({
"name": "mtg_set"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update collection data
unmarshal({
"name": "set"
}, collection)
return app.save(collection)
})

View file

@ -0,0 +1,42 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1755402631")
// update field
collection.fields.addAt(0, new Field({
"autogeneratePattern": "[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}",
"hidden": false,
"id": "text3208210256",
"max": 36,
"min": 36,
"name": "id",
"pattern": "^[a-z0-9\\-]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1755402631")
// update field
collection.fields.addAt(0, new Field({
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
}))
return app.save(collection)
})

View file

@ -0,0 +1,42 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update field
collection.fields.addAt(0, new Field({
"autogeneratePattern": "[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}",
"hidden": false,
"id": "text3208210256",
"max": 36,
"min": 36,
"name": "id",
"pattern": "^[a-z0-9\\-]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update field
collection.fields.addAt(0, new Field({
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
}))
return app.save(collection)
})

View file

@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// add field
collection.fields.addAt(6, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text2363381545",
"max": 0,
"min": 0,
"name": "type",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// remove field
collection.fields.removeById("text2363381545")
return app.save(collection)
})

View file

@ -0,0 +1,42 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update field
collection.fields.addAt(6, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text2363381545",
"max": 0,
"min": 0,
"name": "type",
"pattern": "",
"presentable": true,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_3912384429")
// update field
collection.fields.addAt(6, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text2363381545",
"max": 0,
"min": 0,
"name": "type",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
})

View file

@ -0,0 +1,95 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text724990059",
"max": 0,
"min": 0,
"name": "title",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "relation3182418120",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"convertURLs": false,
"hidden": false,
"id": "editor37359206",
"maxSize": 0,
"name": "article",
"presentable": false,
"required": false,
"system": false,
"type": "editor"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_1125843985",
"indexes": [],
"listRule": null,
"name": "posts",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1125843985");
return app.delete(collection);
})

View file

@ -0,0 +1,95 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1125843985");
return app.delete(collection);
}, (app) => {
const collection = new Collection({
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text724990059",
"max": 0,
"min": 0,
"name": "title",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "relation3182418120",
"maxSelect": 1,
"minSelect": 0,
"name": "author",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"convertURLs": false,
"hidden": false,
"id": "editor37359206",
"maxSize": 0,
"name": "article",
"presentable": false,
"required": false,
"system": false,
"type": "editor"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_1125843985",
"indexes": [],
"listRule": null,
"name": "posts",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
});
return app.save(collection);
})

View file

@ -0,0 +1,42 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// update field
collection.fields.addAt(0, new Field({
"autogeneratePattern": "[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}",
"hidden": false,
"id": "text3208210256",
"max": 36,
"min": 36,
"name": "id",
"pattern": "^[a-z0-9\\-]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// update field
collection.fields.addAt(0, new Field({
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
}))
return app.save(collection)
})

View file

@ -0,0 +1,40 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// update field
collection.fields.addAt(11, new Field({
"cascadeDelete": false,
"collectionId": "pbc_3912384429",
"hidden": false,
"id": "relation3860080092",
"maxSelect": 1,
"minSelect": 0,
"name": "mtg_set",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// update field
collection.fields.addAt(11, new Field({
"cascadeDelete": false,
"collectionId": "pbc_3912384429",
"hidden": false,
"id": "relation3860080092",
"maxSelect": 1,
"minSelect": 0,
"name": "set",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
})

View file

@ -0,0 +1,25 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// remove field
collection.fields.removeById("autodate3332085495")
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1905410326")
// add field
collection.fields.addAt(16, new Field({
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}))
return app.save(collection)
})

View file

@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1755402631")
// update collection data
unmarshal({
"createRule": "@request.auth.id != \"\"",
"deleteRule": "owner ?= @request.auth.id",
"listRule": "owner ?= @request.auth.id",
"updateRule": "owner ?= @request.auth.id",
"viewRule": "owner ?= @request.auth.id"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1755402631")
// update collection data
unmarshal({
"createRule": null,
"deleteRule": null,
"listRule": null,
"updateRule": null,
"viewRule": null
}, collection)
return app.save(collection)
})

176
backend/routes.go Normal file
View file

@ -0,0 +1,176 @@
package main
import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"os"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/apis"
)
const frontEndDevPort = "5173"
const frontEndProdPort = "3000"
type DeckImportCard struct {
Name string `json:"name"`
Amount int `json:"amount"`
}
type DeckImport struct {
Name string `json:"name"`
Url string `json:"url"`
SelectedBset string `json:"selected_bset"`
Commander string `json:"commander_name"`
Cards []DeckImportCard `json:"cards"`
}
func (app *application) SetupRoutes() {
app.pb.OnServe().BindFunc(func(se *core.ServeEvent) error {
mode := os.Getenv("GO_ENV")
path := ""
if mode == "" {
log.Fatal("You must the env variable GO_ENV to either 'dev' or 'production'")
}
if mode == "dev" {
path = fmt.Sprintf("http://localhost:%s", frontEndDevPort)
} else if mode == "production" {
path = fmt.Sprintf("http://localhost:%s", frontEndProdPort)
} else {
log.Fatal(fmt.Sprintf("Error, unrecognized GO_ENV : %s\nSet it to either 'dev' or 'production'", mode))
}
target, err := url.Parse(path)
if err != nil {
log.Println("[ERROR] failed to parse proxy target URL: %w", err)
}
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, fmt.Sprintf("proxy error: %v", err), http.StatusBadGateway)
}
// Cache Route
se.Router.GET("/json/{path...}", func(re *core.RequestEvent) error {
path := fmt.Sprintf("json/%s",re.Request.PathValue("path"))
cacheVal := app.pb.Store().Get(path)
if cacheVal != nil {
return re.JSON(http.StatusOK, cacheVal)
} else {
return re.JSON(http.StatusNotFound, map[string]string{"message": "json not found in cache"})
}
})
// Search API
se.Router.GET("/api/search", func(re *core.RequestEvent) error {
searchData := app.pb.Store().Get("searchData").([]CacheCarteSearch)
search := re.Request.URL.Query().Get("q")
resultData := filter(searchData, func(card CacheCarteSearch) bool { return strings.Contains(strings.ToLower(card.Name), strings.ToLower(search)) })
return re.JSON(http.StatusOK, resultData)
})
// Create deck API
se.Router.POST("/api/deck/create", func(re *core.RequestEvent) error {
authRecord := re.Auth
data := DeckImport{}
if err := re.BindBody(&data); err != nil {
log.Println(err)
return re.BadRequestError("Failed to read request data", err)
}
selectedBrawlset := Brawlset{}
err := app.pb.DB().
NewQuery(fmt.Sprintf("SELECT id, sets FROM brawlset WHERE sanitized_name = '%v'", data.SelectedBset)).
One(&selectedBrawlset)
if err != nil {
log.Println(err)
return re.BadRequestError("No brawlsets with this id", err)
}
possibleCards := []Carte{}
setsIds := []string{}
for _, v := range selectedBrawlset.Sets {
setsIds = append(setsIds, fmt.Sprintf("'%s'", v))
}
query := ""
if len(selectedBrawlset.Sets) > 1 {
query = fmt.Sprintf("SELECT id, name, color_identity FROM carte WHERE mtg_set IN (%s)", strings.Join(setsIds, ", "))
} else {
query = fmt.Sprintf("SELECT id, name, color_identity FROM carte WHERE mtg_set = %s", setsIds[0])
}
err = app.pb.DB().
NewQuery(query).
All(&possibleCards)
if err != nil {
log.Println(err)
return re.BadRequestError("Error with cards query", err)
}
collection, err := app.pb.FindCollectionByNameOrId("deck")
if err != nil {
return err
}
record := core.NewRecord(collection)
record.Set("name", data.Name)
record.Set("url", data.Url)
record.Set("brawlset", selectedBrawlset.ID)
record.Set("owner", authRecord.Id)
for _, c := range possibleCards {
if data.Commander == strings.Split(c.Name, " // ")[0] {
record.Set("commander", c.ID)
record.Set("color_identity", c.ColorIdentity)
}
}
cardInDeck := []DeckCard{}
for _, v := range data.Cards {
cardId := ""
for _, c := range possibleCards {
if v.Name == strings.Split(c.Name, " // ")[0] {
cardId = c.ID
}
}
if cardId == "" {
log.Println(fmt.Sprintf("Card not found : %s",v.Name))
}
cardInDeck = append(cardInDeck, DeckCard{ID: cardId, Amount: v.Amount})
}
record.Set("cartes", cardInDeck)
err = app.pb.Save(record)
if err != nil {
log.Println(err)
re.BadRequestError("Problem with creating deck", err)
}
return re.JSON(http.StatusOK, map[string]string{"message": "deck created"})
}).Bind(apis.RequireAuth())
// Proxy to svelte app
se.Router.Any("/{path...}", func(re *core.RequestEvent) error {
proxy.ServeHTTP(re.Response, re.Request)
return nil
})
return se.Next()
})
}
func filter(ss []CacheCarteSearch, test func(CacheCarteSearch) bool) (ret []CacheCarteSearch) {
for _, s := range ss {
if test(s) {
ret = append(ret, s)
}
}
return
}

252
backend/scryfall_api.go Normal file
View file

@ -0,0 +1,252 @@
package main
import (
"log"
"encoding/json"
"io/ioutil"
"net/http"
"time"
)
type ScryfallCard struct {
Object string `json:"object"`
ID string `json:"id"`
OracleID string `json:"oracle_id"`
MultiverseIds []int `json:"multiverse_ids"`
MtgoID int `json:"mtgo_id"`
MtgoFoilID int `json:"mtgo_foil_id"`
TcgplayerID int `json:"tcgplayer_id"`
CardmarketID int `json:"cardmarket_id"`
Name string `json:"name"`
Lang string `json:"lang"`
ReleasedAt string `json:"released_at"`
URI string `json:"uri"`
ScryfallURI string `json:"scryfall_uri"`
Layout string `json:"layout"`
HighresImage bool `json:"highres_image"`
ImageStatus string `json:"image_status"`
ImageUris struct {
Small string `json:"small"`
Normal string `json:"normal"`
Large string `json:"large"`
Png string `json:"png"`
ArtCrop string `json:"art_crop"`
BorderCrop string `json:"border_crop"`
} `json:"image_uris"`
CardFaces []struct {
Object string `json:"object"`
Name string `json:"name"`
ManaCost string `json:"mana_cost"`
TypeLine string `json:"type_line"`
OracleText string `json:"oracle_text"`
Colors []string `json:"colors"`
Power string `json:"power"`
Toughness string `json:"toughness"`
Artist string `json:"artist"`
ArtistID string `json:"artist_id"`
IllustrationID string `json:"illustration_id"`
ImageUris struct {
Small string `json:"small"`
Normal string `json:"normal"`
Large string `json:"large"`
Png string `json:"png"`
ArtCrop string `json:"art_crop"`
BorderCrop string `json:"border_crop"`
} `json:"image_uris"`
ColorIndicator []string `json:"color_indicator,omitempty"`
} `json:"card_faces"`
ManaCost string `json:"mana_cost"`
Cmc float32 `json:"cmc"`
TypeLine string `json:"type_line"`
OracleText string `json:"oracle_text"`
Power string `json:"power"`
Toughness string `json:"toughness"`
Colors []string `json:"colors"`
ColorIdentity []string `json:"color_identity"`
Keywords []string `json:"keywords"`
Legalities struct {
Standard string `json:"standard"`
Future string `json:"future"`
Historic string `json:"historic"`
Timeless string `json:"timeless"`
Gladiator string `json:"gladiator"`
Pioneer string `json:"pioneer"`
Explorer string `json:"explorer"`
Modern string `json:"modern"`
Legacy string `json:"legacy"`
Pauper string `json:"pauper"`
Vintage string `json:"vintage"`
Penny string `json:"penny"`
Commander string `json:"commander"`
Oathbreaker string `json:"oathbreaker"`
Standardbrawl string `json:"standardbrawl"`
Brawl string `json:"brawl"`
Alchemy string `json:"alchemy"`
Paupercommander string `json:"paupercommander"`
Duel string `json:"duel"`
Oldschool string `json:"oldschool"`
Premodern string `json:"premodern"`
Predh string `json:"predh"`
} `json:"legalities"`
Games []string `json:"games"`
Reserved bool `json:"reserved"`
GameChanger bool `json:"game_changer"`
Foil bool `json:"foil"`
Nonfoil bool `json:"nonfoil"`
Finishes []string `json:"finishes"`
Oversized bool `json:"oversized"`
Promo bool `json:"promo"`
Reprint bool `json:"reprint"`
Variation bool `json:"variation"`
SetID string `json:"set_id"`
Set string `json:"set"`
SetName string `json:"set_name"`
SetType string `json:"set_type"`
SetURI string `json:"set_uri"`
SetSearchURI string `json:"set_search_uri"`
ScryfallSetURI string `json:"scryfall_set_uri"`
RulingsURI string `json:"rulings_uri"`
PrintsSearchURI string `json:"prints_search_uri"`
CollectorNumber string `json:"collector_number"`
Digital bool `json:"digital"`
Rarity string `json:"rarity"`
CardBackID string `json:"card_back_id"`
Artist string `json:"artist"`
ArtistIds []string `json:"artist_ids"`
IllustrationID string `json:"illustration_id"`
BorderColor string `json:"border_color"`
Frame string `json:"frame"`
FullArt bool `json:"full_art"`
Textless bool `json:"textless"`
Booster bool `json:"booster"`
StorySpotlight bool `json:"story_spotlight"`
EdhrecRank int `json:"edhrec_rank"`
PennyRank int `json:"penny_rank"`
Prices struct {
Usd string `json:"usd"`
UsdFoil string `json:"usd_foil"`
UsdEtched interface{} `json:"usd_etched"`
Eur string `json:"eur"`
EurFoil string `json:"eur_foil"`
Tix string `json:"tix"`
} `json:"prices"`
RelatedUris struct {
Gatherer string `json:"gatherer"`
TcgplayerInfiniteArticles string `json:"tcgplayer_infinite_articles"`
TcgplayerInfiniteDecks string `json:"tcgplayer_infinite_decks"`
Edhrec string `json:"edhrec"`
} `json:"related_uris"`
PurchaseUris struct {
Tcgplayer string `json:"tcgplayer"`
Cardmarket string `json:"cardmarket"`
Cardhoarder string `json:"cardhoarder"`
} `json:"purchase_uris"`
}
type ScryfallCardAPI struct {
Object string `json:"object"`
TotalCards int `json:"total_cards"`
HasMore bool `json:"has_more"`
NextPage string `json:"next_page"`
Data []ScryfallCard `json:"data"`
}
type ScryfallSet struct {
Object string `json:"object"`
ID string `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
Uri string `json:"uri"`
ScryfallUri string `json:"scryfall_uri"`
SearchUri string `json:"search_uri"`
ReleasedAt string `json:"released_at"`
SetType string `json:"set_type"`
CardCount int `json:"card_count"`
ParentSetCode string `json:"parent_set_code"`
Digital bool `json:"digital"`
NonfoilOnly bool `json:"nonfoil_only"`
FoilOnly bool `json:"foil_only"`
IconSvgUri string `json:"icon_svg_uri"`
}
type ScryfallSetAPI struct {
Object string `json:"object"`
HasMore bool `json:"has_more"`
Data []ScryfallSet `json:"data"`
}
func fetchApiCards(url string, cards []ScryfallCard) []ScryfallCard {
reqClient := http.Client{
Timeout: time.Second * 30, // Timeout after 2 seconds
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("User-Agent", "brawlset")
res, err := reqClient.Do(req)
if err != nil {
log.Fatal(err)
}
if res.Body != nil {
defer res.Body.Close()
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}
var scryfallData ScryfallCardAPI
err = json.Unmarshal(body, &scryfallData)
if err != nil {
log.Fatal(err)
}
cards = append(cards, scryfallData.Data...)
if scryfallData.HasMore {
return fetchApiCards(scryfallData.NextPage, cards)
} else {
return cards
}
}
func fetchApiSets() []ScryfallSet {
url := "https://api.scryfall.com/sets"
reqClient := http.Client{
Timeout: time.Second * 30, // Timeout after 2 seconds
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("User-Agent", "brawlset")
res, err := reqClient.Do(req)
if err != nil {
log.Fatal(err)
}
if res.Body != nil {
defer res.Body.Close()
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}
var scryFallData ScryfallSetAPI
err = json.Unmarshal(body, &scryFallData)
if err != nil {
log.Fatal(err)
}
return scryFallData.Data
}

56
backend/types.go Normal file
View file

@ -0,0 +1,56 @@
package main
import (
"github.com/pocketbase/pocketbase/tools/types"
)
type MtgSet struct {
ID string `db:"id" json:"id"`
Code string `db:"code" json:"code"`
Name string `db:"name" json:"name"`
SanitizedName string `db:"sanitized_name" json:"sanitized_name"`
ReleasedAt string `db:"release_at" json:"released_at"`
IconUri string `db:"icon_uri" json:"icon_uri"`
SetType string `db:"type" json:"type"`
}
type Brawlset struct {
ID string `db:"id" json:"id"`
Name string `db:"name" json:"name"`
SanitizedName string `db:"sanitized_name" json:"sanitized_name"`
Sets types.JSONArray[string] `db:"sets" json:"sets"`
}
type Carte struct {
ID string `db:"id" json:"id"`
Name string `db:"name" json:"name"`
SanitizedName string `db:"sanitized_name" json:"sanitized_name"`
Layout string `db:"layout" json:"layout"`
SmallImage string `db:"small_image" json:"small_image"`
SmallImageBack string `db:"small_image_back" json:"small_image_back"`
NormalImage string `db:"normal_image" json:"normal_image"`
NormalImageBack string `db:"normal_image_back" json:"normal_image_back"`
CardType string `db:"type" json:"type"`
ColorIdentity types.JSONArray[string] `db:"color_identity" json:"color_identity"`
ReleasedAt string `db:"released_at" json:"released_at"`
MtgSet string `db:"mtg_set" json:"mtg_set"`
SetCode string `db:"set_code" json:"set_code"`
Price string `db:"price" json:"price"`
CardmarketUri string `db:"cardmarket_url" json:"cardmarket_url"`
CanBeCommander bool `db:"can_be_commander" json:"can_be_commander"`
}
type DeckCard struct {
ID string `json:"id"`
Amount int `json:"amount"`
}
type Deck struct {
ID string `db:"id"`
Name string `db:"name"`
ColorIdentity types.JSONArray[string] `db:"color_identity"`
Owner string `db:"owner"`
Commander string `db:"commander"`
Brawlset string `db:"brawlset"`
Cards types.JSONArray[DeckCard] `db:"cartes"`
}

273
backend/utils.go Normal file
View file

@ -0,0 +1,273 @@
package main
import (
"regexp"
"unicode/utf8"
"slices"
"strings"
"sort"
"encoding/json"
"log"
"fmt"
"time"
)
// https://stackoverflow.com/a/75996347
var defaultDiacriticsRemovalMap = []struct {
base string
letters string
}{
{"A", "\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F"},
{"AA", "\uA732"},
{"AE", "\u00C6\u01FC\u01E2"},
{"AO", "\uA734"},
{"AU", "\uA736"},
{"AV", "\uA738\uA73A"},
{"AY", "\uA73C"},
{"B", "\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181"},
{"C", "\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E"},
{"D", "\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0"},
{"DZ", "\u01F1\u01C4"},
{"Dz", "\u01F2\u01C5"},
{"E", "\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E"},
{"F", "\u0046\u24BB\uFF26\u1E1E\u0191\uA77B"},
{"G", "\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E"},
{"H", "\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D"},
{"I", "\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197"},
{"J", "\u004A\u24BF\uFF2A\u0134\u0248"},
{"K", "\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2"},
{"L", "\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780"},
{"LJ", "\u01C7"},
{"Lj", "\u01C8"},
{"M", "\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C"},
{"N", "\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4"},
{"NJ", "\u01CA"},
{"Nj", "\u01CB"},
{"O", "\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C"},
{"OI", "\u01A2"},
{"OO", "\uA74E"},
{"OU", "\u0222"},
{"OE", "\u008C\u0152"},
{"oe", "\u009C\u0153"},
{"P", "\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754"},
{"Q", "\u0051\u24C6\uFF31\uA756\uA758\u024A"},
{"R", "\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782"},
{"S", "\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784"},
{"T", "\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786"},
{"TZ", "\uA728"},
{"U", "\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244"},
{"V", "\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245"},
{"VY", "\uA760"},
{"W", "\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72"},
{"X", "\u0058\u24CD\uFF38\u1E8A\u1E8C"},
{"Y", "\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE"},
{"Z", "\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762"},
{"a", "\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250"},
{"aa", "\uA733"},
{"ae", "\u00E6\u01FD\u01E3"},
{"ao", "\uA735"},
{"au", "\uA737"},
{"av", "\uA739\uA73B"},
{"ay", "\uA73D"},
{"b", "\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253"},
{"c", "\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184"},
{"d", "\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A"},
{"dz", "\u01F3\u01C6"},
{"e", "\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD"},
{"f", "\u0066\u24D5\uFF46\u1E1F\u0192\uA77C"},
{"g", "\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F"},
{"h", "\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265"},
{"hv", "\u0195"},
{"i", "\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131"},
{"j", "\u006A\u24D9\uFF4A\u0135\u01F0\u0249"},
{"k", "\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3"},
{"l", "\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747"},
{"lj", "\u01C9"},
{"m", "\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F"},
{"n", "\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5"},
{"nj", "\u01CC"},
{"o", "\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275"},
{"oi", "\u01A3"},
{"ou", "\u0223"},
{"oo", "\uA74F"},
{"p", "\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755"},
{"q", "\u0071\u24E0\uFF51\u024B\uA757\uA759"},
{"r", "\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783"},
{"s", "\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B"},
{"t", "\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787"},
{"tz", "\uA729"},
{"u", "\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289"},
{"v", "\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C"},
{"vy", "\uA761"},
{"w", "\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73"},
{"x", "\u0078\u24E7\uFF58\u1E8B\u1E8D"},
{"y", "\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF"},
{"z", "\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763"},
}
var diacriticsMap = map[rune]string{}
func removeDiacritics(str string) string {
for _, mapping := range defaultDiacriticsRemovalMap {
letters := mapping.letters
for _, letter := range letters {
diacriticsMap[letter] = mapping.base
}
}
reg := regexp.MustCompile("[^\u0000-\u007E]")
return reg.ReplaceAllStringFunc(str, func(a string) string {
r, _ := utf8.DecodeRuneInString(a)
if replacement, ok := diacriticsMap[rune(r)]; ok {
return replacement
}
return a
})
}
func SanitizeName(str string) string {
re := regexp.MustCompile(`[^a-zA-Z0-9]+`)
return strings.ToLower(re.ReplaceAllString(removeDiacritics(str), "-"))
}
// Function to create a SQL query to Upsert cards
// Used in : crons.go
func CreateBulkCardsUpsertQuery(data []ScryfallCard) string {
totalCards := len(data)
twoFacedLayouts := []string{"transform","modal_dfc","double_faced_token","reversible_card"}
values := make([]string, totalCards)
for i := 0; i < totalCards; i++ {
id := data[i].ID
name := data[i].Name
sanitizedName := SanitizeName(data[i].Name) // Sanitized Name
layout := data[i].Layout
smallImage := ""
smallImageBack := ""
normalImage := ""
normalImageBack := ""
// Two Faced
if slices.Contains(twoFacedLayouts,data[i].Layout) {
smallImage = data[i].CardFaces[0].ImageUris.Small
smallImageBack = data[i].CardFaces[0].ImageUris.Normal
normalImage = data[i].CardFaces[1].ImageUris.Small
normalImageBack = data[i].CardFaces[1].ImageUris.Normal
} else {
smallImage = data[i].ImageUris.Small
normalImage = data[i].ImageUris.Normal
}
// Type
cardTypeLine := strings.ToLower(data[i].TypeLine)
cardType := ""
if strings.Contains(cardTypeLine, "creature") {
cardType = "creature"
} else if strings.Contains(cardTypeLine, "planeswalker") {
cardType = "planeswalker"
} else if strings.Contains(cardTypeLine, "artifact") {
cardType = "artifact"
} else if strings.Contains(cardTypeLine, "instant") {
cardType = "instant"
} else if strings.Contains(cardTypeLine, "enchantment") {
cardType = "enchantment"
} else if strings.Contains(cardTypeLine, "sorcery") {
cardType = "sorcery"
} else if strings.Contains(cardTypeLine, "land") {
cardType = "land"
} else {
cardType = "unknown"
}
colorIdentityBytes, err := json.Marshal(data[i].ColorIdentity)
if err != nil {
log.Fatal(err)
}
colorIdentity := string(colorIdentityBytes)
releasedAt := data[i].ReleasedAt
setId := data[i].SetID
setCode := data[i].Set
price := data[i].Prices.Eur
cardmarketUri := data[i].PurchaseUris.Cardmarket
canBeCommander := (strings.Contains(cardTypeLine, "legendary") && cardType == "creature") || cardType == "planeswalker"
values[i] = fmt.Sprintf(`("%v", "%s", "%v", "%v", "%v", "%v", "%v", "%v", "%v", '%v', "%v", "%v", "%v", "%v", "%v", "%v")`,id, name, sanitizedName, layout, smallImage, smallImageBack, normalImage, normalImageBack, cardType, colorIdentity, releasedAt, setId, setCode, price, cardmarketUri, canBeCommander)
}
upsertQuery := fmt.Sprintf("INSERT INTO carte(id, name, sanitized_name, layout, small_image, small_image_back, normal_image, normal_image_back, type, color_identity, released_at, mtg_set, set_code, price, cardmarket_url, can_be_commander) VALUES %s ON CONFLICT (id) DO UPDATE SET price = excluded.price", strings.Join(values, ", "))
return upsertQuery
}
// Function to create a SQL query to Insert sets
// Used in : crons.go
func CreateBulkSetsInsertQuery(data []ScryfallSet) string {
totalSets := len(data)
values := make([]string, totalSets)
for i := 0; i < totalSets; i++ {
values[i] = fmt.Sprintf(`("%v", "%v", "%v", "%v", "%v", "%v", "%v")`,data[i].ID, data[i].Name, SanitizeName(data[i].Name), data[i].Code, data[i].IconSvgUri, data[i].SetType, data[i].ReleasedAt)
}
insertQuery := fmt.Sprintf("INSERT OR IGNORE INTO mtg_set(id, name, sanitized_name, code, icon_uri, type, released_at) VALUES %s", strings.Join(values, ", "))
return insertQuery
}
// Timer used to time a function
// Used in : cache.go
func timer(name string) func() {
start := time.Now()
return func() {
log.Printf("%s took %v\n", name, time.Since(start))
}
}
// Function to create all possible combinations of an array with a said length
// Used in : cache.go
// Source (in java) : https://hmkcode.com/calculate-find-all-possible-combinations-of-an-array-using-java/
func CreateCombination(s []string, k int) []string {
res := []string{}
ignore := make([]int, len(s) - k)
combination := make([]string, k)
for w := 0; w < len(ignore); w++ {
ignore[w] = len(s) - 1 - w
}
// Edit fonction above to make it ascending by default
sort.Slice(ignore, func(i, j int) bool { return ignore[i] < ignore[j]})
i := 0
r := 0
g := 0
terminate := false
for !terminate {
for i < len(s) && r < k {
if i != ignore[g] {
combination[r] = s[i];
r++
i++
} else {
if g != len(ignore) - 1 {
g++
}
i++
}
}
i = 0
r = 0
g = 0
res = append(res, strings.Join(combination,""))
terminate = true
for w := 0; w < len(ignore); w++ {
if ignore[w] > w {
ignore[w]--
if w > 0 {
ignore[w-1] = ignore[w]-1
}
terminate = false
break
}
}
}
return res
}