mirror of https://github.com/dirtbags/moth.git
Running again, fix scoreboard.js
This commit is contained in:
parent
cedc8521ff
commit
ab0488ba14
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
if !sort.IsSorted(list) {
|
||||
t.Error("Sorted list thinks it isn't")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,21 +58,25 @@ 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 == "/" {
|
||||
|
@ -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,6 +107,7 @@ 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")
|
||||
|
@ -112,19 +122,20 @@ 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"
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,13 +133,13 @@ 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
|
||||
|
@ -152,7 +150,6 @@ func (mh *MothRequestHandler) ExportAllState() *StateExport {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
export.Puzzles = make(map[string][]int)
|
||||
for _, category := range mh.Puzzles.Inventory() {
|
||||
// Append sentry (end of puzzles)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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)
|
||||
|
|
5
go.mod
5
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
|
||||
)
|
||||
|
|
50
go.sum
50
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=
|
||||
|
|
Binary file not shown.
|
@ -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()
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue