Merge pull request #24 from br0xen/master

Cleaning up the models, making things a little nicer
This commit is contained in:
Brian Buller 2017-10-03 06:55:38 -05:00 committed by GitHub
commit cd67ed4ae6
17 changed files with 786 additions and 852 deletions

View File

@ -11,19 +11,20 @@ func handleAdminClients(w http.ResponseWriter, req *http.Request, page *pageData
vars := mux.Vars(req) vars := mux.Vars(req)
page.SubTitle = "Clients" page.SubTitle = "Clients"
clientId := vars["id"] clientId := vars["id"]
client := db.getClient(clientId)
clientIp, _, _ := net.SplitHostPort(req.RemoteAddr) clientIp, _, _ := net.SplitHostPort(req.RemoteAddr)
if clientId == "" { if clientId == "" {
type clientsPageData struct { type clientsPageData struct {
Clients []Client Clients []Client
} }
page.TemplateData = clientsPageData{Clients: dbGetAllClients()} page.TemplateData = clientsPageData{Clients: db.getAllClients()}
page.SubTitle = "Clients" page.SubTitle = "Clients"
page.show("admin-clients.html", w) page.show("admin-clients.html", w)
} else { } else {
switch vars["function"] { switch vars["function"] {
case "add": case "add":
page.SubTitle = "Authenticate Client" page.SubTitle = "Authenticate Client"
cli := dbGetClient(clientId) cli := db.getClient(clientId)
if cli.IP == "" { if cli.IP == "" {
cli.IP = clientIp cli.IP = clientIp
} }
@ -38,14 +39,17 @@ func handleAdminClients(w http.ResponseWriter, req *http.Request, page *pageData
email := req.FormValue("email") email := req.FormValue("email")
password := req.FormValue("password") password := req.FormValue("password")
clientName := req.FormValue("clientname") clientName := req.FormValue("clientname")
// Authentication isn't required to set a client name
if clientName != "" { if clientName != "" {
dbSetClientName(clientId, clientName) client.Name = clientName
} }
dbUpdateClientIP(clientId, clientIp) client.IP = clientIp
client.save()
if page.LoggedIn || doLogin(email, password) == nil { if page.LoggedIn || doLogin(email, password) == nil {
// Received a valid login // Received a valid login
// Authenticate the client // Authenticate the client
if dbAuthClient(clientId, clientIp) == nil { client.Auth = true
if client.save() == nil {
page.session.setFlashMessage("Client Authenticated", "success") page.session.setFlashMessage("Client Authenticated", "success")
} else { } else {
page.session.setFlashMessage("Client Authentication Failed", "error") page.session.setFlashMessage("Client Authentication Failed", "error")
@ -56,17 +60,17 @@ func handleAdminClients(w http.ResponseWriter, req *http.Request, page *pageData
} }
redirect("/", w, req) redirect("/", w, req)
case "deauth": case "deauth":
dbDeAuthClient(clientId) client.Auth = false
if client.save() == nil {
page.session.setFlashMessage("Client De-Authenticated", "success")
} else {
page.session.setFlashMessage("Client De-Authentication Failed", "success")
}
redirect("/admin/clients", w, req) redirect("/admin/clients", w, req)
} }
} }
} }
func clientIsAuthenticated(cid string, req *http.Request) bool {
return dbClientIsAuth(cid)
//return clientIsServer(req) || dbClientIsAuth(cid)
}
func clientIsServer(req *http.Request) bool { func clientIsServer(req *http.Request) bool {
clientIp, _, _ := net.SplitHostPort(req.RemoteAddr) clientIp, _, _ := net.SplitHostPort(req.RemoteAddr)
ifaces, err := net.Interfaces() ifaces, err := net.Interfaces()

View File

@ -72,7 +72,7 @@ func handleAdminSetAuthMode(w http.ResponseWriter, req *http.Request, page *page
if err != nil { if err != nil {
page.session.setFlashMessage("Invalid Authentication Mode: "+vars["id"], "error") page.session.setFlashMessage("Invalid Authentication Mode: "+vars["id"], "error")
} }
if dbSetAuthMode(newMode) != nil { if db.setAuthMode(newMode) != nil {
page.session.setFlashMessage("Invalid Authentication Mode: "+vars["id"], "error") page.session.setFlashMessage("Invalid Authentication Mode: "+vars["id"], "error")
} }
redirect("/admin", w, req) redirect("/admin", w, req)

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt"
"image" "image"
"image/gif" "image/gif"
"image/jpeg" "image/jpeg"
@ -25,46 +24,50 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData)
Teams []Team Teams []Team
} }
gpd := new(gamesPageData) gpd := new(gamesPageData)
gpd.Teams = dbGetAllTeams() gpd.Teams = db.getAllTeams()
page.TemplateData = gpd page.TemplateData = gpd
page.SubTitle = "Games" page.SubTitle = "Games"
page.show("admin-games.html", w) page.show("admin-games.html", w)
} else { } else {
switch vars["function"] { tm := db.getTeam(teamId)
case "save": if tm != nil {
name := req.FormValue("gamename") switch vars["function"] {
desc := req.FormValue("gamedesc") case "save":
link := req.FormValue("gamelink") gm := newGame(tm.UUID)
if dbIsValidTeam(teamId) { gm.Name = req.FormValue("gamename")
if err := dbUpdateTeamGame(teamId, name, link, desc); err != nil { gm.Link = req.FormValue("gamelink")
gm.Description = req.FormValue("gamedesc")
if err := gm.save(); err != nil {
page.session.setFlashMessage("Error updating game: "+err.Error(), "error") page.session.setFlashMessage("Error updating game: "+err.Error(), "error")
} else { } else {
page.session.setFlashMessage("Team game updated", "success") page.session.setFlashMessage("Team game updated", "success")
} }
redirect("/admin/teams/"+teamId+"#game", w, req) redirect("/admin/teams/"+tm.UUID+"#game", w, req)
case "screenshotupload":
if err := saveScreenshots(tm, req); err != nil {
page.session.setFlashMessage("Error updating game: "+err.Error(), "error")
}
redirect("/admin/teams/"+tm.UUID+"#game", w, req)
case "screenshotdelete":
ssid := vars["subid"]
if err := tm.deleteScreenshot(ssid); err != nil {
page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error")
}
redirect("/admin/teams/"+tm.UUID+"#game", w, req)
} }
case "screenshotupload": } else {
if err := saveScreenshots(teamId, req); err != nil { page.session.setFlashMessage("Not a valid team id", "error")
page.session.setFlashMessage("Error updating game: "+err.Error(), "error") redirect("/admin/teams", w, req)
}
redirect("/admin/teams/"+teamId+"#game", w, req)
case "screenshotdelete":
ssid := vars["subid"]
if err := dbDeleteTeamGameScreenshot(teamId, ssid); err != nil {
page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error")
}
redirect("/admin/teams/"+teamId+"#game", w, req)
} }
} }
} }
func saveScreenshots(teamId string, req *http.Request) error { func saveScreenshots(tm *Team, req *http.Request) error {
var err error var err error
file, hdr, err := req.FormFile("newssfile") file, hdr, err := req.FormFile("newssfile")
if err != nil { if err != nil {
return err return err
} }
fmt.Println("File Received: " + hdr.Filename)
extIdx := strings.LastIndex(hdr.Filename, ".") extIdx := strings.LastIndex(hdr.Filename, ".")
fltp := "png" fltp := "png"
if len(hdr.Filename) > extIdx { if len(hdr.Filename) > extIdx {
@ -90,7 +93,7 @@ func saveScreenshots(teamId string, req *http.Request) error {
} }
thmString = base64.StdEncoding.EncodeToString(thmBuf.Bytes()) thmString = base64.StdEncoding.EncodeToString(thmBuf.Bytes())
return dbSaveTeamGameScreenshot(teamId, &Screenshot{ return tm.saveScreenshot(&Screenshot{
Image: base64.StdEncoding.EncodeToString(buf.Bytes()), Image: base64.StdEncoding.EncodeToString(buf.Bytes()),
Thumbnail: thmString, Thumbnail: thmString,
Filetype: fltp, Filetype: fltp,

View File

@ -11,7 +11,7 @@ import (
) )
func refreshTeamsInMemory() { func refreshTeamsInMemory() {
site.Teams = dbGetAllTeams() site.Teams = db.getAllTeams()
} }
func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData) { func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData) {
@ -23,11 +23,11 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
switch vars["function"] { switch vars["function"] {
case "save": case "save":
name := req.FormValue("teamname") name := req.FormValue("teamname")
if dbGetTeamByName(name) != nil { if db.getTeamByName(name) != nil {
// A team with that name already exists // A team with that name already exists
page.session.setFlashMessage("A team with the name "+name+" already exists!", "error") page.session.setFlashMessage("A team with the name "+name+" already exists!", "error")
} else { } else {
if err := dbCreateNewTeam(name); err != nil { if err := db.newTeam(name); err != nil {
page.session.setFlashMessage(err.Error(), "error") page.session.setFlashMessage(err.Error(), "error")
} else { } else {
page.session.setFlashMessage("Team "+name+" created!", "success") page.session.setFlashMessage("Team "+name+" created!", "success")
@ -41,13 +41,13 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
} }
} else if teamId != "" { } else if teamId != "" {
// Functions for existing team // Functions for existing team
if dbIsValidTeam(teamId) { tm := db.getTeam(teamId)
if tm != nil {
switch vars["function"] { switch vars["function"] {
case "save": case "save":
tm := new(Team)
tm.UUID = teamId tm.UUID = teamId
tm.Name = req.FormValue("teamname") tm.Name = req.FormValue("teamname")
if err := dbUpdateTeam(teamId, tm); err != nil { if err := tm.save(); err != nil {
page.session.setFlashMessage("Error updating team: "+err.Error(), "error") page.session.setFlashMessage("Error updating team: "+err.Error(), "error")
} else { } else {
page.session.setFlashMessage("Team Updated!", "success") page.session.setFlashMessage("Team Updated!", "success")
@ -56,20 +56,20 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
redirect("/admin/teams", w, req) redirect("/admin/teams", w, req)
case "delete": case "delete":
var err error var err error
t := dbGetTeam(teamId) if err = tm.delete(); err != nil {
if err = dbDeleteTeam(teamId); err != nil {
page.session.setFlashMessage("Error deleting team: "+err.Error(), "error") page.session.setFlashMessage("Error deleting team: "+err.Error(), "error")
} else { } else {
page.session.setFlashMessage("Team "+t.Name+" Deleted", "success") page.session.setFlashMessage("Team "+tm.Name+" Deleted", "success")
} }
refreshTeamsInMemory() refreshTeamsInMemory()
redirect("/admin/teams", w, req) redirect("/admin/teams", w, req)
case "savemember": case "savemember":
mbrName := req.FormValue("newmembername") mbrName := req.FormValue("newmembername")
mbrSlack := req.FormValue("newmemberslackid") mbr := newTeamMember(mbrName)
mbrTwitter := req.FormValue("newmembertwitter") mbr.SlackId = req.FormValue("newmemberslackid")
mbrEmail := req.FormValue("newmemberemail") mbr.Twitter = req.FormValue("newmembertwitter")
if err := dbAddTeamMember(teamId, mbrName, mbrEmail, mbrSlack, mbrTwitter); err != nil { mbr.Email = req.FormValue("newmemberemail")
if err := tm.updateTeamMember(mbr); err != nil {
page.session.setFlashMessage("Error adding team member: "+err.Error(), "error") page.session.setFlashMessage("Error adding team member: "+err.Error(), "error")
} else { } else {
page.session.setFlashMessage(mbrName+" added to team!", "success") page.session.setFlashMessage(mbrName+" added to team!", "success")
@ -77,18 +77,21 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
refreshTeamsInMemory() refreshTeamsInMemory()
redirect("/admin/teams/"+teamId+"#members", w, req) redirect("/admin/teams/"+teamId+"#members", w, req)
case "deletemember": case "deletemember":
mbrId := req.FormValue("memberid") m := tm.getTeamMember(req.FormValue("memberid"))
m, _ := dbGetTeamMember(teamId, mbrId) if m != nil {
if err := dbDeleteTeamMember(teamId, mbrId); err != nil { if err := tm.deleteTeamMember(m); err != nil {
page.session.setFlashMessage("Error deleting team member: "+err.Error(), "error") page.session.setFlashMessage("Error deleting team member: "+err.Error(), "error")
} else {
page.session.setFlashMessage(m.Name+" deleted from team", "success")
}
refreshTeamsInMemory()
} else { } else {
page.session.setFlashMessage(m.Name+" deleted from team", "success") page.session.setFlashMessage("Couldn't find team member to delete", "error")
} }
refreshTeamsInMemory()
redirect("/admin/teams/"+teamId+"#members", w, req) redirect("/admin/teams/"+teamId+"#members", w, req)
default: default:
page.SubTitle = "Edit Team" page.SubTitle = "Edit Team"
t := dbGetTeam(teamId) t := db.getTeam(teamId)
page.TemplateData = t page.TemplateData = t
page.show("admin-editteam.html", w) page.show("admin-editteam.html", w)
} }
@ -101,7 +104,7 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
type teamsPageData struct { type teamsPageData struct {
Teams []Team Teams []Team
} }
page.TemplateData = teamsPageData{Teams: dbGetAllTeams()} page.TemplateData = teamsPageData{Teams: db.getAllTeams()}
page.SubTitle = "Teams" page.SubTitle = "Teams"
page.show("admin-teams.html", w) page.show("admin-teams.html", w)
} }

View File

@ -29,7 +29,7 @@ func handleAdminDoLogin(w http.ResponseWriter, req *http.Request) {
// If it can't, it returns an error // If it can't, it returns an error
func doLogin(email, password string) error { func doLogin(email, password string) error {
if strings.TrimSpace(email) != "" && strings.TrimSpace(password) != "" { if strings.TrimSpace(email) != "" && strings.TrimSpace(password) != "" {
return dbCheckCredentials(email, password) return db.checkCredentials(email, password)
} }
return errors.New("Invalid Credentials") return errors.New("Invalid Credentials")
} }
@ -53,12 +53,12 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData)
switch vars["function"] { switch vars["function"] {
case "save": case "save":
email = req.FormValue("email") email = req.FormValue("email")
if dbIsValidUserEmail(email) { if db.isValidUserEmail(email) {
// User already exists // User already exists
page.session.setFlashMessage("A user with email address "+email+" already exists!", "error") page.session.setFlashMessage("A user with email address "+email+" already exists!", "error")
} else { } else {
password := req.FormValue("password") password := req.FormValue("password")
if err := dbUpdateUserPassword(email, string(password)); err != nil { if err := db.updateUserPassword(email, string(password)); err != nil {
page.session.setFlashMessage(err.Error(), "error") page.session.setFlashMessage(err.Error(), "error")
} else { } else {
page.session.setFlashMessage("User "+email+" created!", "success") page.session.setFlashMessage("User "+email+" created!", "success")
@ -73,10 +73,10 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData)
switch vars["function"] { switch vars["function"] {
case "save": case "save":
var err error var err error
if dbIsValidUserEmail(email) { if db.isValidUserEmail(email) {
password := req.FormValue("password") password := req.FormValue("password")
if password != "" { if password != "" {
if err = dbUpdateUserPassword(email, password); err != nil { if err = db.updateUserPassword(email, password); err != nil {
page.session.setFlashMessage(err.Error(), "error") page.session.setFlashMessage(err.Error(), "error")
} else { } else {
page.session.setFlashMessage("User "+email+" created!", "success") page.session.setFlashMessage("User "+email+" created!", "success")
@ -86,8 +86,8 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData)
} }
case "delete": case "delete":
var err error var err error
if dbIsValidUserEmail(email) { if db.isValidUserEmail(email) {
if err = dbDeleteUser(email); err != nil { if err = db.deleteUser(email); err != nil {
page.session.setFlashMessage(err.Error(), "error") page.session.setFlashMessage(err.Error(), "error")
} else { } else {
page.session.setFlashMessage("User "+email+" deleted!", "success") page.session.setFlashMessage("User "+email+" deleted!", "success")
@ -96,7 +96,7 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData)
redirect("/admin/users", w, req) redirect("/admin/users", w, req)
default: default:
page.SubTitle = "Edit Admin User" page.SubTitle = "Edit Admin User"
if !dbIsValidUserEmail(email) { if !db.isValidUserEmail(email) {
page.session.setFlashMessage("Couldn't find the requested user, please try again.", "error") page.session.setFlashMessage("Couldn't find the requested user, please try again.", "error")
redirect("/admin/users", w, req) redirect("/admin/users", w, req)
} }
@ -107,7 +107,7 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData)
type usersPageData struct { type usersPageData struct {
Users []string Users []string
} }
page.TemplateData = usersPageData{Users: dbGetAllUsers()} page.TemplateData = usersPageData{Users: db.getAllUsers()}
page.SubTitle = "Admin Users" page.SubTitle = "Admin Users"
page.show("admin-users.html", w) page.show("admin-users.html", w)

View File

@ -2,7 +2,6 @@ package main
import ( import (
"errors" "errors"
"fmt"
"net/http" "net/http"
"time" "time"
@ -56,14 +55,12 @@ func getCondorcetResult() []Ranking {
teamWins[allPairs[i].winner.UUID]++ teamWins[allPairs[i].winner.UUID]++
} }
} }
fmt.Println(teamWins)
// Rank them by wins // Rank them by wins
rankedWins := make(map[int][]string) rankedWins := make(map[int][]string)
for k, v := range teamWins { for k, v := range teamWins {
rankedWins[v] = append(rankedWins[v], k) rankedWins[v] = append(rankedWins[v], k)
} }
fmt.Println(rankedWins)
currRank := 1 currRank := 1
for len(rankedWins) > 0 { for len(rankedWins) > 0 {
topWins := 0 topWins := 0

134
assets.go
View File

@ -191,7 +191,7 @@ var _escData = map[string]*_escFile{
"/assets/css/admin.css": { "/assets/css/admin.css": {
local: "assets/css/admin.css", local: "assets/css/admin.css",
size: 291, size: 291,
modtime: 1501270412, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/2TPwW7DIAwG4DtPYalnqnbTDkmueRECjFkDjAxMlaa8+yCJom29/nz4tw1+XTXFYmOB H4sIAAAJbogA/2TPwW7DIAwG4DtPYalnqnbTDkmueRECjFkDjAxMlaa8+yCJom29/nz4tw1+XTXFYmOB
bwEQFDuM0tv3MsL97ZYek1iF6GyhUijInJS2v+2ed31gDO5aPmpYokK/SV05E4+QCFsVnyPZ5upLln0D bwEQFDuM0tv3MsL97ZYek1iF6GyhUijInJS2v+2ed31gDO5aPmpYokK/SV05E4+QCFsVnyPZ5upLln0D
@ -203,7 +203,7 @@ hdHyphdiY5u+pwdk8mjgMgzDdL5IVgZrHuG1F7ZU6U/HVKNpg3zvuczzPD1v+HLbPyRlDEZ3XPk//atX
"/assets/css/gjvote.css": { "/assets/css/gjvote.css": {
local: "assets/css/gjvote.css", local: "assets/css/gjvote.css",
size: 4373, size: 4373,
modtime: 1501250721, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/6xX22rkOBO+z1MIwsAEfpvunvQk3YHhhzm8wFwOcyFbsi0iW0aWO50sefctndySLfcs H4sIAAAJbogA/6xX22rkOBO+z1MIwsAEfpvunvQk3YHhhzm8wFwOcyFbsi0iW0aWO50sefctndySLfcs
yy47kJZKVV9VfXVwo1qO/rpBqGVd1lBWN+qItpvNh6eb95ubQpBXc1sKLuQR3T48PDytCWMjqehZZYSW yy47kJZKVV9VfXVwo1qO/rpBqGVd1lBWN+qItpvNh6eb95ubQpBXc1sKLuQR3T48PDytCWMjqehZZYSW
@ -233,7 +233,7 @@ JADP0qw5bZuDO7DKg4/AWTubtBtBQ0NonI0TDT8K76+iCImX9nDSHXwI/mFgRSPLUWamaxjLkg7Durbd
"/assets/img/favicon.png": { "/assets/img/favicon.png": {
local: "assets/img/favicon.png", local: "assets/img/favicon.png",
size: 4287, size: 4287,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/wC/EEDviVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QAAAAA H4sIAAAJbogA/wC/EEDviVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QAAAAA
AAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QQDEzgbdWMDlwAAAAxpVFh0Q29tbWVu AAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QQDEzgbdWMDlwAAAAxpVFh0Q29tbWVu
@ -313,7 +313,7 @@ GwM5xLYdRhVY/PPdX2X8mMmnqO5/AdOTL+cjjP0hAAAAAElFTkSuQmCCAQAA//+FC1PpvxAAAA==
"/assets/js/admin.js": { "/assets/js/admin.js": {
local: "assets/js/admin.js", local: "assets/js/admin.js",
size: 0, size: 0,
modtime: 1500039735, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/wEAAP//AAAAAAAAAAA= H4sIAAAJbogA/wEAAP//AAAAAAAAAAA=
`, `,
@ -322,7 +322,7 @@ H4sIAAAJbogA/wEAAP//AAAAAAAAAAA=
"/assets/js/gjvote.js": { "/assets/js/gjvote.js": {
local: "assets/js/gjvote.js", local: "assets/js/gjvote.js",
size: 3561, size: 3561,
modtime: 1500995424, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/6xXX2/jNgx/z6fQcg920NRwBgwDmuUOt9sNG9DeDWjfij4oNpNoVSTPktML2nz3UbL8 H4sIAAAJbogA/6xXX2/jNgx/z6fQcg920NRwBgwDmuUOt9sNG9DeDWjfij4oNpNoVSTPktML2nz3UbL8
N3LSbteH2hZ/5E8iKZJZFSLRTAqi5XrN4WO6ZeIvKoCHE/I8ImRHc7IFUZAFSWVS4KuO/ikg398Ch0TL N3LSbteH2hZ/5E8iKZJZFSLRTAqi5XrN4WO6ZeIvKoCHE/I8ImRHc7IFUZAFSWVS4KuO/ikg398Ch0TL
@ -348,7 +348,7 @@ bQEn8BBF8LXoRH5KZnEcezLEg/3vGzYqxl9mzj52YT22GQDOzmPkqeCzeTMP4cJ7rCKec9W9sh48PDS2
"/assets/vendor/css/grids-responsive-min.css": { "/assets/vendor/css/grids-responsive-min.css": {
local: "assets/vendor/css/grids-responsive-min.css", local: "assets/vendor/css/grids-responsive-min.css",
size: 8032, size: 8032,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/8SVQY/bRgyF7/4VzqFAG1SyTWlGsvdSNM2hQFAUyKlHxxbWQm3JsLVJtov977UhjVd8 H4sIAAAJbogA/8SVQY/bRgyF7/4VzqFAG1SyTWlGsvdSNM2hQFAUyKlHxxbWQm3JsLVJtov977UhjVd8
JGeR0/r2KHokksP3zd6/m/z9cKqmX+epT2nyoT0+nur7XTel+SKb/rPete27yad6UzXnajt9aLbVadrt JGeR0/r2KHokksP3zd6/m/z9cKqmX+epT2nyoT0+nur7XTel+SKb/rPete27yad6UzXnajt9aLbVadrt
@ -378,7 +378,7 @@ m2lyu18rhduak8S4HXzF4rbigQa3g/9Y3NbsKMbtfm0Vbms2ZnFbc7gIt4P/WdzW7FDndvBJi9u9zSjc
"/assets/vendor/css/pure-min.css": { "/assets/vendor/css/pure-min.css": {
local: "assets/vendor/css/pure-min.css", local: "assets/vendor/css/pure-min.css",
size: 16449, size: 16449,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/+w7aY/bRrLf/SsYGwY8BqmRqHNIxHg5nPfykBc8IPtl4cwCLbIlcYcXSGoOa/Xft/og H4sIAAAJbogA/+w7aY/bRrLf/SsYGwY8BqmRqHNIxHg5nPfykBc8IPtl4cwCLbIlcYcXSGoOa/Xft/og
WX1Q1GQXi/2wUWyT3dXV1XV1VXXz9uM3b/7/WFHncTpZTfw3PxTlS5XsD43jT2dz58/kUBTfvPkliWhe WX1Q1GQXi/2wUWyT3dXV1XV1VXXz9uM3b/7/WFHncTpZTfw3PxTlS5XsD43jT2dz58/kUBTfvPkliWhe
@ -452,7 +452,7 @@ I6RidK8h9u8BAAD//+G2/iZBQAAA
"/assets/vendor/font-awesome/HELP-US-OUT.txt": { "/assets/vendor/font-awesome/HELP-US-OUT.txt": {
local: "assets/vendor/font-awesome/HELP-US-OUT.txt", local: "assets/vendor/font-awesome/HELP-US-OUT.txt",
size: 323, size: 323,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/zyPwU7zMBCE73mKuf0/Umjv3BCoqDdewbhrOTTxWN5No7w961Jxnd35ZuaMzCrYuWLm H4sIAAAJbogA/zyPwU7zMBCE73mKuf0/Umjv3BCoqDdewbhrOTTxWN5No7w961Jxnd35ZuaMzCrYuWLm
TXBiMbxuolzkgHPql3+uJ67lgsmwqqR1HlFnCSq4EIsgIIUbG4L/xCzxCq6GZcccTNRQG78l2jic2P7w TXBiMbxuolzkgHPql3+uJ67lgsmwqqR1HlFnCSq4EIsgIIUbG4L/xCzxCq6GZcccTNRQG78l2jic2P7w
@ -464,7 +464,7 @@ yVJ08kHdDRVzQkNk3e/la1CTXyC3chiGz/vMTu2rvI0Pc/n5PTjjwy378BMAAP//GVwmskMBAAA=
"/assets/vendor/font-awesome/css/font-awesome.css": { "/assets/vendor/font-awesome/css/font-awesome.css": {
local: "assets/vendor/font-awesome/css/font-awesome.css", local: "assets/vendor/font-awesome/css/font-awesome.css",
size: 37414, size: 37414,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/9R934/kuJHm+/wVeTPwTbdRWV1S/m7DZ8+dYZwBe23AY2Af9oWSqBS7JFFDSVWVvej/ H4sIAAAJbogA/9R934/kuJHm+/wVeTPwTbdRWV1S/m7DZ8+dYZwBe23AY2Af9oWSqBS7JFFDSVWVvej/
fUkpM/lRmRGqBeZlG3ZPp/QxRIrBYETwI/Xpt//ru8VvF4s/67pb/PQqW13Jxfpx9/i0SE6LP2biRR5F fUkpM/lRmRGqBeZlG3ZPp/QxRIrBYETwI/Xpt//ru8VvF4s/67pb/PQqW13Jxfpx9/i0SE6LP2biRR5F
@ -596,7 +596,7 @@ GGshktbGUJ38nb06fip4ETVv7tf5O8OXn42dm53fvnhyvyphbMz7ebE8372e67EoVJbJ2l0bvtGwcKr5
"/assets/vendor/font-awesome/css/font-awesome.min.css": { "/assets/vendor/font-awesome/css/font-awesome.min.css": {
local: "assets/vendor/font-awesome/css/font-awesome.min.css", local: "assets/vendor/font-awesome/css/font-awesome.min.css",
size: 31000, size: 31000,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/8x9S4/kuJX13r8iv2746yojIzvFeGfB454HDBiwMQt7MYvZUBIVYqUkqvTIzKhC/fch H4sIAAAJbogA/8x9S4/kuJX13r8iv2746yojIzvFeGfB454HDBiwMQt7MYvZUBIVYqUkqvTIzKhC/fch
JV6KjDyUe4BezGDQrhQPKYq8vC8eMn7+w//73d0f7u7+rJrh7l9fRa9qcbd7OD483qXXu19y/iIuvMmv JV6KjDyUe4BezGDQrhQPKYq8vC8eMn7+w//73d0f7u7+rJrh7l9fRa9qcbd7OD483qXXu19y/iIuvMmv
@ -722,7 +722,7 @@ fwIAAP//3Dj1Ahh5AAA=
"/assets/vendor/font-awesome/fonts/FontAwesome.otf": { "/assets/vendor/font-awesome/fonts/FontAwesome.otf": {
local: "assets/vendor/font-awesome/fonts/FontAwesome.otf", local: "assets/vendor/font-awesome/fonts/FontAwesome.otf",
size: 134808, size: 134808,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5y7d1xT1xs4fA4kN+HeDEZCJeEmaqu2dQNBcbRuW63WWmrdyAgEhQSTMGVvCCIjyBZU H4sIAAAJbogA/5y7d1xT1xs4fA4kN+HeDEZCJeEmaqu2dQNBcbRuW63WWmrdyAgEhQSTMGVvCCIjyBZU
tE6c1dpqS7fVbmm/rd21/Xba9VVP4AT6PjfRjvf3x/t+fl5PzrjPOc85z37uvTwaHf0oYlEh8kUj5y9a tE6c1dpqS7fVbmm/rd21/Xba9VVP4AT6PjfRjvf3x/t+fl5PzrjPOc85z37uvTwaHf0oYlEh8kUj5y9a
@ -2570,7 +2570,7 @@ oJ2u1bkNoTENr2D7KYPKTdVa+vdzTkMI+XuTqY14mp/Xf/Tv/wcAAP//MaGCwpgOAgA=
"/assets/vendor/font-awesome/fonts/fontawesome-webfont.eot": { "/assets/vendor/font-awesome/fonts/fontawesome-webfont.eot": {
local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.eot", local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.eot",
size: 165742, size: 165742,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/8y9CXyUxfk4PjPvtff17pFsNslu9spFCNkrhJAs95GAKCgKiAFFEERRQJR6rEJVkCoC H4sIAAAJbogA/8y9CXyUxfk4PjPvtff17pFsNslu9spFCNkrhJAs95GAKCgKiAFFEERRQJR6rEJVkCoC
IlqPoJVKay316NcLu7XVXkptpdZa229sa7X1aEBrFbJv/s/M++5mswRov9/f7/f5Q+Z9553zmWeemXme IlqPoJVKay316NcLu7XVXkptpdZa229sa7X1aEBrFbJv/s/M++5mswRov9/f7/f5Q+Z9553zmWeemXme
@ -4216,7 +4216,7 @@ P//xbwAAAP//T3Adum6HAgA=
"/assets/vendor/font-awesome/fonts/fontawesome-webfont.svg": { "/assets/vendor/font-awesome/fonts/fontawesome-webfont.svg": {
local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.svg", local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.svg",
size: 444379, size: 444379,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/+z963PcWJIliH+PvwK//Jntlx0ocR94dXXNWE+/bM02t8dsZ3ZtP6VREjPJKUgUKIhZ H4sIAAAJbogA/+z963PcWJIliH+PvwK//Jntlx0ocR94dXXNWE+/bM02t8dsZ3ZtP6VREjPJKUgUKIhZ
pb9+/ZzjFxFBRARJpVKV1dtVKTIYAC7u069f9+PH//6//PndVD1c33+8vXv/x+/Cq+a76uNy9f7t1XT3 pb9+/ZzjFxFBRARJpVKV1dtVKTIYAC7u069f9+PH//6//PndVD1c33+8vXv/x+/Cq+a76uNy9f7t1XT3
@ -6490,7 +6490,7 @@ v9vYP//T//6//6/9Xy7//E+/f/3f//4v//xP//78f/7L5f8NAAD//67xO1/bxwYA
"/assets/vendor/font-awesome/fonts/fontawesome-webfont.ttf": { "/assets/vendor/font-awesome/fonts/fontawesome-webfont.ttf": {
local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.ttf", local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.ttf",
size: 165548, size: 165548,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/8y9CXxU1fU4fu992+zbmyWZTJKZzJaNEDJbCCEZ9iUBUVAUEAOKIIiigChVGYWqIHUB H4sIAAAJbogA/8y9CXxU1fU4fu992+zbmyWZTJKZzJaNEDJbCCEZ9iUBUVAUEAOKIIiigChVGYWqIHUB
RLQuQSuV1lrq0q8bdmqr3ZTaSq21tt/Y1mrr0oDWKmRe/ufe92YyGQK03+/v9/v8Ife9d/dzzz333nPO RLQuQSuV1lrq0q8bdmqr3ZTaSq21tt/Y1mrr0oDWKmRe/ufe92YyGQK03+/v9/v8Ife9d/dzzz333nPO
@ -8134,7 +8134,7 @@ t3e3d7d3t3e3d7d3t3fYO+wd9g57h73D3mHvYFKqpcPSYemwdFg6LB3jfLR09+kT/13/8eyZf9V6Po26
"/assets/vendor/font-awesome/fonts/fontawesome-webfont.woff": { "/assets/vendor/font-awesome/fonts/fontawesome-webfont.woff": {
local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.woff", local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.woff",
size: 98024, size: 98024,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/2y3Y3AvzxfuG9t2dmzs2LadHdu2bdu2bdvJN7Zt2/f3r3vOuzNVz3T3Z56Z7lnVNWuW H4sIAAAJbogA/2y3Y3AvzxfuG9t2dmzs2LadHdu2bdu2bdvJN7Zt2/f3r3vOuzNVz3T3Z56Z7lnVNWuW
m7yYGBAwEBAQsO8ZEPx/LUhoNRAYECTQ/+MQE1OR/c/I+F8X73+y6hXvEBcRFfuPyfw3JvxPf0DugMDk m7yYGBAwEBAQsO8ZEPx/LUhoNRAYECTQ/+MQE1OR/c/I+F8X73+y6hXvEBcRFfuPyfw3JvxPf0DugMDk
@ -9775,7 +9775,7 @@ FiYWDAzs/xcAAP//GDN74Oh+AQA=
"/assets/vendor/font-awesome/fonts/fontawesome-webfont.woff2": { "/assets/vendor/font-awesome/fonts/fontawesome-webfont.woff2": {
local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.woff2", local: "assets/vendor/font-awesome/fonts/fontawesome-webfont.woff2",
size: 77160, size: 77160,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/wAiQN2/d09GMgABAAAAAS1oAA0AAAAChpgAAS0OAAQBywAAAAAAAAAAAAAAAAAAAAAA H4sIAAAJbogA/wAiQN2/d09GMgABAAAAAS1oAA0AAAAChpgAAS0OAAQBywAAAAAAAAAAAAAAAAAAAAAA
AAAAP0ZGVE0cGiAGYACFchEIComZKIe2WAE2AiQDlXALlhAABCAFiQYHtHVbUglyR2H3kYQqug2BJ+09 AAAAP0ZGVE0cGiAGYACFchEIComZKIe2WAE2AiQDlXALlhAABCAFiQYHtHVbUglyR2H3kYQqug2BJ+09
@ -11070,7 +11070,7 @@ k9HLPfid1jIi//FfI/7/veJjnx5xAvDNUt22m/rzx3w2xw0l/ovVf9HS/C+Nth0dvj7vJck5gSE83UoW
"/assets/vendor/font-awesome/less/animated.less": { "/assets/vendor/font-awesome/less/animated.less": {
local: "assets/vendor/font-awesome/less/animated.less", local: "assets/vendor/font-awesome/less/animated.less",
size: 713, size: 713,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/9xQzUrEMBC+5ynmIqyHuF1F0HqpRx8jthMZ2k5CJqJS8u7GQLVCtT3vQAgk3+8cj/DI H4sIAAAJbogA/9xQzUrEMBC+5ynmIqyHuF1F0HqpRx8jthMZ2k5CJqJS8u7GQLVCtT3vQAgk3+8cj/DI
NJqIHTy1jkXlF/3nKHXVTNboVkT7gJbekxZPDJMC0G/43FPUpgiS4xoytHxfCxBbYooIAzGa8JAJ8+wk NJqIHTy1jkXlF/3nKHXVTNboVkT7gJbekxZPDJMC0G/43FPUpgiS4xoytHxfCxBbYooIAzGa8JAJ8+wk
@ -11082,7 +11082,7 @@ yudU7ZC9ub3fI7yApdLnPHp8BgAA//912/xDyQIAAA==
"/assets/vendor/font-awesome/less/bordered-pulled.less": { "/assets/vendor/font-awesome/less/bordered-pulled.less": {
local: "assets/vendor/font-awesome/less/bordered-pulled.less", local: "assets/vendor/font-awesome/less/bordered-pulled.less",
size: 585, size: 585,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/4xRQWrDMBC86xVzyiEgKWlTKM4llD6gX1AtyRUolpFsKBj/vSu7xYVWxj4s7O7M7Iwl H4sIAAAJbogA/4xRQWrDMBC86xVzyiEgKWlTKM4llD6gX1AtyRUolpFsKBj/vSu7xYVWxj4s7O7M7Iwl
JV5C1CYajQPeBu+NZjTkpY8xcRut4nVKvIvGus+Jv88KGBnQKa1d21QQD+ZO5SnXM9UrLRdchRS80xCn JV5C1CYajQPeBu+NZjTkpY8xcRut4nVKvIvGus+Jv88KGBnQKa1d21QQD+ZO5SnXM9UrLRdchRS80xCn
@ -11095,7 +11095,7 @@ AAA=
"/assets/vendor/font-awesome/less/core.less": { "/assets/vendor/font-awesome/less/core.less": {
local: "assets/vendor/font-awesome/less/core.less", local: "assets/vendor/font-awesome/less/core.less",
size: 452, size: 452,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3SQTU7DQAyF93MKSyzYMBQkNrSb8iPuMUmcxOrErsamv+rdcQYQEohsrNjvfX6exQKe H4sIAAAJbogA/3SQTU7DQAyF93MKSyzYMBQkNrSb8iPuMUmcxOrErsamv+rdcQYQEohsrNjvfX6exQKe
kyK85KQKr9gTk5Fw8EH87wvhdn3uU2xV47a453CBcwDoSLc5HZdAnIkxNlnazcoHvbAtgaVMKf8qa+fM kyK85KQKr9gTk5Fw8EH87wvhdn3uU2xV47a453CBcwDoSLc5HZdAnIkxNlnazcoHvbAtgaVMKf8qa+fM
@ -11108,7 +11108,7 @@ cErcY7Mh+zp8Eqkap7FRyuT3dvOzxUlOUfTwRzeUdNQ2ZVyFcAkfAQAA//8hEyifxAEAAA==
"/assets/vendor/font-awesome/less/fixed-width.less": { "/assets/vendor/font-awesome/less/fixed-width.less": {
local: "assets/vendor/font-awesome/less/fixed-width.less", local: "assets/vendor/font-awesome/less/fixed-width.less",
size: 119, size: 119,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/9LXV3DLrEhNUQjPTCnJUPBMzs8r5gIK6uICXHoO1WmJusnFxboFRalpmRW1umnlCtVc H4sIAAAJbogA/9LXV3DLrEhNUQjPTCnJUPBMzs8r5gIK6uICXHoO1WmJusnFxboFRalpmRW1umnlCtVc
CgrlIBOsFDQMLVJzFfQVDE00rYGiJakVJbqJOZnpeVYKyal5JalF1ly1XIAAAAD//wJXRMV3AAAA CgrlIBOsFDQMLVJzFfQVDE00rYGiJakVJbqJOZnpeVYKyal5JalF1ly1XIAAAAD//wJXRMV3AAAA
@ -11118,7 +11118,7 @@ CgrlIBOsFDQMLVJzFfQVDE00rYGiJakVJbqJOZnpeVYKyal5JalF1ly1XIAAAAD//wJXRMV3AAAA
"/assets/vendor/font-awesome/less/font-awesome.less": { "/assets/vendor/font-awesome/less/font-awesome.less": {
local: "assets/vendor/font-awesome/less/font-awesome.less", local: "assets/vendor/font-awesome/less/font-awesome.less",
size: 495, size: 495,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3TPzUrEMBAH8HufYtyTLrZ1QRDqpSIsLFQ81BeYNtPdwTQJk7gfb2/WD1ZIPOTy+8/8 H4sIAAAJbogA/3TPzUrEMBAH8HufYtyTLrZ1QRDqpSIsLFQ81BeYNtPdwTQJk7gfb2/WD1ZIPOTy+8/8
w9TLqwKWAGtrAjwdyNuZ4L56qO5gOEGrcE9bNOoEJexCcE1dT3ESvwcrttHbP/LV1fFIxtN/K7X+ya/P w9TLqwKWAGtrAjwdyNuZ4L56qO5gOEGrcE9bNOoEJexCcE1dT3ESvwcrttHbP/LV1fFIxtN/K7X+ya/P
@ -11131,7 +11131,7 @@ Ex9JlQdWmR7NPiQ4WFEkccd9aE0qydHwjCETiA1nLyfNzmVyH3B8zziPNnO4H4XIlEKoLld9BgAA//+H
"/assets/vendor/font-awesome/less/icons.less": { "/assets/vendor/font-awesome/less/icons.less": {
local: "assets/vendor/font-awesome/less/icons.less", local: "assets/vendor/font-awesome/less/icons.less",
size: 49712, size: 49712,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5SdzbbsqJG253UVOfw+r852ud01cU9ckx574gsgJTLFSUnoICn33sfL99786Afl0fsG H4sIAAAJbogA/5SdzbbsqJG253UVOfw+r852ud01cU9ckx574gsgJTLFSUnoICn33sfL99786Afl0fsG
Nal1dvEQUiIIIoIA/vyny//afrr8/qFH2+nLPOrxMjX68s/eVLbWl38481KT/3vUl9+dVpf/949//v7/ Nal1dvEQUiIIIoIA/vyny//afrr8/qFH2+nLPOrxMjX68s/eVLbWl38481KT/3vUl9+dVpf/949//v7/
@ -11249,7 +11249,7 @@ HjBHN/PtFIrFFuQy7BBoFa0nmvKagFD5/wIAAP//3HvahDDCAAA=
"/assets/vendor/font-awesome/less/larger.less": { "/assets/vendor/font-awesome/less/larger.less": {
local: "assets/vendor/font-awesome/less/larger.less", local: "assets/vendor/font-awesome/less/larger.less",
size: 370, size: 370,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3SOwWrEIBCG7z7Ff1loF6bS1Vw2l1577hNImJihxhSVZWnYd6+1lxISwYN+3/z/aI33 H4sIAAAJbogA/3SOwWrEIBCG7z7Ff1loF6bS1Vw2l1577hNImJihxhSVZWnYd6+1lxISwYN+3/z/aI33
YYn4kG/Oqr7o6Cilz5jdJ2eUiTEuscCYE4JLnhMSB1fkxihL4/KbWm9xEis+a/Xyto6OhpzpK/Eo9wcF YYn4kG/Oqr7o6Cilz5jdJ2eUiTEuscCYE4JLnhMSB1fkxihL4/KbWm9xEis+a/Xyto6OhpzpK/Eo9wcF
@ -11261,7 +11261,7 @@ j1WhRVGuC1zxZHmGhnnu63+okzSx+KlUYhqxjdw4FRlcIBfExyvotTv16rFTcblj/V9w4bnHnmg2ojkS
"/assets/vendor/font-awesome/less/list.less": { "/assets/vendor/font-awesome/less/list.less": {
local: "assets/vendor/font-awesome/less/list.less", local: "assets/vendor/font-awesome/less/list.less",
size: 377, size: 377,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3SPwU7DMBBE7/6KOaEgtARQT6mEekXiJ0yyCSu5thVvoVWUf8c2FAkoPtkz3pm3bYtn H4sIAAAJbogA/3SPwU7DMBBE7/6KOaEgtARQT6mEekXiJ0yyCSu5thVvoVWUf8c2FAkoPtkz3pm3bYtn
SYqnPvhk8ov+O8bc7pbRUp8SxZlHOa50cFgMEO0wiJ/I8agd7rZZ2tt5Ev+l7PKYE3qXQV+L6XIjJT05 SYqnPvhk8ov+O8bc7pbRUp8SxZlHOa50cFgMEO0wiJ/I8agd7rZZ2tt5Ev+l7PKYE3qXQV+L6XIjJT05
@ -11273,7 +11273,7 @@ Jj1F7uCD52I8ZgsLYkiiEnyHmZ1VeeMtVrNeACjfC8D3gH1JwR20pn2W0+/2evkLpSF2aB54jxb3m+sq
"/assets/vendor/font-awesome/less/mixins.less": { "/assets/vendor/font-awesome/less/mixins.less": {
local: "assets/vendor/font-awesome/less/mixins.less", local: "assets/vendor/font-awesome/less/mixins.less",
size: 1603, size: 1603,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/7RU32/bNhB+919xyIDVAUJL7o+HyijgNOvaAcsKLB62V1o6SddQPIGkozhF/vcdKWvO H4sIAAAJbogA/7RU32/bNhB+919xyIDVAUJL7o+HyijgNOvaAcsKLB62V1o6SddQPIGkozhF/vcdKWvO
sibYS+UHk8e7j3ffd8csg0u6JetnslJPfrPZotaKSrbzU/g6A6jI90bvCyBryKLaGi6vV3JQsw0FWHad sibYS+UHk8e7j3ffd8csg0u6JetnslJPfrPZotaKSrbzU/g6A6jI90bvCyBryKLaGi6vV3JQsw0FWHad
@ -11294,7 +11294,7 @@ Op0m+0NdfGzjcmyxgy7pDUuGSZqjZdIjH7dHOW7I09bgaB4VmaLuhf+/AwAA//+AodbcQwYAAA==
"/assets/vendor/font-awesome/less/path.less": { "/assets/vendor/font-awesome/less/path.less": {
local: "assets/vendor/font-awesome/less/path.less", local: "assets/vendor/font-awesome/less/path.less",
size: 771, size: 771,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5ySwW6DMAyG7zyFpUpLW40i7Qia1l6qnbYd+gJpcSBSSFBiYKji3RfouqJV6zY4Yfv3 H4sIAAAJbogA/5ySwW6DMAyG7zyFpUpLW40i7Qia1l6qnbYd+gJpcSBSSFBiYKji3RfouqJV6zY4Yfv3
91uxoyVsX1928LbZPQewhPDHD5ZREKyF0RQKfkA4BgCfUSFVGwPb+mjToDMFssRXnT3EUFk1Z+uj4OEg 91uxoyVsX1928LbZPQewhPDHD5ZREKyF0RQKfkA4BgCfUSFVGwPb+mjToDMFssRXnT3EUFk1Z+uj4OEg
@ -11307,7 +11307,7 @@ HDWkWKMypdTZcI/ufJcNyiynGHQPUMk57ahVeMl2wUcAAAD//5aYxFMDAwAA
"/assets/vendor/font-awesome/less/rotated-flipped.less": { "/assets/vendor/font-awesome/less/rotated-flipped.less": {
local: "assets/vendor/font-awesome/less/rotated-flipped.less", local: "assets/vendor/font-awesome/less/rotated-flipped.less",
size: 622, size: 622,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/4SQwU6EMBCG732K/2TWhNmF9eCye/Gica++AWFbbSQdUhpjJLy7QzXGKKWcyMz//Qzf H4sIAAAJbogA/4SQwU6EMBCG732K/2TWhNmF9eCye/Gica++AWFbbSQdUhpjJLy7QzXGKKWcyMz//Qzf
bocnDk3QF1zhobN9L2/nlt2gZEWpR6nt3WgaaoeBeq+NfZ/Ixx6qS2DEVpZWar6nm7q86OcC1fUJmNJw bocnDk3QF1zhobN9L2/nlt2gZEWpR6nt3WgaaoeBeq+NfZ/Ixx6qS2DEVpZWar6nm7q86OcC1fUJmNJw
@ -11319,7 +11319,7 @@ mV9h2ON8f6A6o/nomQNWZBe5iMjLZsRROvPHTyb4Y2JUgLFd0P4Ix06f1KQ+AwAA//8sXplCbgIAAA==
"/assets/vendor/font-awesome/less/screen-reader.less": { "/assets/vendor/font-awesome/less/screen-reader.less": {
local: "assets/vendor/font-awesome/less/screen-reader.less", local: "assets/vendor/font-awesome/less/screen-reader.less",
size: 118, size: 118,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/9LXVwhOLkpNzVMISk1MSS0q5gKK6OICXFx6xUW6+Xk5lQrVCjCmhqa1Qi1cQjctP7m0 H4sIAAAJbogA/9LXVwhOLkpNzVMISk1MSS0q5gKK6OICXFx6xUW6+Xk5lQrVCjCmhqa1Qi1cQjctP7m0
ODEpJxVJCUIQohgQAAD//wZe7992AAAA ODEpJxVJCUIQohgQAAD//wZe7992AAAA
@ -11329,7 +11329,7 @@ ODEpJxVJCUIQohgQAAD//wZe7992AAAA
"/assets/vendor/font-awesome/less/stacked.less": { "/assets/vendor/font-awesome/less/stacked.less": {
local: "assets/vendor/font-awesome/less/stacked.less", local: "assets/vendor/font-awesome/less/stacked.less",
size: 476, size: 476,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3yQzVLDMAyE73kKXbhhmvaYXnrlzBO4ttJoqtoZS5RApu+OXH4GOgTftNqd/eTVCp7U H4sIAAAJbogA/3yQzVLDMAyE73kKXbhhmvaYXnrlzBO4ttJoqtoZS5RApu+OXH4GOgTftNqd/eTVCp7U
hyNGeAw5SWOCW3pN87Cbe++CiBsL9jRdnNQ0zA3AmIWUcuqgIHulM25NjSQj+9cOKDEldHvO4VgXLxR1 hyNGeAw5SWOCW3pN87Cbe++CiBsL9jRdnNQ0zA3AmIWUcuqgIHulM25NjSQj+9cOKDEldHvO4VgXLxR1
@ -11341,7 +11341,7 @@ vwACJsXyPwDMv2+hNGAh3cJyqJJBn5M6oTf8OP9POyX7EkFzh8y5dLAzw6dYE+8BAAD//+m7zRzcAQAA
"/assets/vendor/font-awesome/less/variables.less": { "/assets/vendor/font-awesome/less/variables.less": {
local: "assets/vendor/font-awesome/less/variables.less", local: "assets/vendor/font-awesome/less/variables.less",
size: 22563, size: 22563,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/4R8ubqkupK1f56ivnud/zeoAnLu4/TX3XabbbUjhMhUbUCUgMy96+lbIiOEFkPdbZxB H4sIAAAJbogA/4R8ubqkupK1f56ivnud/zeoAnLu4/TX3XabbbUjhMhUbUCUgMy96+lbIiOEFkPdbZxB
K4Tm0IpB+ePHt/8RVouiVv1f7n+S3b+//vr3SiSVaYekE8Pj377R3z++f//hS/t//D1L9Pq3SgrRKy+W K4Tm0IpB+ePHt/8RVouiVv1f7n+S3b+//vr3SiSVaYekE8Pj377R3z++f//hS/t//D1L9Pq3SgrRKy+W
@ -11445,7 +11445,7 @@ lxmH8DsD58samQPIGeRrMAyT5ffvX/8XAAD//wGLrUUjWAAA
"/assets/vendor/font-awesome/scss/_animated.scss": { "/assets/vendor/font-awesome/scss/_animated.scss": {
local: "assets/vendor/font-awesome/scss/_animated.scss", local: "assets/vendor/font-awesome/scss/_animated.scss",
size: 715, size: 715,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/9yPzUrAMAzH732KgArzULcpgs6LV88+QZ3pCNvS0lRURt/dOphOmG5nA6XQ/j9+KUt4 H4sIAAAJbogA/9yPzUrAMAzH732KgArzULcpgs6LV88+QZ3pCNvS0lRURt/dOphOmG5nA6XQ/j9+KUt4
9MRM3MFD61hUftG/jlIXJ9OpNboV0T6gpbekJQfApAD0Kz71FLVhGk0kxw1k6fx9KUBsiSkiDMRowl02 9MRM3MFD61hUftG/jlIXJ9OpNboV0T6gpbekJQfApAD0Kz71FLVhGk0kxw1k6fx9KUBsiSkiDMRowl02
@ -11457,7 +11457,7 @@ LHPQkDbr/csguNdfr+Ikopfi5nyHYNuSGe6Xnh7fbTAjypfnk6I6m69vnBgMi3VhbCC4aCIW1TN2P+oB
"/assets/vendor/font-awesome/scss/_bordered-pulled.scss": { "/assets/vendor/font-awesome/scss/_bordered-pulled.scss": {
local: "assets/vendor/font-awesome/scss/_bordered-pulled.scss", local: "assets/vendor/font-awesome/scss/_bordered-pulled.scss",
size: 592, size: 592,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/4xR0WqEMBB8z1cMtNzDQZK79grFeyv9gP5CahIbyBlJFAriv3ejLfZBPX1Y2N2Z2Rkj H4sIAAAJbogA/4xR0WqEMBB8z1cMtNzDQZK79grFeyv9gP5CahIbyBlJFAriv3ejLfZBPX1Y2N2Z2Rkj
Jd5C1CYajQM+Ou+NZjTkax9j4qF/tIqXKfEmGuu+B/45SqBnQKO0dnVVQDyZG5WXXM9Ur7SccAVS8E5D Jd5C1CYajQM+Ou+NZjTkax9j4qF/tIqXKfEmGuu+B/45SqBnQKO0dnVVQDyZG5WXXM9Ur7SccAVS8E5D
@ -11470,7 +11470,7 @@ AgAA
"/assets/vendor/font-awesome/scss/_core.scss": { "/assets/vendor/font-awesome/scss/_core.scss": {
local: "assets/vendor/font-awesome/scss/_core.scss", local: "assets/vendor/font-awesome/scss/_core.scss",
size: 459, size: 459,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3SQzWrjQAzH734KQRb2srPZhV6anPpB30O2ZVtkLIWRmk/y7pXtlkJLfRk8+v1/kma9 H4sIAAAJbogA/3SQzWrjQAzH734KQRb2srPZhV6anPpB30O2ZVtkLIWRmk/y7pXtlkJLfRk8+v1/kma9
hkc0gqeMZvBMHQs7q1RRSD99VfV3df3VYWrM0r5E6HSDawXQsu0znjfAklko1Vmb3TYKnYpvQLSMmL8c hkc0gqeMZvBMHQs7q1RRSD99VfV3df3VYWrM0r5E6HSDawXQsu0znjfAklko1Vmb3TYKnYpvQLSMmL8c
@ -11483,7 +11483,7 @@ i2kCkvElMjHObb3czpKBuB98uYeX4B6OZDrSFmJGG7Q4CUs/94CWmowF5xWWtrN1mmigwj6HGpTfDgMe
"/assets/vendor/font-awesome/scss/_fixed-width.scss": { "/assets/vendor/font-awesome/scss/_fixed-width.scss": {
local: "assets/vendor/font-awesome/scss/_fixed-width.scss", local: "assets/vendor/font-awesome/scss/_fixed-width.scss",
size: 120, size: 120,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/9LXV3DLrEhNUQjPTCnJUPBMzs8r5gIK6uICXHrK1SppibrJxcW6BUWpaZkVtbpp5QrV H4sIAAAJbogA/9LXV3DLrEhNUQjPTCnJUPBMzs8r5gIK6uICXHrK1SppibrJxcW6BUWpaZkVtbpp5QrV
XAoK5SAjrBQ0DC1ScxX0FQxNNK2BoiWpFSW6iTmZ6XlWCsmpeSWpRdZctVyAAAAA///0UODoeAAAAA== XAoK5SAjrBQ0DC1ScxX0FQxNNK2BoiWpFSW6iTmZ6XlWCsmpeSWpRdZctVyAAAAA///0UODoeAAAAA==
@ -11493,7 +11493,7 @@ XAoK5SAjrBQ0DC1ScxX0FQxNNK2BoiWpFSW6iTmZ6XlWCsmpeSWpRdZctVyAAAAA///0UODoeAAAAA==
"/assets/vendor/font-awesome/scss/_icons.scss": { "/assets/vendor/font-awesome/scss/_icons.scss": {
local: "assets/vendor/font-awesome/scss/_icons.scss", local: "assets/vendor/font-awesome/scss/_icons.scss",
size: 50498, size: 50498,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5Sdy7LkKJL39/UUYfZ9i5m2ienq6alNz6o2s+5NPwAhESHySEKJ0LlkW7/7cNEFRcb/ H4sIAAAJbogA/5Sdy7LkKJL39/UUYfZ9i5m2ienq6alNz6o2s+5NPwAhESHySEKJ0LlkW7/7cNEFRcb/
79SmLE/xw6VA4Lg7Dvz5T5f/taO//P6hZzvoyzLr+eI7ffnHaBrb6svfnXlXPvw968vvTqvLv/39H7// 79SmLE/xw6VA4Lg7Dvz5T5f/taO//P6hZzvoyzLr+eI7ffnHaBrb6svfnXlXPvw968vvTqvLv/39H7//
@ -11611,7 +11611,7 @@ fHSj4kHBCG9FMsUBoZbR2tPU3AzE2v8XAAD//0NCmAVCxQAA
"/assets/vendor/font-awesome/scss/_larger.scss": { "/assets/vendor/font-awesome/scss/_larger.scss": {
local: "assets/vendor/font-awesome/scss/_larger.scss", local: "assets/vendor/font-awesome/scss/_larger.scss",
size: 375, size: 375,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3SOQWrDMBBF9zrFhzbQBqaikbyJT9B1TyDMWB4qy0USIdTk7lXVTTCxQAvpvfl/tMbH H4sIAAAJbogA/3SOQWrDMBBF9zrFhzbQBqaikbyJT9B1TyDMWB4qy0USIdTk7lXVTTCxQAvpvfl/tMbH
sER8yg9nVV+0d5TSR8zuizPKxBiXWGDMAcElzwmJgytyYZSlcflLrbc4iRUftXp7Wp9HR0PO9J14lOuN sER8yg9nVV+0d5TSR8zuizPKxBiXWGDMAcElzwmJgytyYZSlcflLrbc4iRUftXp7Wp9HR0PO9J14lOuN
@ -11623,7 +11623,7 @@ Ne3GtLtmtzG7f/M3AAD//+ckNq13AQAA
"/assets/vendor/font-awesome/scss/_list.scss": { "/assets/vendor/font-awesome/scss/_list.scss": {
local: "assets/vendor/font-awesome/scss/_list.scss", local: "assets/vendor/font-awesome/scss/_list.scss",
size: 378, size: 378,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3SPUUvEMBCE3/MrBhQ5kbUq99QD3wX/RGy3dSGXhGZPr5T+d5MoIlrzlMxkZ75tGjxL H4sIAAAJbogA/3SPUUvEMBCE3/MrBhQ5kbUq99QD3wX/RGy3dSGXhGZPr5T+d5MoIlrzlMxkZ75tGjxL
Ujx1wSeTX/TfMeb2YrkcLHUpUZx4kPNKJ4fFANH2vfiRHA/a4u6QpaOdRvFfShlzQu/S62sxXa6kpLNj Ujx1wSeTX/TfMeb2YrkcLHUpUZx4kPNKJ4fFANH2vfiRHA/a4u6QpaOdRvFfShlzQu/S62sxXa6kpLNj
@ -11635,7 +11635,7 @@ z0rWyZiDO/bKU1GvtjjGyrHZiRvs9j9jyy4fAQAA///94pSpegEAAA==
"/assets/vendor/font-awesome/scss/_mixins.scss": { "/assets/vendor/font-awesome/scss/_mixins.scss": {
local: "assets/vendor/font-awesome/scss/_mixins.scss", local: "assets/vendor/font-awesome/scss/_mixins.scss",
size: 1637, size: 1637,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/7RUTW/bRhC961cMHKORAa9I5eMQCgHquGlSoG6AWkV7XZFDcuLlDrG7Mi0H+u+dXYpx H4sIAAAJbogA/7RUTW/bRhC961cMHKORAa9I5eMQCgHquGlSoG6AWkV7XZFDcuLlDrG7Mi0H+u+dXYpx
qsRBL6EO4g7ffL03s1kGV3RH1s/kTT36zGY/dxEGtVZUsp2fwacZQEW+N3pXAFlDFtXGcHmzkg8121CA qsRBL6EO4g7ffL03s1kGV3RH1s/kTT36zGY/dxEGtVZUsp2fwacZQEW+N3pXAFlDFtXGcHmzkg8121CA
@ -11657,7 +11657,7 @@ AA==
"/assets/vendor/font-awesome/scss/_path.scss": { "/assets/vendor/font-awesome/scss/_path.scss": {
local: "assets/vendor/font-awesome/scss/_path.scss", local: "assets/vendor/font-awesome/scss/_path.scss",
size: 783, size: 783,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5ySwW6DMAyG7zyFpU5LW40i7Qiatl6qnbYd+gJpcSBSSFBiYKji3RfouiJ161Ryip3f H4sIAAAJbogA/5ySwW6DMAyG7zyFpU5LW40i7Qiatl6qnbYd+gJpcSBSSFBiYKji3RfouiJ161Ryip3f
32/FjpaweX/bwsd6+xrAEsI/DyyjIHgRRlMo+B7hEAB8R4VUbQxs46N1g84UyBL/6uw+hsqqOZsd7gQP 32/FjpaweX/bwsd6+xrAEsI/DyyjIHgRRlMo+B7hEAB8R4VUbQxs46N1g84UyBL/6uw+hsqqOZsd7gQP
@ -11670,7 +11670,7 @@ oclRQ4o1KlNKnQ276U472qDMcopB9wCVnNKOWoXnbBd8BQAA///XLL2lDwMAAA==
"/assets/vendor/font-awesome/scss/_rotated-flipped.scss": { "/assets/vendor/font-awesome/scss/_rotated-flipped.scss": {
local: "assets/vendor/font-awesome/scss/_rotated-flipped.scss", local: "assets/vendor/font-awesome/scss/_rotated-flipped.scss",
size: 672, size: 672,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/4TRwUrEMBAG4Hue4gdFVui47Xpwu3vxorhX36C0iQZLpqRRxNJ3dxrFg3STngrz85H5 H4sIAAAJbogA/4TRwUrEMBAG4Hue4gdFVui47Xpwu3vxorhX36C0iQZLpqRRxNJ3dxrFg3STngrz85H5
Z7vFM4cm6A5XeOztMMjfqWU3KhnRuU+pm4vp0jTUjiMNXhv7OZOPENUlMOHeurZ/7zQkZMX7nW7qstMv Z7vFM4cm6A5XeOztMMjfqWU3KhnRuU+pm4vp0jTUjiMNXhv7OZOPENUlMOHeurZ/7zQkZMX7nW7qstMv
@ -11683,7 +11683,7 @@ GAv1s9ZygSfmNxj2OD3sqc7c5OCZA1KXKbIZaTYfkuISoX+l5ZJ/tUwKMLYP2h/g2OmjmtV3AAAA//8K
"/assets/vendor/font-awesome/scss/_screen-reader.scss": { "/assets/vendor/font-awesome/scss/_screen-reader.scss": {
local: "assets/vendor/font-awesome/scss/_screen-reader.scss", local: "assets/vendor/font-awesome/scss/_screen-reader.scss",
size: 134, size: 134,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/9LXVwhOLkpNzVMISk1MSS0q5gKK6OICXFx6xUW6+Xk5lQrVCg6Zeck5pSmpClAhDU1r H4sIAAAJbogA/9LXVwhOLkpNzVMISk1MSS0q5gKK6OICXFx6xUW6+Xk5lQrVCg6Zeck5pSmpClAhDU1r
hVq4At20/OTS4sSknFQsShGSEE2AAAAA///6AJxphgAAAA== hVq4At20/OTS4sSknFQsShGSEE2AAAAA///6AJxphgAAAA==
@ -11693,7 +11693,7 @@ hVq4At20/OTS4sSknFQsShGSEE2AAAAA///6AJxphgAAAA==
"/assets/vendor/font-awesome/scss/_stacked.scss": { "/assets/vendor/font-awesome/scss/_stacked.scss": {
local: "assets/vendor/font-awesome/scss/_stacked.scss", local: "assets/vendor/font-awesome/scss/_stacked.scss",
size: 482, size: 482,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/4SQTVLDMAyF9zmFZoAdpmmX6QlYcwLXVhpNVTtjiWLI9O7Y5WcgQ6h3enpv3metVvCk H4sIAAAJbogA/4SQTVLDMAyF9zmFZoAdpmmX6QlYcwLXVhpNVTtjiWLI9O7Y5WcgQ6h3enpv3metVvCk
1h3Qw6OLQZoimKXXNA83021vjRMxY8Ke8tlIjcPUAIxRSCmGDhKyVTrhtqieZGT72gEFpoBmx9Ed6uKF 1h3Qw6OLQZoimKXXNA83021vjRMxY8Ke8tlIjcPUAIxRSCmGDhKyVTrhtqieZGT72gEFpoBmx9Ed6uKF
@ -11706,7 +11706,7 @@ AA==
"/assets/vendor/font-awesome/scss/_variables.scss": { "/assets/vendor/font-awesome/scss/_variables.scss": {
local: "assets/vendor/font-awesome/scss/_variables.scss", local: "assets/vendor/font-awesome/scss/_variables.scss",
size: 22644, size: 22644,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/4R8uaKkupKtf76i3rnPeM+gCsi5r9fddptttSOEyFRtQJSAzL3r61siI4QWQ91tnEEr H4sIAAAJbogA/4R8uaKkupKtf76i3rnPeM+gCsi5r9fddptttSOEyFRtQJSAzL3r61siI4QWQ91tnEEr
AI0RKwbljx/f/ltYLYpa9X+5/0l2//766/9WIqlMOySdGB7/9o3+/v7+/Ydv7f/+9n9KVYmxHv45i/b6 AI0RKwbljx/f/ltYLYpa9X+5/0l2//766/9WIqlMOySdGB7/9o3+/v7+/Ydv7f/+9n9KVYmxHv45i/b6
@ -11810,7 +11810,7 @@ gOjaZygKhLqYz+mH+6LjAtfDvvwPBxTO3+HfTsqhECGG8RJLvC5f4mE4kxkP4ktuvVL+4UWqJiIFtvGL
"/assets/vendor/font-awesome/scss/font-awesome.scss": { "/assets/vendor/font-awesome/scss/font-awesome.scss": {
local: "assets/vendor/font-awesome/scss/font-awesome.scss", local: "assets/vendor/font-awesome/scss/font-awesome.scss",
size: 430, size: 430,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3SPS07DMBCG9znF0BVUJKESElLYBCFVqhTEIlxgYk/aEY5tjU0ftyctIOxFl//3P+yp H4sIAAAJbogA/3SPS07DMBCG9znF0BVUJKESElLYBCFVqhTEIlxgYk/aEY5tjU0ftyctIOxFl//3P+yp
lzcFLAHWzkZ4OVBwE8Fj9VQ9wHCCVuOetmj1CUrYxeibuh7nJP4EK3YzbxNy2epYkQ10rVKbX//2/GgD lzcFLAHWzkZ4OVBwE8Fj9VQ9wHCCVuOetmj1CUrYxeibuh7nJP4EK3YzbxNy2epYkQ10rVKbX//2/GgD
@ -11823,7 +11823,7 @@ AAA=
"/assets/vendor/js/snack-min.js": { "/assets/vendor/js/snack-min.js": {
local: "assets/vendor/js/snack-min.js", local: "assets/vendor/js/snack-min.js",
size: 8142, size: 8142,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5RZe5PbthH/v5+CQj0a4oSjdE4605JmVMePxhk7du1LmqkkZ0AQfNzxSJWkTncj8bt3 H4sIAAAJbogA/5RZe5PbthH/v5+CQj0a4oSjdE4605JmVMePxhk7du1LmqkkZ0AQfNzxSJWkTncj8bt3
FwBfOqWTZiY+CcAuFvv47UPzi8mfLOvCqnIubp2byrIFtT4/8tx6mxWlzIVU20ldbyt3Po/TOtkFjiju FwBfOqWTZiY+CcAuFvv47UPzi8mfLOvCqnIubp2byrIFtT4/8tx6mxWlzIVU20ldbyt3Po/TOtkFjiju
@ -11889,7 +11889,7 @@ VPzfAAAA//9Iiv1pzh8AAA==
"/templates/admin-activateclient.html": { "/templates/admin-activateclient.html": {
local: "templates/admin-activateclient.html", local: "templates/admin-activateclient.html",
size: 1355, size: 1355,
modtime: 1501251393, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/7RUTWvcQAy951cMujsDPRbbEJoWcmkXkj+g9Si7A+OZYT7cLiH/vfLn2ktSCsledqXH H4sIAAAJbogA/7RUTWvcQAy951cMujsDPRbbEJoWcmkXkj+g9Si7A+OZYT7cLiH/vfLn2ktSCsledqXH
s/SeJLtUuhONwRgraMgmClDfCFE+u9DOuM+BigFYogKNPlhSILBJ2tkKJKpWW9kYzVWifHkRt0/UeoOJ s/SeJLtUuhONwRgraMgmClDfCFE+u9DOuM+BigFYogKNPlhSILBJ2tkKJKpWW9kYzVWifHkRt0/UeoOJ
@ -11905,7 +11905,7 @@ meKMt1/fTCL+BgAA//8UF+dLSwUAAA==
"/templates/admin-addgame.html": { "/templates/admin-addgame.html": {
local: "templates/admin-addgame.html", local: "templates/admin-addgame.html",
size: 499, size: 499,
modtime: 1496946580, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/2xRzWrzMBC85ymWvfvTC9iGDwql9NBC8wKKtUkE+kNapQkh796NYqc+VAdbszvemR33 H4sIAAAJbogA/2xRzWrzMBC85ymWvfvTC9iGDwql9NBC8wKKtUkE+kNapQkh796NYqc+VAdbszvemR33
xp5gcrqUAScKTBnHDUC/j9kv9VQzda3wvHXa2UMgg6AntjEMqLTxNqiD9lRUoG9V9IkQPPExmgE/P762 xp5gcrqUAScKTBnHDUC/j9kv9VQzda3wvHXa2UMgg6AntjEMqLTxNqiD9lRUoG9V9IkQPPExmgE/P762
@ -11918,7 +11918,7 @@ LnXnraiv45gJq3uXsvU6X3D8bwy8yg69enTmiNU6Y0Hyy8bNrPsTAAD//ynWXmvzAQAA
"/templates/admin-addteam.html": { "/templates/admin-addteam.html": {
local: "templates/admin-addteam.html", local: "templates/admin-addteam.html",
size: 446, size: 446,
modtime: 1498076184, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/0xQW27DMAz77ykE/Qe+QBJgF9gGrBdwbLU14BdsOVtvPyVNA/8YpEyQlEbrVjBe1zqh H4sIAAAJbogA/0xQW27DMAz77ykE/Qe+QBJgF9gGrBdwbLU14BdsOVtvPyVNA/8YpEyQlEbrVjBe1zqh
ochUcL4AjLdUwnueW6FhH5xo0N7dI1kEbdilOKHSNriomHSoKtKvqnolhED8SHbC76+f6+68eTvythK/ ochUcL4AjLdUwnueW6FhH5xo0N7dI1kEbdilOKHSNriomHSoKtKvqnolhED8SHbC76+f6+68eTvythK/
@ -11931,7 +11931,7 @@ csT+BwAA//+M7Jg8vgEAAA==
"/templates/admin-adduser.html": { "/templates/admin-adduser.html": {
local: "templates/admin-adduser.html", local: "templates/admin-adduser.html",
size: 842, size: 842,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/6yTTW7CMBCF95zCmn3kCySWuui6qD/rysQDWPKf7DGU23dIQghIRaraVfwmz2/mUyat H4sIAAAJbogA/6yTTW7CMBCF95zCmn3kCySWuui6qD/rysQDWPKf7DGU23dIQghIRaraVfwmz2/mUyat
sQfRO11KBz0GwgxqJUS7jdlf6qlmbIbCfGq0s7uABoTuycbQgdTG2yBrwVxkwKMs+oAgPNI+mg7WL2/v sQfRO11KBz0GwgxqJUS7jdlf6qlmbIbCfGq0s7uABoTuycbQgdTG2yBrwVxkwKMs+oAgPNI+mg7WL2/v
@ -11945,7 +11945,7 @@ K5ery4r/BLWa+n4HAAD//wwzWIlKAwAA
"/templates/admin-clients.html": { "/templates/admin-clients.html": {
local: "templates/admin-clients.html", local: "templates/admin-clients.html",
size: 1023, size: 1023,
modtime: 1501252619, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5RTQavbMAy+91eI0OPSwI7DCYyVQdh4vMPeYUclVhqDYwfHL6OU/vfJSdMlbdPxCg2y H4sIAAAJbogA/5RTQavbMAy+91eI0OPSwI7DCYyVQdh4vMPeYUclVhqDYwfHL6OU/vfJSdMlbdPxCg2y
9En6JH8+nUBVYKyH3S9qWo2e9uhx900rMr6D83kjpOqh1Nh1adS1WFLck/OqRB1lLxZQSuWVNagZNCbV 9En6JH8+nUBVYKyH3S9qWo2e9uhx900rMr6D83kjpOqh1Nh1adS1WFLck/OqRB1lLxZQSuWVNagZNCbV
@ -11960,7 +11960,7 @@ H2ey4EHOWfcfFlX1hMawwPnuZructHSLZf+oKDbCqxlf4Bj9GwAA///E71UP/wMAAA==
"/templates/admin-editteam.html": { "/templates/admin-editteam.html": {
local: "templates/admin-editteam.html", local: "templates/admin-editteam.html",
size: 13536, size: 13536,
modtime: 1500999296, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/+xaW2/bOBZ+z6/gagvYxsb2LHamD77tTCedokA7u5gkD4vBPNASbXNDSYZIOfUG/u97 H4sIAAAJbogA/+xaW2/bOBZ+z6/gagvYxsb2LHamD77tTCedokA7u5gkD4vBPNASbXNDSYZIOfUG/u97
eJFESpSspGmLYtqHRubl3Ph9h4eUHh7QizynEZot0eSGxHuGBbnCAk9ub99eodPpYhHRAwoZ5nwZhCQR eJFESpSspGmLYtqHRubl3Ph9h4eUHh7QizynEZot0eSGxHuGBbnCAk9ub99eodPpYhHRAwoZ5nwZhCQR
@ -12011,7 +12011,7 @@ AAA=
"/templates/admin-edituser.html": { "/templates/admin-edituser.html": {
local: "templates/admin-edituser.html", local: "templates/admin-edituser.html",
size: 1654, size: 1654,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/7xUwW7bMAy99ysIXZwASXzPXA/bukMPw4qtPQ3DoEhMLFSWBEluEQT59zGSk9pZ26E7 H4sIAAAJbogA/7xUwW7bMAy99ysIXZwASXzPXA/bukMPw4qtPQ3DoEhMLFSWBEluEQT59zGSk9pZ26E7
7BJQ4tMjH/PMSqoHEJqHcMkEmoie1RcA1dr69njvOo/zdHGK5lyrjUHJgIuorLlkJZetMmUX0Idyt4PF 7BJQ4tMjH/PMSqoHEJqHcMkEmoie1RcA1dr69njvOo/zdHGK5lyrjUHJgIuorLlkJZetMmUX0Idyt4PF
@ -12030,7 +12030,7 @@ zrZTSnYGAAA=
"/templates/admin-games.html": { "/templates/admin-games.html": {
local: "templates/admin-games.html", local: "templates/admin-games.html",
size: 507, size: 507,
modtime: 1500039735, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3RRwW6DMAy98xUW4jjofQJOk3brZfsBg98KEiRVkiFNVf99TrJq3dpeome/Z/vZOZ1o H4sIAAAJbogA/3RRwW6DMAy98xUW4jjofQJOk3brZfsBg98KEiRVkiFNVf99TrJq3dpeome/Z/vZOZ1o
/iBjAzXvWI8LB7xwYA149XQ+F63MW7+3dOAVnibeQANgaHRQrbS7yBfaBotHKgg8LKBZujLV1CkuaVzY /iBjAzXvWI8LB7xwYA149XQ+F63MW7+3dOAVnibeQANgaHRQrbS7yBfaBotHKgg8LKBZujLV1CkuaVzY
@ -12043,7 +12043,7 @@ GgriffNPZfY7AAD//1NqSJj7AQAA
"/templates/admin-login.html": { "/templates/admin-login.html": {
local: "templates/admin-login.html", local: "templates/admin-login.html",
size: 806, size: 806,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/6SSTW7DIBCF9zkFYm/5AralLrqsGjW9AIZJggoMgqFNbl9i4x8WrVR1Fd7kzZtvrOmU H4sIAAAJbogA/6SSTW7DIBCF9zkFYm/5AralLrqsGjW9AIZJggoMgqFNbl9i4x8WrVR1Fd7kzZtvrOmU
/mTSiBh7LsERBD4cGOvOGOxS9ylAMxXWVyOMvjhQnAlJGl3PW6Gsdq1CgxftOLNAV1Q9P76e3qfIR6gG /mTSiBh7LsERBD4cGOvOGOxS9ylAMxXWVyOMvjhQnAlJGl3PW6Gsdq1CgxftOLNAV1Q9P76e3qfIR6gG
@ -12057,7 +12057,7 @@ Xo6z3V9nVvnKh0MZ+h0AAP//kX1qvCYDAAA=
"/templates/admin-main.html": { "/templates/admin-main.html": {
local: "templates/admin-main.html", local: "templates/admin-main.html",
size: 1541, size: 1541,
modtime: 1501270519, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/6xTTW/bMAy991dwRoBcJrvtboVjIMiAnfaBrtvOis04QmUpk2QHQdH/PtJygmRN2yDt H4sIAAAJbogA/6xTTW/bMAy991dwRoBcJrvtboVjIMiAnfaBrtvOis04QmUpk2QHQdH/PtJygmRN2yDt
xbLIp6dHPiqvVAellt5PktKagCYkxQUAx3mlv+Wn4kc716qEr7bCPKN9TMzbEKwBa0pK3k+StTKVXafa xbLIp6dHPiqvVAellt5PktKagCYkxQUAx3mlv+Wn4kc716qEr7bCPKN9TMzbEKwBa0pK3k+StTKVXafa
@ -12073,7 +12073,7 @@ EXnfsPcmr6nBTP6F1/cmbz06Jv/F65EZHZZ/AQAA//8pWoRoBQYAAA==
"/templates/admin-menu.html": { "/templates/admin-menu.html": {
local: "templates/admin-menu.html", local: "templates/admin-menu.html",
size: 0, size: 0,
modtime: 1500039735, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/wEAAP//AAAAAAAAAAA= H4sIAAAJbogA/wEAAP//AAAAAAAAAAA=
`, `,
@ -12082,7 +12082,7 @@ H4sIAAAJbogA/wEAAP//AAAAAAAAAAA=
"/templates/admin-teams.html": { "/templates/admin-teams.html": {
local: "templates/admin-teams.html", local: "templates/admin-teams.html",
size: 1090, size: 1090,
modtime: 1500052988, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5xTwYrcMAy9z1eIMMdmfC8eQ2GhFNo97X6AEms2po4z2E5KWfbfK9tJmxmSlm5Osvyk H4sIAAAJbogA/5xTwYrcMAy9z1eIMMdmfC8eQ2GhFNo97X6AEms2po4z2E5KWfbfK9tJmxmSlm5Osvyk
J708S20maC2GcK6aIcahr8MVW4KWXCRfqQOARDCar6P7pPUTYV8tFdfRU92MXOZgFddhbFsKoYLO0+Vc J708S20maC2GcK6aIcahr8MVW4KWXCRfqQOARDCar6P7pPUTYV8tFdfRU92MXOZgFddhbFsKoYLO0+Vc
@ -12098,7 +12098,7 @@ SRlCBAAA
"/templates/admin-users.html": { "/templates/admin-users.html": {
local: "templates/admin-users.html", local: "templates/admin-users.html",
size: 1005, size: 1005,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5xTwY7TMBC99ytGYaWmEok5QxqJFXtA4rbwAa5n0lg4drCdrqoq/87E2VShKkJsL3ke H4sIAAAJbogA/5xTwY7TMBC99ytGYaWmEok5QxqJFXtA4rbwAa5n0lg4drCdrqoq/87E2VShKkJsL3ke
+82bmTetUJ9AGRnCPju4GF1XhF4qAkU2ks/qDUAlQSNfR/sZ8Ufg6MLoB0/FYWCahRUuwqAUhZBB66nZ +82bmTetUJ9AGRnCPju4GF1XhF4qAkU2ks/qDUAlQSNfR/sZ8Ufg6MLoB0/FYWCahRUuwqAUhZBB66nZ
@ -12114,7 +12114,7 @@ XzHfrrS2uzLN6JsOkTvo3Iny7fxf2KYCxs3I30osvf4OAAD//yR+jxHtAwAA
"/templates/admin-votes.html": { "/templates/admin-votes.html": {
local: "templates/admin-votes.html", local: "templates/admin-votes.html",
size: 837, size: 837,
modtime: 1501270323, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3xSTW/cIBC9768YWXusbam9RRip2lxy6SFd9Y7NJIuKwYJZS1WU/94Bm5Av5cTM4w3z H4sIAAAJbogA/3xSTW/cIBC9768YWXusbam9RRip2lxy6SFd9Y7NJIuKwYJZS1WU/94Bm5Av5cTM4w3z
5g1CmxUmq2IcmoDxaim2k3ekjMPQyIO4fJenawjoCO63e9Ezdnh6gqDcI8LRfIPjCjcDdGecF6sIbxWp 5g1CmxUmq2IcmoDxaim2k3ekjMPQyIO4fJenawjoCO63e9Ezdnh6gqDcI8LRfIPjCjcDdGecF6sIbxWp
@ -12129,7 +12129,7 @@ bmfD8/MBoLIp0SnzjytXqHmnZBJD98r9ZeQmp7R2v9SMnIsxQC+3p9DpVFMj0fMYLJbUaBGMHprVE8Y2
"/templates/footer.html": { "/templates/footer.html": {
local: "templates/footer.html", local: "templates/footer.html",
size: 228, size: 228,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/1yPwQqDMAyG7z5FyV1Ez7HvUteAhWwZJhV8+7lNsTaH5PB95OfHLqbVN7hvl+IIT4mB H4sIAAAJbogA/1yPwQqDMAyG7z5FyV1Ez7HvUteAhWwZJhV8+7lNsTaH5PB95OfHLqbVN7hvl+IIT4mB
W1lp4bCBb5zDn+D2wbkvHEvGBB67uT/xUGDN02UMh3FPmSRuX3wFVDybyUvhgJX34KA6wkJK1r4zc/Hq W1lp4bCBb5zDn+D2wbkvHEvGBB67uT/xUGDN02UMh3FPmSRuX3wFVDybyUvhgJX34KA6wkJK1r4zc/Hq
@ -12140,7 +12140,7 @@ bPU/nwAAAP///4iBjuQAAAA=
"/templates/header.html": { "/templates/header.html": {
local: "templates/header.html", local: "templates/header.html",
size: 1364, size: 1364,
modtime: 1500039735, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/9xUQW7bMBC8+xUL1cfaBHpsJQNpiqIBklP6AVpcRwtTVEBSAgpBf+9SomSqTlGjx5ws H4sIAAAJbogA/9xUQW7bMBC8+xUL1cfaBHpsJQNpiqIBklP6AVpcRwtTVEBSAgpBf+9SomSqTlGjx5ws
cmeHM8sxc1daevWHTSctlJrQ+AcFBWR9v7+Py2HIvmxyMSNzRR1DpXNFduKfCkqGoQVu+R7W96HGTYcN cmeHM8sxc1daevWHTSctlJrQ+AcFBWR9v7+Py2HIvmxyMSNzRR1DpXNFduKfCkqGoQVu+R7W96HGTYcN
@ -12156,7 +12156,7 @@ tbL5p7Gw1g4Tpzcpk0r9h6y3RV3N+m8xgynfDee7/kfkvo6gdxW8+KYsH+kbEw8Pf1mebXyUxpQ+t8ef
"/templates/htmlfooter.html": { "/templates/htmlfooter.html": {
local: "templates/htmlfooter.html", local: "templates/htmlfooter.html",
size: 115, size: 115,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/1IAAhv9lMwyOy4Qs7paoSgxLz1VQSVTR0GlTMHKVkEvOLkos6CkWKG2FqzEphjMVygu H4sIAAAJbogA/1IAAhv9lMwyOy4Qs7paoSgxLz1VQSVTR0GlTMHKVkEvOLkos6CkWKG2FqzEphjMVygu
SrZVAqoHqqqtVbKz0YeIw81JzUuBaLHRT8pPqbTjstHPKMnNseMCBAAA//8zytRAcwAAAA== SrZVAqoHqqqtVbKz0YeIw81JzUuBaLHRT8pPqbTjstHPKMnNseMCBAAA//8zytRAcwAAAA==
@ -12166,7 +12166,7 @@ SrZVAqoHqqqtVbKz0YeIw81JzUuBaLHRT8pPqbTjstHPKMnNseMCBAAA//8zytRAcwAAAA==
"/templates/htmlheader.html": { "/templates/htmlheader.html": {
local: "templates/htmlheader.html", local: "templates/htmlheader.html",
size: 801, size: 801,
modtime: 1496942648, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/5xSTY/TMBC991dMrT2uYxBCwCrpZVkJLoDUvaDVHlxnUk9x7GBPslRV/jtOP2hh98Qp H4sIAAAJbogA/5xSTY/TMBC991dMrT2uYxBCwCrpZVkJLoDUvaDVHlxnUk9x7GBPslRV/jtOP2hh98Qp
fvPx5s3LlPOPX2/vv3+7A8utW8zKwwegtKjr6ZGfLbIGY3VMyJXouZHvxWXK6xYrMRA+dSGyABM8o8+l fvPx5s3LlPOPX2/vv3+7A8utW8zKwwegtKjr6ZGfLbIGY3VMyJXouZHvxWXK6xYrMRA+dSGyABM8o8+l
@ -12182,7 +12182,7 @@ AAAA//9ablv2IQMAAA==
"/templates/public-teammgmt.html": { "/templates/public-teammgmt.html": {
local: "templates/public-teammgmt.html", local: "templates/public-teammgmt.html",
size: 10204, size: 10204,
modtime: 1500999274, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/+xa2Y/buBl/n7+CVQPYRsf2Ft3Ngy9ss5NdBMimxc4MimKRB1qibTaUaIiUJ+7A/3s/ H4sIAAAJbogA/+xa2Y/buBl/n7+CVQPYRsf2Ft3Ngy9ss5NdBMimxc4MimKRB1qibTaUaIiUJ+7A/3s/
HpJIibI1kwMoNvMyEo/v/H0HaT0+ohdFQRM0W6LJHUn3DEtygyWe3N+/uUGn09UioQcUMyzEMopJJkke HpJIibI1kwMoNvMyEo/v/H0HaT0+ohdFQRM0W6LJHUn3DEtygyWe3N+/uUGn09UioQcUMyzEMopJJkke
@ -12228,7 +12228,7 @@ TMvrt/8FAAD//x6rSUXcJwAA
"/templates/public-voting.html": { "/templates/public-voting.html": {
local: "templates/public-voting.html", local: "templates/public-voting.html",
size: 12039, size: 12039,
modtime: 1500477012, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/+xaXY/bNta+n1/B6C0qG+OP9t3FXsyHgWTS7mbRpEAyyaIYDArZom0lsmhI1Ezcwfz3 H4sIAAAJbogA/+xaXY/bNta+n1/B6C0qG+OP9t3FXsyHgWTS7mbRpEAyyaIYDArZom0lsmhI1Ezcwfz3
fc4hKVGy5HGy3aIXm4uMTB4ens/nHFJ6eBDJUmRKi8m13GzTSMuXkY7wI9oU4vHxRIiLOLmbvVFiFW1k fc4hKVGy5HGy3aIXm4uMTB4ens/nHFJ6eBDJUmRKi8m13GzTSMuXkY7wI9oU4vHxRIiLOLmbvVFiFW1k
@ -12291,7 +12291,7 @@ keHbF0X7EWiY9EaXLdydVrKhth9phno/0i6m7guY+muQfwcAAP//0nhPKAcvAAA=
"/templates/public-waiting.html": { "/templates/public-waiting.html": {
local: "templates/public-waiting.html", local: "templates/public-waiting.html",
size: 24, size: 24,
modtime: 1498146095, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/7JJySyz83QOUXBPzE1V8ErMtdEHiXABAgAA//+MadFWGAAAAA== H4sIAAAJbogA/7JJySyz83QOUXBPzE1V8ErMtdEHiXABAgAA//+MadFWGAAAAA==
`, `,
@ -12300,7 +12300,7 @@ H4sIAAAJbogA/7JJySyz83QOUXBPzE1V8ErMtdEHiXABAgAA//+MadFWGAAAAA==
"/templates/unauthorized.html": { "/templates/unauthorized.html": {
local: "templates/unauthorized.html", local: "templates/unauthorized.html",
size: 90, size: 90,
modtime: 1501246590, modtime: 1506689947,
compressed: ` compressed: `
H4sIAAAJbogA/3LOyUzNK1EIzUssLcnIL8qsSk3Rs0kqUtC344JSnmkKlfmlCmmpqTkKJRmZxQoglKeQ H4sIAAAJbogA/3LOyUzNK1EIzUssLcnIL8qsSk3Rs0kqUtC344JSnmkKlfmlCmmpqTkKJRmZxQoglKeQ
WlSUX6SjkJyfV5KYXKKQmKeQmJKbmZdZXFKUWJJfpMcFCAAA//8ZCPIcWgAAAA== WlSUX6SjkJyfV5KYXKKQmKeQmJKbmZdZXFKUWJJfpMcFCAAA//8ZCPIcWgAAAA==

59
main.go
View File

@ -23,29 +23,6 @@ import (
const AppName = "gjvote" const AppName = "gjvote"
const DbName = AppName + ".db" const DbName = AppName + ".db"
// SiteData is stuff that stays the same
type siteData struct {
Title string
Port int
SessionName string
ServerDir string
DevMode bool
CurrentJam string
Teams []Team
Votes []Vote
}
func (s *siteData) getTeamByUUID(uuid string) *Team {
for i := range s.Teams {
if s.Teams[i].UUID == uuid {
return &s.Teams[i]
}
}
return nil
}
// pageData is stuff that changes per request // pageData is stuff that changes per request
type pageData struct { type pageData struct {
Site *siteData Site *siteData
@ -85,8 +62,9 @@ var site *siteData
var r *mux.Router var r *mux.Router
func main() { func main() {
db = new(gjDatabase)
loadConfig() loadConfig()
dbSaveSiteConfig(site) site.save()
initialize() initialize()
r = mux.NewRouter() r = mux.NewRouter()
@ -132,7 +110,7 @@ func main() {
} }
func loadConfig() { func loadConfig() {
site = dbGetSiteConfig() site = db.getSiteConfig()
if len(os.Args) > 1 { if len(os.Args) > 1 {
for _, v := range os.Args { for _, v := range os.Args {
@ -177,9 +155,9 @@ func loadConfig() {
func initialize() { func initialize() {
// Check if the database has been created // Check if the database has been created
assertError(initDatabase()) assertError(db.initialize())
if !dbHasUser() { if !db.hasUser() {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
fmt.Println("Create new Admin user") fmt.Println("Create new Admin user")
fmt.Print("Email: ") fmt.Print("Email: ")
@ -197,20 +175,20 @@ func initialize() {
fmt.Println("Entered Passwords don't match!") fmt.Println("Entered Passwords don't match!")
} }
} }
assertError(dbUpdateUserPassword(email, string(pw1))) assertError(db.updateUserPassword(email, string(pw1)))
} }
if !dbHasCurrentJam() { if !db.hasCurrentJam() {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
fmt.Println("Create New Game Jam") fmt.Println("Create New Game Jam")
fmt.Print("GameJam Name: ") fmt.Print("GameJam Name: ")
gjName, _ := reader.ReadString('\n') gjName, _ := reader.ReadString('\n')
gjName = strings.TrimSpace(gjName) gjName = strings.TrimSpace(gjName)
if dbSetCurrentJam(gjName) != nil { if db.setCurrentJam(gjName) != nil {
fmt.Println("Error saving Current Jam") fmt.Println("Error saving Current Jam")
} }
} }
jmNm, err := dbGetCurrentJam() jmNm, err := db.getCurrentJam()
if err == nil { if err == nil {
fmt.Println("Current Jam Name: " + jmNm) fmt.Println("Current Jam Name: " + jmNm)
} else { } else {
@ -218,8 +196,8 @@ func initialize() {
} }
// Load all votes into memory // Load all votes into memory
site.Votes = dbGetAllVotes() site.Votes = db.getAllVotes()
site.Teams = dbGetAllTeams() site.Teams = db.getAllTeams()
} }
func loggingHandler(h http.Handler) http.Handler { func loggingHandler(h http.Handler) http.Handler {
@ -246,7 +224,7 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData {
// First check if we're logged in // First check if we're logged in
userEmail, _ := p.session.getStringValue("email") userEmail, _ := p.session.getStringValue("email")
// With a valid account // With a valid account
p.LoggedIn = dbIsValidUserEmail(userEmail) p.LoggedIn = db.isValidUserEmail(userEmail)
p.Site = site p.Site = site
p.SubTitle = "GameJam Voting" p.SubTitle = "GameJam Voting"
@ -282,19 +260,20 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData {
} }
p.HideAdminMenu = true p.HideAdminMenu = true
if p.CurrentJam, err = dbGetCurrentJam(); err != nil { if p.CurrentJam, err = db.getCurrentJam(); err != nil {
p.FlashMessage = "Error Loading Current GameJam: " + err.Error() p.FlashMessage = "Error Loading Current GameJam: " + err.Error()
p.FlashClass = "error" p.FlashClass = "error"
} }
p.ClientId = p.session.getClientId() p.ClientId = p.session.getClientId()
p.ClientIsAuth = clientIsAuthenticated(p.ClientId, req) cl := db.getClient(p.ClientId)
p.ClientIsAuth = cl.Auth
p.ClientIsServer = clientIsServer(req) p.ClientIsServer = clientIsServer(req)
// Public Mode // Public Mode
p.PublicMode = dbGetPublicSiteMode() p.PublicMode = db.getPublicSiteMode()
// Authentication Mode // Authentication Mode
p.AuthMode = dbGetAuthMode() p.AuthMode = db.getAuthMode()
return p return p
} }
@ -330,7 +309,7 @@ func redirect(url string, w http.ResponseWriter, req *http.Request) {
} }
func resetToDefaults() { func resetToDefaults() {
def := GetDefaultSiteConfig() def := NewSiteData()
fmt.Println("Reset settings to defaults?") fmt.Println("Reset settings to defaults?")
fmt.Print(site.Title, " -> ", def.Title, "\n") fmt.Print(site.Title, " -> ", def.Title, "\n")
fmt.Print(site.Port, " -> ", def.Port, "\n") fmt.Print(site.Port, " -> ", def.Port, "\n")
@ -341,7 +320,7 @@ func resetToDefaults() {
conf, _ := reader.ReadString('\n') conf, _ := reader.ReadString('\n')
conf = strings.ToUpper(strings.TrimSpace(conf)) conf = strings.ToUpper(strings.TrimSpace(conf))
if strings.HasPrefix(conf, "Y") { if strings.HasPrefix(conf, "Y") {
if dbSaveSiteConfig(def) != nil { if def.save() != nil {
errorExit("Error resetting to defaults") errorExit("Error resetting to defaults")
} }
fmt.Println("Reset to defaults") fmt.Println("Reset to defaults")

119
model.go
View File

@ -7,14 +7,12 @@ import (
"github.com/br0xen/boltease" "github.com/br0xen/boltease"
) )
var db *boltease.DB type gjDatabase struct {
var dbOpened int bolt *boltease.DB
dbOpened int
}
const ( var db *gjDatabase
SiteModeWaiting = iota
SiteModeVoting
SiteModeError
)
const ( const (
AuthModeAuthentication = iota AuthModeAuthentication = iota
@ -22,20 +20,11 @@ const (
AuthModeError AuthModeError
) )
func GetDefaultSiteConfig() *siteData { func (db *gjDatabase) open() error {
ret := new(siteData) db.dbOpened += 1
ret.Title = "ICT GameJam" if db.dbOpened == 1 {
ret.Port = 8080
ret.SessionName = "ict-gamejam"
ret.ServerDir = "./"
return ret
}
func openDatabase() error {
dbOpened += 1
if dbOpened == 1 {
var err error var err error
db, err = boltease.Create(DbName, 0600, nil) db.bolt, err = boltease.Create(DbName, 0600, nil)
if err != nil { if err != nil {
return err return err
} }
@ -43,60 +32,60 @@ func openDatabase() error {
return nil return nil
} }
func closeDatabase() error { func (db *gjDatabase) close() error {
dbOpened -= 1 db.dbOpened -= 1
if dbOpened == 0 { if db.dbOpened == 0 {
return db.CloseDB() return db.bolt.CloseDB()
} }
return nil return nil
} }
func initDatabase() error { func (db *gjDatabase) initialize() error {
var err error var err error
if err = openDatabase(); err != nil { if err = db.open(); err != nil {
return err return err
} }
defer closeDatabase() defer db.close()
// Create the path to the bucket to store admin users // Create the path to the bucket to store admin users
if err := db.MkBucketPath([]string{"users"}); err != nil { if err := db.bolt.MkBucketPath([]string{"users"}); err != nil {
return err return err
} }
// Create the path to the bucket to store jam informations // Create the path to the bucket to store jam informations
if err := db.MkBucketPath([]string{"jam"}); err != nil { if err := db.bolt.MkBucketPath([]string{"jam"}); err != nil {
return err return err
} }
// Create the path to the bucket to store site config data // Create the path to the bucket to store site config data
return db.MkBucketPath([]string{"site"}) return db.bolt.MkBucketPath([]string{"site"})
} }
func dbSetCurrentJam(name string) error { func (db *gjDatabase) setCurrentJam(name string) error {
var err error var err error
if err = openDatabase(); err != nil { if err = db.open(); err != nil {
return err return err
} }
defer closeDatabase() defer db.close()
return db.SetValue([]string{"site"}, "current-jam", name) return db.bolt.SetValue([]string{"site"}, "current-jam", name)
} }
func dbHasCurrentJam() bool { func (db *gjDatabase) hasCurrentJam() bool {
var err error var err error
if _, err = dbGetCurrentJam(); err != nil { if _, err = db.getCurrentJam(); err != nil {
return false return false
} }
return true return true
} }
func dbGetCurrentJam() (string, error) { func (db *gjDatabase) getCurrentJam() (string, error) {
var ret string var ret string
var err error var err error
if err = openDatabase(); err != nil { if err = db.open(); err != nil {
return "", err return "", err
} }
defer closeDatabase() defer db.close()
ret, err = db.GetValue([]string{"site"}, "current-jam") ret, err = db.bolt.GetValue([]string{"site"}, "current-jam")
if err == nil && strings.TrimSpace(ret) == "" { if err == nil && strings.TrimSpace(ret) == "" {
return ret, errors.New("No Jam Name Specified") return ret, errors.New("No Jam Name Specified")
@ -104,69 +93,49 @@ func dbGetCurrentJam() (string, error) {
return ret, err return ret, err
} }
func dbGetSiteConfig() *siteData { func (db *gjDatabase) getSiteConfig() *siteData {
var ret *siteData var ret *siteData
def := GetDefaultSiteConfig() def := NewSiteData()
var err error var err error
if err = openDatabase(); err != nil { if err = db.open(); err != nil {
return def return def
} }
defer closeDatabase() defer db.close()
ret = new(siteData) ret = new(siteData)
siteConf := []string{"site"} siteConf := []string{"site"}
if ret.Title, err = db.GetValue(siteConf, "title"); err != nil { if ret.Title, err = db.bolt.GetValue(siteConf, "title"); err != nil {
ret.Title = def.Title ret.Title = def.Title
} }
if ret.Port, err = db.GetInt(siteConf, "port"); err != nil { if ret.Port, err = db.bolt.GetInt(siteConf, "port"); err != nil {
ret.Port = def.Port ret.Port = def.Port
} }
if ret.SessionName, err = db.GetValue(siteConf, "session-name"); err != nil { if ret.SessionName, err = db.bolt.GetValue(siteConf, "session-name"); err != nil {
ret.SessionName = def.SessionName ret.SessionName = def.SessionName
} }
if ret.ServerDir, err = db.GetValue(siteConf, "server-dir"); err != nil { if ret.ServerDir, err = db.bolt.GetValue(siteConf, "server-dir"); err != nil {
ret.ServerDir = def.ServerDir ret.ServerDir = def.ServerDir
} }
return ret return ret
} }
func dbSaveSiteConfig(dat *siteData) error { func (db *gjDatabase) getAuthMode() int {
var err error if ret, err := db.bolt.GetInt([]string{"site"}, "auth-mode"); err != nil {
if err = openDatabase(); err != nil {
return err
}
defer closeDatabase()
siteConf := []string{"site"}
if err = db.SetValue(siteConf, "title", dat.Title); err != nil {
return err
}
if err = db.SetInt(siteConf, "port", dat.Port); err != nil {
return err
}
if err = db.SetValue(siteConf, "session-name", dat.SessionName); err != nil {
return err
}
return db.SetValue(siteConf, "server-dir", dat.ServerDir)
}
func dbGetAuthMode() int {
if ret, err := db.GetInt([]string{"site"}, "auth-mode"); err != nil {
return AuthModeAuthentication return AuthModeAuthentication
} else { } else {
return ret return ret
} }
} }
func dbSetAuthMode(mode int) error { func (db *gjDatabase) setAuthMode(mode int) error {
if mode < 0 || mode >= AuthModeError { if mode < 0 || mode >= AuthModeError {
return errors.New("Invalid site mode") return errors.New("Invalid site mode")
} }
return db.SetInt([]string{"site"}, "auth-mode", mode) return db.bolt.SetInt([]string{"site"}, "auth-mode", mode)
} }
func dbGetPublicSiteMode() int { func (db *gjDatabase) getPublicSiteMode() int {
if ret, err := db.GetInt([]string{"site"}, "public-mode"); err != nil { if ret, err := db.bolt.GetInt([]string{"site"}, "public-mode"); err != nil {
return SiteModeWaiting return SiteModeWaiting
} else { } else {
return ret return ret
@ -177,5 +146,5 @@ func dbSetPublicSiteMode(mode int) error {
if mode < 0 || mode >= SiteModeError { if mode < 0 || mode >= SiteModeError {
return errors.New("Invalid site mode") return errors.New("Invalid site mode")
} }
return db.SetInt([]string{"site"}, "public-mode", mode) return db.bolt.SetInt([]string{"site"}, "public-mode", mode)
} }

View File

@ -1,5 +1,12 @@
package main package main
import (
"fmt"
"strconv"
"strings"
"time"
)
type Client struct { type Client struct {
UUID string UUID string
Auth bool Auth bool
@ -7,49 +14,49 @@ type Client struct {
IP string IP string
} }
func dbGetAllClients() []Client { func (db *gjDatabase) getAllClients() []Client {
var ret []Client var ret []Client
var err error var err error
if err = db.OpenDB(); err != nil { if err = db.open(); err != nil {
return ret return ret
} }
defer db.CloseDB() defer db.close()
var clientUids []string var clientUids []string
if clientUids, err = db.GetBucketList([]string{"clients"}); err != nil { if clientUids, err = db.bolt.GetBucketList([]string{"clients"}); err != nil {
return ret return ret
} }
for _, v := range clientUids { for _, v := range clientUids {
if cl := dbGetClient(v); cl != nil { if cl := db.getClient(v); cl != nil {
ret = append(ret, *cl) ret = append(ret, *cl)
} }
} }
return ret return ret
} }
func dbGetClient(id string) *Client { func (db *gjDatabase) getClient(id string) *Client {
var err error var err error
if err = db.OpenDB(); err != nil { if err = db.open(); err != nil {
return nil return nil
} }
defer db.CloseDB() defer db.close()
cl := new(Client) cl := new(Client)
cl.UUID = id cl.UUID = id
cl.Auth = dbClientIsAuth(id) cl.Auth, _ = db.bolt.GetBool([]string{"clients", id}, "auth")
cl.Name, _ = db.GetValue([]string{"clients", id}, "name") cl.Name, _ = db.bolt.GetValue([]string{"clients", id}, "name")
cl.IP, _ = db.GetValue([]string{"clients", id}, "ip") cl.IP, _ = db.bolt.GetValue([]string{"clients", id}, "ip")
return cl return cl
} }
func dbGetClientByIp(ip string) *Client { func (db *gjDatabase) getClientByIp(ip string) *Client {
var err error var err error
if err = db.OpenDB(); err != nil { if err = db.open(); err != nil {
return nil return nil
} }
defer db.CloseDB() defer db.close()
allClients := dbGetAllClients() allClients := db.getAllClients()
for i := range allClients { for i := range allClients {
if allClients[i].IP == ip { if allClients[i].IP == ip {
return &allClients[i] return &allClients[i]
@ -58,85 +65,90 @@ func dbGetClientByIp(ip string) *Client {
return nil return nil
} }
func dbSetClientName(cid, name string) error { func (c *Client) save() error {
var err error var err error
if err = db.OpenDB(); err != nil { if err = db.open(); err != nil {
return nil return nil
} }
defer db.CloseDB() defer db.close()
err = db.SetValue([]string{"clients", cid}, "name", name) if err = db.bolt.SetBool([]string{"clients", c.UUID}, "auth", c.Auth); err != nil {
return err
}
if err = db.bolt.SetValue([]string{"clients", c.UUID}, "name", c.Name); err != nil {
return err
}
return db.bolt.SetValue([]string{"clients", c.UUID}, "ip", c.IP)
}
func (c *Client) getVotes() []Vote {
var ret []Vote
var err error
if err = db.open(); err != nil {
return ret
}
defer db.close()
var times []string
votesBkt := []string{"votes", c.UUID}
if times, err = db.bolt.GetBucketList(votesBkt); err != nil {
return ret
}
for _, t := range times {
var tm time.Time
if tm, err = time.Parse(time.RFC3339, t); err == nil {
var vt *Vote
if vt, err = c.getVote(tm); err == nil {
ret = append(ret, *vt)
} else {
fmt.Println(err)
}
}
}
return ret
}
func (c *Client) getVote(timestamp time.Time) (*Vote, error) {
var err error
if err = db.open(); err != nil {
return nil, err
}
defer db.close()
vt := new(Vote)
vt.Timestamp = timestamp
vt.ClientId = c.UUID
votesBkt := []string{"votes", c.UUID, timestamp.Format(time.RFC3339)}
var choices []string
if choices, err = db.bolt.GetKeyList(votesBkt); err != nil {
// Couldn't find the vote...
return nil, err
}
for _, v := range choices {
ch := new(GameChoice)
var rank int
if rank, err = strconv.Atoi(v); err == nil {
ch.Rank = rank
ch.Team, err = db.bolt.GetValue(votesBkt, v)
vt.Choices = append(vt.Choices, *ch)
}
}
return vt, nil
}
func (c *Client) saveVote(timestamp time.Time, votes []string) error {
var err error
if err = db.open(); err != nil {
return nil
}
defer db.close()
// Make sure we don't clobber a duplicate vote
votesBkt := []string{"votes", c.UUID, timestamp.Format(time.RFC3339)}
for i := range votes {
if strings.TrimSpace(votes[i]) != "" {
db.bolt.SetValue(votesBkt, strconv.Itoa(i), votes[i])
}
}
return err return err
} }
func dbGetClientName(cid string) string {
if err := db.OpenDB(); err != nil {
return ""
}
defer db.CloseDB()
name, _ := db.GetValue([]string{"clients", cid}, "name")
return name
}
func dbAddDeauthClient(cid, ip string) error {
var err error
if err = db.OpenDB(); err != nil {
return err
}
defer db.CloseDB()
err = db.SetBool([]string{"clients", cid}, "auth", false)
if err != nil {
return err
}
return db.SetValue([]string{"clients", cid}, "ip", ip)
}
func dbAuthClient(cid, ip string) error {
var err error
if err = db.OpenDB(); err != nil {
return err
}
defer db.CloseDB()
err = db.SetBool([]string{"clients", cid}, "auth", true)
if err != nil {
return err
}
return db.SetValue([]string{"clients", cid}, "ip", ip)
}
func dbDeAuthClient(cid string) error {
var err error
if err = db.OpenDB(); err != nil {
return err
}
defer db.CloseDB()
return db.SetBool([]string{"clients", cid}, "auth", false)
}
func dbClientIsAuth(cid string) bool {
var err error
if err = db.OpenDB(); err != nil {
return false
}
defer db.CloseDB()
var isAuth bool
if isAuth, err = db.GetBool([]string{"clients", cid}, "auth"); err != nil {
return false
}
return isAuth
}
func dbUpdateClientIP(cid, ip string) error {
var err error
if err = db.OpenDB(); err != nil {
return err
}
defer db.CloseDB()
return db.SetValue([]string{"clients", cid}, "ip", ip)
}

View File

@ -1,10 +1,6 @@
package main package main
import ( import "errors"
"errors"
"github.com/pborman/uuid"
)
type Game struct { type Game struct {
Name string Name string
@ -22,150 +18,61 @@ type Screenshot struct {
Filetype string Filetype string
} }
func dbUpdateTeamGame(teamId, name, link, desc string) error { // Create a new game object, must have a valid team id
func newGame(tmId string) *Game {
var err error var err error
if err = openDatabase(); err != nil { if err = db.open(); err != nil {
return err return nil
} }
defer closeDatabase() defer db.close()
// Make sure the team is valid tm := db.getTeam(tmId)
tm := dbGetTeam(teamId)
if tm == nil { if tm == nil {
return errors.New("Invalid team") return nil
} }
gamePath := []string{"teams", teamId, "game"} return &Game{TeamId: tmId}
}
if err := db.MkBucketPath(gamePath); err != nil { func (gm *Game) save() error {
var err error
if err = db.open(); err != nil {
return err return err
} }
if name == "" { defer db.close()
name = tm.Name + "'s Game"
tm := db.getTeam(gm.TeamId)
if tm == nil {
return errors.New("Invalid Team: " + gm.TeamId)
} }
if err := db.SetValue(gamePath, "name", name); err != nil { gamePath := []string{"teams", gm.TeamId, "game"}
if err := db.bolt.MkBucketPath(gamePath); err != nil {
return err return err
} }
if err := db.SetValue(gamePath, "link", link); err != nil {
if gm.Name == "" {
gm.Name = tm.Name + "'s Game"
}
if err := db.bolt.SetValue(gamePath, "name", gm.Name); err != nil {
return err return err
} }
if err := db.SetValue(gamePath, "description", desc); err != nil { if err := db.bolt.SetValue(gamePath, "link", gm.Link); err != nil {
return err return err
} }
if err := db.MkBucketPath(append(gamePath, "screenshots")); err != nil { if err := db.bolt.SetValue(gamePath, "description", gm.Description); err != nil {
return err
}
if err := db.bolt.MkBucketPath(append(gamePath, "screenshots")); err != nil {
return err return err
} }
return err return err
} }
func dbGetAllGames() []Game { func (db *gjDatabase) getAllGames() []Game {
var ret []Game var ret []Game
tms := dbGetAllTeams() tms := db.getAllTeams()
for i := range tms { for i := range tms {
ret = append(ret, *dbGetTeamGame(tms[i].UUID)) ret = append(ret, *tms[i].getGame())
} }
return ret return ret
} }
func dbGetTeamGame(teamId string) *Game {
var err error
if err = openDatabase(); err != nil {
return nil
}
defer closeDatabase()
gamePath := []string{"teams", teamId, "game"}
gm := new(Game)
if gm.Name, err = db.GetValue(gamePath, "name"); err != nil {
gm.Name = ""
}
gm.TeamId = teamId
if gm.Description, err = db.GetValue(gamePath, "description"); err != nil {
gm.Description = ""
}
if gm.Link, err = db.GetValue(gamePath, "link"); err != nil {
gm.Link = ""
}
gm.Screenshots = dbGetTeamGameScreenshots(teamId)
return gm
}
// Screenshots are saved as base64 encoded pngs
func dbGetTeamGameScreenshots(teamId string) []Screenshot {
var ret []Screenshot
var err error
ssPath := []string{"teams", teamId, "game", "screenshots"}
var ssIds []string
if ssIds, err = db.GetBucketList(ssPath); err != nil {
return ret
}
for _, v := range ssIds {
if ss := dbGetTeamGameScreenshot(teamId, v); ss != nil {
ret = append(ret, *ss)
}
}
return ret
}
func dbGetTeamGameScreenshot(teamId, ssId string) *Screenshot {
var err error
ssPath := []string{"teams", teamId, "game", "screenshots", ssId}
ret := new(Screenshot)
ret.UUID = ssId
if ret.Description, err = db.GetValue(ssPath, "description"); err != nil {
return nil
}
if ret.Image, err = db.GetValue(ssPath, "image"); err != nil {
return nil
}
if ret.Thumbnail, err = db.GetValue(ssPath, "thumbnail"); err != nil {
return nil
}
if ret.Thumbnail == "" {
ret.Thumbnail = ret.Image
}
if ret.Filetype, err = db.GetValue(ssPath, "filetype"); err != nil {
return nil
}
return ret
}
func dbSaveTeamGameScreenshot(teamId string, ss *Screenshot) error {
var err error
if err = openDatabase(); err != nil {
return nil
}
defer closeDatabase()
ssPath := []string{"teams", teamId, "game", "screenshots"}
// Generate a UUID for this screenshot
uuid := uuid.New()
ssPath = append(ssPath, uuid)
if err := db.MkBucketPath(ssPath); err != nil {
return err
}
if err := db.SetValue(ssPath, "description", ss.Description); err != nil {
return err
}
if err := db.SetValue(ssPath, "image", ss.Image); err != nil {
return err
}
if err := db.SetValue(ssPath, "thumbnail", ss.Thumbnail); err != nil {
return err
}
if err := db.SetValue(ssPath, "filetype", ss.Filetype); err != nil {
return err
}
return nil
}
func dbDeleteTeamGameScreenshot(teamId, ssId string) error {
var err error
if err = openDatabase(); err != nil {
return nil
}
defer closeDatabase()
ssPath := []string{"teams", teamId, "game", "screenshots"}
return db.DeleteBucket(ssPath, ssId)
}

62
model_site.go Normal file
View File

@ -0,0 +1,62 @@
package main
const (
SiteModeWaiting = iota
SiteModeVoting
SiteModeError
)
// SiteData is stuff that stays the same
type siteData struct {
Title string
Port int
SessionName string
ServerDir string
DevMode bool
Mode int
CurrentJam string
Teams []Team
Votes []Vote
}
// NewSiteData returns a siteData object with the default values
func NewSiteData() *siteData {
ret := new(siteData)
ret.Title = "ICT GameJam"
ret.Port = 8080
ret.SessionName = "ict-gamejam"
ret.ServerDir = "./"
return ret
}
func (s *siteData) getTeamByUUID(uuid string) *Team {
for i := range s.Teams {
if s.Teams[i].UUID == uuid {
return &s.Teams[i]
}
}
return nil
}
// save 's' to the database
func (s *siteData) save() error {
var err error
if err = db.open(); err != nil {
return err
}
defer db.close()
siteConf := []string{"site"}
if err = db.bolt.SetValue(siteConf, "title", s.Title); err != nil {
return err
}
if err = db.bolt.SetInt(siteConf, "port", s.Port); err != nil {
return err
}
if err = db.bolt.SetValue(siteConf, "session-name", s.SessionName); err != nil {
return err
}
return db.bolt.SetValue(siteConf, "server-dir", s.ServerDir)
}

View File

@ -13,250 +13,68 @@ type Team struct {
Game *Game Game *Game
} }
type TeamMember struct { // newTeam creates a team with name nm and stores it in the DB
UUID string func (db *gjDatabase) newTeam(nm string) error {
Name string
SlackId string
Twitter string
Email string
}
func dbCreateNewTeam(nm string) error {
var err error var err error
if err = openDatabase(); err != nil { if err = db.open(); err != nil {
return err return err
} }
defer closeDatabase() defer db.close()
// Generate a UUID // Generate a UUID
uuid := uuid.New() uuid := uuid.New()
teamPath := []string{"teams", uuid} teamPath := []string{"teams", uuid}
if err := db.MkBucketPath(teamPath); err != nil { if err := db.bolt.MkBucketPath(teamPath); err != nil {
return err return err
} }
if err := db.SetValue(teamPath, "name", nm); err != nil { if err := db.bolt.SetValue(teamPath, "name", nm); err != nil {
return err return err
} }
if err := db.MkBucketPath(append(teamPath, "members")); err != nil { if err := db.bolt.MkBucketPath(append(teamPath, "members")); err != nil {
return err return err
} }
gamePath := append(teamPath, "game") gamePath := append(teamPath, "game")
if err := db.MkBucketPath(gamePath); err != nil { if err := db.bolt.MkBucketPath(gamePath); err != nil {
return err return err
} }
if err := db.SetValue(append(gamePath), "name", ""); err != nil { if err := db.bolt.SetValue(append(gamePath), "name", ""); err != nil {
return err return err
} }
return db.MkBucketPath(append(gamePath, "screenshots")) return db.bolt.MkBucketPath(append(gamePath, "screenshots"))
} }
func dbIsValidTeam(id string) bool { // getTeam returns a team with the given id, or nil
func (db *gjDatabase) getTeam(id string) *Team {
var err error var err error
if err = openDatabase(); err != nil { if err = db.open(); err != nil {
return false
}
defer closeDatabase()
teamPath := []string{"teams"}
if teamUids, err := db.GetBucketList(teamPath); err == nil {
for _, v := range teamUids {
if v == id {
return true
}
}
}
return false
}
func dbGetAllTeams() []Team {
var ret []Team
var err error
if err = openDatabase(); err != nil {
return ret
}
defer closeDatabase()
teamPath := []string{"teams"}
var teamUids []string
if teamUids, err = db.GetBucketList(teamPath); err != nil {
return ret
}
for _, v := range teamUids {
if tm := dbGetTeam(v); tm != nil {
ret = append(ret, *tm)
}
}
return ret
}
func dbGetTeam(id string) *Team {
var err error
if err = openDatabase(); err != nil {
return nil return nil
} }
defer closeDatabase() defer db.close()
teamPath := []string{"teams", id} teamPath := []string{"teams", id}
tm := new(Team) tm := new(Team)
tm.UUID = id tm.UUID = id
if tm.Name, err = db.GetValue(teamPath, "name"); err != nil { if tm.Name, err = db.bolt.GetValue(teamPath, "name"); err != nil {
return nil return nil
} }
tm.Members, _ = dbGetTeamMembers(id) tm.Members = tm.getTeamMembers()
tm.Game = dbGetTeamGame(id) tm.Game = tm.getGame()
return tm return tm
} }
func dbGetTeamByName(nm string) *Team {
var err error
if err = openDatabase(); err != nil {
return nil
}
defer closeDatabase()
teamPath := []string{"teams"}
var teamUids []string
if teamUids, err = db.GetBucketList(teamPath); err != nil {
for _, v := range teamUids {
var name string
if name, err = db.GetValue(append(teamPath, v), "name"); name == nm {
return dbGetTeam(v)
}
}
}
return nil
}
func dbUpdateTeam(id string, tm *Team) error {
var err error
if err = openDatabase(); err != nil {
return err
}
defer closeDatabase()
teamPath := []string{"teams", id}
return db.SetValue(teamPath, "name", tm.Name)
}
func dbDeleteTeam(id string) error {
var err error
if err = openDatabase(); err != nil {
return err
}
defer closeDatabase()
teamPath := []string{"teams"}
return db.DeleteBucket(teamPath, id)
}
func dbEditTeamGame(teamid, name string) error {
var err error
if err = openDatabase(); err != nil {
return err
}
defer closeDatabase()
gamePath := []string{"teams", teamid, "game"}
return db.SetValue(gamePath, "name", name)
}
func dbAddTeamMember(teamid, mbrName, mbrEmail, mbrSlack, mbrTwitter string) error {
// First check if this member already exists on this team
mbrs, _ := dbGetTeamMembers(teamid)
if len(mbrs) > 0 {
for i := range mbrs {
if mbrs[i].Name == mbrName {
return dbUpdateTeamMember(teamid, mbrs[i].UUID, mbrName, mbrEmail, mbrSlack, mbrTwitter)
}
}
}
// It's really an add
mbrId := uuid.New()
return dbUpdateTeamMember(teamid, mbrId, mbrName, mbrEmail, mbrSlack, mbrTwitter)
}
func dbUpdateTeamMember(teamid, mbrId, mbrName, mbrEmail, mbrSlack, mbrTwitter string) error {
var err error
if err = openDatabase(); err != nil {
return err
}
defer closeDatabase()
mbrPath := []string{"teams", teamid, "members", mbrId}
if db.SetValue(mbrPath, "name", mbrName) != nil {
return err
}
if db.SetValue(mbrPath, "slackid", mbrSlack) != nil {
return err
}
if db.SetValue(mbrPath, "twitter", mbrTwitter) != nil {
return err
}
if db.SetValue(mbrPath, "email", mbrEmail) != nil {
return err
}
return nil
}
func dbGetTeamMembers(teamid string) ([]TeamMember, error) {
var ret []TeamMember
var err error
if err = openDatabase(); err != nil {
return ret, err
}
defer closeDatabase()
teamPath := []string{"teams", teamid, "members"}
var memberUuids []string
if memberUuids, err = db.GetBucketList(teamPath); err == nil {
for _, v := range memberUuids {
var mbr *TeamMember
if mbr, err = dbGetTeamMember(teamid, v); err == nil {
ret = append(ret, *mbr)
}
}
}
return ret, err
}
func dbGetTeamMember(teamid, mbrid string) (*TeamMember, error) {
var err error
if err = openDatabase(); err != nil {
return nil, err
}
defer closeDatabase()
mbr := new(TeamMember)
teamMbrPath := []string{"teams", teamid, "members", mbrid}
mbr.UUID = mbrid
if mbr.Name, err = db.GetValue(teamMbrPath, "name"); err != nil {
return nil, err
}
if mbr.SlackId, err = db.GetValue(teamMbrPath, "slackid"); err != nil {
return nil, err
}
if mbr.Twitter, err = db.GetValue(teamMbrPath, "twitter"); err != nil {
return nil, err
}
if mbr.Email, err = db.GetValue(teamMbrPath, "email"); err != nil {
return nil, err
}
return mbr, err
}
// This function returns the team for a specific member // This function returns the team for a specific member
func dbGetMembersTeam(mbrid string) (*Team, error) { func (db *gjDatabase) getTeamForMember(mbrid string) (*Team, error) {
var err error var err error
if err = openDatabase(); err != nil { if err = db.open(); err != nil {
return nil, err return nil, err
} }
defer closeDatabase() defer db.close()
teams := dbGetAllTeams() teams := db.getAllTeams()
for i := range teams { for i := range teams {
var tmMbrs []TeamMember var tmMbrs []TeamMember
tmMbrs, err = dbGetTeamMembers(teams[i].UUID) tmMbrs = teams[i].getTeamMembers()
if err == nil { if err == nil {
for j := range tmMbrs { for j := range tmMbrs {
if tmMbrs[j].UUID == mbrid { if tmMbrs[j].UUID == mbrid {
@ -268,40 +86,289 @@ func dbGetMembersTeam(mbrid string) (*Team, error) {
return nil, errors.New("Unable to find team member") return nil, errors.New("Unable to find team member")
} }
// This function searches all teams for a member with the given name // getAllTeams returns all teams in the database
func dbGetTeamMembersByName(mbrName string) ([]TeamMember, error) { func (db *gjDatabase) getAllTeams() []Team {
var ret []Team
var err error
if err = db.open(); err != nil {
return ret
}
defer db.close()
teamPath := []string{"teams"}
var teamUids []string
if teamUids, err = db.bolt.GetBucketList(teamPath); err != nil {
return ret
}
for _, v := range teamUids {
if tm := db.getTeam(v); tm != nil {
ret = append(ret, *tm)
}
}
return ret
}
// getTeamByName returns a team with the given name or nil
func (db *gjDatabase) getTeamByName(nm string) *Team {
var err error
if err = db.open(); err != nil {
return nil
}
defer db.close()
teamPath := []string{"teams"}
var teamUids []string
if teamUids, err = db.bolt.GetBucketList(teamPath); err != nil {
for _, v := range teamUids {
var name string
if name, err = db.bolt.GetValue(append(teamPath, v), "name"); name == nm {
return db.getTeam(v)
}
}
}
return nil
}
// save saves the team to the db
func (tm *Team) save() error {
var err error
if err = db.open(); err != nil {
return err
}
defer db.close()
teamPath := []string{"teams", tm.UUID}
if err = db.bolt.SetValue(teamPath, "name", tm.Name); err != nil {
return err
}
// TODO: Save Team Members
// TODO: Save Team Game
return nil
}
// delete removes the team from the database
func (tm *Team) delete() error {
var err error
if err = db.open(); err != nil {
return err
}
defer db.close()
teamPath := []string{"teams"}
return db.bolt.DeleteBucket(teamPath, tm.UUID)
}
func (tm *Team) getGame() *Game {
var err error
if err = db.open(); err != nil {
return nil
}
defer db.close()
gamePath := []string{"teams", tm.UUID, "game"}
gm := new(Game)
if gm.Name, err = db.bolt.GetValue(gamePath, "name"); err != nil {
gm.Name = ""
}
gm.TeamId = tm.UUID
if gm.Description, err = db.bolt.GetValue(gamePath, "description"); err != nil {
gm.Description = ""
}
if gm.Link, err = db.bolt.GetValue(gamePath, "link"); err != nil {
gm.Link = ""
}
gm.Screenshots = tm.getScreenshots()
return gm
}
// Screenshots are saved as base64 encoded pngs
func (tm *Team) saveScreenshot(ss *Screenshot) error {
var err error
if err = db.open(); err != nil {
return nil
}
defer db.close()
ssPath := []string{"teams", tm.UUID, "game", "screenshots"}
// Generate a UUID for this screenshot
uuid := uuid.New()
ssPath = append(ssPath, uuid)
if err := db.bolt.MkBucketPath(ssPath); err != nil {
return err
}
if err := db.bolt.SetValue(ssPath, "description", ss.Description); err != nil {
return err
}
if err := db.bolt.SetValue(ssPath, "image", ss.Image); err != nil {
return err
}
if err := db.bolt.SetValue(ssPath, "thumbnail", ss.Thumbnail); err != nil {
return err
}
if err := db.bolt.SetValue(ssPath, "filetype", ss.Filetype); err != nil {
return err
}
return nil
}
func (tm *Team) getScreenshots() []Screenshot {
var ret []Screenshot
var err error
ssPath := []string{"teams", tm.UUID, "game", "screenshots"}
var ssIds []string
if ssIds, err = db.bolt.GetBucketList(ssPath); err != nil {
return ret
}
for _, v := range ssIds {
if ss := tm.getScreenshot(v); ss != nil {
ret = append(ret, *ss)
}
}
return ret
}
func (tm *Team) getScreenshot(ssId string) *Screenshot {
var err error
ssPath := []string{"teams", tm.UUID, "game", "screenshots", ssId}
ret := new(Screenshot)
ret.UUID = ssId
if ret.Description, err = db.bolt.GetValue(ssPath, "description"); err != nil {
return nil
}
if ret.Image, err = db.bolt.GetValue(ssPath, "image"); err != nil {
return nil
}
if ret.Thumbnail, err = db.bolt.GetValue(ssPath, "thumbnail"); err != nil {
return nil
}
if ret.Thumbnail == "" {
ret.Thumbnail = ret.Image
}
if ret.Filetype, err = db.bolt.GetValue(ssPath, "filetype"); err != nil {
return nil
}
return ret
}
func (tm *Team) deleteScreenshot(ssId string) error {
var err error
if err = db.open(); err != nil {
return nil
}
defer db.close()
ssPath := []string{"teams", tm.UUID, "game", "screenshots"}
return db.bolt.DeleteBucket(ssPath, ssId)
}
type TeamMember struct {
UUID string
Name string
SlackId string
Twitter string
Email string
}
// Create a new team member, only a name is required
func newTeamMember(nm string) *TeamMember {
m := TeamMember{Name: nm}
return &m
}
func (tm *Team) getTeamMember(mbrId string) *TeamMember {
var err error
if err = db.open(); err != nil {
return nil
}
defer db.close()
mbr := new(TeamMember)
mbr.UUID = mbrId
teamMbrPath := []string{"teams", tm.UUID, "members", mbr.UUID}
if mbr.Name, err = db.bolt.GetValue(teamMbrPath, "name"); err != nil {
return nil
}
if mbr.SlackId, err = db.bolt.GetValue(teamMbrPath, "slackid"); err != nil {
return nil
}
if mbr.Twitter, err = db.bolt.GetValue(teamMbrPath, "twitter"); err != nil {
return nil
}
if mbr.Email, err = db.bolt.GetValue(teamMbrPath, "email"); err != nil {
return nil
}
return mbr
}
func (tm *Team) getTeamMembers() []TeamMember {
var ret []TeamMember var ret []TeamMember
var err error var err error
if err = openDatabase(); err != nil { if err = db.open(); err != nil {
return ret, err return ret
} }
defer closeDatabase() defer db.close()
teams := dbGetAllTeams() teamPath := []string{"teams", tm.UUID, "members"}
for i := range teams { var memberUuids []string
var tmMbrs []TeamMember if memberUuids, err = db.bolt.GetBucketList(teamPath); err == nil {
tmMbrs, err = dbGetTeamMembers(teams[i].UUID) for _, v := range memberUuids {
if err == nil { var mbr *TeamMember
for j := range tmMbrs { if mbr = tm.getTeamMember(v); mbr != nil {
if tmMbrs[j].Name == mbrName { ret = append(ret, *mbr)
ret = append(ret, tmMbrs[j]) }
}
}
return ret
}
func (tm *Team) updateTeamMember(mbr *TeamMember) error {
var err error
if err = db.open(); err != nil {
return err
}
defer db.close()
if mbr.UUID == "" {
mbrs := tm.getTeamMembers()
if len(mbrs) > 0 {
for i := range mbrs {
if mbrs[i].Name == mbr.Name {
mbr.UUID = mbrs[i].UUID
break
} }
} }
} }
} }
if len(ret) == 0 { if mbr.UUID == "" {
return nil, errors.New("Couldn't find any members with the requested name") // It's really a new one
mbr.UUID = uuid.New()
} }
return ret, nil
}
func dbDeleteTeamMember(teamId, mbrId string) error { mbrPath := []string{"teams", tm.UUID, "members", mbr.UUID}
var err error if db.bolt.SetValue(mbrPath, "name", mbr.Name) != nil {
if err = openDatabase(); err != nil {
return err return err
} }
defer closeDatabase() if db.bolt.SetValue(mbrPath, "slackid", mbr.SlackId) != nil {
return err
teamPath := []string{"teams", teamId, "members"} }
return db.DeleteBucket(teamPath, mbrId) if db.bolt.SetValue(mbrPath, "twitter", mbr.Twitter) != nil {
return err
}
if db.bolt.SetValue(mbrPath, "email", mbr.Email) != nil {
return err
}
return nil
}
// deleteTeamMember removes a member from the database
func (tm *Team) deleteTeamMember(mbr *TeamMember) error {
var err error
if err = db.open(); err != nil {
return err
}
defer db.close()
teamPath := []string{"teams", tm.UUID, "members"}
return db.bolt.DeleteBucket(teamPath, mbr.UUID)
} }

View File

@ -4,73 +4,73 @@ import "golang.org/x/crypto/bcrypt"
// dbHasUser // dbHasUser
// Returns true if there are any users in the database // Returns true if there are any users in the database
func dbHasUser() bool { func (db *gjDatabase) hasUser() bool {
return len(dbGetAllUsers()) > 0 return len(db.getAllUsers()) > 0
} }
func dbGetAllUsers() []string { func (db *gjDatabase) getAllUsers() []string {
if err := db.OpenDB(); err != nil { if err := db.open(); err != nil {
return []string{} return []string{}
} }
defer db.CloseDB() defer db.close()
usrs, err := db.GetBucketList([]string{"users"}) usrs, err := db.bolt.GetBucketList([]string{"users"})
if err != nil { if err != nil {
return []string{} return []string{}
} }
return usrs return usrs
} }
func dbIsValidUserEmail(email string) bool { func (db *gjDatabase) isValidUserEmail(email string) bool {
if err := db.OpenDB(); err != nil { if err := db.open(); err != nil {
return false return false
} }
defer db.CloseDB() defer db.close()
usrPath := []string{"users", email} usrPath := []string{"users", email}
_, err := db.GetValue(usrPath, "password") _, err := db.bolt.GetValue(usrPath, "password")
return err == nil return err == nil
} }
func dbCheckCredentials(email, pw string) error { func (db *gjDatabase) checkCredentials(email, pw string) error {
var err error var err error
if err = db.OpenDB(); err != nil { if err = db.open(); err != nil {
return err return err
} }
defer db.CloseDB() defer db.close()
var uPw string var uPw string
usrPath := []string{"users", email} usrPath := []string{"users", email}
if uPw, err = db.GetValue(usrPath, "password"); err != nil { if uPw, err = db.bolt.GetValue(usrPath, "password"); err != nil {
return err return err
} }
return bcrypt.CompareHashAndPassword([]byte(uPw), []byte(pw)) return bcrypt.CompareHashAndPassword([]byte(uPw), []byte(pw))
} }
// dbUpdateUserPassword // updateUserPassword
// Takes an email address and a password // Takes an email address and a password
// Creates the user if it doesn't exist, encrypts the password // Creates the user if it doesn't exist, encrypts the password
// and updates it in the db // and updates it in the db
func dbUpdateUserPassword(email, password string) error { func (db *gjDatabase) updateUserPassword(email, password string) error {
cryptPw, cryptError := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) cryptPw, cryptError := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if cryptError != nil { if cryptError != nil {
return cryptError return cryptError
} }
if err := db.OpenDB(); err != nil { if err := db.open(); err != nil {
return err return err
} }
defer db.CloseDB() defer db.close()
usrPath := []string{"users", email} usrPath := []string{"users", email}
return db.SetValue(usrPath, "password", string(cryptPw)) return db.bolt.SetValue(usrPath, "password", string(cryptPw))
} }
func dbDeleteUser(email string) error { func (db *gjDatabase) deleteUser(email string) error {
var err error var err error
if err = db.OpenDB(); err != nil { if err = db.open(); err != nil {
return err return err
} }
defer db.CloseDB() defer db.close()
return db.DeleteBucket([]string{"users"}, email) return db.bolt.DeleteBucket([]string{"users"}, email)
} }

View File

@ -1,11 +1,6 @@
package main package main
import ( import "time"
"fmt"
"strconv"
"strings"
"time"
)
// A Choice is a ranking of a game in a vote // A Choice is a ranking of a game in a vote
type GameChoice struct { type GameChoice struct {
@ -20,94 +15,17 @@ type Vote struct {
Choices []GameChoice Choices []GameChoice
} }
func dbGetAllVotes() []Vote { func (db *gjDatabase) getAllVotes() []Vote {
var ret []Vote var ret []Vote
var err error var err error
if err = db.OpenDB(); err != nil { if err = db.open(); err != nil {
return ret return ret
} }
defer db.CloseDB() defer db.close()
votesBkt := []string{"votes"} clients := db.getAllClients()
var clients []string for _, cl := range clients {
if clients, err = db.GetBucketList(votesBkt); err != nil { ret = append(ret, cl.getVotes()...)
// Couldn't get the list of clients
return ret
}
for _, clid := range clients {
ret = append(ret, dbGetClientVotes(clid)...)
} }
return ret return ret
} }
func dbGetClientVotes(clientId string) []Vote {
var ret []Vote
var err error
if err = db.OpenDB(); err != nil {
return ret
}
defer db.CloseDB()
var times []string
votesBkt := []string{"votes", clientId}
if times, err = db.GetBucketList(votesBkt); err != nil {
return ret
}
for _, t := range times {
var tm time.Time
if tm, err = time.Parse(time.RFC3339, t); err == nil {
var vt *Vote
if vt, err = dbGetVote(clientId, tm); err == nil {
ret = append(ret, *vt)
} else {
fmt.Println(err)
}
}
}
return ret
}
func dbGetVote(clientId string, timestamp time.Time) (*Vote, error) {
var err error
if err = db.OpenDB(); err != nil {
return nil, err
}
defer db.CloseDB()
vt := new(Vote)
vt.Timestamp = timestamp
vt.ClientId = clientId
votesBkt := []string{"votes", clientId, timestamp.Format(time.RFC3339)}
var choices []string
if choices, err = db.GetKeyList(votesBkt); err != nil {
// Couldn't find the vote...
return nil, err
}
for _, v := range choices {
ch := new(GameChoice)
var rank int
if rank, err = strconv.Atoi(v); err == nil {
ch.Rank = rank
ch.Team, err = db.GetValue(votesBkt, v)
vt.Choices = append(vt.Choices, *ch)
}
}
return vt, nil
}
func dbSaveVote(clientId string, timestamp time.Time, votes []string) error {
var err error
if err = db.OpenDB(); err != nil {
return nil
}
defer db.CloseDB()
// Make sure we don't clobber a duplicate vote
votesBkt := []string{"votes", clientId, timestamp.Format(time.RFC3339)}
for i := range votes {
if strings.TrimSpace(votes[i]) != "" {
db.SetValue(votesBkt, strconv.Itoa(i), votes[i])
}
}
return err
}

View File

@ -43,7 +43,7 @@ func (p *pageSession) getClientId() string {
fmt.Println(" Client IP:" + clientIp) fmt.Println(" Client IP:" + clientIp)
if clientIp != "127.0.0.1" { if clientIp != "127.0.0.1" {
fmt.Println(" Pulling data by IP") fmt.Println(" Pulling data by IP")
cli = dbGetClientByIp(clientIp) cli = db.getClientByIp(clientIp)
} }
if cli != nil { if cli != nil {
clientId = cli.UUID clientId = cli.UUID

View File

@ -17,7 +17,7 @@ func initPublicPage(w http.ResponseWriter, req *http.Request) *pageData {
func handleMain(w http.ResponseWriter, req *http.Request) { func handleMain(w http.ResponseWriter, req *http.Request) {
page := initPublicPage(w, req) page := initPublicPage(w, req)
if dbGetPublicSiteMode() == SiteModeWaiting { if db.getPublicSiteMode() == SiteModeWaiting {
page.SubTitle = "" page.SubTitle = ""
page.show("public-waiting.html", w) page.show("public-waiting.html", w)
} else { } else {
@ -28,7 +28,7 @@ func handleMain(w http.ResponseWriter, req *http.Request) {
func loadVotingPage(w http.ResponseWriter, req *http.Request) { func loadVotingPage(w http.ResponseWriter, req *http.Request) {
page := initPublicPage(w, req) page := initPublicPage(w, req)
// Client authentication required // Client authentication required
if (dbGetAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { if (db.getAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth {
page.show("unauthorized.html", w) page.show("unauthorized.html", w)
return return
} }
@ -37,7 +37,7 @@ func loadVotingPage(w http.ResponseWriter, req *http.Request) {
Timestamp string Timestamp string
} }
vpd := new(votingPageData) vpd := new(votingPageData)
tms := dbGetAllTeams() tms := db.getAllTeams()
// Randomize the team list // Randomize the team list
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
@ -55,7 +55,7 @@ func loadVotingPage(w http.ResponseWriter, req *http.Request) {
func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) { func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) {
page := initPublicPage(w, req) page := initPublicPage(w, req)
// Client authentication required // Client authentication required
if (dbGetAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { if (db.getAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth {
page.show("unauthorized.html", w) page.show("unauthorized.html", w)
return return
} }
@ -69,7 +69,8 @@ func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) {
page.session.setFlashMessage("Error parsing timestamp: "+ts, "error") page.session.setFlashMessage("Error parsing timestamp: "+ts, "error")
redirect("/", w, req) redirect("/", w, req)
} }
if _, err := dbGetVote(page.ClientId, timestamp); err == nil { client := db.getClient(page.ClientId)
if _, err := client.getVote(timestamp); err == nil {
// Duplicate vote... Cancel it. // Duplicate vote... Cancel it.
page.session.setFlashMessage("Duplicate vote!", "error") page.session.setFlashMessage("Duplicate vote!", "error")
redirect("/", w, req) redirect("/", w, req)
@ -77,10 +78,10 @@ func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) {
// voteSlice is an ordered string slice of the voters preferences // voteSlice is an ordered string slice of the voters preferences
voteCSV := req.FormValue("uservote") voteCSV := req.FormValue("uservote")
voteSlice := strings.Split(voteCSV, ",") voteSlice := strings.Split(voteCSV, ",")
if err := dbSaveVote(page.ClientId, timestamp, voteSlice); err != nil { if err := client.saveVote(timestamp, voteSlice); err != nil {
page.session.setFlashMessage("Error Saving Vote: "+err.Error(), "error") page.session.setFlashMessage("Error Saving Vote: "+err.Error(), "error")
} }
if newVote, err := dbGetVote(page.ClientId, timestamp); err == nil { if newVote, err := client.getVote(timestamp); err == nil {
site.Votes = append(site.Votes, *newVote) site.Votes = append(site.Votes, *newVote)
} }
page.session.setFlashMessage("Vote Saved!", "success large fading") page.session.setFlashMessage("Vote Saved!", "success large fading")
@ -90,7 +91,12 @@ func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) {
func handleThumbnailRequest(w http.ResponseWriter, req *http.Request) { func handleThumbnailRequest(w http.ResponseWriter, req *http.Request) {
// Thumbnail requests are open even without client authentication // Thumbnail requests are open even without client authentication
vars := mux.Vars(req) vars := mux.Vars(req)
ss := dbGetTeamGameScreenshot(vars["teamid"], vars["imageid"]) tm := db.getTeam(vars["teamid"])
if tm == nil {
http.Error(w, "Couldn't find image", 404)
return
}
ss := tm.getScreenshot(vars["imageid"])
if ss == nil { if ss == nil {
http.Error(w, "Couldn't find image", 404) http.Error(w, "Couldn't find image", 404)
return return
@ -107,7 +113,12 @@ func handleThumbnailRequest(w http.ResponseWriter, req *http.Request) {
func handleImageRequest(w http.ResponseWriter, req *http.Request) { func handleImageRequest(w http.ResponseWriter, req *http.Request) {
// Image requests are open even without client authentication // Image requests are open even without client authentication
vars := mux.Vars(req) vars := mux.Vars(req)
ss := dbGetTeamGameScreenshot(vars["teamid"], vars["imageid"]) tm := db.getTeam(vars["teamid"])
if tm == nil {
http.Error(w, "Couldn't find image", 404)
return
}
ss := tm.getScreenshot(vars["imageid"])
if ss == nil { if ss == nil {
http.Error(w, "Couldn't find image", 404) http.Error(w, "Couldn't find image", 404)
return return
@ -123,70 +134,72 @@ func handleImageRequest(w http.ResponseWriter, req *http.Request) {
func handleTeamMgmtRequest(w http.ResponseWriter, req *http.Request) { func handleTeamMgmtRequest(w http.ResponseWriter, req *http.Request) {
// Team Management pages are open even without client authentication // Team Management pages are open even without client authentication
if dbGetPublicSiteMode() == SiteModeVoting { if db.getPublicSiteMode() == SiteModeVoting {
redirect("/", w, req) redirect("/", w, req)
} }
page := initPublicPage(w, req) page := initPublicPage(w, req)
vars := mux.Vars(req) vars := mux.Vars(req)
page.SubTitle = "Team Details" page.SubTitle = "Team Details"
teamId := vars["id"] teamId := vars["id"]
if teamId != "" { tm := db.getTeam(teamId)
if tm != nil {
// Team self-management functions // Team self-management functions
if !dbIsValidTeam(teamId) {
http.Error(w, "Page Not Found", 404)
return
}
switch vars["function"] { switch vars["function"] {
case "": case "":
page.SubTitle = "Team Management" page.SubTitle = "Team Management"
t := dbGetTeam(teamId) page.TemplateData = tm
page.TemplateData = t
page.show("public-teammgmt.html", w) page.show("public-teammgmt.html", w)
case "savemember": case "savemember":
mbrName := req.FormValue("newmembername") m := newTeamMember(req.FormValue("newmembername"))
mbrSlack := req.FormValue("newmemberslackid") m.SlackId = req.FormValue("newmemberslackid")
mbrTwitter := req.FormValue("newmembertwitter") m.Twitter = req.FormValue("newmembertwitter")
mbrEmail := req.FormValue("newmemberemail") m.Email = req.FormValue("newmemberemail")
if err := dbAddTeamMember(teamId, mbrName, mbrEmail, mbrSlack, mbrTwitter); err != nil { if err := tm.updateTeamMember(m); err != nil {
page.session.setFlashMessage("Error adding team member: "+err.Error(), "error") page.session.setFlashMessage("Error adding team member: "+err.Error(), "error")
} else { } else {
page.session.setFlashMessage(mbrName+" added to team!", "success") page.session.setFlashMessage(m.Name+" added to team!", "success")
} }
refreshTeamsInMemory() refreshTeamsInMemory()
redirect("/team/"+teamId+"#members", w, req) redirect("/team/"+tm.UUID+"#members", w, req)
case "deletemember": case "deletemember":
mbrId := req.FormValue("memberid") mbrId := req.FormValue("memberid")
m, _ := dbGetTeamMember(teamId, mbrId) m := tm.getTeamMember(mbrId)
if err := dbDeleteTeamMember(teamId, mbrId); err != nil { if m != nil {
page.session.setFlashMessage("Error deleting team member: "+err.Error(), "error") if err := tm.deleteTeamMember(m); err != nil {
page.session.setFlashMessage("Error deleting team member: "+err.Error(), "error")
} else {
page.session.setFlashMessage(m.Name+" deleted from team", "success")
}
} else { } else {
page.session.setFlashMessage(m.Name+" deleted from team", "success") page.session.setFlashMessage("Couldn't find member to delete", "error")
} }
refreshTeamsInMemory() refreshTeamsInMemory()
redirect("/team/"+teamId, w, req) redirect("/team/"+tm.UUID, w, req)
case "savegame": case "savegame":
name := req.FormValue("gamename") gm := newGame(tm.UUID)
link := req.FormValue("gamelink") gm.Name = req.FormValue("gamename")
desc := req.FormValue("gamedesc") gm.Link = req.FormValue("gamelink")
if dbIsValidTeam(teamId) { gm.Description = req.FormValue("gamedesc")
if err := dbUpdateTeamGame(teamId, name, link, desc); err != nil { if err := gm.save(); err != nil {
page.session.setFlashMessage("Error updating game: "+err.Error(), "error") page.session.setFlashMessage("Error updating game: "+err.Error(), "error")
} else { } else {
page.session.setFlashMessage("Team game updated", "success") page.session.setFlashMessage("Team game updated", "success")
}
redirect("/team/"+teamId, w, req)
} }
redirect("/team/"+tm.UUID, w, req)
case "screenshotupload": case "screenshotupload":
if err := saveScreenshots(teamId, req); err != nil { if err := saveScreenshots(tm, req); err != nil {
page.session.setFlashMessage("Error updating game: "+err.Error(), "error") page.session.setFlashMessage("Error updating game: "+err.Error(), "error")
} }
redirect("/team/"+teamId, w, req) redirect("/team/"+tm.UUID, w, req)
case "screenshotdelete": case "screenshotdelete":
ssid := vars["subid"] ssid := vars["subid"]
if err := dbDeleteTeamGameScreenshot(teamId, ssid); err != nil { if err := tm.deleteScreenshot(ssid); err != nil {
page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error") page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error")
} }
redirect("/team/"+teamId, w, req) redirect("/team/"+tm.UUID, w, req)
} }
} else {
http.Error(w, "Page Not Found", 404)
return
} }
} }