update
This commit is contained in:
172
backend/stage_sessions.go
Normal file
172
backend/stage_sessions.go
Normal file
@@ -0,0 +1,172 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user