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

5
.dockerignore Normal file
View file

@ -0,0 +1,5 @@
frontend/node_modules
frontend/.svelte-kit
frontend/build
backend/pb_data

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
frontend/node_modules
frontend/.svelte-kit
frontend/build
backend/pb_data

47
Dockerfile Normal file
View file

@ -0,0 +1,47 @@
FROM golang:1.24.1 AS gobuild
WORKDIR /usr/src/app
# Download Go modules
COPY backend/go.mod backend/go.sum .
RUN go mod download
# Copy the source code. Note the slash at the end, as explained in
# https://docs.docker.com/engine/reference/builder/#copy
COPY backend/*.go ./
# Build
RUN CGO_ENABLED=0 GOOS=linux go build -o custom_pocketbase
FROM oven/bun:latest AS base
WORKDIR /usr/src/app
# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY frontend/package.json frontend/bun.lock /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY frontend .
# [optional] tests & build
ENV NODE_ENV=production
RUN bun run build
# copy production dependencies and source code into final image
FROM oven/bun:alpine AS release
WORKDIR /usr/src/app
ENV GO_ENV=production
COPY start_server.sh .
COPY --from=prerelease /usr/src/app/build .
COPY --from=gobuild /usr/src/app/custom_pocketbase .
COPY backend/pb_migrations ./pb_migrations
# run the app
EXPOSE 8090
ENTRYPOINT [ "./start_server.sh" ]

38
README.md Normal file
View file

@ -0,0 +1,38 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

15
TODO.md Normal file
View file

@ -0,0 +1,15 @@
# Choses qu'il me reste à faire pour une 100% parité avec l'ancienne version
- [x] création de decks
- [x] Backend
- [x] Frontend
- [ ] affichage des top commandants
- [ ] backend (créer le cache commandant)
- [ ] frontend (créer les page avec slug)
- [x] Filtrer les cache par pourcentage d'utilisation
- [x] Affichage des stats par carte commandants
- [x] Backend
- [x] Frontend
- [ ] Affichage des stats par carte normale
- [ ] Backend
- [ ] Frontend

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
}

1
frontend/.npmrc Normal file
View file

@ -0,0 +1 @@
engine-strict=true

290
frontend/bun.lock Normal file
View file

@ -0,0 +1,290 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "brawlset-rewrite",
"dependencies": {
"pocketbase": "^0.25.2",
"svelte-adapter-bun": "^0.5.2",
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.0.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.0.0",
"vite": "^6.2.5",
},
},
},
"packages": {
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.2", "", { "os": "android", "cpu": "arm" }, "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.2", "", { "os": "android", "cpu": "x64" }, "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.2", "", { "os": "linux", "cpu": "arm" }, "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.2", "", { "os": "linux", "cpu": "none" }, "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.2", "", { "os": "linux", "cpu": "x64" }, "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.2", "", { "os": "none", "cpu": "arm64" }, "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.2", "", { "os": "none", "cpu": "x64" }, "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.2", "", { "os": "win32", "cpu": "x64" }, "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.39.0", "", { "os": "android", "cpu": "arm" }, "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.39.0", "", { "os": "android", "cpu": "arm64" }, "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.39.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.39.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.39.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.39.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.39.0", "", { "os": "linux", "cpu": "arm" }, "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.39.0", "", { "os": "linux", "cpu": "arm" }, "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.39.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.39.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.39.0", "", { "os": "linux", "cpu": "none" }, "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.39.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.39.0", "", { "os": "linux", "cpu": "none" }, "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.39.0", "", { "os": "linux", "cpu": "none" }, "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.39.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.39.0", "", { "os": "linux", "cpu": "x64" }, "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.39.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.39.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.39.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.39.0", "", { "os": "win32", "cpu": "x64" }, "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug=="],
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="],
"@sveltejs/adapter-auto": ["@sveltejs/adapter-auto@4.0.0", "", { "dependencies": { "import-meta-resolve": "^4.1.0" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-kmuYSQdD2AwThymQF0haQhM8rE5rhutQXG4LNbnbShwhMO4qQGnKaaTy+88DuNSuoQDi58+thpq8XpHc1+oEKQ=="],
"@sveltejs/kit": ["@sveltejs/kit@2.20.5", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", "devalue": "^5.1.0", "esm-env": "^1.2.2", "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.3 || ^6.0.0" }, "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-zT/97KvVUo19jEGZa972ls7KICjPCB53j54TVxnEFT5VEwL16G+YFqRVwJbfxh7AmS7/Ptr1rKF7Qt4FBMDNlw=="],
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@5.0.3", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.0", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.15", "vitefu": "^1.0.4" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw=="],
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@4.0.1", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.3", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.29.2", "tailwindcss": "4.1.3" } }, "sha512-H/6r6IPFJkCfBJZ2dKZiPJ7Ueb2wbL592+9bQEl2r73qbX6yGnmQVIfiUvDRB2YI0a3PWDrzUwkvQx1XW1bNkA=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.3", "@tailwindcss/oxide-darwin-arm64": "4.1.3", "@tailwindcss/oxide-darwin-x64": "4.1.3", "@tailwindcss/oxide-freebsd-x64": "4.1.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.3", "@tailwindcss/oxide-linux-arm64-musl": "4.1.3", "@tailwindcss/oxide-linux-x64-gnu": "4.1.3", "@tailwindcss/oxide-linux-x64-musl": "4.1.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.3", "@tailwindcss/oxide-win32-x64-msvc": "4.1.3" } }, "sha512-t16lpHCU7LBxDe/8dCj9ntyNpXaSTAgxWm1u2XQP5NiIu4KGSyrDJJRlK9hJ4U9yJxx0UKCVI67MJWFNll5mOQ=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.3", "", { "os": "android", "cpu": "arm64" }, "sha512-cxklKjtNLwFl3mDYw4XpEfBY+G8ssSg9ADL4Wm6//5woi3XGqlxFsnV5Zb6v07dxw1NvEX2uoqsxO/zWQsgR+g=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mqkf2tLR5VCrjBvuRDwzKNShRu99gCAVMkVsaEOFvv6cCjlEKXRecPu9DEnxp6STk5z+Vlbh1M5zY3nQCXMXhw=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-7sGraGaWzXvCLyxrc7d+CCpUN3fYnkkcso3rCzwUmo/LteAl2ZGCDlGvDD8Y/1D3ngxT8KgDj1DSwOnNewKhmg=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-E2+PbcbzIReaAYZe997wb9rId246yDkCwAakllAWSGqe6VTg9hHle67hfH6ExjpV2LSK/siRzBUs5wVff3RW9w=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3", "", { "os": "linux", "cpu": "arm" }, "sha512-GvfbJ8wjSSjbLFFE3UYz4Eh8i4L6GiEYqCtA8j2Zd2oXriPuom/Ah/64pg/szWycQpzRnbDiJozoxFU2oJZyfg=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-35UkuCWQTeG9BHcBQXndDOrpsnt3Pj9NVIB4CgNiKmpG8GnCNXeMczkUpOoqcOhO6Cc/mM2W7kaQ/MTEENDDXg=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-dm18aQiML5QCj9DQo7wMbt1Z2tl3Giht54uVR87a84X8qRtuXxUqnKQkRDK5B4bCOmcZ580lF9YcoMkbDYTXHQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-LMdTmGe/NPtGOaOfV2HuO7w07jI3cflPrVq5CXl+2O93DCewADK0uW1ORNAcfu2YxDUS035eY2W38TxrsqngxA=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.3", "", { "os": "linux", "cpu": "x64" }, "sha512-aalNWwIi54bbFEizwl1/XpmdDrOaCjRFQRgtbv9slWjmNPuJJTIKPHf5/XXDARc9CneW9FkSTqTbyvNecYAEGw=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-PEj7XR4OGTGoboTIAdXicKuWl4EQIjKHKuR+bFy9oYN7CFZo0eu74+70O4XuERX4yjqVZGAkCdglBODlgqcCXg=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.3", "", { "os": "win32", "cpu": "x64" }, "sha512-T8gfxECWDBENotpw3HR9SmNiHC9AOJdxs+woasRZ8Q/J4VHN0OMs7F+4yVNZ9EVN26Wv6mZbK0jv7eHYuLJLwA=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.3", "", { "dependencies": { "@tailwindcss/node": "4.1.3", "@tailwindcss/oxide": "4.1.3", "tailwindcss": "4.1.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-lUI/QaDxLtlV52Lho6pu07CG9pSnRYLOPmKGIQjyHdTBagemc6HmgZxyjGAQ/5HMPrNeWBfTVIpQl0/jLXvWHQ=="],
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="],
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
"devalue": ["devalue@5.1.1", "", {}, "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="],
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
"esbuild": ["esbuild@0.25.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.2", "@esbuild/android-arm": "0.25.2", "@esbuild/android-arm64": "0.25.2", "@esbuild/android-x64": "0.25.2", "@esbuild/darwin-arm64": "0.25.2", "@esbuild/darwin-x64": "0.25.2", "@esbuild/freebsd-arm64": "0.25.2", "@esbuild/freebsd-x64": "0.25.2", "@esbuild/linux-arm": "0.25.2", "@esbuild/linux-arm64": "0.25.2", "@esbuild/linux-ia32": "0.25.2", "@esbuild/linux-loong64": "0.25.2", "@esbuild/linux-mips64el": "0.25.2", "@esbuild/linux-ppc64": "0.25.2", "@esbuild/linux-riscv64": "0.25.2", "@esbuild/linux-s390x": "0.25.2", "@esbuild/linux-x64": "0.25.2", "@esbuild/netbsd-arm64": "0.25.2", "@esbuild/netbsd-x64": "0.25.2", "@esbuild/openbsd-arm64": "0.25.2", "@esbuild/openbsd-x64": "0.25.2", "@esbuild/sunos-x64": "0.25.2", "@esbuild/win32-arm64": "0.25.2", "@esbuild/win32-ia32": "0.25.2", "@esbuild/win32-x64": "0.25.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ=="],
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
"esrap": ["esrap@1.4.6", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw=="],
"fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"globalyzer": ["globalyzer@0.1.0", "", {}, "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="],
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="],
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.2", "", { "os": "linux", "cpu": "arm" }, "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"pocketbase": ["pocketbase@0.25.2", "", {}, "sha512-ONZl1+qHJMnhR2uacBlBJ90lm7njtL/zy0606+1ROfK9hSL4LRBRc8r89rMcNRzPzRqCNyoFTh2Qg/lYXdEC1w=="],
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"rollup": ["rollup@4.39.0", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.39.0", "@rollup/rollup-android-arm64": "4.39.0", "@rollup/rollup-darwin-arm64": "4.39.0", "@rollup/rollup-darwin-x64": "4.39.0", "@rollup/rollup-freebsd-arm64": "4.39.0", "@rollup/rollup-freebsd-x64": "4.39.0", "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", "@rollup/rollup-linux-arm-musleabihf": "4.39.0", "@rollup/rollup-linux-arm64-gnu": "4.39.0", "@rollup/rollup-linux-arm64-musl": "4.39.0", "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", "@rollup/rollup-linux-riscv64-gnu": "4.39.0", "@rollup/rollup-linux-riscv64-musl": "4.39.0", "@rollup/rollup-linux-s390x-gnu": "4.39.0", "@rollup/rollup-linux-x64-gnu": "4.39.0", "@rollup/rollup-linux-x64-musl": "4.39.0", "@rollup/rollup-win32-arm64-msvc": "4.39.0", "@rollup/rollup-win32-ia32-msvc": "4.39.0", "@rollup/rollup-win32-x64-msvc": "4.39.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g=="],
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
"sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"svelte": ["svelte@5.25.10", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.6", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-tOUlvm3goAhmELYKWYX3SchYeqlVlIcUfKA4qIgd6Urm7qDw3KiZP/wJtoRv3ZeRUHPorM9f6+lOlbFxUN8QoA=="],
"svelte-adapter-bun": ["svelte-adapter-bun@0.5.2", "", { "dependencies": { "tiny-glob": "^0.2.9" } }, "sha512-xEtFgaal6UgrCwwkSIcapO9kopoFNUYCYqyKCikdqxX9bz2TDYnrWQZ7qBnkunMxi1HOIERUCvTcebYGiarZLA=="],
"svelte-check": ["svelte-check@4.1.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg=="],
"tailwindcss": ["tailwindcss@4.1.3", "", {}, "sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g=="],
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
"tiny-glob": ["tiny-glob@0.2.9", "", { "dependencies": { "globalyzer": "0.1.0", "globrex": "^0.1.2" } }, "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg=="],
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"vite": ["vite@6.2.6", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw=="],
"vitefu": ["vitefu@1.0.6", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["vite"] }, "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA=="],
"zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="],
}
}

29
frontend/package.json Normal file
View file

@ -0,0 +1,29 @@
{
"name": "brawlset-rewrite",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.0.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.0.0",
"vite": "^6.2.5"
},
"dependencies": {
"pocketbase": "^0.25.2",
"svelte-adapter-bun": "^0.5.2"
}
}

34
frontend/src/app.css Normal file
View file

@ -0,0 +1,34 @@
@import 'tailwindcss';
@font-face {
font-family: "Beleren";
src: url("/fonts/Beleren2016-Bold.woff");
}
@font-face {
font-family: "Inter-Tight-Normal";
src: url("/fonts/inter-tight-latin-400-normal.ttf");
}
@font-face {
font-family: "Inter-Tight-Italic";
src: url("/fonts/inter-tight-latin-400-italic.ttf");
}
@font-face {
font-family: "Inter-Tight-Bold";
src: url("/fonts/inter-tight-latin-800-normal.ttf");
}
@font-face {
font-family: "Inter-Tight-Bold-Italic";
src: url("/fonts/inter-tight-latin-800-italic.ttf");
}
.font-beleren {
font-family: 'Beleren';
}
.font-inter-tight {
font-family: 'Inter-Tight-Normal';
}

13
frontend/src/app.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

12
frontend/src/app.html Normal file
View file

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1,17 @@
<script>
let { name = "", url = "#", sanitizedName = "", smallImage = "", normalImage="", price = 0, cardmarketUri = "", numberOfDecks = 0, numberOfPossibleDecks = undefined, synergy = undefined } = $props()
</script>
<div class="flex flex-col w-full gap-0">
<a class="w-full" href={url}><img src={normalImage} alt={"Scan de carte pour " + name} class="w-full rounded-md aspect-[488/680]" loading="lazy"/></a>
<a href={cardmarketUri} target="_blank" class="text-xs w-full text-center mt-2">{price}</a>
<span class="w-full text-center text-xs">{name}</span>
{#if numberOfPossibleDecks == undefined}
<span class="w-full text-center">{numberOfDecks} Decks</span>
{:else}
<span class="w-full text-center">{numberOfDecks} Decks sur {numberOfPossibleDecks} ({numberOfPossibleDecks != 0 ? Math.round(100 * (numberOfDecks / numberOfPossibleDecks)) : 0}%)</span>
{/if}
{#if synergy != undefined}
<span class="w-full text-center">Synergy {synergy > 0 ? "+" + Math.round(100*synergy).toString() : Math.round(100*synergy).toString()}%</span>
{/if}
</div>

View file

@ -0,0 +1,7 @@
<script>
let { children } = $props();
</script>
<div class="grid grid-cols-1 pl-4 pr-4 md:grid-cols-3 lg:grid-cols-5 gap-4 w-full max-w-6xl">
{@render children()}
</div>

View file

@ -0,0 +1,38 @@
<script>
import Input from "./base/Input.svelte";
let { dialog = $bindable() } = $props()
let searchQuery = $state("")
let searchResult = $state([])
async function search() {
searchResult = []
if(searchQuery.length > 3) {
fetch("/api/search?q=" + searchQuery)
.then( response => response.json() )
.then( data => { searchResult = data; console.log(data) } )
}
}
</script>
<dialog class="max-w-96 w-full backdrop:backdrop-blur-sm backdrop:bg-black/30 p-4 rounded-md"
style="margin: auto; width: calc(100% - 20px);"
bind:this={dialog}
onclick={(e) => { if (e.target === dialog) dialog.close(); }}>
<div>
<Input type="text" placeholder="Rechercher des cartes" bind:value={searchQuery} oninput={search} focus/>
<div class="mt-4 flex flex-col gap-2 h-64 overflow-y-scroll items-center">
{#if searchResult.length == 0}
<span class="mt-8 text-xs">Pas de résultat...</span>
{:else}
{#each searchResult as result}
<a class="flex flex-col gap-0 w-full" href={result.Url}>
<span>{result.Name}</span>
<span class="text-xs">{result.SetName}</span>
</a>
{/each}
{/if}
</div>
</div>
</dialog>

View file

@ -0,0 +1,3 @@
<div class="w-full flex flex-row justify-center text-xs text-center p-4">
<span>Brawlset is unofficial Fan Content permitted under the Fan Content Policy. Not approved/endorsed by Wizards. Portions of the materials used are property of Wizards of the Coast. ©Wizards of the Coast LLC.</span>
</div>

View file

@ -0,0 +1,226 @@
<script>
import { getContext, onMount } from "svelte";
import Blue from "./icons/Blue.svelte";
import White from "./icons/White.svelte";
import Black from "./icons/Black.svelte";
import Colorless from "./icons/Colorless.svelte";
import Red from "./icons/Red.svelte";
import Green from "./icons/Green.svelte";
import IconBase from "./icons/IconBase.svelte";
import CardSearch from "./CardSearch.svelte";
import SearchIcon from "./icons/SearchIcon.svelte";
import DeckIcon from "./icons/DeckIcon.svelte";
import MenuIcon from "./icons/MenuIcon.svelte";
let dialog
let username = $state("")
let brawlsets = $state([])
let isLoggedIn = $state(false)
onMount( async () => {
const storageData = window.localStorage.getItem("pocketbase_auth")
if (storageData != null) {
const jsonData = JSON.parse(storageData)
username = jsonData.record.name
isLoggedIn = true
}
fetch("/json/misc/brawlsets")
.then( response => response.json() )
.then( data => { brawlsets = data; console.log(data) } )
});
</script>
<div class="font-inter-tight flex flex-row justify-between w-screen p-4 h-16">
<a class="flex flex-row gap-2 items-center" href="/">
<img alt="brawlset website logo" src="/assets/logo.png" class="h-8" />
<span class="font-beleren text-3xl mt-2 bg-gradient-to-r from-black to-orange-500 bg-clip-text text-transparent">BrawlSet</span>
</a>
<div class="hidden flex-row gap-4 items-center md:flex">
<details class="dropdown w-0 md:w-fit">
<summary role="button">
<a class="cursor-pointer text-stone-500">Commandants</a>
</summary>
<ul>
<li>Top commandants</li>
<li>
<div>Mono</div>
<ul>
<li><a class="flex flex-row items-center gap-2" href="#"><White/><span>Blanc</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Blue/><span>Bleu</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Black/><span>Noir</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Red/><span>Rouge</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Green/><span>Vert</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Colorless/><span>Incolor</span></a></li>
</ul>
</li>
<li>
<div>2 couleurs</div>
<ul>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/></div><span>Azorius</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Black/></div><span>Dimir</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Red/></div><span>Rakdos</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><Green/></div><span>Gruul</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><White/></div><span>Selesnya</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Black/></div><span>Orzhov</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Red/></div><span>Izzet</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Green/></div><span>Golgari</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><White/></div><span>Boros</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><Blue/></div><span>Simic</span></a></li>
</ul>
</li>
<li>
<div>3 couleurs</div>
<ul>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/><Black/></div><span>Esper</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Black/><Red/></div><span>Grixis</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Red/><Green/></div><span>Jund</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><Green/><White/></div><span>Naya</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><White/><Blue/></div><span>Bant</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Black/><Green/></div><span>Abzan</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Red/><White/></div><span>Jeskai</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Green/><Blue/></div><span>Sultai</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><White/><Black/></div><span>Mardu</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><Blue/><Red/></div><span>Temur</span></a></li>
</ul>
</li>
<li>
<div>4+ couleurs</div>
<ul>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/><Black/><Red/></div><span>Yore-Tiller</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Black/><Red/><Green/></div><span>Glint-Eye</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Red/><Green/><White/></div><span>Dune-Brood</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><Green/><White/><Blue/></div><span>Ink-Treader</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><White/><Blue/><Black/></div><span>Witch-Maw</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/><Black/><Red/><Green/></div><span>5 couleurs</span></a></li>
</ul>
</li>
</ul>
</details>
<details class="dropdown">
<summary role="button">
<a class="cursor-pointer text-stone-500">BSets</a>
</summary>
<ul>
{#each brawlsets.slice(0,5) as brawlset}
<li>
<a class="flex flex-row gap-2 items-center" href={"/bset/" + brawlset.SanitizedName}>
<div class="flex flex-row gap-1">
{#each brawlset.IconsSvgUri as icon}
<IconBase src={icon} alt="brawlset icon"/>
{/each}
</div>
{brawlset.Name}
</a>
</li>
{/each}
<li><a href="/bset/all">Plus de BSets...</a></li>
</ul>
</details>
<a class="cursor-pointer text-stone-500" href="/regles">Règles</a>
<a class="cursor-pointer text-stone-500" href="/faq">F.A.Q</a>
</div>
<div class="flex flex-row gap-4 items-center text-stone-500">
<button class="flex flex-row items-center" onclick={() => dialog.showModal()}>
<SearchIcon class="visible w-fit md:invisible md:w-0" />
<span class="invisible w-0 md:w-fit md:visible">Rechercher</span>
</button>
{#if isLoggedIn}
<a class="cursor-pointer flex flex-row items-center" href="/decks">
<DeckIcon class="visible w-fit md:invisible md:w-0" />
<span class="invisible w-0 md:w-fit md:visible">Decks</span>
</a>
<a class="cursor-pointer hidden md:inline" href="/profil">
<span class="invisible w-0 md:w-fit md:visible">{username}</span>
</a>
{:else}
<a class="cursor-pointer" href="/connexion">Connexion</a>
<a class="cursor-pointer" href="#">Inscription</a>
{/if}
<details class="dropdown inline md:hidden">
<summary role="button">
<a class="cursor-pointer"><MenuIcon /></a>
</summary>
<ul>
{#each brawlsets.slice(0,5) as brawlset}
<li>
<a class="flex flex-row gap-2 items-center" href={"/bset/" + brawlset.SanitizedName}>
<div class="flex flex-row gap-1">
{#each brawlset.IconsSvgUri as icon}
<IconBase src={icon} alt="brawlset icon"/>
{/each}
</div>
{brawlset.Name}
</a>
</li>
{/each}
<li><a href="/bset/all">Plus de BSets...</a></li>
</ul>
</details>
</div>
</div>
<CardSearch bind:dialog={dialog} />
<style>
/* Close the dropdown with outside clicks */
.dropdown > summary::before {
display: none;
}
.dropdown[open] > summary::before {
content: ' ';
display: block;
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 1;
}
.dropdown summary {
list-style: none;
list-style-type: none;
}
.dropdown ul {
white-space: nowrap;
position: absolute;
margin-top: 5px;
padding: 5px;
box-sizing: border-box;
z-index: 2;
background-color: white;
border-radius: 5px;
border: 1px solid #D3D3D3;
box-shadow: 0px 4px 5px #D3D3D3;
list-style: none;
}
.dropdown li {
padding: 5px;
border-radius: 5px;
cursor: pointer;
position: relative;
}
.dropdown ul ul {
display: none;
left: 100%;
position: absolute;
top: 0;
}
.dropdown li:hover {
background-color: hsl(240 4.8% 95.9%);
}
.dropdown li:hover > ul {
display: block;
}
</style>

