173 lines
3.9 KiB
Go
173 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
stagePhasePreliminary = "preliminary"
|
|
stagePhasePrelimTie = "prelim_tiebreak"
|
|
stagePhaseFinal = "final"
|
|
stagePhaseFinalTie = "final_tiebreak"
|
|
)
|
|
|
|
var allowedStagePhases = map[string]bool{
|
|
stagePhasePreliminary: true,
|
|
stagePhasePrelimTie: true,
|
|
stagePhaseFinal: true,
|
|
stagePhaseFinalTie: true,
|
|
}
|
|
|
|
func normalizeStagePhase(raw string) (string, bool) {
|
|
phase := strings.ToLower(strings.TrimSpace(raw))
|
|
if !allowedStagePhases[phase] {
|
|
return "", false
|
|
}
|
|
return phase, true
|
|
}
|
|
|
|
func normalizeStageGroup(phase string, raw string) (string, bool) {
|
|
group := strings.TrimSpace(raw)
|
|
switch phase {
|
|
case stagePhasePreliminary:
|
|
if strings.EqualFold(group, positionBoardUnassigned) {
|
|
return positionBoardUnassigned, true
|
|
}
|
|
group = normalizePreliminaryGroupKey(group)
|
|
if group == "" {
|
|
return "", false
|
|
}
|
|
return group, true
|
|
case stagePhaseFinal:
|
|
parsed, err := strconv.Atoi(group)
|
|
if err != nil || (parsed != 1 && parsed != 2) {
|
|
return "", false
|
|
}
|
|
return strconv.Itoa(parsed), true
|
|
case stagePhasePrelimTie, stagePhaseFinalTie:
|
|
parsed, err := strconv.Atoi(group)
|
|
if err != nil || parsed <= 0 {
|
|
return "", false
|
|
}
|
|
return strconv.Itoa(parsed), true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
func normalizeStageRound(phase string, round int) int {
|
|
switch phase {
|
|
case stagePhasePreliminary:
|
|
if round < 1 {
|
|
return 1
|
|
}
|
|
if round > 3 {
|
|
return 3
|
|
}
|
|
return round
|
|
case stagePhaseFinal:
|
|
if round < 1 {
|
|
return 1
|
|
}
|
|
if round > 2 {
|
|
return 2
|
|
}
|
|
return round
|
|
default:
|
|
return 1
|
|
}
|
|
}
|
|
|
|
func normalizeTimestamp(raw string) string {
|
|
trimmed := strings.TrimSpace(raw)
|
|
if trimmed == "" {
|
|
return ""
|
|
}
|
|
if parsed, err := time.Parse(time.RFC3339Nano, trimmed); err == nil {
|
|
return parsed.UTC().Format(time.RFC3339)
|
|
}
|
|
if parsed, err := time.Parse("2006-01-02 15:04:05", trimmed); err == nil {
|
|
return parsed.UTC().Format(time.RFC3339)
|
|
}
|
|
return trimmed
|
|
}
|
|
|
|
func scanStageSession(scanner interface{ Scan(dest ...any) error }) (*StageSessionState, error) {
|
|
var item StageSessionState
|
|
var ended sql.NullString
|
|
if err := scanner.Scan(&item.ID, &item.Phase, &item.GroupKey, &item.Round, &item.StartedAt, &ended); err != nil {
|
|
return nil, err
|
|
}
|
|
item.Phase = strings.ToLower(strings.TrimSpace(item.Phase))
|
|
item.GroupKey = strings.TrimSpace(item.GroupKey)
|
|
item.StartedAt = normalizeTimestamp(item.StartedAt)
|
|
if ended.Valid {
|
|
item.EndedAt = normalizeTimestamp(ended.String)
|
|
}
|
|
return &item, nil
|
|
}
|
|
|
|
func (a *App) readCurrentStage() (*StageSessionState, error) {
|
|
row := a.db.QueryRow(`
|
|
SELECT id, phase, group_key, round, started_at, ended_at
|
|
FROM stage_sessions
|
|
WHERE ended_at IS NULL
|
|
ORDER BY started_at DESC, id DESC
|
|
LIMIT 1
|
|
`)
|
|
item, err := scanStageSession(row)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("read current stage: %w", err)
|
|
}
|
|
return item, nil
|
|
}
|
|
|
|
func (a *App) readLastStage() (*StageSessionState, error) {
|
|
row := a.db.QueryRow(`
|
|
SELECT id, phase, group_key, round, started_at, ended_at
|
|
FROM stage_sessions
|
|
ORDER BY started_at DESC, id DESC
|
|
LIMIT 1
|
|
`)
|
|
item, err := scanStageSession(row)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("read last stage: %w", err)
|
|
}
|
|
return item, nil
|
|
}
|
|
|
|
func (a *App) readStageHistory() ([]StageSessionState, error) {
|
|
rows, err := a.db.Query(`
|
|
SELECT id, phase, group_key, round, started_at, ended_at
|
|
FROM stage_sessions
|
|
ORDER BY started_at DESC, id DESC
|
|
`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("query stage history: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
items := make([]StageSessionState, 0)
|
|
for rows.Next() {
|
|
item, scanErr := scanStageSession(rows)
|
|
if scanErr != nil {
|
|
return nil, fmt.Errorf("scan stage history: %w", scanErr)
|
|
}
|
|
items = append(items, *item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate stage history: %w", err)
|
|
}
|
|
return items, nil
|
|
}
|