From ae76b3db1e8f686812d6f3d7a0f32d861b33076d Mon Sep 17 00:00:00 2001 From: zuma Date: Mon, 9 Jun 2025 22:51:16 +0200 Subject: [PATCH] Feat: Display deck list in decks --- backend/README.md | 1 + backend/routes.go | 122 ++++++++++++++++++++++--- backend/types.go | 74 +++++++-------- frontend/README.md | 4 +- frontend/src/routes/decks/+page.svelte | 61 +++++++++++-- 5 files changed, 203 insertions(+), 59 deletions(-) diff --git a/backend/README.md b/backend/README.md index c8a3f64..d887cbe 100644 --- a/backend/README.md +++ b/backend/README.md @@ -5,3 +5,4 @@ Le backend du site Brawlset est codé en [GO](https://go.dev/) et utilise [Pocke - [ ] Feat : Créer le système d'ELO - [x] Fix : Changer le recto / verso des cartes - [ ] Feat : Système d'évènements à ajouter sur la page principale +- [ ] Fix : Refuse to create deck when cards are not found diff --git a/backend/routes.go b/backend/routes.go index 231425a..aabf8a3 100644 --- a/backend/routes.go +++ b/backend/routes.go @@ -6,11 +6,12 @@ import ( "net/http" "net/http/httputil" "net/url" - "strings" "os" + "slices" + "strings" - "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/apis" + "github.com/pocketbase/pocketbase/core" ) const frontEndDevPort = "5173" @@ -30,6 +31,17 @@ type DeckImport struct { Cards []DeckImportCard `json:"cards"` } +type DeckAnswerCard struct { + Carte Carte `json:"carte"` + Amount int `json:"amount"` +} + +type DeckAnswer struct { + Name string `json:"name"` + Commander Carte `json:"commander"` + Cartes []DeckAnswerCard `json:"cartes"` +} + func (app *application) SetupRoutes() { app.pb.OnServe().BindFunc(func(se *core.ServeEvent) error { mode := os.Getenv("GO_ENV") @@ -57,7 +69,29 @@ func (app *application) SetupRoutes() { http.Error(w, fmt.Sprintf("proxy error: %v", err), http.StatusBadGateway) } - // Cache Route + // Cache Route - GET "/json/{...}" + GET_Cache(se, app) + + // Search API - GET "/api/search" + GET_Search(se, app) + + // Create deck API - POST "api/deck/create" + POST_CreateDeck(se, app) + + // Get deck list API - GET "/api/deck" + GET_AllDeck(se, app) + + // 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 GET_Cache(se *core.ServeEvent, app *application) { se.Router.GET("/json/{path...}", func(re *core.RequestEvent) error { path := fmt.Sprintf("json/%s",re.Request.PathValue("path")) @@ -68,8 +102,9 @@ func (app *application) SetupRoutes() { return re.JSON(http.StatusNotFound, map[string]string{"message": "json not found in cache"}) } }) +} - // Search API +func GET_Search(se *core.ServeEvent, app *application) { se.Router.GET("/api/search", func(re *core.RequestEvent) error { searchData := app.pb.Store().Get("searchData").([]CacheCarteSearch) search := re.Request.URL.Query().Get("q") @@ -77,8 +112,65 @@ func (app *application) SetupRoutes() { 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 +func GET_AllDeck(se *core.ServeEvent, app *application) { + se.Router.GET("/api/deck", func(re *core.RequestEvent) error { + authRecord := re.Auth + + deckList := []Deck{} + err := app.pb.DB(). + NewQuery(fmt.Sprintf("SELECT name, url, commander, cartes FROM deck WHERE owner = '%v'", authRecord.Id)). + All(&deckList) + if err != nil { + return re.BadRequestError("Problem with " + err.Error(), err) + } + + answer := []DeckAnswer{} + + cardsIds := []string{} + for _, v := range deckList { + if !slices.Contains(cardsIds, v.Commander) { + cardsIds = append(cardsIds, fmt.Sprintf("id = '%s'",v.Commander)) + } + + for _, c := range v.Cards { + if !slices.Contains(cardsIds, c.ID) { + cardsIds = append(cardsIds, fmt.Sprintf("id = '%s'",c.ID)) + } + } + } + + cardsData := []Carte{} + err = app.pb.DB(). + NewQuery(fmt.Sprintf("SELECT * FROM carte WHERE %s", strings.Join(cardsIds, " OR "))). + All(&cardsData) + if err != nil { + return re.BadRequestError("Problem with " + err.Error(), err) + } + + for _, v := range deckList { + tempDeck := DeckAnswer{ + Name: v.Name, + } + + tempDeck.Commander = filter(cardsData, func(carte Carte) bool { return carte.ID == v.Commander })[0] + for _, c := range v.Cards { + cardData := filter(cardsData, func(carte Carte) bool { return carte.ID == c.ID})[0] + tempDeck.Cartes = append(tempDeck.Cartes, DeckAnswerCard{ + Amount: c.Amount, + Carte: cardData, + }) + } + answer = append(answer, tempDeck) + } + + return re.JSON(http.StatusOK, answer) + }).Bind(apis.RequireAuth()) + +} + +func POST_CreateDeck(se *core.ServeEvent, app *application) { se.Router.POST("/api/deck/create", func(re *core.RequestEvent) error { authRecord := re.Auth @@ -134,6 +226,7 @@ func (app *application) SetupRoutes() { } cardInDeck := []DeckCard{} + cardsNotFound := []string{} for _, v := range data.Cards { cardId := "" for _, c := range possibleCards { @@ -143,8 +236,14 @@ func (app *application) SetupRoutes() { } if cardId == "" { log.Println(fmt.Sprintf("Card not found : %s",v.Name)) + cardsNotFound = append(cardsNotFound, v.Name) + } else { + cardInDeck = append(cardInDeck, DeckCard{ID: cardId, Amount: v.Amount}) } - cardInDeck = append(cardInDeck, DeckCard{ID: cardId, Amount: v.Amount}) + } + + if len(cardsNotFound) > 0 { + return re.BadRequestError("Cards not found : " + strings.Join(cardsNotFound, ", "), err) } record.Set("cartes", cardInDeck) @@ -153,20 +252,13 @@ func (app *application) SetupRoutes() { log.Println(err) re.BadRequestError("Problem with creating deck", err) } + log.Println("Deck created") 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) { +func filter[T any](ss []T, test func(T) bool) (ret []T) { for _, s := range ss { if test(s) { ret = append(ret, s) diff --git a/backend/types.go b/backend/types.go index b60e3c8..b519f3b 100644 --- a/backend/types.go +++ b/backend/types.go @@ -5,53 +5,53 @@ import ( ) 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"` + ID string `db:"id" json:"id,omitempty"` + Code string `db:"code" json:"code,omitempty"` + Name string `db:"name" json:"name,omitempty"` + SanitizedName string `db:"sanitized_name" json:"sanitized_name,omitempty"` + ReleasedAt string `db:"release_at" json:"released_at,omitempty"` + IconUri string `db:"icon_uri" json:"icon_uri,omitempty"` + SetType string `db:"type" json:"type,omitempty"` } 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"` + ID string `db:"id" json:"id,omitempty"` + Name string `db:"name" json:"name,omitempty"` + SanitizedName string `db:"sanitized_name" json:"sanitized_name,omitempty"` + Sets types.JSONArray[string] `db:"sets" json:"sets,omitempty"` } 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"` - Banned bool `db:"banned" json:"banned"` + ID string `db:"id" json:"id,omitempty"` + Name string `db:"name" json:"name,omitempty"` + SanitizedName string `db:"sanitized_name" json:"sanitized_name,omitempty"` + Layout string `db:"layout" json:"layout,omitempty"` + SmallImage string `db:"small_image" json:"small_image,omitempty"` + SmallImageBack string `db:"small_image_back" json:"small_image_back,omitempty"` + NormalImage string `db:"normal_image" json:"normal_image,omitempty"` + NormalImageBack string `db:"normal_image_back" json:"normal_image_back,omitempty"` + CardType string `db:"type" json:"type,omitempty"` + ColorIdentity types.JSONArray[string] `db:"color_identity" json:"color_identity,omitempty"` + ReleasedAt string `db:"released_at" json:"released_at,omitempty"` + MtgSet string `db:"mtg_set" json:"mtg_set,omitempty"` + SetCode string `db:"set_code" json:"set_code,omitempty"` + Price string `db:"price" json:"price,omitempty"` + CardmarketUri string `db:"cardmarket_url" json:"cardmarket_url,omitempty"` + CanBeCommander bool `db:"can_be_commander" json:"can_be_commander,omitempty"` + Banned bool `db:"banned" json:"banned,omitempty"` } type DeckCard struct { - ID string `json:"id"` - Amount int `json:"amount"` + ID string `json:"id,omitempty"` + Amount int `json:"amount,omitempty"` } 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"` + ID string `db:"id" json:"id,omitempty"` + Name string `db:"name" json:"name,omitempty"` + ColorIdentity types.JSONArray[string] `db:"color_identity" json:"color_identity,omitempty"` + Owner string `db:"owner" json:"owner,omitempty"` + Commander string `db:"commander" json:"commander,omitempty"` + Brawlset string `db:"brawlset" json:"brawslet,omitempty"` + Cards types.JSONArray[DeckCard] `db:"cartes" json:"cartes,omitempty"` } diff --git a/frontend/README.md b/frontend/README.md index 57c719e..3f84e0b 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -9,7 +9,9 @@ Une fois compilé le frontend utilise [Bun](https://bun.sh/). - [ ] Fix : Ajouter les icones de couleur pour les tops commandants - [ ] Fix : Changer le menu mobile - [x] Fix : Changer les cartes recto / verso -- [ ] Feat : Ajouter la liste des decks dans l'espace decks +- [x] Feat : Ajouter la liste des decks dans l'espace decks +- [ ] Fix : Mettre en forme la liste des decks +- [ ] Feat : Supprimer ou éditer un deck - [ ] Feat : En créant un deck, warning si on possède déjà un deck avec ce commandant - [ ] Feat : Ajouter un calendrier des évènements Brawlset sur la page d'accueil - [ ] Feat : Ajouter une vue globale des statistiques ELO diff --git a/frontend/src/routes/decks/+page.svelte b/frontend/src/routes/decks/+page.svelte index d481ff6..ed7ebd5 100644 --- a/frontend/src/routes/decks/+page.svelte +++ b/frontend/src/routes/decks/+page.svelte @@ -5,12 +5,17 @@ let brawlsets = $state([]) let deckImporter = $state("") - let selectedBset = $state("") + let selectedBset = $state("0") let deckName = $state("") let deckUrl = $state("") let commander = $state("") let token = $state("") + let decks = $state("") + + let error = $state("") + let valid = $state("") + onMount( async () => { const storageData = window.localStorage.getItem("pocketbase_auth") if (storageData != null) { @@ -25,6 +30,22 @@ sortedData.sort((a,b) => a.Name.localeCompare(b.Name)) brawlsets = sortedData }) + + fetch("/api/deck", { + headers: {Authorization: token, "Content-Type": "application/json"}, + }).then((res) => { + if(res.status == 200) { + res.json().then((apiData) => { + decks = apiData + console.log(apiData) + }) + } else if (res.status == 401 || res.status == 400) { + res.json().then((apiData) => { + console.log(apiData) + }) + } + }) + }) function setCommander(txt) { @@ -44,6 +65,8 @@ } function importDeck(){ + error = "" + valid = "" const deckText = deckImporter let lines = deckText.split("\n") lines = lines.filter((line) => line.match(/[0-9]+\s[\w]+/) != undefined) @@ -70,10 +93,12 @@ deckUrl = "" commander = "" console.log(apiData) + valid = apiData["message"] }) - } else if (res.status == 401) { + } else if (res.status == 401 || res.status == 400) { res.json().then((apiData) => { console.log(apiData) + error = apiData["message"] }) } }) @@ -81,10 +106,10 @@ -
-
-
- +
+
+
+ Commandant : {commander}
@@ -99,6 +124,30 @@ {/each} + {#if error != ""} + {error} + {/if} + {#if valid != ""} + {valid} + {/if}
+

Liste de decks

+
+ {#each decks as deck} +
+ +

{deck.name}

+
+
+ {deck.commander.name} +
    + {#each deck.cartes as carteData} +
  • {carteData.amount}x {carteData.carte.name}
  • + {/each} +
+
+
+ {/each} +