749 lines
21 KiB
Go
749 lines
21 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"game-admin/internal/models"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
func (h *GameHandler) ListLeaderboards(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
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, gameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
var leaderboards []models.Leaderboard
|
|
err = h.db.NewSelect().
|
|
Model(&leaderboards).
|
|
Relation("Groups", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.OrderExpr("lbg.id ASC")
|
|
}).
|
|
Where("lb.game_id = ?", gameID).
|
|
OrderExpr("lb.id DESC").
|
|
Scan(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, leaderboards)
|
|
}
|
|
|
|
func (h *GameHandler) GetLeaderboard(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
|
|
}
|
|
leaderboardID, err := strconv.ParseInt(c.Param("leaderboard_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid leaderboard id"})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, gameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
leaderboard, err := h.getLeaderboard(c, gameID, leaderboardID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, leaderboard)
|
|
}
|
|
|
|
func (h *GameHandler) CreateLeaderboard(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.UpsertLeaderboardRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := validateUpsertLeaderboardRequest(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, gameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
isActive := true
|
|
if req.IsActive != nil {
|
|
isActive = *req.IsActive
|
|
}
|
|
|
|
leaderboard := &models.Leaderboard{
|
|
GameID: gameID,
|
|
Key: strings.TrimSpace(req.Key),
|
|
Name: strings.TrimSpace(req.Name),
|
|
SortOrder: req.SortOrder,
|
|
PeriodType: req.PeriodType,
|
|
IsActive: isActive,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
_, err = h.db.NewInsert().Model(leaderboard).Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Leaderboard key already exists"})
|
|
return
|
|
}
|
|
|
|
created, err := h.getLeaderboard(c, gameID, leaderboard.ID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, created)
|
|
}
|
|
|
|
func (h *GameHandler) UpdateLeaderboard(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
|
|
}
|
|
leaderboardID, err := strconv.ParseInt(c.Param("leaderboard_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid leaderboard id"})
|
|
return
|
|
}
|
|
|
|
var req models.UpsertLeaderboardRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := validateUpsertLeaderboardRequest(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, gameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
current, err := h.getLeaderboard(c, gameID, leaderboardID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard not found"})
|
|
return
|
|
}
|
|
|
|
isActive := current.IsActive
|
|
if req.IsActive != nil {
|
|
isActive = *req.IsActive
|
|
}
|
|
|
|
_, err = h.db.NewUpdate().
|
|
Model((*models.Leaderboard)(nil)).
|
|
Set(`"key" = ?`, strings.TrimSpace(req.Key)).
|
|
Set("name = ?", strings.TrimSpace(req.Name)).
|
|
Set("sort_order = ?", req.SortOrder).
|
|
Set("period_type = ?", req.PeriodType).
|
|
Set("is_active = ?", isActive).
|
|
Set("updated_at = ?", time.Now()).
|
|
Where("id = ? AND game_id = ?", leaderboardID, gameID).
|
|
Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Could not update leaderboard"})
|
|
return
|
|
}
|
|
|
|
updated, err := h.getLeaderboard(c, gameID, leaderboardID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, updated)
|
|
}
|
|
|
|
func (h *GameHandler) DeleteLeaderboard(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
|
|
}
|
|
leaderboardID, err := strconv.ParseInt(c.Param("leaderboard_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid leaderboard id"})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, gameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
if _, err := h.getLeaderboard(c, gameID, leaderboardID); err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard not found"})
|
|
return
|
|
}
|
|
|
|
tx, err := h.db.BeginTx(c, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
committed := false
|
|
defer func() {
|
|
if !committed {
|
|
_ = tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
var groups []models.LeaderboardGroup
|
|
if err := tx.NewSelect().Model(&groups).Where("leaderboard_id = ?", leaderboardID).Scan(c); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if len(groups) > 0 {
|
|
groupIDs := make([]int64, 0, len(groups))
|
|
for _, group := range groups {
|
|
groupIDs = append(groupIDs, group.ID)
|
|
}
|
|
if _, err := tx.NewDelete().Model((*models.LeaderboardGroupMember)(nil)).Where("group_id IN (?)", bun.In(groupIDs)).Exec(c); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
|
|
if _, err := tx.NewDelete().Model((*models.LeaderboardGroup)(nil)).Where("leaderboard_id = ?", leaderboardID).Exec(c); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if _, err := tx.NewDelete().Model((*models.LeaderboardScore)(nil)).Where("leaderboard_id = ?", leaderboardID).Exec(c); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if _, err := tx.NewDelete().Model((*models.Leaderboard)(nil)).Where("id = ? AND game_id = ?", leaderboardID, gameID).Exec(c); 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
|
|
}
|
|
committed = true
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Leaderboard deleted"})
|
|
}
|
|
|
|
func (h *GameHandler) ListLeaderboardGroups(c *gin.Context) {
|
|
leaderboardID, err := strconv.ParseInt(c.Param("leaderboard_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid leaderboard id"})
|
|
return
|
|
}
|
|
leaderboard, err := h.getLeaderboardByID(c, leaderboardID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard not found"})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, leaderboard.GameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
var groups []models.LeaderboardGroup
|
|
err = h.db.NewSelect().
|
|
Model(&groups).
|
|
Relation("Members", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.OrderExpr("lbgm.id ASC")
|
|
}).
|
|
Where("lbg.leaderboard_id = ?", leaderboardID).
|
|
OrderExpr("lbg.id ASC").
|
|
Scan(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, groups)
|
|
}
|
|
|
|
func (h *GameHandler) CreateLeaderboardGroup(c *gin.Context) {
|
|
leaderboardID, err := strconv.ParseInt(c.Param("leaderboard_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid leaderboard id"})
|
|
return
|
|
}
|
|
leaderboard, err := h.getLeaderboardByID(c, leaderboardID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard not found"})
|
|
return
|
|
}
|
|
|
|
var req models.UpsertLeaderboardGroupRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := validateUpsertLeaderboardGroupRequest(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, leaderboard.GameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
isDefault := false
|
|
if req.IsDefault != nil {
|
|
isDefault = *req.IsDefault
|
|
}
|
|
|
|
group := &models.LeaderboardGroup{
|
|
LeaderboardID: leaderboardID,
|
|
Key: strings.TrimSpace(req.Key),
|
|
Name: strings.TrimSpace(req.Name),
|
|
IsDefault: isDefault,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
_, err = h.db.NewInsert().Model(group).Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Leaderboard group key already exists"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, group)
|
|
}
|
|
|
|
func (h *GameHandler) UpdateLeaderboardGroup(c *gin.Context) {
|
|
groupID, err := strconv.ParseInt(c.Param("group_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group id"})
|
|
return
|
|
}
|
|
|
|
group, leaderboard, err := h.getLeaderboardGroup(c, groupID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard group not found"})
|
|
return
|
|
}
|
|
|
|
var req models.UpsertLeaderboardGroupRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := validateUpsertLeaderboardGroupRequest(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, leaderboard.GameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
isDefault := group.IsDefault
|
|
if req.IsDefault != nil {
|
|
isDefault = *req.IsDefault
|
|
}
|
|
|
|
_, err = h.db.NewUpdate().
|
|
Model((*models.LeaderboardGroup)(nil)).
|
|
Set(`"key" = ?`, strings.TrimSpace(req.Key)).
|
|
Set("name = ?", strings.TrimSpace(req.Name)).
|
|
Set("is_default = ?", isDefault).
|
|
Set("updated_at = ?", time.Now()).
|
|
Where("id = ?", groupID).
|
|
Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "Could not update leaderboard group"})
|
|
return
|
|
}
|
|
|
|
updated, _, err := h.getLeaderboardGroup(c, groupID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, updated)
|
|
}
|
|
|
|
func (h *GameHandler) DeleteLeaderboardGroup(c *gin.Context) {
|
|
groupID, err := strconv.ParseInt(c.Param("group_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group id"})
|
|
return
|
|
}
|
|
_, leaderboard, err := h.getLeaderboardGroup(c, groupID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard group not found"})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, leaderboard.GameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
tx, err := h.db.BeginTx(c, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
committed := false
|
|
defer func() {
|
|
if !committed {
|
|
_ = tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
if _, err := tx.NewDelete().Model((*models.LeaderboardGroupMember)(nil)).Where("group_id = ?", groupID).Exec(c); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if _, err := tx.NewDelete().Model((*models.LeaderboardGroup)(nil)).Where("id = ?", groupID).Exec(c); 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
|
|
}
|
|
committed = true
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Leaderboard group deleted"})
|
|
}
|
|
|
|
func (h *GameHandler) AddLeaderboardGroupMember(c *gin.Context) {
|
|
groupID, err := strconv.ParseInt(c.Param("group_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group id"})
|
|
return
|
|
}
|
|
group, leaderboard, err := h.getLeaderboardGroup(c, groupID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard group not found"})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, leaderboard.GameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
var req models.AddLeaderboardGroupMemberRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.ensureUserBelongsToGame(c, req.UserID, leaderboard.GameID); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
member := &models.LeaderboardGroupMember{
|
|
GroupID: group.ID,
|
|
UserID: req.UserID,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
_, err = h.db.NewInsert().Model(member).Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "User already in group"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, member)
|
|
}
|
|
|
|
func (h *GameHandler) DeleteLeaderboardGroupMember(c *gin.Context) {
|
|
groupID, err := strconv.ParseInt(c.Param("group_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group id"})
|
|
return
|
|
}
|
|
userID, err := strconv.ParseInt(c.Param("user_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user id"})
|
|
return
|
|
}
|
|
_, leaderboard, err := h.getLeaderboardGroup(c, groupID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard group not found"})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, leaderboard.GameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
res, err := h.db.NewDelete().Model((*models.LeaderboardGroupMember)(nil)).Where("group_id = ? AND user_id = ?", groupID, userID).Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if affected, _ := res.RowsAffected(); affected == 0 {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Group member not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"message": "Group member deleted"})
|
|
}
|
|
|
|
func (h *GameHandler) UpsertLeaderboardScore(c *gin.Context) {
|
|
leaderboardID, err := strconv.ParseInt(c.Param("leaderboard_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid leaderboard id"})
|
|
return
|
|
}
|
|
leaderboard, err := h.getLeaderboardByID(c, leaderboardID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard not found"})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, leaderboard.GameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
var req models.UpsertLeaderboardScoreRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := h.ensureUserGameBelongsToGame(c, req.UserGameID, leaderboard.GameID); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
score := &models.LeaderboardScore{
|
|
LeaderboardID: leaderboardID,
|
|
UserGameID: req.UserGameID,
|
|
Score: req.Score,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
_, err = h.db.NewInsert().Model(score).
|
|
On("CONFLICT (leaderboard_id, user_game_id) DO UPDATE").
|
|
Set("score = EXCLUDED.score").
|
|
Set("updated_at = EXCLUDED.updated_at").
|
|
Exec(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"message": "Leaderboard score saved"})
|
|
}
|
|
|
|
func (h *GameHandler) GetLeaderboardRankings(c *gin.Context) {
|
|
leaderboardID, err := strconv.ParseInt(c.Param("leaderboard_id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid leaderboard id"})
|
|
return
|
|
}
|
|
leaderboard, err := h.getLeaderboardByID(c, leaderboardID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard not found"})
|
|
return
|
|
}
|
|
if err := h.ensureLeaderboardModuleEnabled(c, leaderboard.GameID); err != nil {
|
|
renderLeaderboardModuleError(c, err)
|
|
return
|
|
}
|
|
|
|
limit := 50
|
|
if rawLimit := c.Query("limit"); rawLimit != "" {
|
|
parsed, err := strconv.Atoi(rawLimit)
|
|
if err != nil || parsed <= 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid limit"})
|
|
return
|
|
}
|
|
if parsed > 200 {
|
|
parsed = 200
|
|
}
|
|
limit = parsed
|
|
}
|
|
|
|
query := h.db.NewSelect().
|
|
TableExpr("leaderboard_scores AS lbs").
|
|
ColumnExpr("lbs.user_game_id AS user_game_id").
|
|
ColumnExpr("ug.user_id AS user_id").
|
|
ColumnExpr("u.username AS username").
|
|
ColumnExpr("lbs.score AS score").
|
|
Join("JOIN user_games AS ug ON ug.id = lbs.user_game_id").
|
|
Join("JOIN users AS u ON u.id = ug.user_id").
|
|
Where("lbs.leaderboard_id = ?", leaderboardID)
|
|
|
|
if rawGroupID := c.Query("group_id"); rawGroupID != "" {
|
|
groupID, err := strconv.ParseInt(rawGroupID, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid group_id"})
|
|
return
|
|
}
|
|
group, _, err := h.getLeaderboardGroup(c, groupID)
|
|
if err != nil || group.LeaderboardID != leaderboardID {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Leaderboard group not found"})
|
|
return
|
|
}
|
|
query = query.Join("JOIN leaderboard_group_members AS lbgm ON lbgm.user_id = ug.user_id").
|
|
Where("lbgm.group_id = ?", groupID)
|
|
}
|
|
|
|
switch leaderboard.SortOrder {
|
|
case models.LeaderboardSortOrderAsc:
|
|
query = query.OrderExpr("lbs.score ASC, lbs.updated_at ASC")
|
|
default:
|
|
query = query.OrderExpr("lbs.score DESC, lbs.updated_at ASC")
|
|
}
|
|
|
|
var rows []models.LeaderboardRankItem
|
|
if err := query.Limit(limit).Scan(c, &rows); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
for i := range rows {
|
|
rows[i].Rank = int64(i + 1)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"leaderboard": leaderboard,
|
|
"items": rows,
|
|
})
|
|
}
|
|
|
|
func (h *GameHandler) ensureLeaderboardModuleEnabled(c *gin.Context, gameID int64) error {
|
|
exists, err := h.gameExists(c, gameID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
return fmt.Errorf("game_not_found")
|
|
}
|
|
hasModule, err := h.gameHasModule(c, gameID, "leaderboard")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !hasModule {
|
|
return fmt.Errorf("leaderboard_module_disabled")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func renderLeaderboardModuleError(c *gin.Context, err error) {
|
|
switch err.Error() {
|
|
case "game_not_found":
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Game not found"})
|
|
case "leaderboard_module_disabled":
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Leaderboard module is not enabled for this game"})
|
|
default:
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
}
|
|
}
|
|
|
|
func validateUpsertLeaderboardRequest(req *models.UpsertLeaderboardRequest) error {
|
|
req.Key = strings.TrimSpace(req.Key)
|
|
req.Name = strings.TrimSpace(req.Name)
|
|
if req.Key == "" {
|
|
return fmt.Errorf("key is required")
|
|
}
|
|
if req.Name == "" {
|
|
return fmt.Errorf("name is required")
|
|
}
|
|
if req.SortOrder != models.LeaderboardSortOrderAsc && req.SortOrder != models.LeaderboardSortOrderDesc {
|
|
return fmt.Errorf("sort_order must be asc or desc")
|
|
}
|
|
switch req.PeriodType {
|
|
case models.LeaderboardPeriodAllTime, models.LeaderboardPeriodDaily, models.LeaderboardPeriodWeekly, models.LeaderboardPeriodMonthly:
|
|
default:
|
|
return fmt.Errorf("unsupported period_type")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateUpsertLeaderboardGroupRequest(req *models.UpsertLeaderboardGroupRequest) error {
|
|
req.Key = strings.TrimSpace(req.Key)
|
|
req.Name = strings.TrimSpace(req.Name)
|
|
if req.Key == "" {
|
|
return fmt.Errorf("key is required")
|
|
}
|
|
if req.Name == "" {
|
|
return fmt.Errorf("name is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *GameHandler) getLeaderboard(c *gin.Context, gameID, leaderboardID int64) (*models.Leaderboard, error) {
|
|
leaderboard := new(models.Leaderboard)
|
|
err := h.db.NewSelect().
|
|
Model(leaderboard).
|
|
Relation("Groups", func(q *bun.SelectQuery) *bun.SelectQuery {
|
|
return q.OrderExpr("lbg.id ASC")
|
|
}).
|
|
Where("lb.id = ? AND lb.game_id = ?", leaderboardID, gameID).
|
|
Scan(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return leaderboard, nil
|
|
}
|
|
|
|
func (h *GameHandler) getLeaderboardByID(c *gin.Context, leaderboardID int64) (*models.Leaderboard, error) {
|
|
leaderboard := new(models.Leaderboard)
|
|
err := h.db.NewSelect().
|
|
Model(leaderboard).
|
|
Where("id = ?", leaderboardID).
|
|
Scan(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return leaderboard, nil
|
|
}
|
|
|
|
func (h *GameHandler) getLeaderboardGroup(c *gin.Context, groupID int64) (*models.LeaderboardGroup, *models.Leaderboard, error) {
|
|
group := new(models.LeaderboardGroup)
|
|
err := h.db.NewSelect().Model(group).Where("id = ?", groupID).Scan(c)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
leaderboard, err := h.getLeaderboardByID(c, group.LeaderboardID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return group, leaderboard, nil
|
|
}
|
|
|
|
func (h *GameHandler) ensureUserBelongsToGame(c *gin.Context, userID, gameID int64) error {
|
|
exists, err := h.db.NewSelect().
|
|
Model((*models.UserGame)(nil)).
|
|
Where("user_id = ? AND game_id = ?", userID, gameID).
|
|
Exists(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
return fmt.Errorf("user is not connected to this game")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *GameHandler) ensureUserGameBelongsToGame(c *gin.Context, userGameID, gameID int64) error {
|
|
exists, err := h.db.NewSelect().
|
|
Model((*models.UserGame)(nil)).
|
|
Where("id = ? AND game_id = ?", userGameID, gameID).
|
|
Exists(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !exists {
|
|
return fmt.Errorf("user_game does not belong to this game")
|
|
}
|
|
return nil
|
|
}
|