final
This commit is contained in:
@@ -66,7 +66,30 @@ CREATE TABLE IF NOT EXISTS app_settings (
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO app_settings(key, value) VALUES
|
||||
('view_proof_in_view', '0');
|
||||
('view_proof_in_view', '0'),
|
||||
('live_active_view', 'group_live'),
|
||||
('live_show_group_live', '1'),
|
||||
('live_group_display_mode', 'rotate'),
|
||||
('live_group_fixed_code', ''),
|
||||
('live_show_prelim_tie', '1'),
|
||||
('live_show_prelim_overall', '1'),
|
||||
('live_show_final_groups', '1'),
|
||||
('live_final_display_mode', 'rotate'),
|
||||
('live_final_fixed_group', '1'),
|
||||
('live_show_final_tie', '1'),
|
||||
('live_show_podium', '1'),
|
||||
('live_rotation_interval_sec', '5'),
|
||||
('live_rotation_player_count', '12');
|
||||
|
||||
INSERT OR IGNORE INTO scores(stage, player_id, score)
|
||||
SELECT 'prelim_r1', player_id, score FROM scores WHERE stage = 'preliminary';
|
||||
INSERT OR IGNORE INTO scores(stage, player_id, score)
|
||||
SELECT 'final_r1', player_id, score FROM scores WHERE stage = 'final';
|
||||
|
||||
INSERT OR IGNORE INTO score_attachments(stage, player_id, image_data)
|
||||
SELECT 'prelim_r1', player_id, image_data FROM score_attachments WHERE stage = 'preliminary';
|
||||
INSERT OR IGNORE INTO score_attachments(stage, player_id, image_data)
|
||||
SELECT 'final_r1', player_id, image_data FROM score_attachments WHERE stage = 'final';
|
||||
`
|
||||
if _, err := db.Exec(schema); err != nil {
|
||||
return nil, fmt.Errorf("apply schema: %w", err)
|
||||
|
||||
@@ -58,11 +58,14 @@ func (a *App) handleUpdateAdminSettings(c echo.Context) error {
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return writeError(c, http.StatusBadRequest, "invalid request body")
|
||||
}
|
||||
if req.ViewProofInView == nil {
|
||||
return writeError(c, http.StatusBadRequest, "viewProofInView is required")
|
||||
if req.ViewProofInView == nil && req.LiveMode == nil {
|
||||
return writeError(c, http.StatusBadRequest, "at least one settings field is required")
|
||||
}
|
||||
|
||||
if err := a.updateViewProofInView(*req.ViewProofInView); err != nil {
|
||||
if err := a.updateSettings(req); err != nil {
|
||||
if strings.Contains(err.Error(), "no settings to update") {
|
||||
return writeError(c, http.StatusBadRequest, err.Error())
|
||||
}
|
||||
return writeError(c, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
@@ -334,7 +337,7 @@ ON CONFLICT(stage, player_id) DO UPDATE SET score = excluded.score, updated_at =
|
||||
}
|
||||
|
||||
func (a *App) handleResetStageScores(c echo.Context) error {
|
||||
stage, err := validateStage(c.Param("stage"))
|
||||
targetStages, err := resolveResetStages(c.Param("stage"))
|
||||
if err != nil {
|
||||
return writeError(c, http.StatusBadRequest, err.Error())
|
||||
}
|
||||
@@ -344,13 +347,24 @@ func (a *App) handleResetStageScores(c echo.Context) error {
|
||||
return writeError(c, http.StatusBadRequest, "invalid request body")
|
||||
}
|
||||
|
||||
if _, err := a.db.Exec(`UPDATE scores SET score = 0, updated_at = CURRENT_TIMESTAMP WHERE stage = ?`, stage); err != nil {
|
||||
return writeError(c, http.StatusInternalServerError, fmt.Sprintf("reset stage scores: %v", err))
|
||||
tx, err := a.db.Begin()
|
||||
if err != nil {
|
||||
return writeError(c, http.StatusInternalServerError, "failed to start reset transaction")
|
||||
}
|
||||
if req.ResetProofs {
|
||||
if _, err := a.db.Exec(`DELETE FROM score_attachments WHERE stage = ?`, stage); err != nil {
|
||||
return writeError(c, http.StatusInternalServerError, fmt.Sprintf("reset stage proofs: %v", err))
|
||||
defer tx.Rollback()
|
||||
|
||||
for _, stage := range targetStages {
|
||||
if _, err := tx.Exec(`UPDATE scores SET score = 0, updated_at = CURRENT_TIMESTAMP WHERE stage = ?`, stage); err != nil {
|
||||
return writeError(c, http.StatusInternalServerError, fmt.Sprintf("reset stage scores: %v", err))
|
||||
}
|
||||
if req.ResetProofs {
|
||||
if _, err := tx.Exec(`DELETE FROM score_attachments WHERE stage = ?`, stage); err != nil {
|
||||
return writeError(c, http.StatusInternalServerError, fmt.Sprintf("reset stage proofs: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return writeError(c, http.StatusInternalServerError, fmt.Sprintf("commit stage reset: %v", err))
|
||||
}
|
||||
|
||||
a.events.Broadcast()
|
||||
@@ -363,6 +377,23 @@ func (a *App) handleResetStageScores(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, state)
|
||||
}
|
||||
|
||||
func resolveResetStages(stage string) ([]string, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(stage)) {
|
||||
case "preliminary":
|
||||
return append([]string{}, preliminaryRoundStages...), nil
|
||||
case "final":
|
||||
return append([]string{}, finalRoundStages...), nil
|
||||
case "prelim_tiebreak", "final_tiebreak":
|
||||
normalized, err := validateStage(stage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []string{normalized}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid stage")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleUpdateScoreProof(c echo.Context) error {
|
||||
stage, err := validateStage(c.Param("stage"))
|
||||
if err != nil {
|
||||
|
||||
@@ -6,7 +6,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var scoreStages = []string{"preliminary", "prelim_tiebreak", "final", "final_tiebreak"}
|
||||
var preliminaryRoundStages = []string{"prelim_r1", "prelim_r2", "prelim_r3"}
|
||||
var finalRoundStages = []string{"final_r1", "final_r2"}
|
||||
var tieBreakStages = []string{"prelim_tiebreak", "final_tiebreak"}
|
||||
|
||||
var scoreStages = append(append(append([]string{}, preliminaryRoundStages...), finalRoundStages...), tieBreakStages...)
|
||||
|
||||
type App struct {
|
||||
db *sql.DB
|
||||
@@ -90,7 +94,24 @@ type StateResponse struct {
|
||||
}
|
||||
|
||||
type AppSettings struct {
|
||||
ViewProofInView bool `json:"viewProofInView"`
|
||||
ViewProofInView bool `json:"viewProofInView"`
|
||||
LiveMode LiveModeSettings `json:"liveMode"`
|
||||
}
|
||||
|
||||
type LiveModeSettings struct {
|
||||
ActiveView string `json:"activeView"`
|
||||
ShowGroupLive bool `json:"showGroupLive"`
|
||||
GroupDisplayMode string `json:"groupDisplayMode"`
|
||||
GroupFixedCode string `json:"groupFixedCode"`
|
||||
ShowPrelimTie bool `json:"showPrelimTie"`
|
||||
ShowPrelimOverall bool `json:"showPrelimOverall"`
|
||||
ShowFinalGroups bool `json:"showFinalGroups"`
|
||||
FinalDisplayMode string `json:"finalDisplayMode"`
|
||||
FinalFixedGroup int `json:"finalFixedGroup"`
|
||||
ShowFinalTie bool `json:"showFinalTie"`
|
||||
ShowPodium bool `json:"showPodium"`
|
||||
RotationIntervalSec int `json:"rotationIntervalSec"`
|
||||
RotationPlayerCount int `json:"rotationPlayerCount"`
|
||||
}
|
||||
|
||||
type AdminLoginRequest struct {
|
||||
@@ -121,7 +142,24 @@ type ScoreProofUpdateRequest struct {
|
||||
}
|
||||
|
||||
type AdminSettingsUpdateRequest struct {
|
||||
ViewProofInView *bool `json:"viewProofInView"`
|
||||
ViewProofInView *bool `json:"viewProofInView"`
|
||||
LiveMode *LiveModeSettingsUpdateRequest `json:"liveMode"`
|
||||
}
|
||||
|
||||
type LiveModeSettingsUpdateRequest struct {
|
||||
ActiveView *string `json:"activeView"`
|
||||
ShowGroupLive *bool `json:"showGroupLive"`
|
||||
GroupDisplayMode *string `json:"groupDisplayMode"`
|
||||
GroupFixedCode *string `json:"groupFixedCode"`
|
||||
ShowPrelimTie *bool `json:"showPrelimTie"`
|
||||
ShowPrelimOverall *bool `json:"showPrelimOverall"`
|
||||
ShowFinalGroups *bool `json:"showFinalGroups"`
|
||||
FinalDisplayMode *string `json:"finalDisplayMode"`
|
||||
FinalFixedGroup *int `json:"finalFixedGroup"`
|
||||
ShowFinalTie *bool `json:"showFinalTie"`
|
||||
ShowPodium *bool `json:"showPodium"`
|
||||
RotationIntervalSec *int `json:"rotationIntervalSec"`
|
||||
RotationPlayerCount *int `json:"rotationPlayerCount"`
|
||||
}
|
||||
|
||||
type ResetStageRequest struct {
|
||||
|
||||
@@ -3,33 +3,275 @@ package main
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (a *App) readSettings() (AppSettings, error) {
|
||||
var raw string
|
||||
err := a.db.QueryRow(`SELECT value FROM app_settings WHERE key = 'view_proof_in_view'`).Scan(&raw)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return AppSettings{ViewProofInView: false}, nil
|
||||
}
|
||||
return AppSettings{}, fmt.Errorf("read app setting view_proof_in_view: %w", err)
|
||||
const (
|
||||
settingViewProofInView = "view_proof_in_view"
|
||||
settingLiveActiveView = "live_active_view"
|
||||
settingLiveShowGroupLive = "live_show_group_live"
|
||||
settingLiveGroupDisplayMode = "live_group_display_mode"
|
||||
settingLiveGroupFixedCode = "live_group_fixed_code"
|
||||
settingLiveShowPrelimTie = "live_show_prelim_tie"
|
||||
settingLiveShowPrelimOverall = "live_show_prelim_overall"
|
||||
settingLiveShowFinalGroups = "live_show_final_groups"
|
||||
settingLiveFinalDisplayMode = "live_final_display_mode"
|
||||
settingLiveFinalFixedGroup = "live_final_fixed_group"
|
||||
settingLiveShowFinalTie = "live_show_final_tie"
|
||||
settingLiveShowPodium = "live_show_podium"
|
||||
settingLiveRotationInterval = "live_rotation_interval_sec"
|
||||
settingLiveRotationPlayers = "live_rotation_player_count"
|
||||
)
|
||||
|
||||
func defaultLiveModeSettings() LiveModeSettings {
|
||||
return LiveModeSettings{
|
||||
ActiveView: "group_live",
|
||||
ShowGroupLive: true,
|
||||
GroupDisplayMode: "rotate",
|
||||
GroupFixedCode: "",
|
||||
ShowPrelimTie: true,
|
||||
ShowPrelimOverall: true,
|
||||
ShowFinalGroups: true,
|
||||
FinalDisplayMode: "rotate",
|
||||
FinalFixedGroup: 1,
|
||||
ShowFinalTie: true,
|
||||
ShowPodium: true,
|
||||
RotationIntervalSec: 5,
|
||||
RotationPlayerCount: 12,
|
||||
}
|
||||
return AppSettings{ViewProofInView: settingBool(raw)}, nil
|
||||
}
|
||||
|
||||
func (a *App) updateViewProofInView(enabled bool) error {
|
||||
_, err := a.db.Exec(`
|
||||
INSERT INTO app_settings(key, value, updated_at)
|
||||
VALUES('view_proof_in_view', ?, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP
|
||||
`, settingString(enabled))
|
||||
func (a *App) readSettings() (AppSettings, error) {
|
||||
live := defaultLiveModeSettings()
|
||||
|
||||
viewProofRaw, err := a.readSettingValue(settingViewProofInView)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update app setting view_proof_in_view: %w", err)
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingViewProofInView, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveShowGroupLive); err == nil {
|
||||
live.ShowGroupLive = settingBool(raw)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowGroupLive, err)
|
||||
}
|
||||
if raw, err := a.readSettingValue(settingLiveActiveView); err == nil {
|
||||
live.ActiveView = normalizeLiveActiveView(raw, live.ActiveView)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveActiveView, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveGroupDisplayMode); err == nil {
|
||||
live.GroupDisplayMode = normalizeLiveDisplayMode(raw, live.GroupDisplayMode)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveGroupDisplayMode, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveGroupFixedCode); err == nil {
|
||||
live.GroupFixedCode = strings.ToUpper(strings.TrimSpace(raw))
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveGroupFixedCode, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveShowPrelimTie); err == nil {
|
||||
live.ShowPrelimTie = settingBool(raw)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowPrelimTie, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveShowPrelimOverall); err == nil {
|
||||
live.ShowPrelimOverall = settingBool(raw)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowPrelimOverall, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveShowFinalGroups); err == nil {
|
||||
live.ShowFinalGroups = settingBool(raw)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowFinalGroups, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveFinalDisplayMode); err == nil {
|
||||
live.FinalDisplayMode = normalizeLiveDisplayMode(raw, live.FinalDisplayMode)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveFinalDisplayMode, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveFinalFixedGroup); err == nil {
|
||||
live.FinalFixedGroup = normalizeLiveFinalFixedGroup(raw, live.FinalFixedGroup)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveFinalFixedGroup, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveShowFinalTie); err == nil {
|
||||
live.ShowFinalTie = settingBool(raw)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowFinalTie, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveShowPodium); err == nil {
|
||||
live.ShowPodium = settingBool(raw)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveShowPodium, err)
|
||||
}
|
||||
|
||||
if raw, err := a.readSettingValue(settingLiveRotationInterval); err == nil {
|
||||
live.RotationIntervalSec = normalizeRotationInterval(raw, live.RotationIntervalSec)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveRotationInterval, err)
|
||||
}
|
||||
if raw, err := a.readSettingValue(settingLiveRotationPlayers); err == nil {
|
||||
live.RotationPlayerCount = normalizeRotationPlayerCount(raw, live.RotationPlayerCount)
|
||||
} else {
|
||||
return AppSettings{}, fmt.Errorf("read app setting %s: %w", settingLiveRotationPlayers, err)
|
||||
}
|
||||
|
||||
return AppSettings{
|
||||
ViewProofInView: settingBool(viewProofRaw),
|
||||
LiveMode: live,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) updateSettings(req AdminSettingsUpdateRequest) error {
|
||||
tx, err := a.db.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("begin settings transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
hasUpdate := false
|
||||
|
||||
if req.ViewProofInView != nil {
|
||||
hasUpdate = true
|
||||
if err := upsertSetting(tx, settingViewProofInView, settingString(*req.ViewProofInView)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if req.LiveMode != nil {
|
||||
patch := req.LiveMode
|
||||
if patch.ActiveView != nil {
|
||||
hasUpdate = true
|
||||
value := normalizeLiveActiveView(*patch.ActiveView, "group_live")
|
||||
if err := upsertSetting(tx, settingLiveActiveView, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.ShowGroupLive != nil {
|
||||
hasUpdate = true
|
||||
if err := upsertSetting(tx, settingLiveShowGroupLive, settingString(*patch.ShowGroupLive)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.GroupDisplayMode != nil {
|
||||
hasUpdate = true
|
||||
value := normalizeLiveDisplayMode(*patch.GroupDisplayMode, "rotate")
|
||||
if err := upsertSetting(tx, settingLiveGroupDisplayMode, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.GroupFixedCode != nil {
|
||||
hasUpdate = true
|
||||
value := strings.ToUpper(strings.TrimSpace(*patch.GroupFixedCode))
|
||||
if err := upsertSetting(tx, settingLiveGroupFixedCode, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.ShowPrelimTie != nil {
|
||||
hasUpdate = true
|
||||
if err := upsertSetting(tx, settingLiveShowPrelimTie, settingString(*patch.ShowPrelimTie)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.ShowPrelimOverall != nil {
|
||||
hasUpdate = true
|
||||
if err := upsertSetting(tx, settingLiveShowPrelimOverall, settingString(*patch.ShowPrelimOverall)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.ShowFinalGroups != nil {
|
||||
hasUpdate = true
|
||||
if err := upsertSetting(tx, settingLiveShowFinalGroups, settingString(*patch.ShowFinalGroups)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.FinalDisplayMode != nil {
|
||||
hasUpdate = true
|
||||
value := normalizeLiveDisplayMode(*patch.FinalDisplayMode, "rotate")
|
||||
if err := upsertSetting(tx, settingLiveFinalDisplayMode, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.FinalFixedGroup != nil {
|
||||
hasUpdate = true
|
||||
value := normalizeLiveFinalFixedGroupInt(*patch.FinalFixedGroup)
|
||||
if err := upsertSetting(tx, settingLiveFinalFixedGroup, strconv.Itoa(value)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.ShowFinalTie != nil {
|
||||
hasUpdate = true
|
||||
if err := upsertSetting(tx, settingLiveShowFinalTie, settingString(*patch.ShowFinalTie)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.ShowPodium != nil {
|
||||
hasUpdate = true
|
||||
if err := upsertSetting(tx, settingLiveShowPodium, settingString(*patch.ShowPodium)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.RotationIntervalSec != nil {
|
||||
hasUpdate = true
|
||||
value := normalizeRotationIntervalInt(*patch.RotationIntervalSec)
|
||||
if err := upsertSetting(tx, settingLiveRotationInterval, strconv.Itoa(value)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if patch.RotationPlayerCount != nil {
|
||||
hasUpdate = true
|
||||
value := normalizeRotationPlayerCountInt(*patch.RotationPlayerCount)
|
||||
if err := upsertSetting(tx, settingLiveRotationPlayers, strconv.Itoa(value)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasUpdate {
|
||||
return fmt.Errorf("no settings to update")
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("commit settings transaction: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func upsertSetting(tx *sql.Tx, key string, value string) error {
|
||||
_, err := tx.Exec(`
|
||||
INSERT INTO app_settings(key, value, updated_at)
|
||||
VALUES(?, ?, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP
|
||||
`, key, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update app setting %s: %w", key, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) readSettingValue(key string) (string, error) {
|
||||
var raw string
|
||||
err := a.db.QueryRow(`SELECT value FROM app_settings WHERE key = ?`, key).Scan(&raw)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func settingBool(value string) bool {
|
||||
switch strings.TrimSpace(strings.ToLower(value)) {
|
||||
case "1", "true", "yes", "on":
|
||||
@@ -45,3 +287,80 @@ func settingString(value bool) string {
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func normalizeLiveDisplayMode(value string, fallback string) string {
|
||||
normalized := strings.TrimSpace(strings.ToLower(value))
|
||||
if normalized == "fixed" {
|
||||
return "fixed"
|
||||
}
|
||||
if normalized == "rotate" {
|
||||
return "rotate"
|
||||
}
|
||||
if strings.TrimSpace(strings.ToLower(fallback)) == "fixed" {
|
||||
return "fixed"
|
||||
}
|
||||
return "rotate"
|
||||
}
|
||||
|
||||
func normalizeLiveFinalFixedGroup(value string, fallback int) int {
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(value))
|
||||
if err != nil {
|
||||
return normalizeLiveFinalFixedGroupInt(fallback)
|
||||
}
|
||||
return normalizeLiveFinalFixedGroupInt(parsed)
|
||||
}
|
||||
|
||||
func normalizeLiveFinalFixedGroupInt(value int) int {
|
||||
if value == 2 {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func normalizeRotationInterval(raw string, fallback int) int {
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return normalizeRotationIntervalInt(fallback)
|
||||
}
|
||||
return normalizeRotationIntervalInt(parsed)
|
||||
}
|
||||
|
||||
func normalizeRotationIntervalInt(value int) int {
|
||||
if value < 3 {
|
||||
return 3
|
||||
}
|
||||
if value > 30 {
|
||||
return 30
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func normalizeRotationPlayerCount(raw string, fallback int) int {
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return normalizeRotationPlayerCountInt(fallback)
|
||||
}
|
||||
return normalizeRotationPlayerCountInt(parsed)
|
||||
}
|
||||
|
||||
func normalizeRotationPlayerCountInt(value int) int {
|
||||
if value < 3 {
|
||||
return 3
|
||||
}
|
||||
if value > 40 {
|
||||
return 40
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func normalizeLiveActiveView(value string, fallback string) string {
|
||||
switch strings.TrimSpace(strings.ToLower(value)) {
|
||||
case "group_live", "prelim_tie", "prelim_overall", "final_groups", "final_tie", "podium":
|
||||
return strings.TrimSpace(strings.ToLower(value))
|
||||
default:
|
||||
if strings.TrimSpace(strings.ToLower(fallback)) != "" {
|
||||
return strings.TrimSpace(strings.ToLower(fallback))
|
||||
}
|
||||
return "group_live"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ func computeDerived(players []Player, scores map[string]map[int]int) DerivedStat
|
||||
NameEn: p.NameEn,
|
||||
GroupCode: p.GroupCode,
|
||||
ImageData: p.ImageData,
|
||||
Score: scores["preliminary"][p.ID],
|
||||
Score: scoreTotal(scores, p.ID, preliminaryRoundStages),
|
||||
TieBreak: scores["prelim_tiebreak"][p.ID],
|
||||
})
|
||||
}
|
||||
@@ -349,7 +349,7 @@ func computeDerived(players []Player, scores map[string]map[int]int) DerivedStat
|
||||
NameEn: p.NameEn,
|
||||
GroupCode: p.GroupCode,
|
||||
ImageData: p.ImageData,
|
||||
Score: scores["final"][finalist.PlayerID],
|
||||
Score: scoreTotal(scores, finalist.PlayerID, finalRoundStages),
|
||||
TieBreak: scores["final_tiebreak"][finalist.PlayerID],
|
||||
Seed: finalist.Seed,
|
||||
FinalGroup: finalist.FinalGroup,
|
||||
@@ -460,6 +460,18 @@ func computeDerived(players []Player, scores map[string]map[int]int) DerivedStat
|
||||
}
|
||||
}
|
||||
|
||||
func scoreTotal(scores map[string]map[int]int, playerID int, stages []string) int {
|
||||
total := 0
|
||||
for _, stage := range stages {
|
||||
stageMap, ok := scores[stage]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
total += stageMap[playerID]
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func assignDenseRankByScore(rows []RankingRow) {
|
||||
assignDenseRankBy(rows, func(a, b RankingRow) bool {
|
||||
return a.Score == b.Score
|
||||
|
||||
Reference in New Issue
Block a user