add leaderboard

This commit is contained in:
2026-02-22 12:28:48 +01:00
parent 4004d83411
commit 190e1a3a16
10 changed files with 554 additions and 0 deletions

View 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)
}

View File

@@ -19,6 +19,7 @@ func InitializeRepositories(c *dig.Container) {
c.Provide(NewLookupRepository)
c.Provide(NewSteamCredentialsRepository)
c.Provide(NewMembershipRepository)
c.Provide(NewLeaderboardRepository)
if err := c.Provide(func() *model.Steam2FAManager {
manager := model.NewSteam2FAManager()