View file

@ -0,0 +1,8 @@
<script>
let { children, ...restProps } = $props()
</script>
<button class="bg-orange-500 cursor-pointer text-white rounded-md p-2" {...restProps}>
{@render children()}
</button>

View file

@ -0,0 +1,5 @@
<script>
let { type, placeholder = "", name = "", value = $bindable(), ...restProps} = $props()
</script>
<input type={type} placeholder={placeholder} name={name} class="flex h-auto min-h-9 w-full border-l-orange-500 border-l-4 bg-transparent px-3 py-1 text-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" bind:value {...restProps} />

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/B.svg" alt="Black mana icon"/>

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/U.svg" alt="Blue mana icon"/>

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/C.svg" alt="Colorless mana icon"/>

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...$$props}>
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<rect width="18" height="18" x="3" y="3" rx="2" />
<path d="M3 9a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2M3 11h3c.8 0 1.6.3 2.1.9l1.1.9c1.6 1.6 4.1 1.6 5.7 0l1.1-.9c.5-.5 1.3-.9 2.1-.9H21" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 399 B

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/G.svg" alt="Green mana icon"/>

View file

@ -0,0 +1,5 @@
<script>
let { src, alt } = $props();
</script>
<img src={src} alt={alt} loading="lazy" class="min-h-4 min-w-4 size-4" />

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...$$props}>
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 12h16M4 6h16M4 18h16" />
</svg>

