2026-04-01 11:47:03 +04:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"database/sql"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-03 09:55:36 +04:00
|
|
|
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...)
|
2026-04-01 11:47:03 +04:00
|
|
|
|
|
|
|
|
type App struct {
|
|
|
|
|
db *sql.DB
|
|
|
|
|
cfg Config
|
|
|
|
|
sessions *SessionStore
|
|
|
|
|
events *EventHub
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SessionStore struct {
|
|
|
|
|
mu sync.RWMutex
|
|
|
|
|
tokens map[string]time.Time
|
|
|
|
|
duration time.Duration
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type EventHub struct {
|
|
|
|
|
mu sync.RWMutex
|
|
|
|
|
nextID int
|
|
|
|
|
subscribers map[int]chan struct{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type CompetitionMeta struct {
|
|
|
|
|
TitleAr string `json:"titleAr"`
|
|
|
|
|
TitleEn string `json:"titleEn"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Player struct {
|
|
|
|
|
ID int `json:"id"`
|
|
|
|
|
NameAr string `json:"nameAr"`
|
|
|
|
|
NameEn string `json:"nameEn"`
|
|
|
|
|
GroupCode string `json:"groupCode"`
|
|
|
|
|
ImageData string `json:"imageData"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type RankingRow struct {
|
|
|
|
|
PlayerID int `json:"playerId"`
|
|
|
|
|
NameAr string `json:"nameAr"`
|
|
|
|
|
NameEn string `json:"nameEn"`
|
|
|
|
|
GroupCode string `json:"groupCode"`
|
|
|
|
|
ImageData string `json:"imageData"`
|
|
|
|
|
Score int `json:"score"`
|
|
|
|
|
TieBreak int `json:"tieBreak"`
|
|
|
|
|
Rank int `json:"rank"`
|
|
|
|
|
Seed int `json:"seed"`
|
|
|
|
|
FinalGroup int `json:"finalGroup"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TieBreakInfo struct {
|
|
|
|
|
Required bool `json:"required"`
|
|
|
|
|
Resolved bool `json:"resolved"`
|
|
|
|
|
Slots int `json:"slots"`
|
|
|
|
|
PlayerIDs []int `json:"playerIds"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type DerivedState struct {
|
|
|
|
|
PreliminaryRanking RankingBundle `json:"preliminaryRanking"`
|
|
|
|
|
Finalists []RankingRow `json:"finalists"`
|
|
|
|
|
FinalGroups FinalGroups `json:"finalGroups"`
|
|
|
|
|
FinalRanking RankingBundle `json:"finalRanking"`
|
|
|
|
|
Podium []RankingRow `json:"podium"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type RankingBundle struct {
|
|
|
|
|
Rows []RankingRow `json:"rows"`
|
|
|
|
|
TieBreak TieBreakInfo `json:"tieBreak"`
|
|
|
|
|
Unresolved bool `json:"unresolved"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type FinalGroups struct {
|
|
|
|
|
Group1 []RankingRow `json:"group1"`
|
|
|
|
|
Group2 []RankingRow `json:"group2"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type StateResponse struct {
|
|
|
|
|
Competition CompetitionMeta `json:"competition"`
|
|
|
|
|
Players []Player `json:"players"`
|
|
|
|
|
Scores map[string]map[string]int `json:"scores"`
|
|
|
|
|
ScoreProofs map[string]map[string]string `json:"scoreProofs,omitempty"`
|
|
|
|
|
Settings AppSettings `json:"settings"`
|
|
|
|
|
Derived DerivedState `json:"derived"`
|
|
|
|
|
ServerTime string `json:"serverTime"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AppSettings struct {
|
2026-04-03 09:55:36 +04:00
|
|
|
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"`
|
2026-04-01 11:47:03 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AdminLoginRequest struct {
|
|
|
|
|
Username string `json:"username"`
|
|
|
|
|
Password string `json:"password"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type PlayerCreateRequest struct {
|
|
|
|
|
NameAr string `json:"nameAr"`
|
|
|
|
|
NameEn string `json:"nameEn"`
|
|
|
|
|
GroupCode string `json:"groupCode"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type PlayerUpdateRequest struct {
|
|
|
|
|
NameAr *string `json:"nameAr"`
|
|
|
|
|
NameEn *string `json:"nameEn"`
|
|
|
|
|
GroupCode *string `json:"groupCode"`
|
|
|
|
|
ImageData *string `json:"imageData"`
|
|
|
|
|
ClearImage bool `json:"clearImage"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ScoreUpdateRequest struct {
|
|
|
|
|
Score int `json:"score"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ScoreProofUpdateRequest struct {
|
|
|
|
|
ImageData string `json:"imageData"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AdminSettingsUpdateRequest struct {
|
2026-04-03 09:55:36 +04:00
|
|
|
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"`
|
2026-04-01 11:47:03 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ResetStageRequest struct {
|
|
|
|
|
ResetProofs bool `json:"resetProofs"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type AutoGroupPlayersRequest struct {
|
|
|
|
|
Groups []string `json:"groups"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ScoreAdviceResponse struct {
|
|
|
|
|
Stage string `json:"stage"`
|
|
|
|
|
PlayerID int `json:"playerId"`
|
|
|
|
|
AdvisedScore int `json:"advisedScore"`
|
|
|
|
|
Reason string `json:"reason"`
|
|
|
|
|
GeneratedAt string `json:"generatedAt"`
|
|
|
|
|
}
|