Skip to content

Commit

Permalink
Merge pull request #13 from HamoonZamiri/dev
Browse files Browse the repository at this point in the history
introduce loot modules (chests and items)
  • Loading branch information
HamoonZamiri authored Oct 26, 2024
2 parents fd8eb6e + 380f513 commit ef56f0e
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 76 deletions.
4 changes: 4 additions & 0 deletions backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ up:
@docker compose up -d
down:
@docker compose down
start: up
@echo "Waiting for Docker to start..."
@sleep 2 # Optional: wait for a few seconds to ensure services are up
@make run
74 changes: 74 additions & 0 deletions backend/db/migrations/20241015224211_add_chests_and_items.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
-- +goose Up
-- +goose StatementBegin
-- Create the enum types first
CREATE TYPE chest_type AS ENUM ('bronze', 'silver', 'gold');
CREATE TYPE item_status AS ENUM ('equipped', 'not_equipped');
CREATE TYPE chest_status AS ENUM ('opened', 'not_opened');
CREATE TYPE item_type AS ENUM ('common', 'rare', 'epic', 'legendary'); -- Assuming this is what `item_type` refers to.

-- Create table `chest_items`
CREATE TABLE chest_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
image_url VARCHAR,
title VARCHAR NOT NULL,
rarity item_type NOT NULL,
price INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);

-- Create table `chests`
CREATE TABLE chests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type chest_type NOT NULL,
description TEXT NOT NULL,
price INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);

-- Create table `chest_item_drop_rates`
CREATE TABLE chest_item_drop_rates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
item_id UUID REFERENCES chest_items(id),
chest_id UUID REFERENCES chests(id),
drop_rate FLOAT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);

-- Create table `user_items`
CREATE TABLE user_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
item_id UUID REFERENCES chest_items(id),
status item_status DEFAULT 'not_equipped',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);

-- Create table `user_chests`
CREATE TABLE user_chests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
chest_id UUID REFERENCES chests(id),
quantity_owned INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
-- Drop tables in reverse order of creation to respect foreign key constraints
DROP TABLE IF EXISTS user_chests;
DROP TABLE IF EXISTS user_items;
DROP TABLE IF EXISTS chest_item_drop_rates;
DROP TABLE IF EXISTS chests;
DROP TABLE IF EXISTS chest_items;

-- Drop enums
DROP TYPE IF EXISTS chest_status;
DROP TYPE IF EXISTS item_status;
DROP TYPE IF EXISTS chest_type;
DROP TYPE IF EXISTS item_type;
-- +goose StatementEnd
44 changes: 44 additions & 0 deletions backend/entities/loot_entities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package entities

import (
"goalify/utils/options"
"time"

"github.com/google/uuid"
)

type Chest struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Id uuid.UUID `db:"id" json:"id"`
Type string `db:"type" json:"type"` // bronze | silver | gold
Description string `db:"description" json:"description"`
Price int `db:"price" json:"price"`
}

type ChestItem struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Id uuid.UUID `db:"id" json:"id"`
ImageUrl string `db:"image_url" json:"image_url"`
Rarity string `db:"rarity" json:"rarity"` // common | rare | epic | legendary
Price options.Option[int] `db:"price" json:"price"`
}

type ChestItemDropRate struct {
ItemId uuid.UUID `db:"item_id" json:"item_id"`
ChestId uuid.UUID `db:"chest_id" json:"chest_id"`
DropRate float32 `db:"drop_rate" json:"drop_rate"`
}

type UserChest struct {
UserId uuid.UUID `db:"user_id" json:"user_id"`
ChestId uuid.UUID `db:"chest_id" json:"chest_id"`
QuantityOwned int `db:"quantity_owned" json:"quantity_owned"`
}

type UserItem struct {
UserId uuid.UUID `db:"user_id" json:"user_id"`
ItemId uuid.UUID `db:"item_id" json:"item_id"`
Status string `db:"status" json:"status"` // equipped | unequipped
}
59 changes: 28 additions & 31 deletions backend/goals/handler/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,39 @@ import (
"github.com/google/uuid"
)

type GoalHandler struct {
goalService service.GoalService
traceLogger stacktrace.TraceLogger
}
type (
GoalHandler struct {
goalService service.GoalService
traceLogger stacktrace.TraceLogger
}
CreateGoalRequest struct {
Title string `json:"title"`
Description string `json:"description"`
CategoryId string `json:"category_id"`
}
CreateGoalCategoryRequest struct {
Title string `json:"title"`
XpPerGoal int `json:"xp_per_goal"`
}
UpdateGoalCategoryRequest struct {
Title options.Option[string] `json:"title"`
XpPerGoal options.Option[int] `json:"xp_per_goal"`
}
UpdateGoalRequest struct {
Title options.Option[string] `json:"title"`
Description options.Option[string] `json:"description"`
CategoryId options.Option[string] `json:"category_id"`
Status options.Option[string] `json:"status"`
}
DeleteGoalRequest struct {
GoalId string `json:"goal_id"`
}
)

func NewGoalHandler(goalService service.GoalService, traceLogger stacktrace.TraceLogger) *GoalHandler {
return &GoalHandler{goalService, traceLogger}
}

type CreateGoalRequest struct {
Title string `json:"title"`
Description string `json:"description"`
CategoryId string `json:"category_id"`
}

