474 lines
13 KiB
Go
474 lines
13 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"game-admin/internal/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
func (h *GameHandler) ListVariables(c *gin.Context) {
|
|
gameID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid game id"})
|
|
return
|
|
}
|
|
|
|
exists, err := h.gameExists(c, gameID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if !exists {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Game not found"})
|
|
return
|
|
}
|
|
|
|
hasModule, err := h.gameHasModule(c, gameID, "variables")
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if !hasModule {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Variables module is not enabled for this game"})
|
|
return
|
|
}
|
|
|
|
var variables []models.GameVariable
|
|
err = h.db.NewSelect().
|
|
Model(&variables).
|
|
Relation("Items", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.OrderExpr("gvi.id ASC")
|
|
}).
|
|
Where("gv.game_id = ?", gameID).
|
|
OrderExpr("gv.id DESC").
|
|
Scan(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
for i := range variables {
|
|
normalizeGameVariableResponse(&variables[i])
|
|
}
|
|
|
|
c.JSON(http.StatusOK, variables)
|
|
}
|
|
|
|
func (h *GameHandler) CreateVariable(c *gin.Context) {
|
|
gameID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid game id"})
|
|
return
|
|
}
|
|
|
|
var req models.UpsertGameVariableRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := validateUpsertGameVariableRequest(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
exists, err := h.gameExists(c, gameID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if !exists {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Game not found"})
|
|
return
|
|
}
|
|
|
|
hasModule, err := h.gameHasModule(c, gameID, "variables")
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if !hasModule {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Variables module is not enabled for this game"})
|
|
return
|
|
}
|
|
|
|
tx, err := h.db.BeginTx(c, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
now := time.Now()
|
|
variable := &models.GameVariable{
|
|
GameID: gameID,
|
|
Key: strings.TrimSpace(req.Key),
|
|
Type: req.Type,
|
|
NumberValue: req.NumberValue,
|
|
StringValue: req.StringValue,
|
|
TableValueType: req.TableValueType,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
_, err = tx.NewInsert().Model(variable).Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Game variable key already exists"})
|
|
return
|
|
}
|
|
|
|
if err := insertGameVariableItems(c, tx, variable.ID, req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
created, err := h.getGameVariable(c, gameID, variable.ID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, created)
|
|
}
|
|
|
|
func (h *GameHandler) UpdateVariable(c *gin.Context) {
|
|
gameID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid game id"})
|
|
return
|
|
}
|
|
variableID, err := strconv.ParseInt(c.Param("var_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid variable id"})
|
|
return
|
|
}
|
|
|
|
var req models.UpsertGameVariableRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := validateUpsertGameVariableRequest(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
hasModule, err := h.gameHasModule(c, gameID, "variables")
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if !hasModule {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Variables module is not enabled for this game"})
|
|
return
|
|
}
|
|
|
|
existing, err := h.getGameVariable(c, gameID, variableID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Game variable not found"})
|
|
return
|
|
}
|
|
|
|
tx, err := h.db.BeginTx(c, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
numberValue, stringValue, tableValueType := dbValuesFromVariableRequest(req)
|
|
|
|
_, err = tx.NewUpdate().
|
|
Model((*models.GameVariable)(nil)).
|
|
Set(`"key" = ?`, strings.TrimSpace(req.Key)).
|
|
Set("type = ?", req.Type).
|
|
Set("number_value = ?", numberValue).
|
|
Set("string_value = ?", stringValue).
|
|
Set("table_value_type = ?", tableValueType).
|
|
Set("updated_at = ?", time.Now()).
|
|
Where("id = ? AND game_id = ?", existing.ID, gameID).
|
|
Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Could not update game variable"})
|
|
return
|
|
}
|
|
|
|
_, err = tx.NewDelete().
|
|
Model((*models.GameVariableItem)(nil)).
|
|
Where("game_variable_id = ?", existing.ID).
|
|
Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := insertGameVariableItems(c, tx, existing.ID, req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
updated, err := h.getGameVariable(c, gameID, existing.ID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, updated)
|
|
}
|
|
|
|
func (h *GameHandler) DeleteVariable(c *gin.Context) {
|
|
gameID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid game id"})
|
|
return
|
|
}
|
|
variableID, err := strconv.ParseInt(c.Param("var_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid variable id"})
|
|
return
|
|
}
|
|
|
|
existing, err := h.getGameVariable(c, gameID, variableID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Game variable not found"})
|
|
return
|
|
}
|
|
|
|
hasModule, err := h.gameHasModule(c, gameID, "variables")
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if !hasModule {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Variables module is not enabled for this game"})
|
|
return
|
|
}
|
|
|
|
tx, err := h.db.BeginTx(c, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
_, err = tx.NewDelete().
|
|
Model((*models.GameVariableItem)(nil)).
|
|
Where("game_variable_id = ?", existing.ID).
|
|
Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
_, err = tx.NewDelete().
|
|
Model((*models.GameVariable)(nil)).
|
|
Where("id = ? AND game_id = ?", existing.ID, gameID).
|
|
Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Game variable deleted"})
|
|
}
|
|
|
|
func (h *GameHandler) gameExists(ctx context.Context, gameID int64) (bool, error) {
|
|
return h.db.NewSelect().
|
|
Model((*models.Game)(nil)).
|
|
Where("id = ?", gameID).
|
|
Exists(ctx)
|
|
}
|
|
|
|
func (h *GameHandler) getGameVariable(ctx context.Context, gameID, variableID int64) (*models.GameVariable, error) {
|
|
variable := new(models.GameVariable)
|
|
err := h.db.NewSelect().
|
|
Model(variable).
|
|
Relation("Items", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.OrderExpr("gvi.id ASC")
|
|
}).
|
|
Where("gv.id = ? AND gv.game_id = ?", variableID, gameID).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
normalizeGameVariableResponse(variable)
|
|
return variable, nil
|
|
}
|
|
|
|
func insertGameVariableItems(ctx context.Context, tx bun.Tx, variableID int64, req models.UpsertGameVariableRequest) error {
|
|
if req.Type != models.GameVariableTypeTable && req.Type != models.GameVariableTypeVector {
|
|
return nil
|
|
}
|
|
|
|
for _, itemReq := range req.Items {
|
|
key := strings.TrimSpace(itemReq.Key)
|
|
if req.Type == models.GameVariableTypeVector {
|
|
key = strconv.FormatInt(*itemReq.Index, 10)
|
|
}
|
|
item := &models.GameVariableItem{
|
|
GameVariableID: variableID,
|
|
Key: key,
|
|
NumberValue: itemReq.NumberValue,
|
|
StringValue: itemReq.StringValue,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
if _, err := tx.NewInsert().Model(item).Exec(ctx); err != nil {
|
|
return fmt.Errorf("could not save table items")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateUpsertGameVariableRequest(req *models.UpsertGameVariableRequest) error {
|
|
req.Key = strings.TrimSpace(req.Key)
|
|
if req.Key == "" {
|
|
return fmt.Errorf("key is required")
|
|
}
|
|
|
|
switch req.Type {
|
|
case models.GameVariableTypeNumber:
|
|
if req.NumberValue == nil {
|
|
return fmt.Errorf("number_value is required for number type")
|
|
}
|
|
if req.StringValue != nil || req.TableValueType != nil || len(req.Items) > 0 {
|
|
return fmt.Errorf("number type only accepts number_value")
|
|
}
|
|
case models.GameVariableTypeString:
|
|
if req.StringValue == nil {
|
|
return fmt.Errorf("string_value is required for string type")
|
|
}
|
|
if req.NumberValue != nil || req.TableValueType != nil || len(req.Items) > 0 {
|
|
return fmt.Errorf("string type only accepts string_value")
|
|
}
|
|
case models.GameVariableTypeTable:
|
|
if req.NumberValue != nil || req.StringValue != nil {
|
|
return fmt.Errorf("table type does not accept scalar values")
|
|
}
|
|
if req.TableValueType == nil {
|
|
return fmt.Errorf("table_value_type is required for table type")
|
|
}
|
|
if *req.TableValueType != models.GameVariableTableValueTypeNumber &&
|
|
*req.TableValueType != models.GameVariableTableValueTypeString {
|
|
return fmt.Errorf("unsupported table_value_type")
|
|
}
|
|
seenKeys := make(map[string]struct{}, len(req.Items))
|
|
for i := range req.Items {
|
|
req.Items[i].Key = strings.TrimSpace(req.Items[i].Key)
|
|
if req.Items[i].Key == "" {
|
|
return fmt.Errorf("items[%d].key is required", i)
|
|
}
|
|
if _, exists := seenKeys[req.Items[i].Key]; exists {
|
|
return fmt.Errorf("duplicate table item key: %s", req.Items[i].Key)
|
|
}
|
|
seenKeys[req.Items[i].Key] = struct{}{}
|
|
|
|
switch *req.TableValueType {
|
|
case models.GameVariableTableValueTypeNumber:
|
|
if req.Items[i].NumberValue == nil || req.Items[i].StringValue != nil {
|
|
return fmt.Errorf("items[%d] must contain only number_value", i)
|
|
}
|
|
case models.GameVariableTableValueTypeString:
|
|
if req.Items[i].StringValue == nil || req.Items[i].NumberValue != nil {
|
|
return fmt.Errorf("items[%d] must contain only string_value", i)
|
|
}
|
|
}
|
|
}
|
|
case models.GameVariableTypeVector:
|
|
if req.NumberValue != nil || req.StringValue != nil {
|
|
return fmt.Errorf("vector type does not accept scalar values")
|
|
}
|
|
if req.TableValueType == nil {
|
|
return fmt.Errorf("table_value_type is required for vector type")
|
|
}
|
|
if *req.TableValueType != models.GameVariableTableValueTypeNumber &&
|
|
*req.TableValueType != models.GameVariableTableValueTypeString {
|
|
return fmt.Errorf("unsupported table_value_type")
|
|
}
|
|
seenIndexes := make(map[int64]struct{}, len(req.Items))
|
|
for i := range req.Items {
|
|
if req.Items[i].Index == nil {
|
|
return fmt.Errorf("items[%d].index is required", i)
|
|
}
|
|
if *req.Items[i].Index < 0 {
|
|
return fmt.Errorf("items[%d].index must be >= 0", i)
|
|
}
|
|
if _, exists := seenIndexes[*req.Items[i].Index]; exists {
|
|
return fmt.Errorf("duplicate vector item index: %d", *req.Items[i].Index)
|
|
}
|
|
seenIndexes[*req.Items[i].Index] = struct{}{}
|
|
|
|
switch *req.TableValueType {
|
|
case models.GameVariableTableValueTypeNumber:
|
|
if req.Items[i].NumberValue == nil || req.Items[i].StringValue != nil {
|
|
return fmt.Errorf("items[%d] must contain only number_value", i)
|
|
}
|
|
case models.GameVariableTableValueTypeString:
|
|
if req.Items[i].StringValue == nil || req.Items[i].NumberValue != nil {
|
|
return fmt.Errorf("items[%d] must contain only string_value", i)
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
return fmt.Errorf("unsupported variable type")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func dbValuesFromVariableRequest(req models.UpsertGameVariableRequest) (interface{}, interface{}, interface{}) {
|
|
var numberValue interface{}
|
|
if req.NumberValue != nil {
|
|
numberValue = *req.NumberValue
|
|
}
|
|
|
|
var stringValue interface{}
|
|
if req.StringValue != nil {
|
|
stringValue = *req.StringValue
|
|
}
|
|
|
|
var tableValueType interface{}
|
|
if req.TableValueType != nil {
|
|
tableValueType = string(*req.TableValueType)
|
|
}
|
|
|
|
return numberValue, stringValue, tableValueType
|
|
}
|
|
|
|
func normalizeGameVariableResponse(variable *models.GameVariable) {
|
|
if variable.Type != models.GameVariableTypeVector {
|
|
return
|
|
}
|
|
|
|
for i := range variable.Items {
|
|
index, err := strconv.ParseInt(variable.Items[i].Key, 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
variable.Items[i].Index = &index
|
|
variable.Items[i].Key = ""
|
|
}
|
|
}
|