diff --git a/cmd/mothd/award.go b/cmd/mothd/award.go index f615d5b..9f06425 100644 --- a/cmd/mothd/award.go +++ b/cmd/mothd/award.go @@ -9,7 +9,7 @@ import ( type Award struct { // Unix epoch time of this event When int64 - TeamId string + TeamID string Category string Points int } @@ -18,26 +18,25 @@ type AwardList []*Award // Implement sort.Interface on AwardList func (awards AwardList) Len() int { - return len(awards) + return len(awards) } func (awards AwardList) Less(i, j int) bool { - return awards[i].When.Before(awards[j].When) + return awards[i].When < awards[j].When } func (awards AwardList) Swap(i, j int) { - tmp := awards[i] - awards[i] = awards[j] - awards[j] = tmp + tmp := awards[i] + awards[i] = awards[j] + awards[j] = tmp } - func ParseAward(s string) (*Award, error) { ret := Award{} s = strings.TrimSpace(s) - n, err := fmt.Sscanf(s, "%d %s %s %d", &ret.When, &ret.TeamId, &ret.Category, &ret.Points) + n, err := fmt.Sscanf(s, "%d %s %s %d", &ret.When, &ret.TeamID, &ret.Category, &ret.Points) if err != nil { return nil, err } else if n != 4 { @@ -48,7 +47,7 @@ func ParseAward(s string) (*Award, error) { } func (a *Award) String() string { - return fmt.Sprintf("%d %s %s %d", a.When, a.TeamId, a.Category, a.Points) + return fmt.Sprintf("%d %s %s %d", a.When, a.TeamID, a.Category, a.Points) } func (a *Award) MarshalJSON() ([]byte, error) { @@ -57,7 +56,7 @@ func (a *Award) MarshalJSON() ([]byte, error) { } ao := []interface{}{ a.When, - a.TeamId, + a.TeamID, a.Category, a.Points, } @@ -67,7 +66,7 @@ func (a *Award) MarshalJSON() ([]byte, error) { func (a *Award) Same(o *Award) bool { switch { - case a.TeamId != o.TeamId: + case a.TeamID != o.TeamID: return false case a.Category != o.Category: return false diff --git a/cmd/mothd/award_test.go b/cmd/mothd/award_test.go index ccbe7ca..12eb536 100644 --- a/cmd/mothd/award_test.go +++ b/cmd/mothd/award_test.go @@ -1,8 +1,8 @@ package main import ( - "testing" "sort" + "testing" ) func TestAward(t *testing.T) { @@ -12,7 +12,7 @@ func TestAward(t *testing.T) { t.Error(err) return } - if a.TeamId != "1a2b3c4d" { + if a.TeamID != "1a2b3c4d" { t.Error("TeamID parsed wrong") } if a.Category != "counting" { @@ -41,21 +41,21 @@ func TestAward(t *testing.T) { } func TestAwardList(t *testing.T) { - a, _ := ParseAward("1536958399 1a2b3c4d counting 1") - b, _ := ParseAward("1536958400 1a2b3c4d counting 1") - c, _ := ParseAward("1536958300 1a2b3c4d counting 1") - list := AwardList{a, b, c} - - if sort.IsSorted(list) { - t.Error("Unsorted list thinks it's sorted") - } - - sort.Stable(list) - if (list[0] != c) || (list[1] != a) || (list[2] != b) { - t.Error("Sorting didn't") - } - - if ! sort.IsSorted(list) { - t.Error("Sorted list thinks it isn't") - } + a, _ := ParseAward("1536958399 1a2b3c4d counting 1") + b, _ := ParseAward("1536958400 1a2b3c4d counting 1") + c, _ := ParseAward("1536958300 1a2b3c4d counting 1") + list := AwardList{a, b, c} + + if sort.IsSorted(list) { + t.Error("Unsorted list thinks it's sorted") + } + + sort.Stable(list) + if (list[0] != c) || (list[1] != a) || (list[2] != b) { + t.Error("Sorting didn't") + } + + if !sort.IsSorted(list) { + t.Error("Sorted list thinks it isn't") + } } diff --git a/cmd/mothd/httpd.go b/cmd/mothd/httpd.go index f7d2137..e62725b 100644 --- a/cmd/mothd/httpd.go +++ b/cmd/mothd/httpd.go @@ -3,16 +3,18 @@ package main import ( "log" "net/http" - "strings" "strconv" + "strings" ) +// HTTPServer is a MOTH HTTP server type HTTPServer struct { *http.ServeMux - server *MothServer - base string + server *MothServer + base string } +// NewHTTPServer creates a MOTH HTTP server, with handler functions registered func NewHTTPServer(base string, server *MothServer) *HTTPServer { base = strings.TrimRight(base, "/") h := &HTTPServer{ @@ -28,6 +30,7 @@ func NewHTTPServer(base string, server *MothServer) *HTTPServer { return h } +// HandleMothFunc binds a new handler function which creates a new MothServer with every request func (h *HTTPServer) HandleMothFunc( pattern string, mothHandler func(MothRequestHandler, http.ResponseWriter, *http.Request), @@ -36,10 +39,10 @@ func (h *HTTPServer) HandleMothFunc( mh := h.server.NewHandler(req.FormValue("id")) mothHandler(mh, w, req) } - h.HandleFunc(h.base + pattern, handler) + h.HandleFunc(h.base+pattern, handler) } -// This gives Instances the signature of http.Handler +// ServeHTTP provides the http.Handler interface func (h *HTTPServer) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) { w := MothResponseWriter{ statusCode: new(int), @@ -55,27 +58,31 @@ func (h *HTTPServer) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) { ) } +// MothResponseWriter provides a ResponseWriter that remembers what the status code was type MothResponseWriter struct { statusCode *int http.ResponseWriter } +// WriteHeader sends an HTTP response header with the provided status code func (w MothResponseWriter) WriteHeader(statusCode int) { *w.statusCode = statusCode w.ResponseWriter.WriteHeader(statusCode) } +// Run binds to the provided bindStr, and serves incoming requests until failure func (h *HTTPServer) Run(bindStr string) { log.Printf("Listening on %s", bindStr) log.Fatal(http.ListenAndServe(bindStr, h)) } +// ThemeHandler serves up static content from the theme directory func (h *HTTPServer) ThemeHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) { path := req.URL.Path if path == "/" { path = "/index.html" } - + f, mtime, err := mh.ThemeOpen(path) if err != nil { http.NotFound(w, req) @@ -85,10 +92,12 @@ func (h *HTTPServer) ThemeHandler(mh MothRequestHandler, w http.ResponseWriter, http.ServeContent(w, req, path, mtime, f) } +// StateHandler returns the full JSON-encoded state of the event func (h *HTTPServer) StateHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) { JSONWrite(w, mh.ExportState()) } +// RegisterHandler handles attempts to register a team func (h *HTTPServer) RegisterHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) { teamName := req.FormValue("name") if err := mh.Register(teamName); err != nil { @@ -98,13 +107,14 @@ func (h *HTTPServer) RegisterHandler(mh MothRequestHandler, w http.ResponseWrite } } +// AnswerHandler checks answer correctness and awards points func (h *HTTPServer) AnswerHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) { cat := req.FormValue("cat") pointstr := req.FormValue("points") answer := req.FormValue("answer") - + points, _ := strconv.Atoi(pointstr) - + if err := mh.CheckAnswer(cat, points, answer); err != nil { JSendf(w, JSendFail, "not accepted", err.Error()) } else { @@ -112,22 +122,23 @@ func (h *HTTPServer) AnswerHandler(mh MothRequestHandler, w http.ResponseWriter, } } +// ContentHandler returns static content from a given puzzle func (h *HTTPServer) ContentHandler(mh MothRequestHandler, w http.ResponseWriter, req *http.Request) { trimLen := len(h.base) + len("/content/") parts := strings.SplitN(req.URL.Path[trimLen:], "/", 3) - if len(parts) < 3 { - http.Error(w, "Not Found", http.StatusNotFound) - return - } + if len(parts) < 3 { + http.Error(w, "Not Found", http.StatusNotFound) + return + } cat := parts[0] pointsStr := parts[1] filename := parts[2] - if (filename == "") { + if filename == "" { filename = "puzzles.json" } - + points, _ := strconv.Atoi(pointsStr) mf, mtime, err := mh.PuzzlesOpen(cat, points, filename) @@ -137,5 +148,5 @@ func (h *HTTPServer) ContentHandler(mh MothRequestHandler, w http.ResponseWriter } defer mf.Close() - http.ServeContent(w, req, filename, mtime, mf) + http.ServeContent(w, req, filename, mtime, mf) } diff --git a/cmd/mothd/main.go b/cmd/mothd/main.go index c720bff..4da75dc 100644 --- a/cmd/mothd/main.go +++ b/cmd/mothd/main.go @@ -2,10 +2,11 @@ package main import ( "flag" - "github.com/spf13/afero" "log" "mime" "time" + + "github.com/spf13/afero" ) func custodian(updateInterval time.Duration, components []Component) { @@ -17,7 +18,7 @@ func custodian(updateInterval time.Duration, components []Component) { ticker := time.NewTicker(updateInterval) update() - for _ = range ticker.C { + for range ticker.C { update() } } diff --git a/cmd/mothd/server.go b/cmd/mothd/server.go index 1a569da..2fcdf31 100644 --- a/cmd/mothd/server.go +++ b/cmd/mothd/server.go @@ -1,10 +1,10 @@ package main import ( - "io" - "time" "fmt" + "io" "strconv" + "time" ) type Category struct { @@ -25,7 +25,7 @@ type StateExport struct { Messages string TeamNames map[string]string PointsLog []Award - Puzzles map[string][]int + Puzzles map[string][]int } type PuzzleProvider interface { @@ -49,30 +49,28 @@ type StateProvider interface { Component } - type Component interface { Update() } - type MothServer struct { Puzzles PuzzleProvider - Theme ThemeProvider - State StateProvider + Theme ThemeProvider + State StateProvider } func NewMothServer(puzzles PuzzleProvider, theme ThemeProvider, state StateProvider) *MothServer { return &MothServer{ Puzzles: puzzles, - Theme: theme, - State: state, + Theme: theme, + State: state, } } func (s *MothServer) NewHandler(teamId string) MothRequestHandler { return MothRequestHandler{ MothServer: s, - teamId: teamId, + teamId: teamId, } } @@ -91,7 +89,7 @@ func (mh *MothRequestHandler) PuzzlesOpen(cat string, points int, path string) ( return mh.Puzzles.Open(cat, points, path) } } - + return nil, time.Time{}, fmt.Errorf("Puzzle locked") } @@ -112,11 +110,11 @@ func (mh *MothRequestHandler) CheckAnswer(cat string, points int, answer string) if err := mh.Puzzles.CheckAnswer(cat, points, answer); err != nil { return err } - + if err := mh.State.AwardPoints(mh.teamId, cat, points); err != nil { return err } - + return nil } @@ -124,7 +122,7 @@ func (mh *MothRequestHandler) ExportAllState() *StateExport { export := StateExport{} teamName, _ := mh.State.TeamName(mh.teamId) - + export.Messages = mh.State.Messages() export.TeamNames = map[string]string{"self": teamName} @@ -135,24 +133,23 @@ func (mh *MothRequestHandler) ExportAllState() *StateExport { export.PointsLog = make([]Award, len(pointsLog)) for logno, award := range pointsLog { exportAward := *award - if id, ok := exportIds[award.TeamId]; ok { - exportAward.TeamId = id + if id, ok := exportIds[award.TeamID]; ok { + exportAward.TeamID = id } else { exportId := strconv.Itoa(logno) - name, _ := mh.State.TeamName(award.TeamId) - exportAward.TeamId = exportId - exportIds[award.TeamId] = exportAward.TeamId + name, _ := mh.State.TeamName(award.TeamID) + exportAward.TeamID = exportId + exportIds[award.TeamID] = exportAward.TeamID export.TeamNames[exportId] = name } export.PointsLog[logno] = exportAward - + // Record the highest-value unlocked puzzle in each category if award.Points > maxSolved[award.Category] { maxSolved[award.Category] = award.Points } } - export.Puzzles = make(map[string][]int) for _, category := range mh.Puzzles.Inventory() { // Append sentry (end of puzzles) @@ -175,7 +172,7 @@ func (mh *MothRequestHandler) ExportAllState() *StateExport { func (mh *MothRequestHandler) ExportState() *StateExport { export := mh.ExportAllState() - + // We don't give this out to just anybody, // because back when we did, // we got a bad reputation on some secretive blacklist, @@ -183,6 +180,6 @@ func (mh *MothRequestHandler) ExportState() *StateExport { if export.TeamNames["self"] == "" { export.Puzzles = map[string][]int{} } - + return export } diff --git a/cmd/mothd/state.go b/cmd/mothd/state.go index a7bebdc..1bfdfab 100644 --- a/cmd/mothd/state.go +++ b/cmd/mothd/state.go @@ -3,13 +3,14 @@ package main import ( "bufio" "fmt" - "github.com/spf13/afero" "log" "math/rand" "os" "path/filepath" "strings" "time" + + "github.com/spf13/afero" ) // Stuff people with mediocre handwriting could write down unambiguously, and can be entered without holding down shift @@ -81,30 +82,30 @@ func (s *State) UpdateEnabled() { } // Returns team name given a team ID. -func (s *State) TeamName(teamId string) (string, error) { +func (s *State) TeamName(teamID string) (string, error) { // XXX: directory traversal - teamFile := filepath.Join("teams", teamId) + teamFile := filepath.Join("teams", teamID) teamNameBytes, err := afero.ReadFile(s, teamFile) teamName := strings.TrimSpace(string(teamNameBytes)) if os.IsNotExist(err) { - return "", fmt.Errorf("Unregistered team ID: %s", teamId) + return "", fmt.Errorf("Unregistered team ID: %s", teamID) } else if err != nil { - return "", fmt.Errorf("Unregistered team ID: %s (%s)", teamId, err) + return "", fmt.Errorf("Unregistered team ID: %s (%s)", teamID, err) } return teamName, nil } // Write out team name. This can only be done once. -func (s *State) SetTeamName(teamId, teamName string) error { +func (s *State) SetTeamName(teamID, teamName string) error { if f, err := s.Open("teamids.txt"); err != nil { return fmt.Errorf("Team IDs file does not exist") } else { found := false scanner := bufio.NewScanner(f) for scanner.Scan() { - if scanner.Text() == teamId { + if scanner.Text() == teamID { found = true break } @@ -115,7 +116,7 @@ func (s *State) SetTeamName(teamId, teamName string) error { } } - teamFile := filepath.Join("teams", teamId) + teamFile := filepath.Join("teams", teamID) err := afero.WriteFile(s, teamFile, []byte(teamName), os.ModeExclusive|0644) if os.IsExist(err) { return fmt.Errorf("Team ID is already registered") @@ -152,20 +153,20 @@ func (s *State) Messages() string { return string(bMessages) } -// AwardPoints gives points to teamId in category. +// AwardPoints gives points to teamID in category. // It first checks to make sure these are not duplicate points. // This is not a perfect check, you can trigger a race condition here. // It's just a courtesy to the user. // 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(teamID, category string, points int) error { a := Award{ When: time.Now().Unix(), - TeamId: teamId, + TeamID: teamID, Category: category, Points: points, } - _, err := s.TeamName(teamId) + _, err := s.TeamName(teamID) if err != nil { return err } @@ -176,7 +177,7 @@ func (s *State) AwardPoints(teamId, category string, points int) error { } } - fn := fmt.Sprintf("%s-%s-%d", teamId, category, points) + fn := fmt.Sprintf("%s-%s-%d", teamID, category, points) tmpfn := filepath.Join("points.tmp", fn) newfn := filepath.Join("points.new", fn) diff --git a/cmd/mothd/state_test.go b/cmd/mothd/state_test.go index f2d49e4..a8ce98f 100644 --- a/cmd/mothd/state_test.go +++ b/cmd/mothd/state_test.go @@ -2,9 +2,10 @@ package main import ( "bytes" - "github.com/spf13/afero" "os" "testing" + + "github.com/spf13/afero" ) func TestState(t *testing.T) { @@ -56,7 +57,7 @@ func TestState(t *testing.T) { pl = s.PointsLog() if len(pl) != 1 { t.Errorf("After awarding points, points log has length %d", len(pl)) - } else if (pl[0].TeamId != teamId) || (pl[0].Category != category) || (pl[0].Points != points) { + } else if (pl[0].TeamID != teamId) || (pl[0].Category != category) || (pl[0].Points != points) { t.Errorf("Incorrect logged award %v", pl) } diff --git a/cmd/mothd/theme_test.go b/cmd/mothd/theme_test.go index ff2653d..eb38056 100644 --- a/cmd/mothd/theme_test.go +++ b/cmd/mothd/theme_test.go @@ -1,23 +1,31 @@ package main import ( - "github.com/spf13/afero" "io/ioutil" "testing" + + "github.com/spf13/afero" ) func TestTheme(t *testing.T) { + filename := "/index.html" fs := new(afero.MemMapFs) index := "this is the index" - afero.WriteFile(fs, "/index.html", []byte(index), 0644) + afero.WriteFile(fs, filename, []byte(index), 0644) + fileInfo, err := fs.Stat(filename) + if err != nil { + t.Error(err) + } s := NewTheme(fs) - if f, err := s.Open("/index.html"); err != nil { + if f, timestamp, err := s.Open("/index.html"); err != nil { t.Error(err) } else if buf, err := ioutil.ReadAll(f); err != nil { t.Error(err) } else if string(buf) != index { t.Error("Read wrong value from index") + } else if !timestamp.Equal(fileInfo.ModTime()) { + t.Error("Timestamp compared wrong") } } diff --git a/cmd/transpile/go.mod b/cmd/transpile/go.mod new file mode 100644 index 0000000..cb38492 --- /dev/null +++ b/cmd/transpile/go.mod @@ -0,0 +1,9 @@ +module github.com/russross/blackfriday/v2 + +go 1.13 + +require ( + github.com/russross/blackfriday v2.0.0+incompatible + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + gopkg.in/yaml.v2 v2.3.0 +) diff --git a/cmd/transpile/go.sum b/cmd/transpile/go.sum new file mode 100644 index 0000000..8ee6ac9 --- /dev/null +++ b/cmd/transpile/go.sum @@ -0,0 +1,7 @@ +github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= +github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/cmd/transpile/puzzle.go b/cmd/transpile/puzzle.go index 4b197a7..d250869 100644 --- a/cmd/transpile/puzzle.go +++ b/cmd/transpile/puzzle.go @@ -4,12 +4,13 @@ import ( "bufio" "bytes" "fmt" - "gopkg.in/russross/blackfriday.v2" - "gopkg.in/yaml.v2" "io" "log" "net/mail" "strings" + + "github.com/russross/blackfriday" + "gopkg.in/yaml.v2" ) type Attachment struct { @@ -55,7 +56,7 @@ func YamlParser(input []byte) (*Puzzle, error) { return puzzle, nil } -func AttachmentParser(val []string) ([]Attachment) { +func AttachmentParser(val []string) []Attachment { ret := make([]Attachment, len(val)) for idx, txt := range val { parts := strings.SplitN(txt, " ", 3) @@ -150,7 +151,7 @@ func ParseMoth(r io.Reader) (*Puzzle, error) { if err != nil { return nil, err } - + // Markdownify the body bodyB := blackfriday.Run(bodyBuf.Bytes()) if (puzzle.Pre.Body != "") && (len(bodyB) > 0) { diff --git a/go.mod b/go.mod index fe6b061..3ac2f9c 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,10 @@ go 1.13 require ( github.com/namsral/flag v1.7.4-pre + github.com/pkg/sftp v1.11.0 // indirect + github.com/russross/blackfriday v2.0.0+incompatible // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/spf13/afero v1.2.2 + golang.org/x/tools v0.0.0-20200814172026-c4923e618c08 // indirect + gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index f0f321c..3cea630 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,56 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/namsral/flag v1.7.4-pre h1:b2ScHhoCUkbsq0d2C15Mv+VU8bl8hAXV8arnWiOHNZs= github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7MsceFUpB5yDo= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.11.0 h1:4Zv0OGbpkg4yNuUtH0s8rvoYxRCNyT29NVUo6pgPmxI= +github.com/pkg/sftp v1.11.0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= +github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.1.32 h1:5tjfNdR2ki3yYQ842+eX2sQHeiwpKJ0RnHO4IYOc4V8= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200814172026-c4923e618c08 h1:sfBQLM20fzeXhOixVQirwEbuW4PGStP773EXQpsBB6E= +golang.org/x/tools v0.0.0-20200814172026-c4923e618c08/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/mothballs/counting.mb b/mothballs/counting.mb new file mode 100644 index 0000000..1842ddb Binary files /dev/null and b/mothballs/counting.mb differ diff --git a/theme/scoreboard.js b/theme/scoreboard.js index 0c4d836..c5b64f1 100644 --- a/theme/scoreboard.js +++ b/theme/scoreboard.js @@ -18,20 +18,20 @@ function scoreboardInit() { } let element = document.getElementById("rankings") - let teamNames = state.teams - let pointsLog = state.points + let teamNames = state.TeamNames + let pointsLog = state.PointsLog // Every machine that's displaying the scoreboard helpfully stores the last 20 values of // points.json for us, in case of catastrophe. Thanks, y'all! // // We have been doing some variation on this "everybody backs up the server state" trick since 2009. // We have needed it 0 times. - let pointsHistory = JSON.parse(localStorage.getItem("pointsHistory")) || [] - if (pointsHistory.length >= 20) { - pointsHistory.shift() + let stateHistory = JSON.parse(localStorage.getItem("stateHistory")) || [] + if (stateHistory.length >= 20) { + stateHistory.shift() } - pointsHistory.push(pointsLog) - localStorage.setItem("pointsHistory", JSON.stringify(pointsHistory)) + stateHistory.push(state) + localStorage.setItem("stateHistory", JSON.stringify(stateHistory)) let teams = {} let highestCategoryScore = {} // map[string]int @@ -216,7 +216,7 @@ function scoreboardInit() { } function refresh() { - fetch("points.json") + fetch("state") .then(resp => { return resp.json() })