diff --git a/client/src/lib/queryKeys.ts b/client/src/lib/queryKeys.ts
index f663f5a..443a8b1 100644
--- a/client/src/lib/queryKeys.ts
+++ b/client/src/lib/queryKeys.ts
@@ -3,6 +3,8 @@
* Organized by domain for easy cache invalidation.
*/
+import { all } from "axios";
+
export const queryKeys = {
profiles: {
all: ["profiles"],
@@ -14,6 +16,9 @@ export const queryKeys = {
items: {
all: ["items"],
},
+ characters: {
+ all: ["characters"]
+ },
purchases: {
byProfileId: (profileId: number) => ["purchases", "byProfileId", profileId],
},
diff --git a/client/src/pages/admin/AdminPageContent.tsx b/client/src/pages/admin/AdminPageContent.tsx
index 47e1bba..a5be868 100644
--- a/client/src/pages/admin/AdminPageContent.tsx
+++ b/client/src/pages/admin/AdminPageContent.tsx
@@ -25,7 +25,7 @@ export function AdminPageContent({ animReady = true }: { animReady?: boolean })
return (
<>
{useAnimations ? (
({
- queryKey: queryKeys.items.all,
+ queryKey: queryKeys.characters.all,
queryFn: async () => {
const { data } = await apiClient.get("/characters");
return data.map(itemDTOToWebstoreItem);
diff --git a/cmd/seeder/main.go b/cmd/seeder/main.go
index 7ed4e47..755f174 100644
--- a/cmd/seeder/main.go
+++ b/cmd/seeder/main.go
@@ -32,9 +32,7 @@ func main() {
seeder.WithContext(ctx),
seeder.WithSeeder(seeder.NewRoleSeeder()),
seeder.WithSeeder(seeder.NewUserSeeder()),
- seeder.WithSeeder(seeder.NewRaritySeeder()),
- seeder.WithSeeder(seeder.NewCategorySeeder()),
- seeder.WithSeeder(seeder.NewCharacterSeeder()),
+ seeder.WithSeeder(seeder.NewCharacterSeeder(), seeder.NewCategorySeeder(), seeder.NewRaritySeeder()),
)
go func() {
diff --git a/internal/controllers/player_profiles_controller.go b/internal/controllers/player_profiles_controller.go
index 40bca74..3b1ea74 100644
--- a/internal/controllers/player_profiles_controller.go
+++ b/internal/controllers/player_profiles_controller.go
@@ -2,6 +2,7 @@ package controllers
import (
"errors"
+ "log"
"net/http"
"os"
dtos "smaash-web/internal/DTOs"
@@ -94,9 +95,13 @@ func (pc PlayerProfileController) Create(c *gin.Context) {
path := c.Request.URL.Path
var body dtos.PlayerProfileCreateDTO
if err := c.ShouldBindJSON(&body); err != nil {
+ log.Printf("profiles.create bind error: %v", err)
c.JSON(http.StatusUnprocessableEntity, dtos.NewErrResp(err.Error(), path))
+ return
}
+ log.Printf("profiles.create requested: user_id=%d display_name=%q", body.UserID, body.DisplayName)
+
currentProfiles, err := pc.profilesRepo.ReadByUserID(c.Request.Context(), body.UserID)
if err != nil {
c.JSON(http.StatusInternalServerError, dtos.NewErrResp(err.Error(), path))
@@ -109,7 +114,8 @@ func (pc PlayerProfileController) Create(c *gin.Context) {
}
newProfile := dtos.CreateDTOToPlayerProfile(body)
- if err := pc.profilesRepo.CreateWithBaseCharacters(c, &newProfile); err != nil {
+ if err := pc.profilesRepo.CreateWithBaseCharacters(c.Request.Context(), &newProfile); err != nil {
+ log.Printf("profiles.create failed: user_id=%d display_name=%q err=%v", body.UserID, body.DisplayName, err)
if errors.Is(err, gorm.ErrDuplicatedKey) {
c.JSON(http.StatusConflict, dtos.NewErrResp("Display name already taken", path))
return
@@ -118,6 +124,8 @@ func (pc PlayerProfileController) Create(c *gin.Context) {
return
}
+ log.Printf("profiles.create success: profile_id=%d user_id=%d", newProfile.ID, newProfile.UserID)
+
c.JSON(http.StatusCreated, newProfile)
}
@@ -330,7 +338,13 @@ func (pc PlayerProfileController) ReadNotOwnedCharacters(c *gin.Context) {
return
}
- c.JSON(http.StatusOK, utils.Map(player.Characters, dtos.CharacterToDTO))
+ characters, err := pc.profilesRepo.ReadNotOwnedCharacters(c.Request.Context(), player.ID)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, dtos.NewErrResp(err.Error(), path))
+ return
+ }
+
+ c.JSON(http.StatusOK, utils.Map(characters, dtos.CharacterToDTO))
}
func (pc PlayerProfileController) MountRoutes(apiGroup *gin.RouterGroup) {
@@ -344,4 +358,5 @@ func (pc PlayerProfileController) MountRoutes(apiGroup *gin.RouterGroup) {
profiles.GET("/:id/pfp", middlewares.Authorize(middlewares.ANY), middlewares.ValidateUrl, pc.GetPFP)
profiles.GET("/:id/purchases", middlewares.Authorize(middlewares.ANY), middlewares.ValidateUrl, pc.ReadPurchases)
profiles.GET("/:id/characters", middlewares.Authorize(middlewares.ANY), middlewares.ValidateUrl, pc.ReadCharacters)
+ profiles.GET("/:id/unowned", middlewares.Authorize(middlewares.ANY), middlewares.ValidateUrl, pc.ReadNotOwnedCharacters)
}
diff --git a/internal/controllers/users_controller.go b/internal/controllers/users_controller.go
index b8354aa..c99f426 100644
--- a/internal/controllers/users_controller.go
+++ b/internal/controllers/users_controller.go
@@ -200,7 +200,7 @@ func (uc *UserController) AddProfileToUser(c *gin.Context) {
newProfile := dtos.AppendDTOToPlayerProfile(body)
newProfile.UserID = id.(uint)
- if err := uc.profilesRepo.Create(c.Request.Context(), &newProfile); err != nil {
+ if err := uc.profilesRepo.CreateWithBaseCharacters(c.Request.Context(), &newProfile); err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
c.JSON(http.StatusConflict, dtos.NewErrResp("Display name already taken", path))
return
diff --git a/internal/repository/profiles_repository_actions.go b/internal/repository/profiles_repository_actions.go
index 9fd21c0..3d72a74 100644
--- a/internal/repository/profiles_repository_actions.go
+++ b/internal/repository/profiles_repository_actions.go
@@ -2,6 +2,7 @@ package repository
import (
"context"
+ "log"
"smaash-web/internal/models"
"gorm.io/gorm"
@@ -38,10 +39,9 @@ func (pra ProfilesRepositoryActions) HardDelete(c context.Context, id uint) erro
func (pra ProfilesRepositoryActions) ReadNotOwnedCharacters(c context.Context, playerID uint) ([]models.Character, error) {
var result []models.Character
err := pra.conn.
- Select("characters.*").
- Table("characters").
- Joins("LEFT JOIN player_characters ON characters.id = player_characters.character_id AND player_characters.player_profile_id = ?", playerID).
- Where("player_characters.character_id IS NULL").
+ Model(&models.Character{}).
+ Joins("LEFT JOIN profile_character ON characters.id = profile_character.character_id AND profile_character.player_profile_id = ?", playerID).
+ Where("profile_character.character_id IS NULL").
Find(&result).Error
return result, err
@@ -49,18 +49,20 @@ func (pra ProfilesRepositoryActions) ReadNotOwnedCharacters(c context.Context, p
func (pra ProfilesRepositoryActions) CreateWithBaseCharacters(c context.Context, profile *models.PlayerProfile) error {
return pra.conn.Transaction(func(tx *gorm.DB) error {
- if err := gorm.G[models.PlayerProfile](pra.conn).Create(c, profile); err != nil {
+ log.Printf("profiles.create tx start: user_id=%d display_name=%q", profile.UserID, profile.DisplayName)
+
+ if err := gorm.G[models.PlayerProfile](tx).Create(c, profile); err != nil {
+ log.Printf("profiles.create tx profile insert failed: err=%v", err)
return err
}
// These characters are seeded into the database, so they're guaranteed to have ids of 1 and 2
- if err := pra.conn.
+ characters, _ := gorm.G[models.Character](tx).Where("id IN (1, 2)").Find(c)
+
+ if err := tx.
Model(profile).
Association("Characters").
- Append([]models.Character{
- {Model: gorm.Model{ID: 1}},
- {Model: gorm.Model{ID: 2}},
- }); err != nil {
+ Append(characters); err != nil {
return err
}
diff --git a/internal/seeder/category_seeder.go b/internal/seeder/category_seeder.go
index 6b16fc0..57edf78 100644
--- a/internal/seeder/category_seeder.go
+++ b/internal/seeder/category_seeder.go
@@ -6,18 +6,27 @@ import (
"errors"
"log"
"os"
+ "slices"
"smaash-web/internal/models"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
-type CategorySeeder struct{}
+type CategorySeeder struct{ children []Seeder }
-func NewCategorySeeder() *CategorySeeder {
+func NewCategorySeeder() Seeder {
return &CategorySeeder{}
}
+func (cs CategorySeeder) GetChildren() []Seeder {
+ return cs.children
+}
+
+func (cs CategorySeeder) AppendChildren(children ...Seeder) {
+ cs.children = slices.Concat(cs.children, children)
+}
+
type CategoryDataFormat struct {
Name string
}
diff --git a/internal/seeder/character_seeder.go b/internal/seeder/character_seeder.go
index 55f88e9..84c55ca 100644
--- a/internal/seeder/character_seeder.go
+++ b/internal/seeder/character_seeder.go
@@ -6,18 +6,27 @@ import (
"errors"
"log"
"os"
+ "slices"
"smaash-web/internal/models"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
-type CharacterSeeder struct{}
+type CharacterSeeder struct{ children []Seeder }
-func NewCharacterSeeder() *CharacterSeeder {
+func NewCharacterSeeder() Seeder {
return &CharacterSeeder{}
}
+func (cs *CharacterSeeder) GetChildren() []Seeder {
+ return cs.children
+}
+
+func (cs *CharacterSeeder) AppendChildren(children ...Seeder) {
+ cs.children = slices.Concat(cs.children, children)
+}
+
type CharacterDataFormat struct {
Name string
Description string
@@ -54,12 +63,14 @@ func (rs CharacterSeeder) Seed(c context.Context, data_root_path string, db *gor
return err
}
+ newCharacter, _ := gorm.G[models.Character](tx).Where("name = ?", val.Name).First(c)
+
for _, category := range val.Categories {
cat, err := gorm.G[models.Category](tx).Where("name = ?", category).First(c)
if err != nil {
return err
}
- if err := tx.Model(&val).Association("Categories").Append(cat); err != nil {
+ if err := tx.Model(&newCharacter).Association("Categories").Append(cat); err != nil {
return err
}
}
diff --git a/internal/seeder/rarity_seeder.go b/internal/seeder/rarity_seeder.go
index b83bebe..bfd3980 100644
--- a/internal/seeder/rarity_seeder.go
+++ b/internal/seeder/rarity_seeder.go
@@ -6,18 +6,27 @@ import (
"errors"
"log"
"os"
+ "slices"
"smaash-web/internal/models"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
-type RaritySeeder struct{}
+type RaritySeeder struct{ children []Seeder }
-func NewRaritySeeder() *RaritySeeder {
+func NewRaritySeeder() Seeder {
return &RaritySeeder{}
}
+func (rs RaritySeeder) GetChildren() []Seeder {
+ return rs.children
+}
+
+func (rs RaritySeeder) AppendChildren(children ...Seeder) {
+ rs.children = slices.Concat(rs.children, children)
+}
+
type RarityDataFormat struct {
Name string
}
diff --git a/internal/seeder/role_seeder.go b/internal/seeder/role_seeder.go
index 5e7ce7f..7c68278 100644
--- a/internal/seeder/role_seeder.go
+++ b/internal/seeder/role_seeder.go
@@ -6,18 +6,27 @@ import (
"errors"
"log"
"os"
+ "slices"
"smaash-web/internal/models"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
-type RoleSeeder struct{}
+type RoleSeeder struct{ children []Seeder }
-func NewRoleSeeder() *RoleSeeder {
+func NewRoleSeeder() Seeder {
return &RoleSeeder{}
}
+func (rs RoleSeeder) GetChildren() []Seeder {
+ return rs.children
+}
+
+func (rs RoleSeeder) AppendChildren(children ...Seeder) {
+ rs.children = slices.Concat(rs.children, children)
+}
+
type RoleDataFormat struct {
Name string
}
diff --git a/internal/seeder/seeder.go b/internal/seeder/seeder.go
index 05bf0c9..bc80180 100644
--- a/internal/seeder/seeder.go
+++ b/internal/seeder/seeder.go
@@ -15,6 +15,8 @@ import (
type Seeder interface {
Seed(ctx context.Context, seed_data_root string, conn *gorm.DB, errStream chan error, logger logger.Interface)
+ GetChildren() []Seeder
+ AppendChildren(...Seeder)
}
type SeederManager struct {
@@ -48,8 +50,9 @@ func NewSeedManager(seed_data_root string, dbUrl string, errStream chan error, o
return seederManager
}
-func WithSeeder(seeder Seeder) SeederOpt {
+func WithSeeder(seeder Seeder, children ...Seeder) SeederOpt {
return func(sm *SeederManager) {
+ seeder.AppendChildren(children...)
sm.seeders = append(sm.seeders, seeder)
}
}
@@ -82,6 +85,10 @@ func (sm *SeederManager) Seed() {
for _, seeder := range sm.seeders {
wg.Go(func() {
+ log.Println(seeder)
+ for _, child := range seeder.GetChildren() {
+ child.Seed(sm.ctx, sm.seed_data_root, conn, sm.errStream, sm.logger)
+ }
seeder.Seed(sm.ctx, sm.seed_data_root, conn, sm.errStream, sm.logger)
})
}
diff --git a/internal/seeder/test_source/characters.json b/internal/seeder/test_source/characters.json
new file mode 100644
index 0000000..b930b96
--- /dev/null
+++ b/internal/seeder/test_source/characters.json
@@ -0,0 +1,26 @@
+[
+ {
+ "Name": "Samurai",
+ "Description": "A balanced melee character for new profiles.",
+ "RarityID": 1,
+ "Price": 0,
+ "Implemented": true,
+ "ImgURI": "/characters/samurai.png",
+ "Categories": [
+ "Character",
+ "Melee"
+ ]
+ },
+ {
+ "Name": "Slime",
+ "Description": "A ranged character with simple starter stats.",
+ "RarityID": 1,
+ "Price": 0,
+ "Implemented": true,
+ "ImgURI": "/characters/slime.png",
+ "Categories": [
+ "Character",
+ "Ranged"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/internal/seeder/user_seeder.go b/internal/seeder/user_seeder.go
index a731f7d..d7370eb 100644
--- a/internal/seeder/user_seeder.go
+++ b/internal/seeder/user_seeder.go
@@ -6,6 +6,7 @@ import (
"errors"
"log"
"os"
+ "slices"
"smaash-web/internal/models"
"time"
@@ -14,12 +15,20 @@ import (
"gorm.io/gorm/logger"
)
-type UserSeeder struct{}
+type UserSeeder struct{ children []Seeder }
-func NewUserSeeder() *UserSeeder {
+func NewUserSeeder() Seeder {
return &UserSeeder{}
}
+func (us UserSeeder) GetChildren() []Seeder {
+ return us.children
+}
+
+func (us UserSeeder) AppendChildren(children ...Seeder) {
+ us.children = slices.Concat(us.children, children)
+}
+
type UserDataFormat struct {
Email string
Password string