Remove participant IDs, add team ID links

This commit is contained in:
Neale Pickett 2022-05-19 20:53:51 -06:00
parent 8e0f4561a5
commit ec2a547637
9 changed files with 136 additions and 104 deletions

View File

@ -44,9 +44,8 @@ func (h *HTTPServer) HandleMothFunc(
mothHandler func(MothRequestHandler, http.ResponseWriter, *http.Request), mothHandler func(MothRequestHandler, http.ResponseWriter, *http.Request),
) { ) {
handler := func(w http.ResponseWriter, req *http.Request) { handler := func(w http.ResponseWriter, req *http.Request) {
participantID := req.FormValue("pid")
teamID := req.FormValue("id") teamID := req.FormValue("id")
mh := h.server.NewHandler(participantID, teamID) mh := h.server.NewHandler(teamID)
mothHandler(mh, w, req) mothHandler(mh, w, req)
} }
h.HandleFunc(h.base+pattern, handler) h.HandleFunc(h.base+pattern, handler)

View File

@ -11,11 +11,8 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
) )
const TestParticipantID = "shipox"
func (hs *HTTPServer) TestRequest(path string, args map[string]string) *httptest.ResponseRecorder { func (hs *HTTPServer) TestRequest(path string, args map[string]string) *httptest.ResponseRecorder {
vals := url.Values{} vals := url.Values{}
vals.Set("pid", TestParticipantID)
vals.Set("id", TestTeamID) vals.Set("id", TestTeamID)
for k, v := range args { for k, v := range args {
vals.Set(k, v) vals.Set(k, v)

View File

@ -15,7 +15,7 @@ func TestIssue156(t *testing.T) {
afero.WriteFile(state, "teams/bloop", []byte("bloop: the team"), 0644) afero.WriteFile(state, "teams/bloop", []byte("bloop: the team"), 0644)
state.refresh() state.refresh()
handler := server.NewHandler("", "bloop") handler := server.NewHandler("bloop")
es := handler.ExportState() es := handler.ExportState()
if _, ok := es.TeamNames["self"]; !ok { if _, ok := es.TeamNames["self"]; !ok {
t.Fail() t.Fail()

View File

@ -55,13 +55,19 @@ type ThemeProvider interface {
type StateProvider interface { type StateProvider interface {
Messages() string Messages() string
PointsLog() award.List PointsLog() award.List
TeamName(teamID string) (string, error) TeamName(teamID string) (Team, error)
SetTeamName(teamID, teamName string) error SetTeamName(teamID, teamName string) error
AwardPoints(teamID string, cat string, points int) error AwardPoints(team Team, cat string, points int) error
LogEvent(event, participantID, teamID, cat string, points int, extra ...string) LogEvent(event, teamID, cat string, points int, extra ...string)
Maintainer Maintainer
} }
// Team defines a team entry
type Team struct {
Name string
ID string
}
// Maintainer is something that can be maintained. // Maintainer is something that can be maintained.
type Maintainer interface { type Maintainer interface {
// Maintain is the maintenance loop. // Maintain is the maintenance loop.
@ -92,19 +98,17 @@ func NewMothServer(config Configuration, theme ThemeProvider, state StateProvide
} }
// NewHandler returns a new http.RequestHandler for the provided teamID. // NewHandler returns a new http.RequestHandler for the provided teamID.
func (s *MothServer) NewHandler(participantID, teamID string) MothRequestHandler { func (s *MothServer) NewHandler(teamID string) MothRequestHandler {
return MothRequestHandler{ return MothRequestHandler{
MothServer: s, MothServer: s,
participantID: participantID, teamID: teamID,
teamID: teamID,
} }
} }
// MothRequestHandler provides http.RequestHandler for a MothServer. // MothRequestHandler provides http.RequestHandler for a MothServer.
type MothRequestHandler struct { type MothRequestHandler struct {
*MothServer *MothServer
participantID string teamID string
teamID string
} }
// PuzzlesOpen opens a file associated with a puzzle. // PuzzlesOpen opens a file associated with a puzzle.
@ -131,7 +135,7 @@ func (mh *MothRequestHandler) PuzzlesOpen(cat string, points int, path string) (
// Log puzzle.json loads // Log puzzle.json loads
if path == "puzzle.json" { if path == "puzzle.json" {
mh.State.LogEvent("load", mh.participantID, mh.teamID, cat, points) mh.State.LogEvent("load", mh.teamID, cat, points)
} }
return return
@ -139,6 +143,11 @@ func (mh *MothRequestHandler) PuzzlesOpen(cat string, points int, path string) (
// CheckAnswer returns an error if answer is not a correct answer for puzzle points in category cat // CheckAnswer returns an error if answer is not a correct answer for puzzle points in category cat
func (mh *MothRequestHandler) CheckAnswer(cat string, points int, answer string) error { func (mh *MothRequestHandler) CheckAnswer(cat string, points int, answer string) error {
team, err := mh.State.TeamName(mh.teamID)
if err != nil {
return fmt.Errorf("invalid team ID")
}
correct := false correct := false
for _, provider := range mh.PuzzleProviders { for _, provider := range mh.PuzzleProviders {
if ok, err := provider.CheckAnswer(cat, points, answer); err != nil { if ok, err := provider.CheckAnswer(cat, points, answer); err != nil {
@ -148,19 +157,16 @@ func (mh *MothRequestHandler) CheckAnswer(cat string, points int, answer string)
} }
} }
if !correct { if !correct {
mh.State.LogEvent("wrong", mh.participantID, mh.teamID, cat, points) mh.State.LogEvent("wrong", mh.teamID, cat, points)
return fmt.Errorf("incorrect answer") return fmt.Errorf("incorrect answer")
} }
mh.State.LogEvent("correct", mh.participantID, mh.teamID, cat, points) if err := mh.State.AwardPoints(team, cat, points); err != nil {
if _, err := mh.State.TeamName(mh.teamID); err != nil {
return fmt.Errorf("invalid team ID")
}
if err := mh.State.AwardPoints(mh.teamID, cat, points); err != nil {
return fmt.Errorf("error awarding points: %s", err) return fmt.Errorf("error awarding points: %s", err)
} }
mh.State.LogEvent("correct", mh.teamID, cat, points)
return nil return nil
} }
@ -175,7 +181,7 @@ func (mh *MothRequestHandler) Register(teamName string) error {
if teamName == "" { if teamName == "" {
return fmt.Errorf("empty team name") return fmt.Errorf("empty team name")
} }
mh.State.LogEvent("register", mh.participantID, mh.teamID, "", 0) mh.State.LogEvent("register", mh.teamID, "", 0)
return mh.State.SetTeamName(mh.teamID, teamName) return mh.State.SetTeamName(mh.teamID, teamName)
} }
@ -194,7 +200,7 @@ func (mh *MothRequestHandler) exportStateIfRegistered(forceRegistered bool) *Sta
export := StateExport{} export := StateExport{}
export.Config = mh.Config export.Config = mh.Config
teamName, err := mh.State.TeamName(mh.teamID) team, err := mh.State.TeamName(mh.teamID)
registered := forceRegistered || mh.Config.Devel || (err == nil) registered := forceRegistered || mh.Config.Devel || (err == nil)
export.Messages = mh.State.Messages() export.Messages = mh.State.Messages()
@ -207,7 +213,7 @@ func (mh *MothRequestHandler) exportStateIfRegistered(forceRegistered bool) *Sta
export.PointsLog = make(award.List, len(pointsLog)) export.PointsLog = make(award.List, len(pointsLog))
if registered { if registered {
export.TeamNames["self"] = teamName export.TeamNames["self"] = team.Name
exportIDs[mh.teamID] = "self" exportIDs[mh.teamID] = "self"
} }
for logno, awd := range pointsLog { for logno, awd := range pointsLog {
@ -215,10 +221,10 @@ func (mh *MothRequestHandler) exportStateIfRegistered(forceRegistered bool) *Sta
awd.TeamID = id awd.TeamID = id
} else { } else {
exportID := strconv.Itoa(logno) exportID := strconv.Itoa(logno)
name, _ := mh.State.TeamName(awd.TeamID) team, _ := mh.State.TeamName(awd.TeamID)
exportIDs[awd.TeamID] = exportID exportIDs[awd.TeamID] = exportID
awd.TeamID = exportID awd.TeamID = exportID
export.TeamNames[exportID] = name export.TeamNames[exportID] = team.Name
} }
export.PointsLog[logno] = awd export.PointsLog[logno] = awd

View File

@ -42,7 +42,7 @@ func (ts TestServer) refresh() {
func TestDevelServer(t *testing.T) { func TestDevelServer(t *testing.T) {
server := NewTestServer() server := NewTestServer()
server.Config.Devel = true server.Config.Devel = true
anonHandler := server.NewHandler("badParticipantId", "badTeamId") anonHandler := server.NewHandler("badTeamId")
{ {
es := anonHandler.ExportState() es := anonHandler.ExportState()
@ -57,12 +57,11 @@ func TestDevelServer(t *testing.T) {
func TestProdServer(t *testing.T) { func TestProdServer(t *testing.T) {
teamName := "OurTeam" teamName := "OurTeam"
participantID := "participantID"
teamID := TestTeamID teamID := TestTeamID
server := NewTestServer() server := NewTestServer()
handler := server.NewHandler(participantID, teamID) handler := server.NewHandler(teamID)
anonHandler := server.NewHandler("badParticipantId", "badTeamId") anonHandler := server.NewHandler("badTeamId")
{ {
es := handler.ExportState() es := handler.ExportState()

View File

@ -27,6 +27,10 @@ const DistinguishableChars = "34678abcdefhikmnpqrtwxy="
// This is also a valid RFC3339 format. // This is also a valid RFC3339 format.
const RFC3339Space = "2006-01-02 15:04:05Z07:00" const RFC3339Space = "2006-01-02 15:04:05Z07:00"
// TeamLinkPrefix is prepended to a Team ID and stored as a team name
// to handle "linked" team IDs, used for participant ID to team mapping.
const TeamLinkPrefix = "link>"
// ErrAlreadyRegistered means a team cannot be registered because it was registered previously. // ErrAlreadyRegistered means a team cannot be registered because it was registered previously.
var ErrAlreadyRegistered = errors.New("team ID has already been registered") var ErrAlreadyRegistered = errors.New("team ID has already been registered")
@ -45,11 +49,11 @@ type State struct {
eventWriterFile afero.File eventWriterFile afero.File
// Caches, so we're not hammering NFS with metadata operations // Caches, so we're not hammering NFS with metadata operations
teamNamesLastChange time.Time teamsLastChange time.Time
teamNames map[string]string teams map[string]Team
pointsLog award.List pointsLog award.List
messages string messages string
lock sync.RWMutex lock sync.RWMutex
} }
// NewState returns a new State struct backed by the given Fs // NewState returns a new State struct backed by the given Fs
@ -60,7 +64,7 @@ func NewState(fs afero.Fs) *State {
refreshNow: make(chan bool, 5), refreshNow: make(chan bool, 5),
eventStream: make(chan []string, 80), eventStream: make(chan []string, 80),
teamNames: make(map[string]string), teams: make(map[string]Team),
} }
if err := s.reopenEventLog(); err != nil { if err := s.reopenEventLog(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -121,29 +125,33 @@ func (s *State) updateEnabled() {
s.Enabled = nextEnabled s.Enabled = nextEnabled
log.Printf("Setting enabled=%v: %s", s.Enabled, why) log.Printf("Setting enabled=%v: %s", s.Enabled, why)
if s.Enabled { if s.Enabled {
s.LogEvent("enabled", "", "", "", 0, why) s.LogEvent("enabled", "", "", 0, why)
} else { } else {
s.LogEvent("disabled", "", "", "", 0, why) s.LogEvent("disabled", "", "", 0, why)
} }
} }
} }
// TeamName returns team name given a team ID. // TeamName returns a Team given a team ID.
func (s *State) TeamName(teamID string) (string, error) { //
// The returned team ID will be the dereferenced filename of the team.
// This allows you to have symbolic links to team IDs,
// in order to implement participant IDs
func (s *State) TeamName(teamID string) (Team, error) {
s.lock.RLock() s.lock.RLock()
name, ok := s.teamNames[teamID] team, ok := s.teams[teamID]
s.lock.RUnlock() s.lock.RUnlock()
if !ok { if !ok {
return "", fmt.Errorf("unregistered team ID: %s", teamID) return Team{}, fmt.Errorf("unregistered team ID: %s", teamID)
} }
return name, nil return team, nil
} }
// SetTeamName writes out team name. // SetTeamName writes out team name.
// This can only be done once per team. // This can only be done once per team.
func (s *State) SetTeamName(teamID, teamName string) error { func (s *State) SetTeamName(teamID, teamName string) error {
s.lock.RLock() s.lock.RLock()
_, ok := s.teamNames[teamID] _, ok := s.teams[teamID]
s.lock.RUnlock() s.lock.RUnlock()
if ok { if ok {
return ErrAlreadyRegistered return ErrAlreadyRegistered
@ -166,6 +174,9 @@ func (s *State) SetTeamName(teamID, teamName string) error {
return fmt.Errorf("team ID not found in list of valid team IDs") return fmt.Errorf("team ID not found in list of valid team IDs")
} }
for strings.HasPrefix(teamName, TeamLinkPrefix) {
teamName = teamName[len(TeamLinkPrefix):]
}
teamFilename := filepath.Join("teams", teamID) teamFilename := filepath.Join("teams", teamID)
teamFile, err := s.Fs.OpenFile(teamFilename, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644) teamFile, err := s.Fs.OpenFile(teamFilename, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
if os.IsExist(err) { if os.IsExist(err) {
@ -199,20 +210,20 @@ func (s *State) Messages() string {
return s.messages return s.messages
} }
// AwardPoints gives points to teamID in category. // AwardPoints gives points to team in category.
// This doesn't attempt to ensure the teamID has been registered. // This doesn't attempt to ensure the team ID has been registered.
// It first checks to make sure these are not duplicate points. // It first checks to make sure these are not duplicate points.
// This is not a perfect check, you can trigger a race condition here. // This is not a perfect check, you can trigger a race condition here.
// It's just a courtesy to the user. // It's just a courtesy to the user.
// The update task makes sure we never have duplicate points in the log. // The update task makes sure we never have duplicate points in the log.
func (s *State) AwardPoints(teamID, category string, points int) error { func (s *State) AwardPoints(team Team, category string, points int) error {
return s.awardPointsAtTime(time.Now().Unix(), teamID, category, points) return s.awardPointsAtTime(time.Now().Unix(), team, category, points)
} }
func (s *State) awardPointsAtTime(when int64, teamID string, category string, points int) error { func (s *State) awardPointsAtTime(when int64, team Team, category string, points int) error {
a := award.T{ a := award.T{
When: when, When: when,
TeamID: teamID, TeamID: team.ID,
Category: category, Category: category,
Points: points, Points: points,
} }
@ -322,7 +333,7 @@ func (s *State) maybeInitialize() {
if err := s.reopenEventLog(); err != nil { if err := s.reopenEventLog(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
s.LogEvent("init", "", "", "", 0) s.LogEvent("init", "", "", 0)
// Make sure various subdirectories exist // Make sure various subdirectories exist
s.Mkdir("points.tmp", 0755) s.Mkdir("points.tmp", 0755)
@ -381,12 +392,11 @@ func (s *State) maybeInitialize() {
} }
// LogEvent writes to the event log // LogEvent writes to the event log
func (s *State) LogEvent(event, participantID, teamID, cat string, points int, extra ...string) { func (s *State) LogEvent(event, teamID, cat string, points int, extra ...string) {
s.eventStream <- append( s.eventStream <- append(
[]string{ []string{
strconv.FormatInt(time.Now().Unix(), 10), strconv.FormatInt(time.Now().Unix(), 10),
event, event,
participantID,
teamID, teamID,
cat, cat,
strconv.Itoa(points), strconv.Itoa(points),
@ -443,12 +453,12 @@ func (s *State) updateCaches() {
_, ismmfs := s.Fs.(*afero.MemMapFs) // Tests run so quickly that the time check isn't precise enough _, ismmfs := s.Fs.(*afero.MemMapFs) // Tests run so quickly that the time check isn't precise enough
if fi, err := s.Fs.Stat("teams"); err != nil { if fi, err := s.Fs.Stat("teams"); err != nil {
log.Printf("Getting modification time of teams directory: %v", err) log.Printf("Getting modification time of teams directory: %v", err)
} else if ismmfs || s.teamNamesLastChange.Before(fi.ModTime()) { } else if ismmfs || s.teamsLastChange.Before(fi.ModTime()) {
s.teamNamesLastChange = fi.ModTime() s.teamsLastChange = fi.ModTime()
// The compiler recognizes this as an optimization case // The compiler recognizes this as an optimization case
for k := range s.teamNames { for k := range s.teams {
delete(s.teamNames, k) delete(s.teams, k)
} }
teamsFs := afero.NewBasePathFs(s.Fs, "teams") teamsFs := afero.NewBasePathFs(s.Fs, "teams")
@ -456,13 +466,31 @@ func (s *State) updateCaches() {
log.Printf("Reading team ids: %v", err) log.Printf("Reading team ids: %v", err)
} else { } else {
for _, dirent := range dirents { for _, dirent := range dirents {
teamID := dirent.Name() team := Team{
if teamNameBytes, err := afero.ReadFile(teamsFs, teamID); err != nil { Name: "",
log.Printf("Reading team %s: %v", teamID, err) ID: dirent.Name(),
} else {
teamName := strings.TrimSpace(string(teamNameBytes))
s.teamNames[teamID] = teamName
} }
// Dereference links a few times before giving up
for i := 0; i < 2; i++ {
teamNameBytes, err := afero.ReadFile(teamsFs, team.ID)
if err != nil {
log.Printf("Reading team %s: %v", team.ID, err)
team.Name = err.Error()
}
team.Name = string(teamNameBytes)
if !strings.HasPrefix(team.Name, TeamLinkPrefix) {
team.Name = strings.TrimSpace(team.Name)
if team.Name == "" {
team.Name = "∅" // Empty set
}
break
}
team.ID = team.Name[len(TeamLinkPrefix):]
team.Name = "[Too Many Links]"
}
s.teams[team.ID] = team
} }
} }
} }
@ -520,11 +548,15 @@ func NewDevelState(sp StateProvider) *DevelState {
// //
// If one's registered, it will use it. // If one's registered, it will use it.
// Otherwise, it returns "<devel:$ID>" // Otherwise, it returns "<devel:$ID>"
func (ds *DevelState) TeamName(teamID string) (string, error) { func (ds *DevelState) TeamName(teamID string) (Team, error) {
if name, err := ds.StateProvider.TeamName(teamID); err == nil { if team, err := ds.StateProvider.TeamName(teamID); err == nil {
return name, nil return team, nil
} }
return fmt.Sprintf("«devel:%s»", teamID), nil team := Team{
Name: fmt.Sprintf("«devel:%s»", teamID),
ID: teamID,
}
return team, nil
} }
// SetTeamName associates a team name with any teamID // SetTeamName associates a team name with any teamID

View File

@ -17,8 +17,16 @@ func NewTestState() *State {
return s return s
} }
func slurp(c chan bool) {
for range c {
// Nothing
}
}
func TestState(t *testing.T) { func TestState(t *testing.T) {
s := NewTestState() s := NewTestState()
defer close(s.refreshNow)
go slurp(s.refreshNow)
mustExist := func(path string) { mustExist := func(path string) {
_, err := s.Fs.Stat(path) _, err := s.Fs.Stat(path)
@ -63,15 +71,19 @@ func TestState(t *testing.T) {
t.Errorf("Registering team a second time didn't fail") t.Errorf("Registering team a second time didn't fail")
} }
s.refresh() s.refresh()
if name, err := s.TeamName(teamID); err != nil {
team, err := s.TeamName(teamID)
if err != nil {
t.Error(err) t.Error(err)
} else if name != teamName { } else if teamName != team.Name {
t.Error("Incorrect team name:", name) t.Errorf("Incorrect team name: %#v != %#v", teamName, team.Name)
} else if teamID != team.ID {
t.Error("Incorrect team ID", team.ID)
} }
category := "poot" category := "poot"
points := 3928 points := 3928
if err := s.AwardPoints(teamID, category, points); err != nil { if err := s.AwardPoints(team, category, points); err != nil {
t.Error(err) t.Error(err)
} }
// Flex duplicate detection with different timestamp // Flex duplicate detection with different timestamp
@ -82,7 +94,7 @@ func TestState(t *testing.T) {
f.Close() f.Close()
} }
s.AwardPoints(teamID, category, points) s.AwardPoints(team, category, points)
s.refresh() s.refresh()
pl = s.PointsLog() pl = s.PointsLog()
if len(pl) != 1 { if len(pl) != 1 {
@ -94,11 +106,11 @@ func TestState(t *testing.T) {
t.Errorf("Incorrect logged award %v", pl) t.Errorf("Incorrect logged award %v", pl)
} }
if err := s.AwardPoints(teamID, category, points); err == nil { if err := s.AwardPoints(team, category, points); err == nil {
t.Error("Duplicate points award after refresh didn't fail") t.Error("Duplicate points award after refresh didn't fail")
} }
if err := s.AwardPoints(teamID, category, points+1); err != nil { if err := s.AwardPoints(team, category, points+1); err != nil {
t.Error("Awarding more points:", err) t.Error("Awarding more points:", err)
} }
@ -112,7 +124,7 @@ func TestState(t *testing.T) {
if len(s.PointsLog()) != 0 { if len(s.PointsLog()) != 0 {
t.Errorf("Intentional parse error breaks pointslog") t.Errorf("Intentional parse error breaks pointslog")
} }
if err := s.AwardPoints(teamID, category, points); err != nil { if err := s.AwardPoints(team, category, points); err != nil {
t.Error(err) t.Error(err)
} }
s.refresh() s.refresh()
@ -139,10 +151,10 @@ func TestStateOutOfOrderAward(t *testing.T) {
points := 100 points := 100
now := time.Now().Unix() now := time.Now().Unix()
if err := s.awardPointsAtTime(now+20, "AA", category, points); err != nil { if err := s.awardPointsAtTime(now+20, Team{ID: "AA"}, category, points); err != nil {
t.Error("Awarding points to team ZZ:", err) t.Error("Awarding points to team ZZ:", err)
} }
if err := s.awardPointsAtTime(now+10, "ZZ", category, points); err != nil { if err := s.awardPointsAtTime(now+10, Team{ID: "ZZ"}, category, points); err != nil {
t.Error("Awarding points to team AA:", err) t.Error("Awarding points to team AA:", err)
} }
s.refresh() s.refresh()
@ -157,16 +169,16 @@ func TestStateOutOfOrderAward(t *testing.T) {
func TestStateEvents(t *testing.T) { func TestStateEvents(t *testing.T) {
s := NewTestState() s := NewTestState()
s.LogEvent("moo", "", "", "", 0) s.LogEvent("moo", "", "", 0)
s.LogEvent("moo 2", "", "", "", 0) s.LogEvent("moo 2", "", "", 0)
if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "init::::0" { if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "init:::0" {
t.Error("Wrong message from event stream:", msg) t.Error("Wrong message from event stream:", msg)
} }
if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "moo::::0" { if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "moo:::0" {
t.Error("Wrong message from event stream:", msg) t.Error("Wrong message from event stream:", msg)
} }
if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "moo 2::::0" { if msg := <-s.eventStream; strings.Join(msg[1:], ":") != "moo 2:::0" {
t.Error("Wrong message from event stream:", msg) t.Error("Wrong message from event stream:", msg)
} }
} }
@ -266,7 +278,7 @@ func TestStateMaintainer(t *testing.T) {
t.Error("Team ID too short:", teamID) t.Error("Team ID too short:", teamID)
} }
s.LogEvent("Hello!", "", "", "", 0) s.LogEvent("Hello!", "", "", 0)
if len(s.PointsLog()) != 0 { if len(s.PointsLog()) != 0 {
t.Error("Points log is not empty") t.Error("Points log is not empty")
@ -274,7 +286,7 @@ func TestStateMaintainer(t *testing.T) {
if err := s.SetTeamName(teamID, "The Patricks"); err != nil { if err := s.SetTeamName(teamID, "The Patricks"); err != nil {
t.Error(err) t.Error(err)
} }
if err := s.AwardPoints(teamID, "pategory", 31337); err != nil { if err := s.AwardPoints(Team{ID: teamID}, "pategory", 31337); err != nil {
t.Error(err) t.Error(err)
} }
time.Sleep(updateInterval) time.Sleep(updateInterval)
@ -307,13 +319,14 @@ func TestDevelState(t *testing.T) {
} else if err == nil { } else if err == nil {
t.Error("Registering a team that doesn't exist didn't return ErrAlreadyRegistered") t.Error("Registering a team that doesn't exist didn't return ErrAlreadyRegistered")
} }
if n, err := ds.TeamName("boog"); err != nil { team, err := ds.TeamName("boog")
if err != nil {
t.Error("Devel State returned error on team name lookup") t.Error("Devel State returned error on team name lookup")
} else if n != "«devel:boog»" { } else if team.Name != "«devel:boog»" {
t.Error("Wrong team name", n) t.Error("Wrong team name", team.Name)
} }
if err := ds.AwardPoints("blerg", "dog", 82); err != nil { if err := ds.AwardPoints(team, "dog", 82); err != nil {
t.Error("Devel State AwardPoints returned an error", err) t.Error("Devel State AwardPoints returned an error", err)
} }
} }

View File

@ -16,11 +16,6 @@
</div> </div>
<form id="login"> <form id="login">
<!--
<span id="pid">
Participant ID: <input name="pid"> (optional) <br>
</span>
-->
Team ID: <input name="id"> <br> Team ID: <input name="id"> <br>
Team name: <input name="name"> <br> Team name: <input name="name"> <br>
<input type="submit" value="Sign In"> <input type="submit" value="Sign In">

View File

@ -83,7 +83,6 @@ function renderPuzzles(obj) {
let url = new URL("puzzle.html", window.location) let url = new URL("puzzle.html", window.location)
url.searchParams.set("cat", cat) url.searchParams.set("cat", cat)
url.searchParams.set("points", points) url.searchParams.set("points", points)
if (id) { url.searchParams.set("pid", id) }
a.href = url.toString() a.href = url.toString()
} }
} }
@ -105,7 +104,6 @@ function renderState(obj) {
if (devel) { if (devel) {
let params = new URLSearchParams(window.location.search) let params = new URLSearchParams(window.location.search)
sessionStorage.id = "1" sessionStorage.id = "1"
sessionStorage.pid = "rodney"
renderPuzzles(obj.Puzzles) renderPuzzles(obj.Puzzles)
} else if (Object.keys(obj.Puzzles).length > 0) { } else if (Object.keys(obj.Puzzles).length > 0) {
renderPuzzles(obj.Puzzles) renderPuzzles(obj.Puzzles)
@ -115,12 +113,8 @@ function renderState(obj) {
function heartbeat() { function heartbeat() {
let teamId = sessionStorage.id || "" let teamId = sessionStorage.id || ""
let participantId = sessionStorage.pid
let url = new URL("state", window.location) let url = new URL("state", window.location)
url.searchParams.set("id", teamId) url.searchParams.set("id", teamId)
if (participantId) {
url.searchParams.set("pid", participantId)
}
let fd = new FormData() let fd = new FormData()
fd.append("id", teamId) fd.append("id", teamId)
fetch(url) fetch(url)
@ -152,8 +146,6 @@ function login(e) {
e.preventDefault() e.preventDefault()
let name = document.querySelector("[name=name]").value let name = document.querySelector("[name=name]").value
let teamId = document.querySelector("[name=id]").value let teamId = document.querySelector("[name=id]").value
let pide = document.querySelector("[name=pid]")
let participantId = pide?pide.value:Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
fetch("register", { fetch("register", {
method: "POST", method: "POST",
@ -166,7 +158,6 @@ function login(e) {
if ((obj.status == "success") || (obj.data.short == "Already registered")) { if ((obj.status == "success") || (obj.data.short == "Already registered")) {
toast("Logged in") toast("Logged in")
sessionStorage.id = teamId sessionStorage.id = teamId
sessionStorage.pid = participantId
showPuzzles() showPuzzles()
heartbeat() heartbeat()
} else { } else {