mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
68 lines
2.1 KiB
Go
68 lines
2.1 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/cosmic-clash/cosmic-clash/server/domain"
|
|
)
|
|
|
|
const DueSeasonRolloversSQL = `SELECT s.season_id, r.player_id, r.rating, r.deviation,
|
|
r.volatility, r.ranked_games
|
|
FROM seasons s
|
|
CROSS JOIN ratings r
|
|
LEFT JOIN ranked_season_rollovers rr ON rr.season_id = s.season_id AND rr.player_id = r.player_id
|
|
WHERE s.playlist = 'ranked' AND s.ends_at <= $1 AND rr.player_id IS NULL
|
|
ORDER BY s.ends_at, s.season_id, r.player_id
|
|
LIMIT $2`
|
|
|
|
const MarkSeasonRolledOverSQL = `UPDATE seasons SET rolled_over_at = $2
|
|
WHERE season_id = $1 AND rolled_over_at IS NULL
|
|
AND NOT EXISTS (SELECT 1 FROM ratings r
|
|
LEFT JOIN ranked_season_rollovers rr ON rr.season_id = $1 AND rr.player_id = r.player_id
|
|
WHERE rr.player_id IS NULL)`
|
|
|
|
type dueSeasonRollover struct {
|
|
seasonID string
|
|
playerID string
|
|
profile domain.RankedProfile
|
|
}
|
|
|
|
// RolloverDueSeasons processes a bounded batch. Each player update is its own
|
|
// exactly-once SERIALIZABLE transaction, so a worker crash can safely resume.
|
|
func RolloverDueSeasons(ctx context.Context, db *sql.DB, now time.Time, limit int) (int, error) {
|
|
if db == nil || now.IsZero() || limit < 1 || limit > 1000 {
|
|
return 0, fmt.Errorf("invalid season maintenance arguments")
|
|
}
|
|
rows, err := db.QueryContext(ctx, DueSeasonRolloversSQL, now, limit)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer rows.Close()
|
|
var due []dueSeasonRollover
|
|
for rows.Next() {
|
|
var item dueSeasonRollover
|
|
if err := rows.Scan(&item.seasonID, &item.playerID, &item.profile.Value, &item.profile.RD, &item.profile.Volatility, &item.profile.RankedGames); err != nil {
|
|
return 0, err
|
|
}
|
|
due = append(due, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return 0, err
|
|
}
|
|
count := 0
|
|
for _, item := range due {
|
|
if _, applied, err := ApplyRankedSeasonRollover(ctx, db, item.playerID, item.seasonID, item.profile, now); err != nil {
|
|
return count, err
|
|
} else if applied {
|
|
count++
|
|
}
|
|
if _, err := db.ExecContext(ctx, MarkSeasonRolledOverSQL, item.seasonID, now); err != nil {
|
|
return count, err
|
|
}
|
|
}
|
|
return count, nil
|
|
}
|