After

Width:  |  Height:  |  Size: 240 B

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/R.svg" alt="Red mana icon"/>

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...$$props}>
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<circle cx="11" cy="11" r="8" />
<path d="m21 21l-4.3-4.3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 279 B

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/W.svg" alt="White mana icon"/>

View file

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -0,0 +1,15 @@
<script lang="ts">
import '../app.css';
import Navbar from '$lib/components/Navbar.svelte';
import Footer from '$lib/components/Footer.svelte';
let { children } = $props();
</script>
<div class="w-screen min-h-screen flex flex-col gap-0">
<Navbar />
<div class="mt-8 font-inter-tight grow">
{@render children()}
</div>
<Footer />
</div>

View file

@ -0,0 +1,6 @@
<div class="flex flex-col w-full items-center mt-8 md:mt-32 p-4 text-center">
<p class="mb-12 text-3xl text-orange-500">CE SITE EST EN COURS DE DEVELOPPEMENT - NE SAUVEGARDEZ PAS VOS DONNÉES UNIQUEMENT SUR BRAWLSET</p>
<h1 class="text-8xl font-beleren">The BrawlSet</h1>
<p class="text-center text-stone-500 mt-12">Un système de règles MTG basé sur le mode de jeu commander et inventé à Rennes, pays de la galette saucisse.<br />
Pour plus d&apos;informations allez voir les <a href="/rules" class="text-orange-500">règles</a> ou la <a href="/faq" class="text-orange-500">FAQ</a>.</p>
</div>

