From 96a53b60905d2ec9b8c7a9c16de9191f5bc5d826 Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Fri, 30 Jun 2017 13:35:36 -0600 Subject: [PATCH] Voting is working --- admin_games.go | 14 +- assets.go | 77 ++++----- assets/css/gjvote.css | 44 ++++- main.go | 7 +- model_teams.go | 5 +- model_votes.go | 82 ++++++++-- page_session.go | 2 +- public_endpoints.go | 42 ++++- templates/admin-games.html | 8 +- templates/admin-menu.html | 4 +- templates/header.html | 5 +- templates/public-voting.html | 305 ++++++++++++++++++++++++++++++++++- 12 files changed, 515 insertions(+), 80 deletions(-) diff --git a/admin_games.go b/admin_games.go index 72b6e5f..0c5dda4 100644 --- a/admin_games.go +++ b/admin_games.go @@ -15,21 +15,11 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) teamId := vars["id"] if teamId == "" { // Games List - type gamePage struct { - Game *Game - Team *Team - } type gamesPageData struct { - Games []gamePage + Teams []Team } gpd := new(gamesPageData) - allGames := dbGetAllGames() - for _, gm := range allGames { - gamePage := new(gamePage) - gamePage.Game = &gm - gamePage.Team = dbGetTeam(gm.TeamId) - gpd.Games = append(gpd.Games, *gamePage) - } + gpd.Teams = dbGetAllTeams() page.TemplateData = gpd page.SubTitle = "Games" page.show("admin-games.html", w) diff --git a/assets.go b/assets.go index 5e9b6ea..e8b6b55 100644 --- a/assets.go +++ b/assets.go @@ -192,35 +192,38 @@ var _escData = map[string]*_escFile{ "/assets/css/admin.css": { local: "assets/css/admin.css", - size: 83, - modtime: 1497482098, + size: 121, + modtime: 1498145831, compressed: ` -H4sIAAAAAAAA/0rJLNNLzs8rSc0rUajmUlDITSxKz8zTzUlNK7FSMDQ1KKiw5qrl4gIpS8ovKcnP1S0u -SExORVYLEQephigGBAAA//8E7lAbUwAAAA== +H4sIAAAAAAAA/0zKQQoCMQwAwHtekQ/sogcv62u6NdZAk5Q0FUH8u1Q9eB3mwvc1mwZp4BMQJXlhXSpd +Y8Pj6dAeZ3gBzLZbhMnSW8r0f78+9y+zlDVuQ3ZNXD8zD+/mGzZjDfK53gEAAP//D/6XxHkAAAA= `, }, "/assets/css/gjvote.css": { local: "assets/css/gjvote.css", - size: 2862, - modtime: 1497542484, + size: 3710, + modtime: 1498849329, compressed: ` -H4sIAAAAAAAA/6xW22rkOBB9768QNAsJrIydSU8SB4Z9mOx/yFa1XUSWjCR3O7Pk3xdd7PY17MI0/WCX -6nKqdKrKtW0E+edASIOS1oBVbXOSpekfr4fPw6FQ/MOflkoonZPj09PTa1SGusKZMsdLUippQVpv0zLO -UVY5yU5t/7oZYnRcCFa+ey9JDYyDDpiYrlBSAWebk9Qfo2w7m5xRwDxGcoKGJN+hcU45mlawj5ygFCiB -FkI574QUSnPQOcnanhglkJNjWZbhpKemZlxdnZUBS1Kv9a3tyZFzfrOmmnHsTE4eXVafh8OxAdl5NFfk -tr7lNuT6mIb8W2XQopI50SCYxQt4r6x8r7TqJM/JMXvJnrNnJ75C8Y6Wqgvos1BXakqthPC5WtWV9SR0 -0nYaqHukrngoK49mpwpbZlI5lfetq55XOFsUeKyshd5SDqXSLKQolfTpXWu0QE3LSnDCq2btFIMPX3TW -KrkFel42VhglOuulVrU5iYXVoc7xLbLGK2RzmR7IF6QxVQ/xiyQcKwNOZa1qZld9iq4iN0LQkV3R8yJd -1yQMZST5mPFQse185zn6OAOvbgyiw+UNPNqL/KMLXf+1qevoWmn85cxEpKC3G2nZ5ySIQ3c2VWLrrikk -Q7FoiYB10v+xfW4Ge4UZSTYaP+xn/vb2FtxG4s7HxGkI6gk5GTLLI2qQg1lPodMWn0bTAiu69pyNqTof -/sgzjQmsZE6c0B0n3tvq2Ev9eQnSxtpMFYI4BAjPlMVr2lIj5ALaYsnE+mRAzDqrvEPLCgFDYMu/ih1U -h6pH3XXh/dAJDU9Ba6X3ePjzZ5qm6aRNj3/7XyS14kz46ShYWFEXNFigQPuRkxo5B7nfTHGjTHopPu9M -8OF9u56/KEoOvVdLt2mpq4LdpX+S+E9O9xtp/OB4mTbNKZ1Nr9g08XI2a+bLs7nmBmTzFTYMwtWq3rnj -vxrgyMid2+QR5GMKzb1HfduD6/G4LuX0as7Yg9+wn4fRz2ozbEzKlfo4QBYGKNEiE68zbBGId7Ka71sp -bABeUMkV2LkYBcH55JNmMU5G716RuaGTnAUz9f9STkxXlmBMNNogxsPDCzu/BIjLdhpQTr/d9iPfWGBK -DSAJk5zcNayPhPh2ch8K93GCX0K/O9daCepQtUSwAkQyyPxbDLoei/8l5PdHFzAIJ8R8/v0w/g0AAP// -ePEOly4LAAA= +H4sIAAAAAAAA/6xX62rrOBD+36cQhIVTWBmnpzltHDgs9LTvIVuKPUSWjCQnaZe++6KLHcuWuxcW+iOW +5vLNzDczamNajv68Q6gFgRsGdWMKtM3z3w53n3d3paTv7raSXKoCbZ6eng5BmDU1RMLESRp2NZiySipi +QIoCCSmYu6dwziopDBPGSXaEUhB1gba77npIQhgdl5xUp9FKQ/jRmbgANU2BdgFB1jBCmfLxEFWDwJwd +TYFyf90paImKI8rzp2f2FN3jsnYiJalOtZK9oDghDaLrTXYEzuJosh1rUfaDtRY+Bd1x8l4gEBwEwyWX +Ng6ESqkoUwXadlekJQeKNlVV+Zsr1g2h8mK1NDMod1LfuyvaUEpv2lgRCr0u0KPN3whJEXECUWP3FafJ +53mmvZueRoheXl4OQ0kJh1oUqGLCMOXJ0RsjRab7sgWDz9LYRFgUWSvPDEMlhc90r7RNntBYMQ0fng2b +lol+im6o+MCAx4C2kxo8kxTjxMCZHaLaFGiz3W+ft8/2+MLKExgsz0wdubxgXSnJuauLkX3VTFxnXa8Y +tj+xpQ0IX/SViqXUhLQip1SDxGzYzsgwsmClWRC6NGAY1h2pmD28KNJNMTj3Pv8p0HHaSKkl7407NbIr +UEis8nkOX6FfnMA2PlNDS/rTEKqD+EUQtqE8TmmMbKNSz5jonY68C5Zn4drRQUCE9h4jHjKWjjeO0fkZ +eJXo7oFHa55/9nxtMExU3YSSCj6sGg8UdHojLa8F8se+bds6M03floKAF5xMwdDaN4G1RIykGpUf1iN9 +fX0NI88TNR5hu8GpI+BknM6vsAbK9HLe7lL8mamemTJQET7VdgWKlT17Jtol1HiJazsmyiK4baIwtuyh +vc4clsW1O3X3fr4tBCZjL4hgEoqaEkNoiG55MyAmvZHOoCElZ4NjQ7/y7UWHmgXZZdnciPLjATOlpFpj +7a9feZ7nk6bevL29LW0YWdec4SMo7XN361tf3HGXxH3ta5cS+Uw6aIHSsE5nOyoeSJ5keBs584fz7ZV2 +xMkyEKv/dRwpif+CKIJUUNC2qnStRmEJD5/7/T4MKEkJd5uOE/+kOYOGEjiY9wI1QCkT64MxvIsmczH8 +XtnGw3ea7R8YBGVXJ5anR46qS/It/x2Fv2x3nwjjJ4Vz/GKJ0hwGYmidZLYcf5OPmQFZTKxhqS0eoysd ++EfLKBD0zb5VA8jHnLX3DvXtTbNcdctUTktzhCtzL7vPu9HOYssntt5CfFwOMwUQYIDwQ4QtAHFGFrs6 +FUIC8IxKNsF+YIcDb3zyMJ/1zGjdCRK7ULIjJ7r5V8LZbcz9zaCLRl1gy8Ka7quKab1u7+FhT477L+1F +/+usx3HjlK4UYwIRQdG3llwDvb7v7BPyPuz6sx8d1rSSHFtUHeKkZDwbztxXcLpcgf/E5Y9H69AfTmj+ +/P/D+CsAAP//kMgK/X4OAAA= `, }, @@ -315,22 +318,22 @@ H4sIAAAAAAAA/0rOzyvOz0nVy8lP11DydA5RcE/MTfVKzFVwTMnNzFPStOYCBAAA//8imS6KIgAAAA== "/assets/js/gjvote.js": { local: "assets/js/gjvote.js", - size: 2430, - modtime: 1497536571, + size: 2455, + modtime: 1498145831, compressed: ` -H4sIAAAAAAAA/6RWT2+rOBC/8ylG6cGgtGi1l5XComq3W2lXaquV2ttqDwRPwiiOnYcNeajNd3+yDYSk -IUn1emiL5/cHz4zHLCqZG1ISjFouBf7B1yT/zSSKMIL3AICrvFqjNPG3CsvmFQXmRpUhu1mjrFgU5yLT -+om0ib1AyAriHCWLkmAXBEHPV3KFDVdbCSl0piHWxttgbSB1vz8+YEuSq22MNUqTBAB1VgLpR51nG7Ts -TGi067QIJytsJkASeiUYQq1BvMIG0hQmfnViHY6WJ5GV2wEKjeMiD4qjZfz6m4f7N+iAnfvnPHrwLgj6 -XOtCbZ8Vz0SoNnZBe7Ld59ouQ7rP+xLNo0D775/NPzxkDnCnaixF1jAnfgFryAhkUUxSYvmG322mO+PY -BaP7g8fZZHKFrK7mZ5W7+F68W2n1adFj54o3XQIv2FrokeVQZVDII/0XxfFrHtlmg5I/FCT4Z6VBC/Sx -ypi+mAALVYaudSGFXxIg+B2OkLFAuTRFAjSddizfBnMjk/bxs8F/9H9M+rWar8nsaWBJw87JS8wMttsL -mXZ43zH256DdL9OzAbP9e+K1XH2HXXAcS9NKclyQRB7dMzYbwyXjHkWJixELGzp0uDlpYXFnHHJB+WrE -wsUOPPppFr3vTpk5xlm3TOtRt0zrK3LmcGc8NkqTGz2nbbrwoVNJy8KctOvwSRD0veOP5N9vz0/DIzlS -VovveuUabFvzC6W0SJeJl2xtlZneZDnCpirxzlOATceyN2Wng/u9ehMts3wVC9IGJZZhf3zepeI4mxt5 -C+7mmjFXeLa77SFjzdEC+gN2aTx5+tGEmhvZCuza0eTQsTaNwLgmTXMSZBqbF/ckkCUH11JBHP219HP3 -0bhv+23w1UurbSp2DXF4Pxzw3Fj1mfuL6stb6rMcAGwLEhju2fGCSm1c3rsBPAiWuFY1tlU5yek+CX4E -AAD///kqVXt+CQAA +H4sIAAAAAAAA/6RWT0/jOhC/51OMysGJChF6lye1L0Lv8ZB2JUArwW21hzSeNqO6djd20o2g331lO0nT +0rRFywGI5/cnnhl7Mi9lZkhJMGqxEPgvX5H8lkoUYQRvAQBXWblCaeKfJRb1CwrMjCpCdrVCWbIozkSq +9SNpE3uBkOXEOUoWTYNtEAQdX8kl1lxtJCTQmoZYGW+DlYHE/X5/hw1JrjYxVijNNACo0gJIP+gsXaNl +p0KjXad5OFpiPQKS0ClBH2oN4iXWkCQw8qsj63CwPIqs3BZQaBwWuVccLeOvvz3cv0ELbN0/5tGDt0HQ +5VrnavOkeCpCtbYL2pPtPld2GZJd3hdoHgTaf/+rv/KQOcCNqrAQac2c+BmsISOQRTFJicUr/rKZbo1j +F4zu9h4no9EFsrqcnVRu4zvxdqXRp3mHnSletwk8Y2uhB5Z9lV4hD/SfFcfPeaTrNUp+n5PgH5V6LdDF +SmO6YgLMVRG61oUEbqdA8A8cIGOBcmHyKdB43LJ8G8yMnDaPHw2+04+Y9Es5W5HZ0cCS+p2TFZgabLYX +Mu3wvmPsz167n6enPWbz98hrufr2u+AwliSl5DgniTy6Y2wyhJsOe+QFzgcsbGjf4eqohcWdcMgEZcsB +Cxfb8+hus+hte8zMMU66pVoPuqVaX5AzhzvhsVaa3NVz3KYN7zsVtMjNUbsWPw2Crnf8kfzy+vTYP5ID +ZbX4tlcuwTY1P1NKi3SZeE5XVpnpdZohrMsCbzwF2Hgoe2N2PLjbqzfRMs2WsSBtUGIRdsfnTSqOk5mR +1+Am14S5wrPtdQcZao4G0B2wc9eTpx/cUDMjG4FtczU5dKxNLTCuSNOMBJna5sU9CWQW30xbnRVKiFcV +3l7DrR/e3bzKiaOfV382qIZfqPlo+Ow0a7qNXULsD449nrtvfUr/p+r8lrr02+TlJDDcseM5Fdq4grQ3 +cy9Y4EpV2JTrKKf9VvgdAAD//3s8PKeXCQAA `, }, diff --git a/assets/css/gjvote.css b/assets/css/gjvote.css index 4460bbf..d7fe0fc 100644 --- a/assets/css/gjvote.css +++ b/assets/css/gjvote.css @@ -7,16 +7,32 @@ body { min-ehgiht: 100%; } +a { + text-decoration: none; +} + div.content { padding: 15px; min-height: 100%; color: black; } +div.half { + width: 50%; +} + .header { margin-left: 0; } +.primary { + color: #0078e7; +} + +.primary-bg { + background-color: #0078e7; +} + input.file { padding: .5em .6em; display: inline-block; @@ -25,6 +41,20 @@ input.file { border-radius: 4px; } +input.ranking-input { + width: 50px; + border-radius: 5px; + border: 1px solid #CCC; + text-align: center; +} + +button.submit-vote { +} + +i.move-icon { + cursor: ns-resize; +} + #menu { width: 100%; height: 40px; @@ -100,6 +130,11 @@ img.thumbnail { margin-right: 5px; } +.space-vertical { + margin-top: 5px; + margin-bottom: 5px; +} + .big-space { margin: 10px; } @@ -139,16 +174,21 @@ table.padding td { } .pure-button-toggle-middle { border-radius: 0px; - margin-left: -5px; + margin-left: -1px; border-left: 1px solid #CCC; } .pure-button-toggle-last { border-top-left-radius: 0px; border-bottom-left-radius: 0px; - margin-left: -5px; + margin-left: -1px; border-left: 1px solid #CCC; } +.pure-button:disabled { + background-color: #CCC; + color: #999; +} + #modal-overlay { visibility: hidden; position: absolute; diff --git a/main.go b/main.go index cc70594..4648c83 100644 --- a/main.go +++ b/main.go @@ -51,7 +51,7 @@ type pageData struct { HideAdminMenu bool session *pageSession CurrentJam string - ClientID string + ClientId string ClientIsAuth bool ClientIsServer bool TeamID string @@ -91,6 +91,7 @@ func main() { // Public Subrouter pub := r.PathPrefix("/").Subrouter() pub.HandleFunc("/", handleMain) + pub.HandleFunc("/vote", handlePublicSaveVote) // API Subrouter //api := r.PathPrefix("/api").Subtrouter() @@ -265,8 +266,8 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData { p.FlashClass = "error" } - p.ClientID = p.session.getClientID() - p.ClientIsAuth = clientIsAuthenticated(p.ClientID, req) + p.ClientId = p.session.getClientId() + p.ClientIsAuth = clientIsAuthenticated(p.ClientId, req) p.ClientIsServer = clientIsServer(req) // TeamID is for team self-administration p.TeamID, _ = p.session.getStringValue("teamid") diff --git a/model_teams.go b/model_teams.go index 1e89ad2..e116337 100644 --- a/model_teams.go +++ b/model_teams.go @@ -2,7 +2,6 @@ package main import ( "errors" - "fmt" "github.com/pborman/uuid" ) @@ -217,10 +216,8 @@ func dbGetTeamMembers(teamid string) ([]TeamMember, error) { ret = append(ret, *mbr) } } - } else { - fmt.Println(err.Error()) } - return ret, nil + return ret, err } func dbGetTeamMember(teamid, mbrid string) (*TeamMember, error) { diff --git a/model_votes.go b/model_votes.go index bf6e29c..5b30e7a 100644 --- a/model_votes.go +++ b/model_votes.go @@ -1,10 +1,21 @@ package main -import "time" +import ( + "strconv" + "time" +) +// A Choice is a ranking of a game in a vote +type GameChoice struct { + Team string // UUID of team + Rank int +} + +// A Vote is a collection of game rankings type Vote struct { Timestamp time.Time - ClientId string + ClientId string // UUID of client + Choices []GameChoice } func dbGetAllVotes() []Vote { @@ -16,19 +27,70 @@ func dbGetAllVotes() []Vote { defer db.CloseDB() votesBkt := []string{"votes"} + var clients []string + if clients, err = db.GetBucketList(votesBkt); err != nil { + // Couldn't get the list of clients + return ret + } + for _, clid := range clients { + ret = append(ret, dbGetClientVotes(clid)...) + } return ret } -func dbGetVote(clientId string, timestamp time.Time) *Vote { +func dbGetClientVotes(clientId string) []Vote { + var ret []Vote var err error if err = db.OpenDB(); err != nil { - return 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) + } + } + } + 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) - - return vt + vt.Timestamp = timestamp + vt.ClientId = clientId + votesBkt := []string{"votes", clientId, timestamp.Format(time.RFC3339)} + var choices []string + if choices, err = db.GetBucketList(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) + } + if err == nil { + vt.Choices = append(vt.Choices, *ch) + } + } + return vt, nil } func dbSaveVote(clientId string, timestamp time.Time, votes []string) error { @@ -37,8 +99,10 @@ func dbSaveVote(clientId string, timestamp time.Time, votes []string) error { return nil } defer db.CloseDB() - - votesBkt := []string{"votes", clientId} - + // Make sure we don't clobber a duplicate vote + votesBkt := []string{"votes", clientId, timestamp.Format(time.RFC3339)} + for i := range votes { + db.SetValue(votesBkt, strconv.Itoa(i), votes[i]) + } return err } diff --git a/page_session.go b/page_session.go index c4e33ab..f3f787a 100644 --- a/page_session.go +++ b/page_session.go @@ -31,7 +31,7 @@ func (p *pageSession) setStringValue(key, val string) { p.session.Save(p.req, p.w) } -func (p *pageSession) getClientID() string { +func (p *pageSession) getClientId() string { var clientId string var err error if clientId, err = p.getStringValue("client_id"); err != nil { diff --git a/public_endpoints.go b/public_endpoints.go index c5f6299..4bf62e5 100644 --- a/public_endpoints.go +++ b/public_endpoints.go @@ -2,6 +2,8 @@ package main import ( "net/http" + "strings" + "time" ) func initPublicPage(w http.ResponseWriter, req *http.Request) *pageData { @@ -16,6 +18,44 @@ func handleMain(w http.ResponseWriter, req *http.Request) { case SiteModeWaiting: page.show("public-waiting.html", w) case SiteModeVoting: - page.show("public-voting.html", w) + loadVotingPage(w, req) } } + +func loadVotingPage(w http.ResponseWriter, req *http.Request) { + page := initPublicPage(w, req) + type votingPageData struct { + Teams []Team + Timestamp string + } + vpd := new(votingPageData) + vpd.Teams = dbGetAllTeams() + vpd.Timestamp = time.Now().Format(time.RFC3339) + page.TemplateData = vpd + page.show("public-voting.html", w) +} + +func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) { + page := initPublicPage(w, req) + page.SubTitle = "" + + // Check if we already have a vote for this client id/timestamp + ts := req.FormValue("timestamp") + timestamp, err := time.Parse(time.RFC3339, ts) + if err != nil { + page.session.setFlashMessage("Error parsing timestamp: "+ts, "error") + redirect("/", w, req) + } + if _, err := dbGetVote(page.ClientId, timestamp); err == nil { + // Duplicate vote... Cancel it. + page.session.setFlashMessage("Duplicate vote!", "error") + redirect("/", w, req) + } + // voteSlice is an ordered string slice of the voters preferences + voteCSV := req.FormValue("uservote") + voteSlice := strings.Split(voteCSV, ",") + if err := dbSaveVote(page.ClientId, timestamp, voteSlice); err != nil { + page.session.setFlashMessage("Error Saving Vote: "+err.Error(), "error") + } + redirect("/", w, req) +} diff --git a/templates/admin-games.html b/templates/admin-games.html index c00ed01..dfdb7fe 100644 --- a/templates/admin-games.html +++ b/templates/admin-games.html @@ -1,4 +1,4 @@ -{{ if not .TemplateData.Games }} +{{ if not .TemplateData.Teams }}
No games have been created
{{ else }} @@ -10,11 +10,11 @@ - {{ range $i, $v := .TemplateData.Games }} + {{ range $i, $v := .TemplateData.Teams }} + - - + {{ end }} diff --git a/templates/admin-menu.html b/templates/admin-menu.html index b1fa816..0c3a25f 100644 --- a/templates/admin-menu.html +++ b/templates/admin-menu.html @@ -13,10 +13,10 @@ {{ if .ClientIsAuth }} {{ if not .ClientIsServer }} - DeAuth Client + DeAuth Client {{ end }} {{ else }} - Auth Client + Auth Client {{ end }}
{{ $v.Game.Name }} {{ $v.Name }}{{ len $v.Screenshots }}{{ len $v.Game.Screenshots }}
+ + + + + + + + + + + +
RankGame NameTeam NameScreenshots
+ +
+

Unranked Games

+ + + + + + + + + + + {{ range $i, $v := .TemplateData.Teams }} + + + + + + + {{ end }} + +
Game NameTeam NameScreenshots
Add to Vote{{ $v.Game.Name }}{{ $v.Name }} + {{ if not $v.Game.Screenshots }} + (No Screenshots) + {{ else }} + ({{ len $v.Game.Screenshots }}) + {{ end }} +
+
+
+
+ + + +
+
+{{ end }} +