mirror of https://github.com/dirtbags/moth.git
It compiles!
This commit is contained in:
parent
f950dacf5e
commit
2fd03f390f
104
handlers.go
104
handlers.go
|
@ -3,5 +3,109 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func registerHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
teamname := req.FormValue("n")
|
||||||
|
teamid := req.FormValue("h")
|
||||||
|
|
||||||
|
if matched, _ := regexp.MatchString("[^0-9a-z]", teamid); matched {
|
||||||
|
teamid = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (teamid == "") || (teamname == "") {
|
||||||
|
showPage(w, "Invalid Entry", "Oops! Are you sure you got that right?")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! anchoredSearch(statePath("assigned.txt"), teamid, 0) {
|
||||||
|
showPage(w, "Invalid Team ID", "I don't have a record of that team ID. Maybe you used capital letters accidentally?")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(statePath("state", teamid), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
showPage(
|
||||||
|
w,
|
||||||
|
"Registration failed",
|
||||||
|
"Unable to register. Perhaps a teammate has already registered?",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
fmt.Fprintln(f, teamname)
|
||||||
|
showPage(w, "Success", "Okay, your team has been named and you may begin using your team ID!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
teamid := req.FormValue("t")
|
||||||
|
token := req.FormValue("k")
|
||||||
|
|
||||||
|
// Check answer
|
||||||
|
if ! anchoredSearch(token, statePath("tokens.txt"), 0) {
|
||||||
|
showPage(w, "Unrecognized token", "I don't recognize that token. Did you type in the whole thing?")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(token, ":")
|
||||||
|
category := ""
|
||||||
|
pointstr := ""
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
category = parts[0]
|
||||||
|
pointstr = parts[1]
|
||||||
|
}
|
||||||
|
points, err := strconv.Atoi(pointstr)
|
||||||
|
if err != nil {
|
||||||
|
points = 0
|
||||||
|
}
|
||||||
|
// Defang category name; prevent directory traversal
|
||||||
|
if matched, _ := regexp.MatchString("^[A-Za-z0-9_-]", category); matched {
|
||||||
|
category = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category == "") || (points == 0) {
|
||||||
|
showPage(w, "Unrecognized token", "Something doesn't look right about that token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := awardPoints(teamid, category, points); err != nil {
|
||||||
|
showPage(w, "Error awarding points", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func answerHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
teamid := req.FormValue("t")
|
||||||
|
category := req.FormValue("c")
|
||||||
|
pointstr := req.FormValue("p")
|
||||||
|
answer := req.FormValue("a")
|
||||||
|
|
||||||
|
points, err := strconv.Atoi(pointstr)
|
||||||
|
if err != nil {
|
||||||
|
points = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defang category name; prevent directory traversal
|
||||||
|
if matched, _ := regexp.MatchString("^[A-Za-z0-9_-]", category); matched {
|
||||||
|
category = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check answer
|
||||||
|
needle := fmt.Sprintf("%s %s", points, answer)
|
||||||
|
haystack := mothPath("packages", category, "answers.txt")
|
||||||
|
if ! anchoredSearch(haystack, needle, 0) {
|
||||||
|
showPage(w, "Wrong answer", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := awardPoints(teamid, category, points); err != nil {
|
||||||
|
showPage(w, "Error awarding points", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showPage(w, "Points awarded", fmt.Sprintf("%d points for %s!", points, teamid))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,18 +4,13 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"time"
|
"time"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func allfiles(dirpath) []string {
|
func cacheMothball(filepath string, categoryName string) {
|
||||||
files, err := ioutil.ReadDir(dirpath)
|
log.Printf("I'm exploding a mothball %s %s", filepath, categoryName)
|
||||||
if (err != nil) {
|
|
||||||
log.Printf("Error reading directory %s: %s", dirpath, err)
|
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// maintenance runs
|
// maintenance runs
|
||||||
func tidy() {
|
func tidy() {
|
||||||
|
@ -37,11 +32,17 @@ func tidy() {
|
||||||
|
|
||||||
log.Print("Hello, I'm maintaining!")
|
log.Print("Hello, I'm maintaining!")
|
||||||
|
|
||||||
//
|
// Make sure points directories exist
|
||||||
|
os.Mkdir(statePath("points.tmp"), 0755)
|
||||||
|
os.Mkdir(statePath("points.new"), 0755)
|
||||||
|
|
||||||
// Get current list of categories
|
// Get current list of categories
|
||||||
//
|
|
||||||
newCategories := []string{}
|
newCategories := []string{}
|
||||||
for f := range(allfiles(mothPath("packages"))) {
|
files, err := ioutil.ReadDir(mothPath("packages"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading packages: %s", err)
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
filename := f.Name()
|
filename := f.Name()
|
||||||
filepath := mothPath("packages", filename)
|
filepath := mothPath("packages", filename)
|
||||||
if ! strings.HasSuffix(filename, ".mb") {
|
if ! strings.HasSuffix(filename, ".mb") {
|
||||||
|
|
80
mothd.go
80
mothd.go
|
@ -1,41 +1,90 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var basePath = "."
|
var basePath = "/home/neale/src/moth"
|
||||||
var maintenanceInterval = 20 * time.Second
|
var maintenanceInterval = 20 * time.Second
|
||||||
var categories = []string{}
|
var categories = []string{}
|
||||||
|
|
||||||
|
// anchoredSearch looks for needle in filename,
|
||||||
|
// skipping the first skip space-delimited words
|
||||||
|
func anchoredSearch(filename string, needle string, skip int) bool {
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("Can't open %s: %s", filename, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
func mooHandler(w http.ResponseWriter, req *http.Request) {
|
scanner := bufio.NewScanner(f)
|
||||||
moo := req.FormValue("moo")
|
for scanner.Scan() {
|
||||||
fmt.Fprintf(w, "Hello, %q. %s", html.EscapeString(req.URL.Path), html.EscapeString(moo))
|
line := scanner.Text()
|
||||||
|
parts := strings.SplitN(" ", line, skip+1)
|
||||||
|
if parts[skip+1] == needle {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func rootHandler(w http.ResponseWriter, req *http.Request) {
|
return false
|
||||||
if req.URL.Path != "/" {
|
|
||||||
http.NotFound(w, req)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func awardPoints(teamid string, category string, points int) error {
|
||||||
|
fn := fmt.Sprintf("%s-%s-%d", teamid, category, points)
|
||||||
|
tmpfn := statePath("points.tmp", fn)
|
||||||
|
newfn := statePath("points.new", fn)
|
||||||
|
|
||||||
|
contents := fmt.Sprintf("%d %s %s %d\n", time.Now().Unix(), teamid, points)
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(tmpfn, []byte(contents), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(tmpfn, newfn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func showPage(w http.ResponseWriter, title string, body string) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "<!DOCTYPE html>")
|
||||||
|
fmt.Fprintf(w, "<html><head>")
|
||||||
|
fmt.Fprintf(w, "<title>%s</title>", title)
|
||||||
|
fmt.Fprintf(w, "<link rel=\"stylesheet\" href=\"../style.css\">")
|
||||||
|
fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width\"></head>")
|
||||||
|
fmt.Fprintf(w, "<body><h1>%s</h1>", title)
|
||||||
|
fmt.Fprintf(w, "<section>%s</section>", body)
|
||||||
|
fmt.Fprintf(w, "<nav>")
|
||||||
|
fmt.Fprintf(w, "<ul>")
|
||||||
|
fmt.Fprintf(w, "<li><a href=\"../register.html\">Register</a></li>")
|
||||||
|
fmt.Fprintf(w, "<li><a href=\"../puzzles.html\">Puzzles</a></li>")
|
||||||
|
fmt.Fprintf(w, "<li><a href=\"../scoreboard.html\">Scoreboard</a></li>")
|
||||||
|
fmt.Fprintf(w, "</ul>")
|
||||||
|
fmt.Fprintf(w, "</nav>")
|
||||||
|
fmt.Fprintf(w, "</body></html>")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mothPath(parts ...string) string {
|
func mothPath(parts ...string) string {
|
||||||
return path.Join(basePath, parts...)
|
tail := path.Join(parts...)
|
||||||
|
return path.Join(basePath, tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func statePath(parts ...string) string {
|
func statePath(parts ...string) string {
|
||||||
return path.Join(basePath, "state", parts...)
|
tail := path.Join(parts...)
|
||||||
|
return path.Join(basePath, "state", tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func exists(filename string) bool {
|
func exists(filename string) bool {
|
||||||
|
@ -48,8 +97,9 @@ func exists(filename string) bool {
|
||||||
func main() {
|
func main() {
|
||||||
log.Print("Sup")
|
log.Print("Sup")
|
||||||
go maintenance();
|
go maintenance();
|
||||||
http.HandleFunc("/", rootHandler)
|
http.HandleFunc("/register", registerHandler)
|
||||||
http.HandleFunc("/moo/", mooHandler)
|
http.HandleFunc("/token", tokenHandler)
|
||||||
|
http.HandleFunc("/answer", answerHandler)
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
89
points.go
89
points.go
|
@ -1,17 +1,21 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Award struct {
|
type Award struct {
|
||||||
when time.Time,
|
when time.Time
|
||||||
team string,
|
team string
|
||||||
category string,
|
category string
|
||||||
points int,
|
points int
|
||||||
comment string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseAward(s string) (*Award, error) {
|
func ParseAward(s string) (*Award, error) {
|
||||||
|
@ -19,53 +23,86 @@ func ParseAward(s string) (*Award, error) {
|
||||||
|
|
||||||
parts := strings.SplitN(s, " ", 5)
|
parts := strings.SplitN(s, " ", 5)
|
||||||
if len(parts) < 4 {
|
if len(parts) < 4 {
|
||||||
return nil, Error("Malformed award string")
|
return nil, fmt.Errorf("Malformed award string")
|
||||||
}
|
}
|
||||||
|
|
||||||
whenEpoch, err = strconv.Atoi(parts[0])
|
whenEpoch, err := strconv.ParseInt(parts[0], 10, 64)
|
||||||
if (err != nil) {
|
if (err != nil) {
|
||||||
return nil, Errorf("Malformed timestamp: %s", parts[0])
|
return nil, fmt.Errorf("Malformed timestamp: %s", parts[0])
|
||||||
}
|
}
|
||||||
ret.when = time.Unix(whenEpoch, 0)
|
ret.when = time.Unix(whenEpoch, 0)
|
||||||
|
|
||||||
ret.team = parts[1]
|
ret.team = parts[1]
|
||||||
ret.category = parts[2]
|
ret.category = parts[2]
|
||||||
|
|
||||||
points, err = strconv.Atoi(parts[3])
|
points, err := strconv.Atoi(parts[3])
|
||||||
if (err != nil) {
|
if (err != nil) {
|
||||||
return nil, Errorf("Malformed points: %s", parts[3])
|
return nil, fmt.Errorf("Malformed points: %s", parts[3])
|
||||||
}
|
}
|
||||||
|
ret.points = points
|
||||||
|
|
||||||
if len(parts) == 5 {
|
return &ret, nil
|
||||||
ret.comment = parts[4]
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Award) String() string {
|
func (a *Award) String() string {
|
||||||
return fmt.Sprintf("%d %s %s %d %s", a.when.Unix(), a.team, a.category, a.points, a.comment)
|
return fmt.Sprintf("%d %s %s %d", a.when.Unix(), a.team, a.category, a.points)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pointsLog() []Award {
|
||||||
|
var ret []Award
|
||||||
|
|
||||||
|
fn := statePath("points.log")
|
||||||
|
f, err := os.Open(fn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to open %s: %s", fn, err)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
cur, err := ParseAward(line)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Skipping malformed award line %s: %s", line, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret = append(ret, *cur)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// collectPoints gathers up files in points.new/ and appends their contents to points.log,
|
// collectPoints gathers up files in points.new/ and appends their contents to points.log,
|
||||||
// removing each points.new/ file as it goes.
|
// removing each points.new/ file as it goes.
|
||||||
func collectPoints() {
|
func collectPoints() {
|
||||||
pointsLog = os.OpenFile(statePath("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
logf, err := os.OpenFile(statePath("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
defer pointsLog.Close()
|
if err != nil {
|
||||||
|
log.Printf("Can't append to points log: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer logf.Close()
|
||||||
|
|
||||||
for f := range allfiles(statePath("points.new")) {
|
files, err := ioutil.ReadDir(statePath("points.new"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading packages: %s", err)
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
filename := statePath("points.new", f.Name())
|
filename := statePath("points.new", f.Name())
|
||||||
s := ioutil.ReadFile(filename)
|
s, err := ioutil.ReadFile(filename)
|
||||||
award, err := ParseAward(s)
|
if err != nil {
|
||||||
if (err != nil) {
|
log.Printf("Can't read points file %s: %s", filename, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
award, err := ParseAward(string(s))
|
||||||
|
if err != nil {
|
||||||
log.Printf("Can't parse award file %s: %s", filename, err)
|
log.Printf("Can't parse award file %s: %s", filename, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Fprintf(pointsLog, "%s\n", award.String())
|
fmt.Fprintf(logf, "%s\n", award.String())
|
||||||
log.Print(award.String())
|
log.Print(award.String())
|
||||||
pointsLog.Sync()
|
logf.Sync()
|
||||||
err := os.Remove(filename)
|
if err := os.Remove(filename); err != nil {
|
||||||
if (err != nil) {
|
|
||||||
log.Printf("Unable to remove %s: %s", filename, err)
|
log.Printf("Unable to remove %s: %s", filename, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue