mirror of https://github.com/dirtbags/moth.git
Compare commits
No commits in common. "59a6aef0077331938263346dbb70925e8a69745f" and "077dc261e478ce269e76f7a6cfcbd9479509afd7" have entirely different histories.
59a6aef007
...
077dc261e4
|
@ -10,8 +10,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Reworked the built-in theme
|
- Reworked the built-in theme
|
||||||
- [moth.mjs](theme/moth.mjs) is now the standard MOTH library for ECMAScript
|
- [moth.mjs](theme/moth.mjs) is now the standard MOTH library for ECMAScript
|
||||||
- Devel mode no longer accepts an empty team ID
|
- Devel mode no longer accepts an empty team ID
|
||||||
- messages.html moved into theme
|
|
||||||
- Exported state now includes "Enabled" boolean
|
|
||||||
|
|
||||||
## [v4.4.9] - 2022-05-12
|
## [v4.4.9] - 2022-05-12
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -45,7 +45,7 @@ func TestHttpd(t *testing.T) {
|
||||||
|
|
||||||
if r := hs.TestRequest("/state", nil); r.Result().StatusCode != 200 {
|
if r := hs.TestRequest("/state", nil); r.Result().StatusCode != 200 {
|
||||||
t.Error(r.Result())
|
t.Error(r.Result())
|
||||||
} else if r.Body.String() != `{"Config":{"Devel":false},"TeamNames":{},"PointsLog":[],"Puzzles":{}}` {
|
} else if r.Body.String() != `{"Config":{"Devel":false},"Messages":"messages.html","TeamNames":{},"PointsLog":[],"Puzzles":{}}` {
|
||||||
t.Error("Unexpected state", r.Body.String())
|
t.Error("Unexpected state", r.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ func TestHttpd(t *testing.T) {
|
||||||
|
|
||||||
if r := hs.TestRequest("/state", nil); r.Result().StatusCode != 200 {
|
if r := hs.TestRequest("/state", nil); r.Result().StatusCode != 200 {
|
||||||
t.Error(r.Result())
|
t.Error(r.Result())
|
||||||
} else if r.Body.String() != `{"Config":{"Devel":false},"TeamNames":{"self":"GoTeam"},"PointsLog":[],"Puzzles":{"pategory":[1]}}` {
|
} else if r.Body.String() != `{"Config":{"Devel":false},"Messages":"messages.html","TeamNames":{"self":"GoTeam"},"PointsLog":[],"Puzzles":{"pategory":[1]}}` {
|
||||||
t.Error("Unexpected state", r.Body.String())
|
t.Error("Unexpected state", r.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ type Configuration struct {
|
||||||
// StateExport is given to clients requesting the current state.
|
// StateExport is given to clients requesting the current state.
|
||||||
type StateExport struct {
|
type StateExport struct {
|
||||||
Config Configuration
|
Config Configuration
|
||||||
Enabled bool
|
Messages string
|
||||||
TeamNames map[string]string
|
TeamNames map[string]string
|
||||||
PointsLog award.List
|
PointsLog award.List
|
||||||
Puzzles map[string][]int
|
Puzzles map[string][]int
|
||||||
|
@ -53,7 +53,7 @@ type ThemeProvider interface {
|
||||||
|
|
||||||
// StateProvider defines what's required to provide MOTH state.
|
// StateProvider defines what's required to provide MOTH state.
|
||||||
type StateProvider interface {
|
type StateProvider interface {
|
||||||
Enabled() bool
|
Messages() string
|
||||||
PointsLog() award.List
|
PointsLog() award.List
|
||||||
TeamName(teamID string) (string, error)
|
TeamName(teamID string) (string, error)
|
||||||
SetTeamName(teamID, teamName string) error
|
SetTeamName(teamID, teamName string) error
|
||||||
|
@ -194,7 +194,7 @@ func (mh *MothRequestHandler) exportStateIfRegistered(forceRegistered bool) *Sta
|
||||||
teamName, err := mh.State.TeamName(mh.teamID)
|
teamName, err := mh.State.TeamName(mh.teamID)
|
||||||
registered := forceRegistered || mh.Config.Devel || (err == nil)
|
registered := forceRegistered || mh.Config.Devel || (err == nil)
|
||||||
|
|
||||||
export.Enabled = mh.State.Enabled()
|
export.Messages = mh.State.Messages()
|
||||||
export.TeamNames = make(map[string]string)
|
export.TeamNames = make(map[string]string)
|
||||||
|
|
||||||
// Anonymize team IDs in points log, and write out team names
|
// Anonymize team IDs in points log, and write out team names
|
||||||
|
|
|
@ -22,6 +22,7 @@ func NewTestServer() TestServer {
|
||||||
|
|
||||||
state := NewTestState()
|
state := NewTestState()
|
||||||
afero.WriteFile(state, "teamids.txt", []byte("teamID\n"), 0644)
|
afero.WriteFile(state, "teamids.txt", []byte("teamID\n"), 0644)
|
||||||
|
afero.WriteFile(state, "messages.html", []byte("messages.html"), 0644)
|
||||||
state.refresh()
|
state.refresh()
|
||||||
|
|
||||||
theme := NewTestTheme()
|
theme := NewTestTheme()
|
||||||
|
@ -100,6 +101,9 @@ func TestProdServer(t *testing.T) {
|
||||||
if len(es.Puzzles) != 1 {
|
if len(es.Puzzles) != 1 {
|
||||||
t.Error("Puzzle categories wrong length", len(es.Puzzles))
|
t.Error("Puzzle categories wrong length", len(es.Puzzles))
|
||||||
}
|
}
|
||||||
|
if es.Messages != "messages.html" {
|
||||||
|
t.Error("Messages has wrong contents")
|
||||||
|
}
|
||||||
if len(es.PointsLog) != 0 {
|
if len(es.PointsLog) != 0 {
|
||||||
t.Error("Points log not empty")
|
t.Error("Points log not empty")
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ type State struct {
|
||||||
afero.Fs
|
afero.Fs
|
||||||
|
|
||||||
// Enabled tracks whether the current State system is processing updates
|
// Enabled tracks whether the current State system is processing updates
|
||||||
enabled bool
|
Enabled bool
|
||||||
|
|
||||||
enabledWhy string
|
enabledWhy string
|
||||||
refreshNow chan bool
|
refreshNow chan bool
|
||||||
|
@ -49,6 +49,7 @@ type State struct {
|
||||||
teamNamesLastChange time.Time
|
teamNamesLastChange time.Time
|
||||||
teamNames map[string]string
|
teamNames map[string]string
|
||||||
pointsLog award.List
|
pointsLog award.List
|
||||||
|
messages string
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ type State struct {
|
||||||
func NewState(fs afero.Fs) *State {
|
func NewState(fs afero.Fs) *State {
|
||||||
s := &State{
|
s := &State{
|
||||||
Fs: fs,
|
Fs: fs,
|
||||||
enabled: true,
|
Enabled: true,
|
||||||
refreshNow: make(chan bool, 5),
|
refreshNow: make(chan bool, 5),
|
||||||
eventStream: make(chan []string, 80),
|
eventStream: make(chan []string, 80),
|
||||||
|
|
||||||
|
@ -116,11 +117,11 @@ func (s *State) updateEnabled() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextEnabled != s.enabled) || (why != s.enabledWhy) {
|
if (nextEnabled != s.Enabled) || (why != s.enabledWhy) {
|
||||||
s.enabled = nextEnabled
|
s.Enabled = nextEnabled
|
||||||
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)
|
||||||
|
@ -192,9 +193,11 @@ func (s *State) PointsLog() award.List {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enabled returns true if the server is in "enabled" state
|
// Messages retrieves the current messages.
|
||||||
func (s *State) Enabled() bool {
|
func (s *State) Messages() string {
|
||||||
return s.enabled
|
s.lock.RLock() // It's not clear to me that this actually needs to happen
|
||||||
|
defer s.lock.RUnlock()
|
||||||
|
return s.messages
|
||||||
}
|
}
|
||||||
|
|
||||||
// AwardPoints gives points to teamID in category.
|
// AwardPoints gives points to teamID in category.
|
||||||
|
@ -310,6 +313,7 @@ func (s *State) maybeInitialize() {
|
||||||
s.Remove("hours.txt")
|
s.Remove("hours.txt")
|
||||||
s.Remove("points.log")
|
s.Remove("points.log")
|
||||||
s.Remove("events.csv")
|
s.Remove("events.csv")
|
||||||
|
s.Remove("messages.html")
|
||||||
s.Remove("mothd.log")
|
s.Remove("mothd.log")
|
||||||
s.RemoveAll("points.tmp")
|
s.RemoveAll("points.tmp")
|
||||||
s.RemoveAll("points.new")
|
s.RemoveAll("points.new")
|
||||||
|
@ -365,6 +369,11 @@ func (s *State) maybeInitialize() {
|
||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f, err := s.Create("messages.html"); err == nil {
|
||||||
|
fmt.Fprintln(f, "<!-- messages.html: put client broadcast messages here. -->")
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
if f, err := s.Create("points.log"); err == nil {
|
if f, err := s.Create("points.log"); err == nil {
|
||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
@ -456,12 +465,16 @@ func (s *State) updateCaches() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bMessages, err := afero.ReadFile(s, "messages.html"); err == nil {
|
||||||
|
s.messages = string(bMessages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) refresh() {
|
func (s *State) refresh() {
|
||||||
s.maybeInitialize()
|
s.maybeInitialize()
|
||||||
s.updateEnabled()
|
s.updateEnabled()
|
||||||
if s.enabled {
|
if s.Enabled {
|
||||||
s.collectPoints()
|
s.collectPoints()
|
||||||
}
|
}
|
||||||
s.updateCaches()
|
s.updateCaches()
|
||||||
|
@ -510,7 +523,7 @@ func (ds *DevelState) TeamName(teamID string) (string, error) {
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
if teamID == "" {
|
if teamID == "" {
|
||||||
return "", fmt.Errorf("empty team ID")
|
return "", fmt.Errorf("Empty team ID")
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("«devel:%s»", teamID), nil
|
return fmt.Sprintf("«devel:%s»", teamID), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,7 @@ func TestStateDisabled(t *testing.T) {
|
||||||
s := NewTestState()
|
s := NewTestState()
|
||||||
s.refresh()
|
s.refresh()
|
||||||
|
|
||||||
if !s.Enabled() {
|
if !s.Enabled {
|
||||||
t.Error("Brand new state is disabled")
|
t.Error("Brand new state is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,35 +195,35 @@ func TestStateDisabled(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer hoursFile.Close()
|
defer hoursFile.Close()
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if !s.Enabled() {
|
if !s.Enabled {
|
||||||
t.Error("Empty hours.txt not enabled")
|
t.Error("Empty hours.txt not enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(hoursFile, "- 1970-01-01T01:01:01Z")
|
fmt.Fprintln(hoursFile, "- 1970-01-01T01:01:01Z")
|
||||||
hoursFile.Sync()
|
hoursFile.Sync()
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if s.Enabled() {
|
if s.Enabled {
|
||||||
t.Error("1970-01-01")
|
t.Error("1970-01-01")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(hoursFile, "+ 1970-01-02 01:01:01+05:00")
|
fmt.Fprintln(hoursFile, "+ 1970-01-02 01:01:01+05:00")
|
||||||
hoursFile.Sync()
|
hoursFile.Sync()
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if !s.Enabled() {
|
if !s.Enabled {
|
||||||
t.Error("1970-01-02")
|
t.Error("1970-01-02")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(hoursFile, "-")
|
fmt.Fprintln(hoursFile, "-")
|
||||||
hoursFile.Sync()
|
hoursFile.Sync()
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if s.Enabled() {
|
if s.Enabled {
|
||||||
t.Error("bare -")
|
t.Error("bare -")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(hoursFile, "+")
|
fmt.Fprintln(hoursFile, "+")
|
||||||
hoursFile.Sync()
|
hoursFile.Sync()
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if !s.Enabled() {
|
if !s.Enabled {
|
||||||
t.Error("bare +")
|
t.Error("bare +")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,21 +231,21 @@ func TestStateDisabled(t *testing.T) {
|
||||||
fmt.Fprintln(hoursFile, "# Comment")
|
fmt.Fprintln(hoursFile, "# Comment")
|
||||||
hoursFile.Sync()
|
hoursFile.Sync()
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if !s.Enabled() {
|
if !s.Enabled {
|
||||||
t.Error("Comment")
|
t.Error("Comment")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(hoursFile, "intentional parse error")
|
fmt.Fprintln(hoursFile, "intentional parse error")
|
||||||
hoursFile.Sync()
|
hoursFile.Sync()
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if !s.Enabled() {
|
if !s.Enabled {
|
||||||
t.Error("intentional parse error")
|
t.Error("intentional parse error")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(hoursFile, "- 1980-01-01T01:01:01Z")
|
fmt.Fprintln(hoursFile, "- 1980-01-01T01:01:01Z")
|
||||||
hoursFile.Sync()
|
hoursFile.Sync()
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if s.Enabled() {
|
if s.Enabled {
|
||||||
t.Error("1980-01-01")
|
t.Error("1980-01-01")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,13 +253,13 @@ func TestStateDisabled(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if !s.Enabled() {
|
if !s.Enabled {
|
||||||
t.Error("Removing `hours.txt` disabled event")
|
t.Error("Removing `hours.txt` disabled event")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Remove("initialized")
|
s.Remove("initialized")
|
||||||
s.refresh()
|
s.refresh()
|
||||||
if !s.Enabled() {
|
if !s.Enabled {
|
||||||
t.Error("Re-initializing didn't start event")
|
t.Error("Re-initializing didn't start event")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ Returns the current Moth event state as a JSON object.
|
||||||
"Config": {
|
"Config": {
|
||||||
"Devel": false // true means this is a development server
|
"Devel": false // true means this is a development server
|
||||||
},
|
},
|
||||||
|
"Messages: "HTML to be rendered as broadcast messages",
|
||||||
"TeamNames": {
|
"TeamNames": {
|
||||||
"self": "Requesting team name", // Only if regestered team id is a provided
|
"self": "Requesting team name", // Only if regestered team id is a provided
|
||||||
"0": "Team 1 Name",
|
"0": "Team 1 Name",
|
||||||
|
@ -90,6 +91,7 @@ Content-Type: application/json
|
||||||
|
|
||||||
{"Config":
|
{"Config":
|
||||||
{"Devel":false},
|
{"Devel":false},
|
||||||
|
"Messages":"<p>Welcome to the event!</p><p>Event ends at 19:00!</p>",
|
||||||
"TeamNames":{
|
"TeamNames":{
|
||||||
"0":"Mike and Jack",
|
"0":"Mike and Jack",
|
||||||
"12":"Team 2",
|
"12":"Team 2",
|
||||||
|
|
|
@ -167,7 +167,9 @@ class App {
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
window.app = new App()
|
window.app = {
|
||||||
|
server: new App()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
common.WhenDOMLoaded(init)
|
common.WhenDOMLoaded(init)
|
||||||
|
|
|
@ -367,8 +367,10 @@ class State {
|
||||||
Devel: obj.Config.Devel,
|
Devel: obj.Config.Devel,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True if the server is in enabled state */
|
/** Global messages, in HTML
|
||||||
this.Enabled = obj.Enabled
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.Messages = obj.Messages
|
||||||
|
|
||||||
/** Map from Team ID to Team Name
|
/** Map from Team ID to Team Name
|
||||||
* @type {Object.<string,string>}
|
* @type {Object.<string,string>}
|
||||||
|
|
Loading…
Reference in New Issue