areweplaying/pkg/data/loaddata.go
Harry Culpan c39bd3ec53 Add mutex to protect players.json and fix URL-encode player names
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>
2026-06-30 18:31:18 -04:00

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)
}