type CreateGoalCategoryRequest struct {
Title string `json:"title"`
XpPerGoal int `json:"xp_per_goal"`
}

type UpdateGoalCategoryRequest struct {
Title options.Option[string] `json:"title"`
XpPerGoal options.Option[int] `json:"xp_per_goal"`
}

type UpdateGoalRequest struct {
Title options.Option[string] `json:"title"`
Description options.Option[string] `json:"description"`
CategoryId options.Option[string] `json:"category_id"`
Status options.Option[string] `json:"status"`
}

type DeleteGoalRequest struct {
GoalId string `json:"goal_id"`
}

const (
TEXT_MAX_LEN = 255
XP_MAX_PER_GOAL = 100
Expand Down
23 changes: 12 additions & 11 deletions backend/goals/stores/category_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ import (
"github.com/jmoiron/sqlx"
)

type GoalCategoryStore interface {
CreateGoalCategory(title string, xpPerGoal int, userId uuid.UUID) (*entities.GoalCategory, error)
GetGoalCategoriesByUserId(userId uuid.UUID) ([]*entities.GoalCategory, error)
GetGoalCategoryById(categoryId uuid.UUID) (*entities.GoalCategory, error)
UpdateGoalCategoryById(categoryId uuid.UUID, updates map[string]any) (*entities.GoalCategory, error)
DeleteGoalCategoryById(categoryId uuid.UUID) error
}

type goalCategoryStore struct {
db *sqlx.DB
}
type (
GoalCategoryStore interface {
CreateGoalCategory(title string, xpPerGoal int, userId uuid.UUID) (*entities.GoalCategory, error)
GetGoalCategoriesByUserId(userId uuid.UUID) ([]*entities.GoalCategory, error)
GetGoalCategoryById(categoryId uuid.UUID) (*entities.GoalCategory, error)
UpdateGoalCategoryById(categoryId uuid.UUID, updates map[string]any) (*entities.GoalCategory, error)
DeleteGoalCategoryById(categoryId uuid.UUID) error
}
goalCategoryStore struct {
db *sqlx.DB
}
)

func NewGoalCategoryStore(db *sqlx.DB) GoalCategoryStore {
return &goalCategoryStore{db: db}
Expand Down
5 changes: 5 additions & 0 deletions backend/loot/stores/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package stores

const (
CHEST_TABLE = "chests"
)
31 changes: 31 additions & 0 deletions backend/loot/stores/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package stores

import (
"fmt"
"goalify/entities"

"github.com/jmoiron/sqlx"
)

type LootStore interface {
CreateChest(chestType, description string, price int) (*entities.Chest, error)
}

type lootStore struct {
db *sqlx.DB
}

func NewChestStore(db *sqlx.DB) LootStore {
return &lootStore{db: db}
}

func (s *lootStore) CreateChest(chestType, description string, price int) (*entities.Chest, error) {
query := fmt.Sprintf(`INSERT INTO %s (type, description, price) VALUES ($1, $2, $3) RETURNING *`, CHEST_TABLE)

var chest entities.Chest
err := s.db.QueryRowx(query, chestType, description, price).StructScan(&chest)
if err != nil {
return nil, err
}
return &chest, nil
}
64 changes: 64 additions & 0 deletions backend/loot/stores/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package stores

import (
"context"
"goalify/db"
"goalify/testsetup"
"log"
"os"
"testing"

"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go/modules/postgres"
)

var (
dbConn *sqlx.DB
cStore LootStore
pgContainer *postgres.PostgresContainer
)

func setup(ctx context.Context) {
var err error

pgContainer, err = testsetup.GetPgContainer()
if err != nil {
panic(err)
}

connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
if err != nil {
panic(err)
}

dbConn, err = db.NewWithConnString(connStr)
if err != nil {
panic(err)
}

cStore = NewChestStore(dbConn)
}

func TestMain(m *testing.M) {
ctx := context.Background()

setup(ctx)
defer func() {
if err := pgContainer.Terminate(ctx); err != nil {
log.Fatalf("Failed to terminate container: %s", err)
}
}()

code := m.Run()
os.Exit(code)
}

func TestCreateChest(t *testing.T) {
t.Parallel()

chest, err := cStore.CreateChest("bronze", "bronze chest", 100)
assert.Nil(t, err)
assert.NotNil(t, chest)
assert.Equal(t, 100, chest.Price)
}
39 changes: 19 additions & 20 deletions backend/users/handler/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,25 @@ import (
"strings"
)

type SignupRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}

type LoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}

type RefreshRequest struct {
UserId string `json:"user_id"`
RefreshToken string `json:"refresh_token"`
}

type UpdateRequest struct {
Xp options.Option[int] `json:"xp"`
LevelId options.Option[int] `json:"level_id"`
CashAvailable options.Option[int] `json:"cash_available"`
}
type (
SignupRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
LoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
RefreshRequest struct {
UserId string `json:"user_id"`
RefreshToken string `json:"refresh_token"`
}
UpdateRequest struct {
Xp options.Option[int] `json:"xp"`
LevelId options.Option[int] `json:"level_id"`
CashAvailable options.Option[int] `json:"cash_available"`
}
)

const (
PASSWORD_MIN_LEN = 8
Expand Down
Loading

0 comments on commit ef56f0e

Please sign in to comment.