mirror of https://github.com/dirtbags/moth.git
parent
c43ed9620b
commit
67e8dda39d
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -58,7 +58,7 @@ type StateProvider interface {
|
||||||
TeamName(teamID string) (string, error)
|
TeamName(teamID string) (string, error)
|
||||||
SetTeamName(teamID, teamName string) error
|
SetTeamName(teamID, teamName string) error
|
||||||
AwardPoints(teamID string, cat string, points int) error
|
AwardPoints(teamID string, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,19 +92,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 +129,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
|
||||||
|
@ -148,11 +146,11 @@ 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)
|
mh.State.LogEvent("correct", mh.teamID, cat, points)
|
||||||
|
|
||||||
if _, err := mh.State.TeamName(mh.teamID); err != nil {
|
if _, err := mh.State.TeamName(mh.teamID); err != nil {
|
||||||
return fmt.Errorf("invalid team ID")
|
return fmt.Errorf("invalid team ID")
|
||||||
|
@ -175,7 +173,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -122,9 +122,9 @@ func (s *State) updateEnabled() {
|
||||||
s.enabledWhy = why
|
s.enabledWhy = why
|
||||||
log.Printf("Setting enabled=%v: %s", s.Enabled, s.enabledWhy)
|
log.Printf("Setting enabled=%v: %s", s.Enabled, s.enabledWhy)
|
||||||
if s.Enabled {
|
if s.Enabled {
|
||||||
s.LogEvent("enabled", "", "", "", 0, s.enabledWhy)
|
s.LogEvent("enabled", "", "", 0, s.enabledWhy)
|
||||||
} else {
|
} else {
|
||||||
s.LogEvent("disabled", "", "", "", 0, s.enabledWhy)
|
s.LogEvent("disabled", "", "", 0, s.enabledWhy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,7 +323,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)
|
||||||
|
@ -380,12 +380,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),
|
||||||
|
|
|
@ -164,19 +164,19 @@ 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.HasPrefix(msg[6], "state/hours.txt") {
|
if msg := <-s.eventStream; !strings.HasPrefix(msg[5], "state/hours.txt") {
|
||||||
t.Error("Wrong message from event stream:", msg[6])
|
t.Error("Wrong message from event stream:", msg[5])
|
||||||
}
|
}
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,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")
|
||||||
|
|
|
@ -49,10 +49,10 @@ It ought to import into any spreadsheet program painlessly.
|
||||||
|
|
||||||
Each line has six fields minimum:
|
Each line has six fields minimum:
|
||||||
|
|
||||||
| `timestamp` | `event` | `participantID` | `teamID` | `category` | `points` | `extra`... |
|
| `timestamp` | `event` | `teamID` | `category` | `points` | `extra`... |
|
||||||
| --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
| int | string | string | string | string | int | string... |
|
| int | string | string | string | int | string... |
|
||||||
| Unix epoch | Event type | Participant's (hopefully) unique ID | Team's unique ID | Name of category, if any | Points awarded, if any | Additional fields, if any |
|
| Unix epoch | Event type | Team's unique ID | Name of category, if any | Points awarded, if any | Additional fields, if any |
|
||||||
|
|
||||||
Fields after `points` contain extra fields associated with the event.
|
Fields after `points` contain extra fields associated with the event.
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -105,7 +105,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 +114,9 @@ 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 +148,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 +160,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 {
|
||||||
|
|
Loading…
Reference in New Issue