Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions client/src/lib/queryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Organized by domain for easy cache invalidation.
*/

import { all } from "axios";

export const queryKeys = {
profiles: {
all: ["profiles"],
Expand All @@ -14,6 +16,9 @@ export const queryKeys = {
items: {
all: ["items"],
},
characters: {
all: ["characters"]
},
purchases: {
byProfileId: (profileId: number) => ["purchases", "byProfileId", profileId],
},
Expand Down
2 changes: 1 addition & 1 deletion client/src/pages/admin/AdminPageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function AdminPageContent({ animReady = true }: { animReady?: boolean })
return (
<>
<div
className={`z-0 flex w-full flex-1 max-w-7xl flex-col gap-3 rounded-xl p-3 sm:gap-5 sm:p-5 xl:flex-row ${cardBg}`}
className={`z-0 flex w-full max-w-7xl flex-1 flex-col gap-3 rounded-xl p-3 sm:gap-5 sm:p-5 xl:flex-row ${cardBg}`}
>
{useAnimations ? (
<motion.div
Expand Down
2 changes: 1 addition & 1 deletion client/src/pages/webstore/useItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function useItems() {

// ── Fetch all items ──────────────────────────────────────────────────────
const { data: fetchedItems = [], isLoading: itemsLoading } = useQuery<WebstoreItem[]>({
queryKey: queryKeys.items.all,
queryKey: queryKeys.characters.all,
queryFn: async () => {
const { data } = await apiClient.get<ItemReadDTO[]>("/characters");
return data.map(itemDTOToWebstoreItem);
Expand Down
4 changes: 1 addition & 3 deletions cmd/seeder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
19 changes: 17 additions & 2 deletions internal/controllers/player_profiles_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package controllers

import (
"errors"
"log"
"net/http"
"os"
dtos "smaash-web/internal/DTOs"
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand All @@ -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)
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
2 changes: 1 addition & 1 deletion internal/controllers/users_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 12 additions & 10 deletions internal/repository/profiles_repository_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package repository

import (
"context"
"log"
"smaash-web/internal/models"

"gorm.io/gorm"
Expand Down Expand Up @@ -38,29 +39,30 @@ 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
}

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
}

Expand Down
13 changes: 11 additions & 2 deletions internal/seeder/category_seeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
17 changes: 14 additions & 3 deletions internal/seeder/character_seeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
Expand Down
13 changes: 11 additions & 2 deletions internal/seeder/rarity_seeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
13 changes: 11 additions & 2 deletions internal/seeder/role_seeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
9 changes: 8 additions & 1 deletion internal/seeder/seeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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)
})
}
Expand Down
26 changes: 26 additions & 0 deletions internal/seeder/test_source/characters.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
]
13 changes: 11 additions & 2 deletions internal/seeder/user_seeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"log"
"os"
"slices"
"smaash-web/internal/models"
"time"

Expand All @@ -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
Expand Down
Loading