From 2c1a3edc8776243bd60da0db616085a3e9e810a1 Mon Sep 17 00:00:00 2001 From: BDonat05 Date: Wed, 15 Apr 2026 19:23:10 +0200 Subject: [PATCH 1/4] seeder now works like a tree --- cmd/seeder/main.go | 4 +--- internal/seeder/category_seeder.go | 13 +++++++++++-- internal/seeder/character_seeder.go | 17 ++++++++++++++--- internal/seeder/rarity_seeder.go | 13 +++++++++++-- internal/seeder/role_seeder.go | 13 +++++++++++-- internal/seeder/seeder.go | 9 ++++++++- internal/seeder/user_seeder.go | 13 +++++++++++-- 7 files changed, 67 insertions(+), 15 deletions(-) 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/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/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 From 5179990b73f1d445f2ed1652e40c1e261057113e Mon Sep 17 00:00:00 2001 From: BGERGO7 Date: Wed, 15 Apr 2026 19:29:40 +0200 Subject: [PATCH 2/4] Profile now has the default characters --- client/src/pages/admin/AdminPageContent.tsx | 2 +- .../controllers/player_profiles_controller.go | 10 ++++- internal/controllers/users_controller.go | 2 +- .../repository/profiles_repository_actions.go | 41 ++++++++++++++----- internal/seeder/test_source/characters.json | 26 ++++++++++++ 5 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 internal/seeder/test_source/characters.json 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 ? ( Date: Wed, 15 Apr 2026 20:39:38 +0200 Subject: [PATCH 3/4] some bugs fixed --- client/src/lib/queryKeys.ts | 5 +++ client/src/pages/webstore/useItems.ts | 2 +- .../controllers/player_profiles_controller.go | 9 +++++- .../repository/profiles_repository_actions.go | 32 ++++++------------- 4 files changed, 24 insertions(+), 24 deletions(-) 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/webstore/useItems.ts b/client/src/pages/webstore/useItems.ts index 2d4d196..6609d1f 100644 --- a/client/src/pages/webstore/useItems.ts +++ b/client/src/pages/webstore/useItems.ts @@ -79,7 +79,7 @@ export function useItems() { // ── Fetch all items ────────────────────────────────────────────────────── const { data: fetchedItems = [], isLoading: itemsLoading } = useQuery({ - queryKey: queryKeys.items.all, + queryKey: queryKeys.characters.all, queryFn: async () => { const { data } = await apiClient.get("/characters"); return data.map(itemDTOToWebstoreItem); diff --git a/internal/controllers/player_profiles_controller.go b/internal/controllers/player_profiles_controller.go index 70f238e..3b1ea74 100644 --- a/internal/controllers/player_profiles_controller.go +++ b/internal/controllers/player_profiles_controller.go @@ -338,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) { @@ -352,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/repository/profiles_repository_actions.go b/internal/repository/profiles_repository_actions.go index b3b28dd..211c40a 100644 --- a/internal/repository/profiles_repository_actions.go +++ b/internal/repository/profiles_repository_actions.go @@ -39,8 +39,12 @@ 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. +<<<<<<< HEAD Select("characters.*"). Table("characters"). +======= + Model(&models.Character{}). +>>>>>>> f17dc65 (some bugs fixed) 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 @@ -57,29 +61,13 @@ func (pra ProfilesRepositoryActions) CreateWithBaseCharacters(c context.Context, return err } - log.Printf("profiles.create tx profile inserted: profile_id=%d", profile.ID) + // These characters are seeded into the database, so they're guaranteed to have ids of 1 and 2 + characters, _ := gorm.G[models.Character](tx).Where("id IN (1, 2)").Find(c) - // Starter characters are seeded with ids 1 and 2. - joinRows := []map[string]any{ - {"player_profile_id": profile.ID, "character_id": 1}, - {"player_profile_id": profile.ID, "character_id": 2}, - } - - res := tx.Table("profile_character").Create(&joinRows) - if res.Error != nil { - log.Printf("profiles.create tx join insert failed: profile_id=%d err=%v", profile.ID, res.Error) - return res.Error - } - - if res.RowsAffected != int64(len(joinRows)) { - log.Printf("profiles.create tx join insert unexpected rows: profile_id=%d expected=%d affected=%d", profile.ID, len(joinRows), res.RowsAffected) - return gorm.ErrInvalidData - } - - log.Printf("profiles.create tx join insert ok: profile_id=%d affected=%d", profile.ID, res.RowsAffected) - - if err := tx.First(&models.PlayerProfile{}, profile.ID).Error; err != nil { - log.Printf("profiles.create tx profile re-read failed: profile_id=%d err=%v", profile.ID, err) + if err := tx. + Model(profile). + Association("Characters"). + Append(characters); err != nil { return err } From e7eaffb770c3ceabcc7b9515e8408f538a9f3772 Mon Sep 17 00:00:00 2001 From: BDonat05 Date: Wed, 15 Apr 2026 20:39:57 +0200 Subject: [PATCH 4/4] merge conflict fixed --- internal/repository/profiles_repository_actions.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/repository/profiles_repository_actions.go b/internal/repository/profiles_repository_actions.go index 211c40a..3d72a74 100644 --- a/internal/repository/profiles_repository_actions.go +++ b/internal/repository/profiles_repository_actions.go @@ -39,12 +39,7 @@ 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. -<<<<<<< HEAD - Select("characters.*"). - Table("characters"). -======= Model(&models.Character{}). ->>>>>>> f17dc65 (some bugs fixed) 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