add leaderboard
This commit is contained in:
@@ -32,6 +32,7 @@ func Init(di *dig.Container, app *fiber.App) {
|
|||||||
Membership: groups.Group("/membership"),
|
Membership: groups.Group("/membership"),
|
||||||
System: groups.Group("/system"),
|
System: groups.Group("/system"),
|
||||||
WebSocket: groups.Group("/ws"),
|
WebSocket: groups.Group("/ws"),
|
||||||
|
Leaderboard: serverIdGroup.Group("/leaderboard"),
|
||||||
}
|
}
|
||||||
|
|
||||||
accessKeyMiddleware := middleware.NewAccessKeyMiddleware()
|
accessKeyMiddleware := middleware.NewAccessKeyMiddleware()
|
||||||
|
|||||||
@@ -59,4 +59,9 @@ func InitializeControllers(c *dig.Container) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Panic("unable to initialize websocket controller")
|
logging.Panic("unable to initialize websocket controller")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = c.Invoke(NewLeaderboardController)
|
||||||
|
if err != nil {
|
||||||
|
logging.Panic("unable to initialize leaderboard controller")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
local/controller/leaderboard.go
Normal file
66
local/controller/leaderboard.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"acc-server-manager/local/middleware"
|
||||||
|
"acc-server-manager/local/model"
|
||||||
|
"acc-server-manager/local/service"
|
||||||
|
"acc-server-manager/local/utl/common"
|
||||||
|
"acc-server-manager/local/utl/error_handler"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LeaderboardController struct {
|
||||||
|
service *service.LeaderboardService
|
||||||
|
errorHandler *error_handler.ControllerErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLeaderboardController(ls *service.LeaderboardService, routeGroups *common.RouteGroups, auth *middleware.AuthMiddleware) *LeaderboardController {
|
||||||
|
lc := &LeaderboardController{
|
||||||
|
service: ls,
|
||||||
|
errorHandler: error_handler.NewControllerErrorHandler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
routeGroups.Leaderboard.Get("/", lc.Get)
|
||||||
|
routeGroups.Leaderboard.Put("/", auth.Authenticate, lc.Update)
|
||||||
|
|
||||||
|
return lc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the leaderboard for a server (public, no auth required).
|
||||||
|
func (lc *LeaderboardController) Get(c *fiber.Ctx) error {
|
||||||
|
serverIDStr := c.Params("id")
|
||||||
|
serverID, err := uuid.Parse(serverIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return lc.errorHandler.HandleUUIDError(c, "server ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := lc.service.Get(c.UserContext(), serverID)
|
||||||
|
if err != nil {
|
||||||
|
return lc.errorHandler.HandleServiceError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update replaces the leaderboard for a server (requires JWT auth).
|
||||||
|
func (lc *LeaderboardController) Update(c *fiber.Ctx) error {
|
||||||
|
serverIDStr := c.Params("id")
|
||||||
|
serverID, err := uuid.Parse(serverIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return lc.errorHandler.HandleUUIDError(c, "server ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
var input model.LeaderboardInput
|
||||||
|
if err := c.BodyParser(&input); err != nil {
|
||||||
|
return lc.errorHandler.HandleParsingError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := lc.service.Update(c.UserContext(), serverID, &input)
|
||||||
|
if err != nil {
|
||||||
|
return lc.errorHandler.HandleServiceError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(data)
|
||||||
|
}
|
||||||
123
local/model/leaderboard.go
Normal file
123
local/model/leaderboard.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Leaderboard struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||||
|
ServerID uuid.UUID `gorm:"uniqueIndex;not null;type:uuid" json:"serverId"`
|
||||||
|
FLPoints int `gorm:"default:1" json:"flPoints"`
|
||||||
|
FLColor string `gorm:"default:'#8b5cf6'" json:"flColor"`
|
||||||
|
FLTextColor string `gorm:"default:'#000000'" json:"flTextColor"`
|
||||||
|
Drivers []LeaderboardDriver `gorm:"foreignKey:LeaderboardID;constraint:OnDelete:CASCADE" json:"drivers"`
|
||||||
|
Races []LeaderboardRace `gorm:"foreignKey:LeaderboardID;constraint:OnDelete:CASCADE" json:"races"`
|
||||||
|
PointRows []LeaderboardPointRow `gorm:"foreignKey:LeaderboardID;constraint:OnDelete:CASCADE" json:"pointRows"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaderboardDriver struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||||
|
LeaderboardID uuid.UUID `gorm:"not null;type:uuid" json:"leaderboardId"`
|
||||||
|
Name string `gorm:"not null" json:"name"`
|
||||||
|
Initials string `json:"initials"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
Position int `json:"position"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaderboardRace struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||||
|
LeaderboardID uuid.UUID `gorm:"not null;type:uuid" json:"leaderboardId"`
|
||||||
|
Name string `gorm:"not null" json:"name"`
|
||||||
|
Position int `json:"position"`
|
||||||
|
FastestLapInitials string `json:"fastestLapInitials"`
|
||||||
|
Results []LeaderboardResult `gorm:"foreignKey:RaceID;constraint:OnDelete:CASCADE" json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaderboardResult struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||||
|
RaceID uuid.UUID `gorm:"not null;type:uuid" json:"raceId"`
|
||||||
|
DriverID uuid.UUID `gorm:"not null;type:uuid" json:"driverId"`
|
||||||
|
Score string `json:"score"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaderboardPointRow struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
|
||||||
|
LeaderboardID uuid.UUID `gorm:"not null;type:uuid" json:"leaderboardId"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Points int `json:"points"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
TextColor string `json:"textColor"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaderboardInput is the wire format matching the HTML's appData shape.
|
||||||
|
type LeaderboardInput struct {
|
||||||
|
Drivers []LeaderboardDriverInput `json:"drivers"`
|
||||||
|
PointsTable []LeaderboardPointRowInput `json:"pointsTable"`
|
||||||
|
FLPoints LeaderboardFLInput `json:"flPoints"`
|
||||||
|
Tracks []LeaderboardTrackInput `json:"tracks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaderboardDriverInput struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
Initials string `json:"initials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaderboardPointRowInput struct {
|
||||||
|
Points int `json:"points"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
TextColor string `json:"textColor"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaderboardFLInput struct {
|
||||||
|
Points int `json:"points"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
TextColor string `json:"textColor"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeaderboardTrackInput struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Results []interface{} `json:"results"`
|
||||||
|
FastestLapInitials string `json:"fastestLapInitials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Leaderboard) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if l.ID == uuid.Nil {
|
||||||
|
l.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *LeaderboardDriver) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if d.ID == uuid.Nil {
|
||||||
|
d.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LeaderboardRace) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if r.ID == uuid.Nil {
|
||||||
|
r.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LeaderboardResult) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if r.ID == uuid.Nil {
|
||||||
|
r.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LeaderboardPointRow) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if p.ID == uuid.Nil {
|
||||||
|
p.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
146
local/repository/leaderboard.go
Normal file
146
local/repository/leaderboard.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"acc-server-manager/local/model"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LeaderboardRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLeaderboardRepository(db *gorm.DB) *LeaderboardRepository {
|
||||||
|
return &LeaderboardRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrCreateByServerID fetches the leaderboard for a server, creating an empty one if it doesn't exist.
|
||||||
|
func (r *LeaderboardRepository) GetOrCreateByServerID(ctx context.Context, serverID uuid.UUID) (*model.Leaderboard, error) {
|
||||||
|
lb := new(model.Leaderboard)
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Drivers").
|
||||||
|
Preload("Races.Results").
|
||||||
|
Preload("PointRows").
|
||||||
|
Where("server_id = ?", serverID).
|
||||||
|
First(lb).Error
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return lb, nil
|
||||||
|
}
|
||||||
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fmt.Errorf("error fetching leaderboard: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lb = &model.Leaderboard{
|
||||||
|
ServerID: serverID,
|
||||||
|
FLPoints: 1,
|
||||||
|
FLColor: "#8b5cf6",
|
||||||
|
FLTextColor: "#000000",
|
||||||
|
}
|
||||||
|
if err := r.db.WithContext(ctx).Create(lb).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating leaderboard: %w", err)
|
||||||
|
}
|
||||||
|
return lb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullReplace replaces all leaderboard data for a server in a single transaction.
|
||||||
|
func (r *LeaderboardRepository) FullReplace(ctx context.Context, serverID uuid.UUID, lb *model.Leaderboard) (*model.Leaderboard, error) {
|
||||||
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
existing := new(model.Leaderboard)
|
||||||
|
err := tx.Where("server_id = ?", serverID).First(existing).Error
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fmt.Errorf("error fetching existing leaderboard: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
lb.ServerID = serverID
|
||||||
|
return tx.Create(lb).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete children
|
||||||
|
if err := tx.Where("leaderboard_id = ?", existing.ID).Delete(&model.LeaderboardPointRow{}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Where("leaderboard_id = ?", existing.ID).Delete(&model.LeaderboardDriver{}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get race IDs to delete results
|
||||||
|
var raceIDs []uuid.UUID
|
||||||
|
tx.Model(&model.LeaderboardRace{}).Where("leaderboard_id = ?", existing.ID).Pluck("id", &raceIDs)
|
||||||
|
if len(raceIDs) > 0 {
|
||||||
|
if err := tx.Where("race_id IN ?", raceIDs).Delete(&model.LeaderboardResult{}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := tx.Where("leaderboard_id = ?", existing.ID).Delete(&model.LeaderboardRace{}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update parent fields
|
||||||
|
existing.FLPoints = lb.FLPoints
|
||||||
|
existing.FLColor = lb.FLColor
|
||||||
|
existing.FLTextColor = lb.FLTextColor
|
||||||
|
if err := tx.Save(existing).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-insert children with correct leaderboard ID
|
||||||
|
for i := range lb.Drivers {
|
||||||
|
lb.Drivers[i].LeaderboardID = existing.ID
|
||||||
|
lb.Drivers[i].ID = uuid.Nil // force new ID
|
||||||
|
}
|
||||||
|
if len(lb.Drivers) > 0 {
|
||||||
|
if err := tx.Create(&lb.Drivers).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range lb.PointRows {
|
||||||
|
lb.PointRows[i].LeaderboardID = existing.ID
|
||||||
|
lb.PointRows[i].ID = uuid.Nil
|
||||||
|
}
|
||||||
|
if len(lb.PointRows) > 0 {
|
||||||
|
if err := tx.Create(&lb.PointRows).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range lb.Races {
|
||||||
|
lb.Races[i].LeaderboardID = existing.ID
|
||||||
|
lb.Races[i].ID = uuid.Nil
|
||||||
|
}
|
||||||
|
if len(lb.Races) > 0 {
|
||||||
|
// Create races first (without results) to get IDs
|
||||||
|
for i := range lb.Races {
|
||||||
|
results := lb.Races[i].Results
|
||||||
|
lb.Races[i].Results = nil
|
||||||
|
if err := tx.Create(&lb.Races[i]).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Re-attach results with correct race ID
|
||||||
|
for j := range results {
|
||||||
|
results[j].RaceID = lb.Races[i].ID
|
||||||
|
results[j].ID = uuid.Nil
|
||||||
|
}
|
||||||
|
if len(results) > 0 {
|
||||||
|
if err := tx.Create(&results).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lb.ID = existing.ID
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.GetOrCreateByServerID(ctx, serverID)
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ func InitializeRepositories(c *dig.Container) {
|
|||||||
c.Provide(NewLookupRepository)
|
c.Provide(NewLookupRepository)
|
||||||
c.Provide(NewSteamCredentialsRepository)
|
c.Provide(NewSteamCredentialsRepository)
|
||||||
c.Provide(NewMembershipRepository)
|
c.Provide(NewMembershipRepository)
|
||||||
|
c.Provide(NewLeaderboardRepository)
|
||||||
|
|
||||||
if err := c.Provide(func() *model.Steam2FAManager {
|
if err := c.Provide(func() *model.Steam2FAManager {
|
||||||
manager := model.NewSteam2FAManager()
|
manager := model.NewSteam2FAManager()
|
||||||
|
|||||||
205
local/service/leaderboard.go
Normal file
205
local/service/leaderboard.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"acc-server-manager/local/model"
|
||||||
|
"acc-server-manager/local/repository"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LeaderboardService struct {
|
||||||
|
repo *repository.LeaderboardRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLeaderboardService(repo *repository.LeaderboardRepository) *LeaderboardService {
|
||||||
|
return &LeaderboardService{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the leaderboard for a server in the wire format (LeaderboardInput).
|
||||||
|
func (s *LeaderboardService) Get(ctx context.Context, serverID uuid.UUID) (*model.LeaderboardInput, error) {
|
||||||
|
lb, err := s.repo.GetOrCreateByServerID(ctx, serverID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toInput(lb), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update replaces the leaderboard for a server from wire format input.
|
||||||
|
func (s *LeaderboardService) Update(ctx context.Context, serverID uuid.UUID, input *model.LeaderboardInput) (*model.LeaderboardInput, error) {
|
||||||
|
lb, err := fromInput(serverID, input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result, err := s.repo.FullReplace(ctx, serverID, lb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toInput(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toInput converts the normalized DB model to the flat wire format.
|
||||||
|
func toInput(lb *model.Leaderboard) *model.LeaderboardInput {
|
||||||
|
// Build driver map (id -> index in slice) for result ordering
|
||||||
|
driverIndexByID := make(map[uuid.UUID]int, len(lb.Drivers))
|
||||||
|
|
||||||
|
// Sort drivers by position
|
||||||
|
drivers := make([]model.LeaderboardDriverInput, len(lb.Drivers))
|
||||||
|
for i, d := range lb.Drivers {
|
||||||
|
drivers[i] = model.LeaderboardDriverInput{
|
||||||
|
Name: d.Name,
|
||||||
|
Color: d.Color,
|
||||||
|
Initials: d.Initials,
|
||||||
|
}
|
||||||
|
driverIndexByID[d.ID] = d.Position
|
||||||
|
}
|
||||||
|
// Re-index by position order
|
||||||
|
driverByPos := make(map[int]model.LeaderboardDriver, len(lb.Drivers))
|
||||||
|
for _, d := range lb.Drivers {
|
||||||
|
driverByPos[d.Position] = d
|
||||||
|
}
|
||||||
|
driverCount := len(lb.Drivers)
|
||||||
|
orderedDrivers := make([]model.LeaderboardDriver, driverCount)
|
||||||
|
for i := 0; i < driverCount; i++ {
|
||||||
|
if d, ok := driverByPos[i]; ok {
|
||||||
|
orderedDrivers[i] = d
|
||||||
|
drivers[i] = model.LeaderboardDriverInput{
|
||||||
|
Name: d.Name,
|
||||||
|
Color: d.Color,
|
||||||
|
Initials: d.Initials,
|
||||||
|
}
|
||||||
|
driverIndexByID[d.ID] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build tracks
|
||||||
|
raceByPos := make(map[int]model.LeaderboardRace, len(lb.Races))
|
||||||
|
for _, r := range lb.Races {
|
||||||
|
raceByPos[r.Position] = r
|
||||||
|
}
|
||||||
|
tracks := make([]model.LeaderboardTrackInput, len(lb.Races))
|
||||||
|
for i := 0; i < len(lb.Races); i++ {
|
||||||
|
race, ok := raceByPos[i]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
results := make([]interface{}, driverCount)
|
||||||
|
for j := range results {
|
||||||
|
results[j] = "0"
|
||||||
|
}
|
||||||
|
for _, res := range race.Results {
|
||||||
|
idx, found := driverIndexByID[res.DriverID]
|
||||||
|
if found && idx < driverCount {
|
||||||
|
results[idx] = res.Score
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracks[i] = model.LeaderboardTrackInput{
|
||||||
|
Name: race.Name,
|
||||||
|
Results: results,
|
||||||
|
FastestLapInitials: race.FastestLapInitials,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build points table
|
||||||
|
pointsTable := make([]model.LeaderboardPointRowInput, len(lb.PointRows))
|
||||||
|
for i, p := range lb.PointRows {
|
||||||
|
pointsTable[i] = model.LeaderboardPointRowInput{
|
||||||
|
Points: p.Points,
|
||||||
|
Label: p.Label,
|
||||||
|
Color: p.Color,
|
||||||
|
TextColor: p.TextColor,
|
||||||
|
Priority: p.Priority,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flColor := lb.FLColor
|
||||||
|
if flColor == "" {
|
||||||
|
flColor = "#8b5cf6"
|
||||||
|
}
|
||||||
|
flTextColor := lb.FLTextColor
|
||||||
|
if flTextColor == "" {
|
||||||
|
flTextColor = "#000000"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.LeaderboardInput{
|
||||||
|
Drivers: drivers,
|
||||||
|
PointsTable: pointsTable,
|
||||||
|
FLPoints: model.LeaderboardFLInput{
|
||||||
|
Points: lb.FLPoints,
|
||||||
|
Label: "FL +1",
|
||||||
|
Color: flColor,
|
||||||
|
TextColor: flTextColor,
|
||||||
|
Priority: len(pointsTable) + 1,
|
||||||
|
},
|
||||||
|
Tracks: tracks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromInput converts the wire format to the normalized DB model.
|
||||||
|
func fromInput(serverID uuid.UUID, input *model.LeaderboardInput) (*model.Leaderboard, error) {
|
||||||
|
if input == nil {
|
||||||
|
return nil, fmt.Errorf("input is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
lb := &model.Leaderboard{
|
||||||
|
ServerID: serverID,
|
||||||
|
FLPoints: input.FLPoints.Points,
|
||||||
|
FLColor: input.FLPoints.Color,
|
||||||
|
FLTextColor: input.FLPoints.TextColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build drivers
|
||||||
|
drivers := make([]model.LeaderboardDriver, len(input.Drivers))
|
||||||
|
driverIDs := make([]uuid.UUID, len(input.Drivers))
|
||||||
|
for i, d := range input.Drivers {
|
||||||
|
id := uuid.New()
|
||||||
|
driverIDs[i] = id
|
||||||
|
drivers[i] = model.LeaderboardDriver{
|
||||||
|
ID: id,
|
||||||
|
Name: d.Name,
|
||||||
|
Initials: d.Initials,
|
||||||
|
Color: d.Color,
|
||||||
|
Position: i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lb.Drivers = drivers
|
||||||
|
|
||||||
|
// Build point rows
|
||||||
|
pointRows := make([]model.LeaderboardPointRow, len(input.PointsTable))
|
||||||
|
for i, p := range input.PointsTable {
|
||||||
|
pointRows[i] = model.LeaderboardPointRow{
|
||||||
|
Label: p.Label,
|
||||||
|
Points: p.Points,
|
||||||
|
Color: p.Color,
|
||||||
|
TextColor: p.TextColor,
|
||||||
|
Priority: p.Priority,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lb.PointRows = pointRows
|
||||||
|
|
||||||
|
// Build races
|
||||||
|
races := make([]model.LeaderboardRace, len(input.Tracks))
|
||||||
|
for i, t := range input.Tracks {
|
||||||
|
results := make([]model.LeaderboardResult, 0, len(t.Results))
|
||||||
|
for j, score := range t.Results {
|
||||||
|
if j >= len(driverIDs) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
scoreStr := fmt.Sprintf("%v", score)
|
||||||
|
results = append(results, model.LeaderboardResult{
|
||||||
|
DriverID: driverIDs[j],
|
||||||
|
Score: scoreStr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
races[i] = model.LeaderboardRace{
|
||||||
|
Name: t.Name,
|
||||||
|
Position: i,
|
||||||
|
FastestLapInitials: t.FastestLapInitials,
|
||||||
|
Results: results,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lb.Races = races
|
||||||
|
|
||||||
|
return lb, nil
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ func InitializeServices(c *dig.Container) {
|
|||||||
c.Provide(NewFirewallService)
|
c.Provide(NewFirewallService)
|
||||||
c.Provide(NewMembershipService)
|
c.Provide(NewMembershipService)
|
||||||
c.Provide(NewWebSocketService)
|
c.Provide(NewWebSocketService)
|
||||||
|
c.Provide(NewLeaderboardService)
|
||||||
|
|
||||||
logging.Debug("Initializing service dependencies")
|
logging.Debug("Initializing service dependencies")
|
||||||
err := c.Invoke(func(server *ServerService, api *ServiceControlService, config *ConfigService) {
|
err := c.Invoke(func(server *ServerService, api *ServiceControlService, config *ConfigService) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type RouteGroups struct {
|
|||||||
Membership fiber.Router
|
Membership fiber.Router
|
||||||
System fiber.Router
|
System fiber.Router
|
||||||
WebSocket fiber.Router
|
WebSocket fiber.Router
|
||||||
|
Leaderboard fiber.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckError(err error) {
|
func CheckError(err error) {
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ func Migrate(db *gorm.DB) {
|
|||||||
&model.User{},
|
&model.User{},
|
||||||
&model.Role{},
|
&model.Role{},
|
||||||
&model.Permission{},
|
&model.Permission{},
|
||||||
|
&model.Leaderboard{},
|
||||||
|
&model.LeaderboardDriver{},
|
||||||
|
&model.LeaderboardRace{},
|
||||||
|
&model.LeaderboardResult{},
|
||||||
|
&model.LeaderboardPointRow{},
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user