View file

@ -0,0 +1,95 @@
<script lang="ts">
import Card from '$lib/components/Card.svelte';
import CardGrid from '$lib/components/CardGrid.svelte';
import { onMount } from 'svelte';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
let commander = $state([])
let creature = $state([])
let instant = $state([])
let planeswalker = $state([])
let artifact = $state([])
let sorcery = $state([])
let enchantment = $state([])
let land = $state([])
let slug = $derived(data.slug)
let test = $state("")
$effect(() => {
commander = creature = instant = planeswalker = artifact = sorcery = enchantment = []
fetch("/json/brawlset/"+slug)
.then( response => response.json() )
.then( data => {
commander = data.Cards.commander
creature = data.Cards.creature
sorcery = data.Cards.sorcery
instant = data.Cards.instant
land = data.Cards.land
enchantment = data.Cards.enchantment
artifact = data.Cards.artifact
planeswalker = data.Cards.planeswalker
})
});
</script>
<div class="flex flex-col w-full items-center">
<h1>{slug}</h1>
<h2>Commandants</h2>
<CardGrid>
{#each commander as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks}/>
{/each}
</CardGrid>
<h2>Planeswalker</h2>
<CardGrid>
{#each planeswalker as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Creature</h2>
<CardGrid>
{#each creature as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Rituels</h2>
<CardGrid>
{#each sorcery as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Artefacts</h2>
<CardGrid>
{#each artifact as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Éphémères</h2>
<CardGrid>
{#each instant as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Enchantements</h2>
<CardGrid>
{#each enchantment as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Terrains</h2>
<CardGrid>
{#each land as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
</div>

View file

@ -0,0 +1,6 @@
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
console.log("test")
return {slug: params.slug, test: "yeeeeheeee"}
};

View file

@ -0,0 +1,29 @@
<script>
import Input from "$lib/components/base/Input.svelte";
import IconBase from "$lib/components/icons/IconBase.svelte";
import { onMount } from "svelte";
let brawlsets = $state([])
onMount(async () => {
fetch("/json/misc/brawlsets")
.then( response => response.json() )
.then( data => { brawlsets = data; console.log(data) } )
})
</script>
<div class="m-auto mt-16 max-w-4xl flex flex-col gap-4">
<Input type="text" placeholder="Rechercher des brawlsets" />
<div class="grid grid-cols-3 gap-2">
{#each brawlsets as brawlset}
<a class="flex flex-row gap-2 items-center" href={"/bset/" + brawlset.SanitizedName}>
<div class="flex flex-row items-center">
{#each brawlset.IconsSvgUri as icon}
<IconBase src={icon} alt={"Set icon for " + brawlset.Name} />
{/each}
</div>
<span>{brawlset.Name}</span>
</a>
{/each}
</div>
</div>

View file

@ -0,0 +1,90 @@
<script lang="ts">
import Card from '$lib/components/Card.svelte';
import CardGrid from '$lib/components/CardGrid.svelte';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
let creature = $state([])
let instant = $state([])
let planeswalker = $state([])
let artifact = $state([])
let sorcery = $state([])
let enchantment = $state([])
let land = $state([])
let slug = $derived(data.slug)
let mainCardData = $state({})
$effect(() => {
creature = instant = planeswalker = artifact = sorcery = enchantment = []
console.log(data)
fetch("/json/commander/"+slug)
.then( response => response.json() )
.then( data => {
creature = data.Cards.creature
sorcery = data.Cards.sorcery
instant = data.Cards.instant
land = data.Cards.land
enchantment = data.Cards.enchantment
artifact = data.Cards.artifact
planeswalker = data.Cards.planeswalker
mainCardData = data.MainCard
})
});
</script>
<div class="flex flex-col w-full items-center">
<div class="w-full max-w-64">
<Card class="" normalImage={mainCardData.NormalImage} name={mainCardData.Name} price={mainCardData.Price} mainCardDatamarketUri={mainCardData.CardmarketUri} numberOfDecks={mainCardData.NumberOfDecks} />
</div>
<h2>Planeswalker</h2>
<CardGrid>
{#each planeswalker as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Creature</h2>
<CardGrid>
{#each creature as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Rituels</h2>
<CardGrid>
{#each sorcery as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Artefacts</h2>
<CardGrid>
{#each artifact as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Éphémères</h2>
<CardGrid>
{#each instant as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Enchantements</h2>
<CardGrid>
{#each enchantment as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Terrains</h2>
<CardGrid>
{#each land as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
</div>

View file

@ -0,0 +1,6 @@
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
console.log("test")
return {slug: params.slug, test: "yeeeeheeee"}
};

View file

@ -0,0 +1,34 @@
<script>
import PocketBase from 'pocketbase';
import Input from '$lib/components/base/Input.svelte';
import { setContext } from 'svelte';
const pb = new PocketBase('http://localhost:8090');
async function login(form) {
const formData = new FormData(form.target)
const data = {};
for (let field of formData) {
const [key, value] = field;
data[key] = value;
}
console.log(data)
try {
const authData = await pb.collection('users').authWithPassword(data.email, data.password);
console.log(authData)
console.log(pb.authStore.token);
window.location.href = "/"
} catch (err) {
console.log("Failed to log");
}
}
</script>
<div class="flex flex-col items-center mt-32 h-full">
<form class="flex flex-col gap-4 w-lg" on:submit|preventDefault={(e) => login(e)}>
<Input name="email" placeholder="email" type="email" />
<Input name="password" placeholder="password" type="password" />
<button class="border rounded p-2 bg-gray-800 text-white hover:bg-gray-700">Connexion</button>
</form>
</div>

View file

@ -0,0 +1,104 @@
<script>
import Button from "$lib/components/base/Button.svelte";
import Input from "$lib/components/base/Input.svelte";
import { onMount } from "svelte";
let brawlsets = $state([])
let deckImporter = $state("")
let selectedBset = $state("")
let deckName = $state("")
let deckUrl = $state("")
let commander = $state("")
let token = $state("")
onMount( async () => {
const storageData = window.localStorage.getItem("pocketbase_auth")
if (storageData != null) {
const jsonData = JSON.parse(storageData)
token = jsonData.token
}
fetch("/json/misc/brawlsets")
.then( response => response.json() )
.then( data => {
let sortedData = data
sortedData.sort((a,b) => a.Name.localeCompare(b.Name))
brawlsets = sortedData
})
})
function setCommander(txt) {
let lines = txt.split("\n")
commander = lines[lines.length - 1].slice(1)
}
function getDataFromLine(line){
if(line != "") {
const data = line.split(" ")
const amount = parseInt(data[0])
const name = data.slice(1).join(" ").split(" // ")[0].split("/")[0]
return {"name":name, "amount":amount}
} else {
return null
}
}
function importDeck(){
const deckText = deckImporter
let lines = deckText.split("\n")
lines = lines.filter((line) => line.match(/[0-9]+\s[\w]+/) != undefined)
const dataToSend = { name: deckName, url: deckUrl, selected_bset: selectedBset,commander_name: getDataFromLine(commander).name, cards: [] }
lines.slice(0, lines.length - 1).forEach((line) => {
const data = getDataFromLine(line)
if(data != null) {
dataToSend.cards.push(data)
}
});
console.log(dataToSend)
fetch('/api/deck/create', {
method: "POST",
headers: {Authorization: token, "Content-Type": "application/json"},
body: JSON.stringify(dataToSend)
}).then((res) => {
if(res.status == 200) {
res.json().then((apiData) => {
deckImporter = ""
selectedBset = ""
deckName = ""
deckUrl = ""
commander = ""
console.log(apiData)
})
} else if (res.status == 401) {
res.json().then((apiData) => {
console.log(apiData)
})
}
})
}
</script>
<div class="flex flex-col w-full items-center p-4">
<div class="flex flex-col-reverse md:flex-row max-w-4xl w-full gap-4">
<div class="flex flex-col w-full">
<textarea placeholder="Deck list dans le format MTGO" class="h-60" bind:value={deckImporter} oninput={(e) => setCommander(e.target.value)}></textarea>
<span>Commandant : {commander}</span>
</div>
<div class="flex flex-col w-full gap-4">
<Input type="text" placeholder="Nom du deck" bind:value={deckName} />
<Input type="text" placeholder="Url du deck (facultatif)" bind:value={deckUrl} />
<select bind:value={selectedBset}>
<option value="0">Selectionnez un Brawlset...</option>
{#each brawlsets as brawlset}
<option value={brawlset.SanitizedName}>
{brawlset.Name}
</option>
{/each}
</select>
<Button onclick={importDeck}>Importer</Button>
</div>
</div>
</div>

View file

@ -0,0 +1,4 @@
<div class="flex flex-col mt-16 w-full items-center">
<h1>F.A.Q</h1>
<p>Coming soon...</p>
</div>

View file

@ -0,0 +1,13 @@
<script>
import Button from "$lib/components/base/Button.svelte";
function removeAuth() {
window.localStorage.removeItem("pocketbase_auth")
window.location.href = "/"
}
</script>
<div class="flex flex-col items-center mt-16">
<Button onclick={removeAuth}>Deconnexion</Button>
</div>

View file

@ -0,0 +1,31 @@
<div class="flex flex-col items-center">
<div class="flex flex-col items-center w-full">
<h1 class="text-3xl">BrawlSet Canon Règles Officielles</h1>
<h2>BrawlSet Committee</h2>
<h2>17 Octobre 2022</h2>
</div>
<div class="flex flex-col max-w-5xl gap-4 mt-8 mb-32">
<h2 class="font-bold">903.BS. Le mode de jeu BrawlSet</h2>
<ul class="flex flex-col gap-4">
<li><strong>903.BS.1</strong> Le BrawlSet est une variante du Commander. Cette variante utilise les règles classiques du Commander avec les modifications suivantes.</li>
<li><strong>903.BS.2</strong> La Règle dOr du format est dutiliser des cartes provenant du même set. Pour des raisons de niveau des cartes et/ou dhistoire, certains sets sont regroupés en « BSet »</li>
<li><strong>903.BS.3</strong> La liste des BSets est disponible dans le document List_BSet_FR.pdf</li>
<li><strong>903.BS.4</strong> Une carte fait partie dun BSet si elle a été imprimée au moins une fois avec le symbole dédition dun des sets qui composent le BSet. La liste complète des symboles dédition peut être trouvée dans la section Card Set Archive du site Magic de Wizard of the Coast (<a href="https://magic.wizards.com/en/products/card-set-archive">lien</a>)</li>
<li>Exemple : <em>Cela prend en compte les cartes Timeshifted pour Time Spiral, les cartes de Brawl Decks, de Planeswalker Decks et de Theme Boosters, ainsi que les cartes de Buy a Box. Cela ne prend pas en compte les cartes des Masterpiece series (Explorer dIxalan, Expeditions de Zendikar, Inventions de Kaladesh, Invocations de Amonkhet, et Planeswalker Mythic Editions de Guilds of Ravnica, Ravnica Allegeance et War of the Spark).</em></li>
<li><strong>903.BS.5</strong> Il ny a pas de sideboard pour modifier son deck entre deux parties de BrawlSet. Cependant, comme certaines cartes permettent damener des cartes dans la partie depuis lextérieur du jeu, les joueur·euses peuvent avoir un « wishboard ». Le nombre maximum de cartes dun wishboard est 15, il ny pas de nombre minimum de cartes.</li>
<li><strong>903.BS.6</strong> Les cartes des wishboard sont hors de la partie, ainsi aucun effet de la partie ne peut permettre à un·e joueur·euse de regarder ou rechercher dans le wishboard dun·e autre joueur·euse. Un·e joueur·euse ne peut amener une carte hors de la partie dans la partie sauf si cette carte vient de son wishboard ou dune partie principale dune sous-partie. Il ny a pas de restriction sur lidentité colorée des cartes quun·e joueur·euse peut amener dans la partie depuis lextérieur du jeu, ceci est une modification de la règle 903.11.</li>
<li>Exemple : <em>Léon lance Emrakul, the Promised End pour prendre le contrôle de ladversaire ciblé durant son prochain tour et prend le contrôle du prochain tour de Dylan. Léon peut faire en sorte que Dylan lance Granted, laventure de Fae of Wishes pendant quil contrôle le tour de Dylan. Léon ne pourra pas chercher ou trouver quoi que ce soit dans le wishboard de Dylan. Parce que leffet demande de chercher dans le wishboard du joueur / de la joueuse lançant cette carte, Léon ne peut pas chercher dans son wishboard. Aucune carte ne sera amenée dans la partie.</em></li>
<li><strong>903.BS.7</strong> Un·e joueur·euse désigne comme commandant un Planeswalker légendaire ou une créature légendaire. Ceci est une modification de la règle 903.3</li>
<li><strong>903.BS.8</strong> Si un deck de BrawlSet utilise un·e compagnon·ne, le/la compagnon·ne doit respecter lidentité colorée du commandant.</li>
<li><strong>903.BS.9</strong> La règle de construction en singleton du Commander sapplique à la combinaison du deck et du wishboard. Ceci est une modification de la règle 903.5b</li>
<li><strong>903.BS.10</strong> Toutes les cartes dun deck de BrawlSet doivent faire partie du même BSet. Cette règle sapplique à la combinaison du deck et du wishboard. Cette règle sapplique aux terrains de base. Ce BSet est le BSet du deck.</li>
<li><strong>903.BS.11</strong> Une carte avec un type de terrain de base ne peut être inclue dans un deck que si elle ne produit que du mana de lidentité colorée du commandant. Si aucun terrain de base faisant partie du BSet de ce deck ne vérifie cette propriété, cette règle ne sapplique pas pour les terrains de base pour ce deck. Ceci est une modification de la règle 903.5d.</li>
<li>Exemple : <em>Paul veut construire un deck de BrawlSet War of the Spark avec Karn, the Great Creator en commandant. Comme lidentité colorée de Karn est incolore et que les Wastes nont pas été éditées dans War of the Spark, Paul peut utiliser nimporte quel terrain de base édité dans War of the Spark pour construire son deck, par exemple une Forest, huit Islands, neuf Mountains, treize Plains et quinze Swamps.</em></li>
<li><strong>903.BS.12</strong> La taille minimum dun deck de BrawlSet est 60, il ny a pas de taille maximum de deck. Ceci est une modification de la règle 903.5a.</li>
<li><strong>903.BS.13</strong> Dans une partie de BrawlSet, le nombre de point de vie de départ est 20. Ceci est une modification de la règle 119.1c</li>
<li><strong>903.BS.14</strong> Les parties de BrawlSet nutilisent pas la règle des blessures de commandant, ainsi laction décrite dans la règle 704.5v, qui fait perdre un·e joueur·euse qui a subi 21 blessures de combat ou plus dun commandant ne sapplique pas. Ceci est une modification de la règle 704.5v.</li>
<li><strong>903.BS.15</strong> Au début dun match, chaque joueur·euse doit déclarer son BSet, il ne peut pas être changé entre les parties dun même match.</li>
<li><strong>903.BS.16</strong> Le format de BrawlSet utilise la règle du commander swap. Cela signifie que nimporte quel joueur·euse peut changer de commandant entre deux parties dun match. Le nouveau commandant doit être une carte du deck, lancien commandant devient une carte normale du deck. Le commandant du deck reste secret jusquà ce quil soit révélé au début de la partie.</li>
</ul>
</div>
</div>

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="849.88568"
id="svg3234">
<defs
id="defs3236" />
<metadata
id="metadata3239">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-209.32562,77.669905)"
id="layer1">
<path
d="m 277.30227,767.01932 c -14.98382,-4.60496 -20.64186,-12.00446 -17.72072,-23.17455 2.54286,-9.72384 25.17485,-17.24278 60.09182,-19.96423 17.63669,-1.37448 37.91459,-4.62606 45.06184,-7.22544 37.25354,-13.54849 73.6648,-41.33741 82.79446,-63.18741 2.40778,-5.76203 6.99974,-43.7987 10.86075,-89.95941 6.1682,-73.74185 6.311,-80.39439 1.83471,-85.34071 -2.67002,-2.95053 -24.65509,-15.2711 -48.8556,-27.37918 C 372.66032,431.42116 364.58431,426.03025 344.2126,405.95958 323.19716,385.25499 319.73422,380.09259 306.75918,350.12703 290.92799,313.56525 263.72307,233.30669 255.45209,198.76349 250.4907,178.04213 248.79634,175.1888 231.38891,158.24067 217.33102,144.5539 212.143,137.20262 210.57731,128.75229 c -3.7542,-20.26282 0.63506,-32.423269 17.27292,-47.854696 l 15.18709,-14.08577 -2.14895,-61.0241488 c -2.08752,-59.2802402 -1.96714,-61.2551122 4.20972,-69.1076892 3.49724,-4.445957 11.36244,-9.473279 17.47826,-11.171974 15.25642,-4.237222 478.24875,-4.237222 493.50479,0 6.11602,1.698695 14.01192,6.756348 17.54646,11.239605 6.24525,7.921151 6.36243,9.719126 4.15075,63.68249188 C 776.52651,30.972325 775.65258,58.5171 775.83626,61.640761 c 0.18274,3.123662 7.04458,11.903485 15.24605,19.510969 16.3485,15.163915 20.73304,27.4421 16.99806,47.60056 -1.56757,8.46032 -6.76369,15.81122 -20.91841,29.59218 -17.48429,17.02274 -19.21595,19.92429 -24.18224,40.52263 -8.56807,35.53582 -35.54297,115.43249 -51.06935,151.25993 -12.98144,29.95501 -16.45739,35.13531 -37.46529,55.83255 -20.3719,20.07067 -28.4479,25.46158 -67.15712,44.82881 -24.20051,12.10808 -46.18539,24.42865 -48.85541,27.37918 -4.47647,4.94632 -4.33273,11.63051 1.84149,85.74461 3.7606,45.14059 8.48368,84.69115 10.77316,90.21299 8.72218,21.03653 45.99719,49.15212 82.87508,62.51128 7.14726,2.58902 27.63596,5.93366 45.52999,7.43229 35.25983,2.95335 57.12094,10.20403 59.62405,19.77603 3.01214,11.51936 -2.67134,18.50347 -19.00533,23.35408 -22.89442,6.79911 -440.58943,6.63691 -462.76872,-0.17897 z"
inkscape:connector-curvature="0"
id="path7117"
style="fill:#000000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="196.8"
height="313.92001"
viewBox="0 0 196.8 313.92001"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="g1">
<path
style="display:inline;fill:#000000"
d="M 96.320001,313.40138 C 95.076773,312.62187 81.49991,295.60736 80.997053,294.19868 l -0.442949,-1.24086 17.681525,-48.57366 c 9.724841,-26.71552 17.628161,-48.62183 17.562951,-48.68071 -0.35562,-0.32102 -44.593783,-24.28395 -45.05205,-24.40379 -0.398576,-0.10423 -2.71562,2.38823 -8.526912,9.17245 -4.388789,5.12356 -8.019833,9.49241 -8.068986,9.70854 -0.04915,0.21614 2.218848,9.13697 5.040001,19.82407 2.821152,10.6871 5.129368,19.61729 5.129368,19.84486 0,0.67091 -7.611897,21.35883 -8.174903,22.21809 -0.516408,0.78813 -2.229594,1.69234 -3.206458,1.69234 -0.287054,0 -1.017772,-0.2353 -1.623817,-0.52289 -0.897986,-0.42612 -1.652705,-1.44763 -4.078312,-5.52 C 25.682028,211.52918 11.152081,171.83985 4.3462228,130.56001 1.6197832,114.02322 0,95.660031 0,81.28766 0,75.078203 0.04263351,74.592113 0.65559264,73.812862 1.6837009,72.505834 96.232205,0.60704129 97.390241,0.25162984 98.163133,0.01442256 98.703079,0.05252569 99.531845,0.40275926 101.46605,1.220148 195.61007,73.16182 196.26493,74.32291 c 0.53622,0.950725 0.56848,1.636703 0.38742,8.237094 -0.49091,17.895296 -1.92443,33.081656 -4.6518,49.280006 -1.50352,8.92965 -2.43272,13.11567 -3.11571,14.03627 -0.92574,1.2478 -2.81622,1.79919 -4.24473,1.23805 -1.65348,-0.6495 -42.83099,-35.30468 -43.40168,-36.52705 -0.25352,-0.543 -3.68019,-12.839587 -7.61482,-27.325745 -3.93463,-14.486158 -7.20557,-26.376247 -7.26874,-26.422419 -0.25098,-0.183427 -34.012768,-12.007881 -34.083517,-11.937132 -0.195052,0.195051 -37.639624,61.515706 -37.768065,61.850426 -0.08113,0.21142 25.483777,21.8881 57.317352,48.59981 31.60509,26.51999 57.62644,48.52205 57.82522,48.89347 0.74334,1.38895 0.37554,2.9055 -1.99701,8.23432 -9.99817,22.45617 -22.91518,44.41087 -38.02711,64.63366 -5.70912,7.63994 -28.06775,35.27685 -29.19434,36.08634 -1.1768,0.84557 -2.941955,0.93211 -4.107399,0.20137 z"
id="path1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600.00006"
height="533.4812"
id="svg3208">
<defs
id="defs3210" />
<metadata
id="metadata3213">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-510.31037,-331.03141)"
id="layer1">
<path
d="m 713.85991,852.97324 c -13.24237,-13.24237 -14.84693,-23.54582 -7.09008,-45.53094 6.99159,-19.81635 16.57282,-30.21975 46.99885,-51.03259 15.37837,-10.51951 42.36291,-30.01837 59.96548,-43.3307 30.71662,-23.23012 46.24631,-32.88718 138.57862,-86.17383 67.21712,-38.79226 157.99762,-74.97988 157.99762,-62.98235 0,5.72718 -21.6024,21.17322 -51.8605,37.08105 -38.8505,20.42524 -148.00006,94.34145 -180.46523,122.21143 -25.57402,21.9543 -59.52308,58.95089 -95.23194,103.78065 -32.31156,40.56494 -48.28299,46.58727 -68.89282,25.97728 z M 582.44653,816.20576 c -8.45298,-9.07328 -10.25942,-20.87627 -6.1929,-40.46499 5.2375,-25.22816 4.44304,-50.05388 -2.02527,-63.29429 -4.62779,-9.47312 -9.75636,-13.42386 -30.8275,-23.74688 -13.90181,-6.81075 -27.06754,-14.83324 -29.25718,-17.82777 -8.88347,-12.14885 -1.85438,-42.35067 16.19924,-69.60247 15.03429,-22.6943 70.08906,-84.7188 103.21529,-116.28207 34.27584,-32.65888 56.12645,-47.6048 82.96195,-56.74722 20.31794,-6.9218 32.05522,-12.39753 98.21751,-45.81973 78.12883,-39.46719 156.03835,-62.44863 156.03835,-46.0273 0,2.79086 -15.37038,11.06447 -42.01036,22.61341 -58.01571,25.15103 -67.51638,30.78852 -109.88679,65.20542 -20.43225,16.59679 -52.72358,41.95507 -71.75852,56.35162 -36.37515,27.5111 -64.18822,55.36967 -93.04461,93.19691 -37.09377,48.6251 -41.04109,58.81668 -29.87389,77.13251 3.29473,5.40382 5.94112,13.84359 5.99037,18.75463 0.11904,11.89398 5.92237,8.12016 11.5416,3.70876 8.32595,-6.53631 22.8854,-19.75439 46.97278,-42.4296 63.70864,-59.9738 148.65491,-122.48685 207.54269,-152.73336 37.96748,-19.50115 139.96581,-61.43062 168.98981,-69.46828 26.6216,-7.37234 42.0707,-8.09195 42.0707,-1.95939 0,5.34202 -7.4131,9.84589 -70.7112,42.96168 -87.20664,45.62406 -123.09569,71.60314 -191.85365,138.87721 -37.24738,36.4438 -103.39288,96.203 -150.30449,135.79298 -5.41638,4.57104 -24.86797,25.80313 -43.2257,47.1823 -18.35757,21.37917 -36.85635,41.60758 -41.10811,44.95205 -9.97667,7.84768 -20.15683,7.72767 -27.66012,-0.32613 z"
inkscape:connector-curvature="0"
id="path7130"
style="fill:#000000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="470.37875"
id="svg3193">
<defs
id="defs3195" />
<metadata
id="metadata3198">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-74.617828,-93.966166)"
id="layer1">
<path
d="M 227.47403,561.75843 C 207.24579,558.40482 79.908114,529.25417 76.620024,527.22197 c -4.4587,-2.75567 0.63436,-6.37383 11.94276,-8.48412 9.97287,-1.86114 82.172696,-20.26915 112.536416,-28.69223 13.22825,-3.66957 19.03248,-12.07303 14.36399,-20.7963 -2.70246,-5.04959 -66.2607,-60.97927 -123.865836,-108.99867 -17.4482,-14.54494 -20.99596,-20.17028 -12.72047,-20.17028 2.49562,0 41.615306,14.96527 86.932496,33.25621 45.31731,18.29081 84.80719,32.79907 87.75534,32.24037 12.29295,-2.32957 8.13469,-17.7239 -30.48361,-112.85687 -36.69419,-90.39306 -42.61293,-108.76388 -33.37597,-103.59465 2.37278,1.32796 33.44868,41.23856 69.05753,88.69028 65.01878,86.64283 70.75428,92.55169 79.54961,81.95408 2.10247,-2.53346 9.05782,-56.11536 16.80808,-129.48607 13.49588,-127.76175 14.71997,-136.317554 19.50335,-136.317554 4.34971,0 5.27361,6.636194 19.16919,137.690004 13.71054,129.30808 14.97592,135.29299 27.7473,131.23949 3.14139,-0.99699 35.22173,-40.97928 71.28978,-88.84954 36.06792,-47.87026 66.73875,-87.03675 68.15723,-87.03675 5.88502,0 0.85671,15.01798 -33.70527,100.66907 -19.95652,49.45557 -37.46987,93.80604 -38.91862,98.55659 -3.15267,10.33757 1.03675,19.876 8.72994,19.876 2.94199,0 40.37442,-14.35605 83.18303,-31.90223 77.0726,-31.59011 94.34154,-37.44717 94.34154,-31.99762 0,1.54671 -20.5133,20.27107 -45.58507,41.60991 -99.32162,84.53292 -101.26842,86.50973 -95.60896,97.08439 3.63533,6.79275 6.01606,7.57393 78.16486,25.6479 66.48498,16.65536 64.76143,16.10807 61.2801,19.4563 -4.07799,3.92193 -104.24375,26.84943 -163.46883,37.41742 -14.61264,2.60731 -16.35991,-1.26179 -16.4407,-36.40478 -0.0616,-26.69068 -1.29076,-35.41639 -7.00754,-49.73205 -9.69782,-24.28443 -34.12561,-51.39814 -59.91009,-62.87234 -34.77689,-15.47576 -73.57113,-14.51621 -103.07505,-0.0244 -47.33861,23.25102 -71.40325,66.16517 -67.37271,119.91724 1.67751,22.37059 1.20421,24.74863 -3.96604,27.76294 -8.05186,4.69324 -17.16443,0.84312 -24.15377,-0.3157 z"
inkscape:connector-curvature="0"
id="path7143"
style="fill:#000000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="798.29449"
id="svg3260">
<defs
id="defs3262" />
<metadata
id="metadata3265">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-126.2445,-111.90626)"
id="layer1">
<path
d="m 371.57707,908.61481 c -0.98691,-0.9867 -0.94614,-4.0257 0.09,-6.7534 1.03667,-2.7275 39.21124,-63.714 84.8321,-135.5256 45.6207,-71.8114 84.60551,-133.6678 86.63266,-137.4585 5.863,-10.9625 3.04531,-25.2559 -5.97086,-30.2919 -7.10708,-3.9695 -24.22149,-1.7812 -193.59847,24.7552 -102.19094,16.0103 -189.33421,29.0708 -193.65129,29.0234 -12.63035,-0.1393 -23.66671,-10.7845 -23.66671,-22.8293 0,-12.7751 13.70899,-39.2564 96.33389,-186.08678 76.14904,-135.32228 74.83463,-130.01779 34.29302,-138.38284 -13.28704,-2.74159 -18.94621,-12.09393 -15.53722,-25.67637 2.92691,-11.66213 79.52303,-137.50094 91.12775,-149.71292 5.66664,-5.96322 15.43242,-12.37896 21.70173,-14.2573 14.40509,-4.31589 326.25013,-4.83092 337.49053,-0.55733 11.073,4.21007 14.825,12.9869 10.1836,23.82282 -2.0915,4.88238 -78.06728,85.99788 -168.83526,180.25652 -90.76781,94.25884 -166.69058,174.54354 -168.71654,178.41048 -2.96548,5.65984 -2.88683,8.12082 0.40223,12.61897 3.93336,5.37923 8.10248,4.7774 111.68733,-16.12173 59.18105,-11.94037 112.66675,-21.69884 118.85742,-21.68576 14.92691,0.0323 20.99833,6.6442 29.31161,31.92359 15.48761,47.09528 20.46141,50.82765 63.56401,47.70085 32.041,-2.32424 40.7414,0.87122 37.5031,13.7744 -1.003,3.9957 -25.1768,30.1217 -53.7198,58.0577 -28.5429,27.9363 -89.04025,87.3578 -134.43826,132.048 -162.15269,159.625 -160.61628,158.2083 -165.8766,152.9478 z"
inkscape:connector-curvature="0"
id="path7156"
style="fill:#000000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="470.40985"
id="svg3167">
<defs
id="defs3169" />
<metadata
id="metadata3172">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-109.58004,-73.095985)"
id="layer1">
<path
d="M 328.63936,541.67929 C 246.53884,533.77761 165.84388,512.6966 132.07303,490.32766 96.641563,466.85884 102.10545,442.36571 155.33678,386.04328 c 47.79682,-50.57247 69.78599,-92.9501 100.81797,-194.29796 20.38021,-66.55995 39.18723,-108.401257 51.90149,-115.468842 19.63437,-10.914083 33.19725,4.882525 59.18602,68.933912 27.62365,68.08066 51.2835,109.36882 80.49105,140.46283 8.81695,9.38627 17.39024,15.77384 21.17158,15.77384 7.47226,0 18.42198,-13.08595 38.06261,-45.48852 15.90054,-26.23243 28.05191,-34.47776 46.56017,-31.59338 17.13916,2.6709 30.08009,19.69425 45.28907,59.57568 7.13786,18.71712 17.37737,42.81959 22.75449,53.56078 10.08757,20.15073 35.72363,57.03791 39.7181,57.14976 4.60422,0.12868 39.1318,34.82074 43.89588,44.10456 14.44499,28.14975 -6.88892,53.0083 -61.48392,71.64177 -65.61796,22.39567 -124.91599,31.36027 -217.5119,32.88281 -38.00751,0.62508 -81.90503,-0.0957 -97.55003,-1.60123 z"
inkscape:connector-curvature="0"
id="path7169"
style="fill:#000000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="1204.1633"
id="svg3570">
<defs
id="defs3572">
<clipPath
id="clipPath3783">
<path
d="M 0,300 147,300 147,0 0,0 0,300 z"
inkscape:connector-curvature="0"
id="path3785" />
</clipPath>
</defs>
<metadata
id="metadata3575">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-128.125,398.84217)"
id="layer1">
<g
transform="matrix(4.0816327,0,0,-4.0816327,128.125,815.48356)"
id="g3777">
<g
id="g3779">
<g
clip-path="url(#clipPath3783)"
id="g3781">
<g
transform="translate(145.458,184.2598)"
id="g3787">
<path
d="m 0,0 c -1.245,32.734 -4.061,45.164 -5.927,45.164 -1.894,0 -2.49,-18.131 -4.979,-34.153 -2.49,-15.985 -6.874,-34.113 -6.874,-34.113 l -11.204,4.268 c 0,0 -3.141,23.131 -4.385,50.851 -1.216,27.721 -2.164,51.931 -5.63,51.931 -3.382,0.029 -4.031,-22.762 -5.276,-52.296 -1.246,-29.517 -5.601,-45.865 -5.601,-45.865 l -10.283,1.433 c 0,0 -4.98,25.602 -6.848,103.807 -0.433,18.509 -4.951,22.223 -4.951,22.223 0,0 -4.52,-3.714 -4.953,-22.223 -1.866,-78.205 -6.874,-103.807 -6.874,-103.807 l -10.257,-1.433 c 0,0 -4.382,16.348 -5.627,45.865 -1.245,29.534 -1.869,52.325 -5.276,52.296 -3.438,0 -4.386,-24.21 -5.659,-51.931 -1.216,-27.72 -4.33,-50.851 -4.33,-50.851 l -11.204,-4.268 c 0,0 -4.382,18.128 -6.872,34.113 -2.489,16.022 -3.113,34.153 -4.979,34.153 -1.868,0 -4.681,-12.43 -5.927,-45.164 -1.245,-32.693 -1.542,-39.084 -1.542,-39.084 0,0 36.777,-15.67 51.093,-56.223 14.343,-40.529 17.969,-75.72 18.077,-79.627 0.188,-6.064 4.33,-6.836 4.33,-6.836 0,0 3.6,0.772 4.33,6.836 0.459,3.879 3.734,39.098 18.075,79.627 14.318,40.553 51.095,56.223 51.095,56.223 0,0 -0.299,6.391 -1.542,39.084"
inkscape:connector-curvature="0"
id="path3789"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="758.45209"
id="svg3338">
<defs
id="defs3340" />
<metadata
id="metadata3343">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-248.75759,103.7998)"
id="layer1">
<path
d="m 549.35881,651.15793 c -7.26111,-3.30528 -9.75906,-6.38344 -9.75906,-12.02521 0,-9.63732 8.08597,-14.82133 32.81288,-21.03582 10.615,-2.66807 21.08723,-6.33414 23.27159,-8.14697 6.81083,-5.65252 4.49888,-16.5977 -5.23635,-24.78929 -23.09774,-19.43541 -67.63066,-21.56509 -115.4088,-5.51909 -36.947,12.4082 -52.50696,14.06376 -79.62666,8.47176 -34.64907,-7.14427 -67.84003,-25.03721 -93.95261,-50.64833 -26.21188,-25.70856 -39.07084,-47.2129 -47.17177,-78.88733 -13.77915,-53.87651 -1.31183,-108.98633 31.84244,-140.75376 22.18432,-21.25618 63.3297,-33.24003 73.21822,-21.32512 3.03843,3.66117 1.3796,5.78081 -9.81608,12.54327 -38.97877,23.54405 -42.44669,77.09646 -7.39267,114.16076 29.4188,31.10591 66.36486,43.04256 133.33259,43.07667 77.97133,0.0397 108.53348,6.46944 138.17357,29.06853 15.91748,12.1362 33.35102,35.33256 37.51949,49.92138 5.0202,17.56954 7.82356,20.67854 15.123,16.77202 9.13048,-4.88654 17.30572,-26.03103 17.38026,-44.95259 0.17058,-43.53187 -29.41295,-86.80809 -73.86362,-108.04745 -17.36811,-8.29885 -26.87761,-10.32104 -98.17715,-20.87833 -23.04844,-3.41301 -33.22998,-7.90698 -48.71307,-21.50106 -11.7892,-10.35119 -19.40549,-22.99003 -19.40549,-32.20276 0,-8.91341 3.13517,-9.47539 23.06736,-4.13482 14.85755,3.98106 19.78241,4.20141 27.00777,1.20854 13.29452,-5.5067 20.36543,-19.68263 20.42174,-40.94091 0.11216,-42.38594 -35.18535,-71.20981 -114.03762,-93.1233 C 356.52243,185.39467 317.72545,156.03943 301.5472,122.99917 284.34055,87.85892 279.29745,39.536552 288.96328,2.4264521 306.88472,-66.378407 371.02643,-108.50168 450.07709,-103.38006 c 35.58306,2.30541 62.68734,13.967959 58.74366,25.276943 -0.4129,1.184015 -14.26332,2.339288 -30.77877,2.567351 -19.8892,0.274798 -34.59065,2.122206 -43.54098,5.471189 -43.63514,16.327808 -61.94402,50.84462 -49.67719,93.654906 7.33612,25.603172 28.66824,44.991379 77.06305,70.040047 48.43336,25.068764 50.03238,26.213994 89.59182,64.170704 37.99478,36.45512 51.65803,44.90072 72.63941,44.90072 48.47589,0 64.72472,-58.86938 28.19389,-102.14586 C 642.01314,88.355472 633.86991,84.008945 592.149,68.443608 565.01575,58.320717 558.94683,54.937385 558.15912,49.494938 c -1.87638,-12.964572 19.99622,-15.887338 58.8897,-7.869829 45.31432,9.341259 94.90108,38.511196 137.35432,80.800391 40.53175,40.37475 65.35563,84.30293 80.83521,143.04448 35.48117,134.64419 -0.2748,268.71238 -90.85178,340.65077 -22.29018,17.70367 -59.43089,35.45314 -87.67712,41.90131 -31.36972,7.1611 -94.45921,9.00407 -107.35064,3.13587 z"
inkscape:connector-curvature="0"
id="path7218"
style="fill:#000000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
frontend/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
frontend/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

31
frontend/svelte.config.js Normal file
View file

@ -0,0 +1,31 @@
import adapter from "svelte-adapter-bun";
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter({
out: "build",
assets: true,
envPrefix: "BRAWLSET_",
development: true,
// precompress: true,
precompress: {
brotli: true,
gzip: true,
files: ["htm", "html"],
},
dynamic_origin: true,
xff_depth: 1,
}),
}
};
export default config;

19
frontend/tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

12
frontend/vite.config.ts Normal file
View file

@ -0,0 +1,12 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
server: {
watch: {
ignored: ["**/backend/**"],
},
},
});

4
start_server.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
bun run start </dev/null &>/dev/null &
./custom_pocketbase serve --http=0.0.0.0:8090