Concurrent HTTP requests and the cron job could race on the file, causing lost updates or torn writes. A package-level RWMutex now guards all reads and writes. Also URL-encodes player names in ToggleUrl to prevent query-parameter injection from names containing '&' or '='. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
70 lines
1.4 KiB
Go
70 lines
1.4 KiB
Go
package data
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
var sessionMu sync.RWMutex
|
|
|
|
func ReadSessionData() (Session, error) {
|
|
var session Session
|
|
|
|
sessionMu.RLock()
|
|
data, err := os.ReadFile("players.json")
|
|
sessionMu.RUnlock()
|
|
|
|
if err != nil {
|
|
return session, err
|
|
}
|
|
err = json.Unmarshal(data, &session)
|
|
|
|
if err == nil {
|
|
for i, p := range session.Players {
|
|
session.Players[i].ToggleUrl = "/sessioninfo?playerName=" + url.QueryEscape(p.Name) + "&playerAttending=toggle"
|
|
}
|
|
}
|
|
|
|
return session, err
|
|
}
|
|
|
|
func PersistSession(session Session) error {
|
|
jsonData, err := json.MarshalIndent(session, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sessionMu.Lock()
|
|
err = os.WriteFile("players.json", jsonData, 0644)
|
|
sessionMu.Unlock()
|
|
return err
|
|
}
|
|
|
|
func AddPlayer(session Session, p Player) error {
|
|
session.Players = append(session.Players, p)
|
|
return PersistSession(session)
|
|
}
|
|
|
|
func DeletePlayer(session Session, name string) error {
|
|
players := make([]Player, 0, len(session.Players))
|
|
for _, p := range session.Players {
|
|
if p.Name != name {
|
|
players = append(players, p)
|
|
}
|
|
}
|
|
session.Players = players
|
|
return PersistSession(session)
|
|
}
|
|
|
|
func UpdatePlayer(session Session, originalName string, updated Player) error {
|
|
for i, p := range session.Players {
|
|
if p.Name == originalName {
|
|
session.Players[i] = updated
|
|
return PersistSession(session)
|
|
}
|
|
}
|
|
return fmt.Errorf("player %q not found", originalName)
|
|
}
|