Making Progress

This commit is contained in:
Brian Buller 2017-04-21 13:17:18 -05:00
parent 3b9efffc3f
commit abd7e803e9
12 changed files with 531 additions and 137 deletions

View File

@ -4,57 +4,13 @@ import (
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/sessions"
) )
func initAdminRequest(w http.ResponseWriter, req *http.Request) *pageData { func initAdminRequest(w http.ResponseWriter, req *http.Request) *pageData {
if site.DevMode { p := InitPageData(w, req)
w.Header().Set("Cache-Control", "no-cache")
}
p := new(pageData)
// Get session
var err error
var s *sessions.Session
if s, err = sessionStore.Get(req, site.SessionName); err != nil {
http.Error(w, err.Error(), 500)
return p
}
p.session = new(pageSession)
p.session.session = s
p.session.req = req
p.session.w = w
// First check if we're logged in
userEmail, _ := p.session.getStringValue("email")
// With a valid account
p.LoggedIn = dbIsValidUserEmail(userEmail)
p.Site = site
p.SubTitle = ""
p.Stylesheets = make([]string, 0, 0)
p.Stylesheets = append(p.Stylesheets, "/assets/css/pure-min.css")
p.Stylesheets = append(p.Stylesheets, "/assets/css/grids-responsive-min.css")
p.Stylesheets = append(p.Stylesheets, "/assets/font-awesome/css/font-awesome.min.css")
p.Stylesheets = append(p.Stylesheets, "/assets/css/gjvote.css")
p.Stylesheets = append(p.Stylesheets, "/assets/css/admin.css") p.Stylesheets = append(p.Stylesheets, "/assets/css/admin.css")
p.HeaderScripts = make([]string, 0, 0)
p.HeaderScripts = append(p.HeaderScripts, "/assets/js/snack-min.js")
p.Scripts = make([]string, 0, 0)
p.Scripts = append(p.Scripts, "/assets/js/admin.js") p.Scripts = append(p.Scripts, "/assets/js/admin.js")
p.FlashMessage, p.FlashClass = p.session.getFlashMessage()
if p.FlashClass == "" {
p.FlashClass = "hidden"
}
// Build the menu
if p.LoggedIn {
p.Menu = append(p.Menu, menuItem{"Votes", "/admin/votes", "fa-sticky-note"})
p.Menu = append(p.Menu, menuItem{"Teams", "/admin/teams", "fa-users"})
p.Menu = append(p.Menu, menuItem{"Games", "/admin/games", "fa-gamepad"})
p.BottomMenu = append(p.BottomMenu, menuItem{"Users", "/admin/users", "fa-user"})
p.BottomMenu = append(p.BottomMenu, menuItem{"Logout", "/admin/dologout", "fa-sign-out"})
}
return p return p
} }
@ -183,6 +139,59 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData)
// handleAdminTeams // handleAdminTeams
func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData) { func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData) {
vars := mux.Vars(req)
page.SubTitle = "Teams"
teamId := vars["id"]
if teamId == "new" {
switch vars["function"] {
case "save":
name := req.FormValue("teamname")
if dbIsValidTeam(name) {
// A team with that name already exists
page.session.setFlashMessage("A team with the name "+name+" already exists!", "error")
} else {
if err := dbCreateNewTeam(name); err != nil {
page.session.setFlashMessage(err.Error(), "error")
} else {
page.session.setFlashMessage("Team "+name+" created!", "success")
}
}
redirect("/admin/teams", w, req)
default:
page.SubTitle = "Add New Team"
page.show("admin-addteam.html", w)
}
} else if teamId != "" {
if dbIsValidTeam(teamId) {
switch vars["function"] {
case "save":
page.session.setFlashMessage("Not implemented yet...", "success")
redirect("/admin/teams", w, req)
case "delete":
var err error
if err = dbDeleteTeam(teamId); err != nil {
page.session.setFlashMessage("Error deleting team: "+err.Error(), "error")
}
redirect("/admin/teams", w, req)
default:
page.SubTitle = "Edit Team"
t := dbGetTeam(teamId)
page.TemplateData = t
page.show("admin-editteam.html", w)
}
} else {
page.session.setFlashMessage("Couldn't find the requested team, please try again.", "error")
redirect("/admin/teams", w, req)
}
} else {
type teamsPageData struct {
Teams []Team
}
page.TemplateData = teamsPageData{Teams: dbGetAllTeams()}
page.SubTitle = "Teams"
page.show("admin-teams.html", w)
}
} }
// handleAdminGames // handleAdminGames

View File

@ -1 +1,17 @@
console.log('ICTGameJam Wahoo!'); function showAdminPanel() {
}
document.onkeydown = function(evt) {
evt = evt || window.event;
var isEscape = false;
if("key" in evt) {
isEscape = (evt.key == "Escape" || evt.key == "Esc");
} else {
isEscape = (evt.keyCode == 27);
}
if(isEscape) {
showAdminPanel();
}
}

View File

@ -1 +1 @@
{"title":"ICT GameJam Voting","port":8080,"session":"ict-gamejam","dir":"./","devmode":true,"db":"gjvote.db"} {"title":"ICT GameJam Voting","port":8080,"session":"ict-gamejam","dir":"./","devmode":true,"db":"gjvote.db","CurrentJam":""}

75
main.go
View File

@ -30,6 +30,8 @@ type siteData struct {
ServerDir string `json:"dir"` ServerDir string `json:"dir"`
DevMode bool `json:"devmode"` DevMode bool `json:"devmode"`
DB string `json:"db"` DB string `json:"db"`
CurrentJam string
} }
// pageData is stuff that changes per request // pageData is stuff that changes per request
@ -47,6 +49,7 @@ type pageData struct {
Menu []menuItem Menu []menuItem
BottomMenu []menuItem BottomMenu []menuItem
session *pageSession session *pageSession
CurrentJam string
TemplateData interface{} TemplateData interface{}
} }
@ -124,7 +127,7 @@ func saveConfig() {
func initialize() { func initialize() {
// Check if the database has been created // Check if the database has been created
assertError(openDatabase()) assertError(initDatabase())
if !dbHasUser() { if !dbHasUser() {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
@ -146,12 +149,82 @@ func initialize() {
} }
assertError(dbUpdateUserPassword(email, string(pw1))) assertError(dbUpdateUserPassword(email, string(pw1)))
} }
if !dbHasCurrentJam() {
reader := bufio.NewReader(os.Stdin)
fmt.Println("Create New Game Jam")
fmt.Print("GameJam Name: ")
gjName, _ := reader.ReadString('\n')
gjName = strings.TrimSpace(gjName)
if dbSetCurrentJam(gjName) != nil {
fmt.Println("Error saving Current Jam")
}
}
} }
func loggingHandler(h http.Handler) http.Handler { func loggingHandler(h http.Handler) http.Handler {
return handlers.LoggingHandler(os.Stdout, h) return handlers.LoggingHandler(os.Stdout, h)
} }
func InitPageData(w http.ResponseWriter, req *http.Request) *pageData {
if site.DevMode {
w.Header().Set("Cache-Control", "no-cache")
}
p := new(pageData)
// Get session
var err error
var s *sessions.Session
if s, err = sessionStore.Get(req, site.SessionName); err != nil {
http.Error(w, err.Error(), 500)
return p
}
p.session = new(pageSession)
p.session.session = s
p.session.req = req
p.session.w = w
// First check if we're logged in
userEmail, _ := p.session.getStringValue("email")
// With a valid account
p.LoggedIn = dbIsValidUserEmail(userEmail)
p.Site = site
p.SubTitle = "GameJam Voting"
p.Stylesheets = make([]string, 0, 0)
p.Stylesheets = append(p.Stylesheets, "/assets/css/pure-min.css")
p.Stylesheets = append(p.Stylesheets, "/assets/css/grids-responsive-min.css")
p.Stylesheets = append(p.Stylesheets, "/assets/font-awesome/css/font-awesome.min.css")
p.Stylesheets = append(p.Stylesheets, "/assets/css/gjvote.css")
p.HeaderScripts = make([]string, 0, 0)
p.HeaderScripts = append(p.HeaderScripts, "/assets/js/snack-min.js")
p.Scripts = make([]string, 0, 0)
p.Scripts = append(p.Scripts, "/assets/js/gjvote.js")
p.FlashMessage, p.FlashClass = p.session.getFlashMessage()
if p.FlashClass == "" {
p.FlashClass = "hidden"
}
// Build the menu
if p.LoggedIn {
p.Menu = append(p.Menu, menuItem{"Admin", "/admin", "fa-key"})
p.Menu = append(p.Menu, menuItem{"Votes", "/admin/votes", "fa-sticky-note"})
p.Menu = append(p.Menu, menuItem{"Teams", "/admin/teams", "fa-users"})
p.Menu = append(p.Menu, menuItem{"Games", "/admin/games", "fa-gamepad"})
p.BottomMenu = append(p.BottomMenu, menuItem{"Users", "/admin/users", "fa-user"})
p.BottomMenu = append(p.BottomMenu, menuItem{"Logout", "/admin/dologout", "fa-sign-out"})
}
if p.CurrentJam, err = dbGetCurrentJam(); err != nil {
p.FlashMessage = "Error Loading Current GameJam: " + err.Error()
p.FlashClass = "error"
}
return p
}
func (p *pageData) show(tmplName string, w http.ResponseWriter) error { func (p *pageData) show(tmplName string, w http.ResponseWriter) error {
for _, tmpl := range []string{ for _, tmpl := range []string{
"htmlheader.html", "htmlheader.html",

109
model.go
View File

@ -1,11 +1,6 @@
package main package main
import ( import "github.com/br0xen/boltease"
"fmt"
"github.com/br0xen/boltease"
"golang.org/x/crypto/bcrypt"
)
var db *boltease.DB var db *boltease.DB
var dbOpened bool var dbOpened bool
@ -24,83 +19,73 @@ func openDatabase() error {
func initDatabase() error { func initDatabase() error {
openDatabase() openDatabase()
db.MkBucketPath([]string{"users"}) // Create the path to the bucket to store admin users
db.MkBucketPath([]string{"teams"}) if err := db.MkBucketPath([]string{"users"}); err != nil {
return nil return err
}
// Create the path to the bucket to store jam informations
if err := db.MkBucketPath([]string{"jams"}); err != nil {
return err
}
// Create the path to the bucket to store site config data
return db.MkBucketPath([]string{"site"})
} }
// dbHasUser func dbSetCurrentJam(name string) error {
// Returns true if there are any users in the database
func dbHasUser() bool {
return len(dbGetAllUsers()) > 0
}
func dbGetAllUsers() []string {
if err := db.OpenDB(); err != nil { if err := db.OpenDB(); err != nil {
return []string{} return err
} }
defer db.CloseDB() defer db.CloseDB()
usrs, err := db.GetBucketList([]string{"users"}) return db.SetValue([]string{"site"}, "current-jam", name)
if err != nil {
return []string{}
}
return usrs
} }
func dbIsValidUserEmail(email string) bool { func dbHasCurrentJam() bool {
if err := db.OpenDB(); err != nil { var nm string
var err error
if nm, err = dbGetCurrentJam(); err != nil {
return false return false
} }
defer db.CloseDB() ret, err := dbIsValidJam(nm)
return ret && err != nil
usrPath := []string{"users", email}
if _, err := db.GetValue(usrPath, "password"); err != nil {
return false
}
return true
} }
func dbCheckCredentials(email, pw string) error { func dbGetCurrentJam() (string, error) {
var err error
if err = db.OpenDB(); err != nil {
return err
}
defer db.CloseDB()
var uPw string
usrPath := []string{"users", email}
if uPw, err = db.GetValue(usrPath, "password"); err != nil {
return err
}
return bcrypt.CompareHashAndPassword([]byte(uPw), []byte(pw))
}
// dbUpdateUserPassword
// Takes an email address and a password
// Creates the user if it doesn't exist, encrypts the password
// and updates it in the db
func dbUpdateUserPassword(email, password string) error {
cryptPw, cryptError := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if cryptError != nil {
return cryptError
}
if err := db.OpenDB(); err != nil { if err := db.OpenDB(); err != nil {
return err return "", err
} }
defer db.CloseDB() defer db.CloseDB()
usrPath := []string{"users", email} return db.GetValue([]string{"site"}, "current-jam")
return db.SetValue(usrPath, "password", string(cryptPw))
} }
func dbDeleteUser(email string) error { func dbIsValidJam(name string) (bool, error) {
var err error var err error
fmt.Println("Deleting User:", email)
if err = db.OpenDB(); err != nil { if err = db.OpenDB(); err != nil {
return err return false, err
} }
defer db.CloseDB() defer db.CloseDB()
return db.DeleteBucket([]string{"users"}, email) // Get all keys in the jams bucket
var keys []string
if keys, err = db.GetKeyList([]string{"jams", name}); err != nil {
return false, err
}
// All valid gamejams will have:
// "name"
// "teams"
for _, v := range []string{"name", "teams"} {
found := false
for j := range keys {
if keys[j] == v {
found = true
break
}
}
if !found {
// If we make it here, we didn't find a key we need
return false, nil
}
}
return true, nil
} }

8
model_games.go Normal file
View File

@ -0,0 +1,8 @@
package main
import "github.com/pborman/uuid"
type Game struct {
UUID *uuid.UUID
Name string
}

167
model_teams.go Normal file
View File

@ -0,0 +1,167 @@
package main
import (
"fmt"
"github.com/pborman/uuid"
)
type Team struct {
UUID string
Name string
Members []TeamMember
Game *Game
}
type TeamMember struct {
UUID string
Name string
SlackId string
Twitter string
Email string
}
func dbCreateNewTeam(nm string) error {
var err error
if err = db.OpenDB(); err != nil {
return err
}
defer db.CloseDB()
var currJam string
if currJam, err = dbGetCurrentJam(); err != nil {
return err
}
// Generate a UUID
uuid := uuid.New()
teamPath := []string{"jams", currJam, "teams", uuid}
if err := db.MkBucketPath(teamPath); err != nil {
fmt.Println("Error at 39: " + uuid)
return err
}
if err := db.SetValue(teamPath, "name", nm); err != nil {
fmt.Println("Error at 43")
return err
}
if err := db.MkBucketPath(append(teamPath, "members")); err != nil {
fmt.Println("Error at 47")
return err
}
gamePath := append(teamPath, "game")
if err := db.MkBucketPath(gamePath); err != nil {
fmt.Println("Error at 52")
return err
}
if err := db.SetValue(append(gamePath), "name", ""); err != nil {
fmt.Println("Error at 56")
return err
}
return db.MkBucketPath(append(gamePath, "screenshots"))
}
func dbIsValidTeam(nm string) bool {
var err error
var currJam string
if err = db.OpenDB(); err != nil {
return false
}
defer db.CloseDB()
if currJam, err = dbGetCurrentJam(); err != nil {
return false
}
teamPath := []string{"jams", currJam, "teams"}
if teamUids, err := db.GetBucketList(teamPath); err == nil {
for _, v := range teamUids {
if tstName, err := db.GetValue(append(teamPath, v), "name"); err == nil {
if tstName == nm {
return true
}
}
}
}
return false
}
func dbGetAllTeams() []Team {
var ret []Team
var err error
var currJam string
if err = db.OpenDB(); err != nil {
return ret
}
defer db.CloseDB()
if currJam, err = dbGetCurrentJam(); err != nil {
return ret
}
teamPath := []string{"jams", currJam, "teams"}
if teamUids, err := db.GetBucketList(teamPath); err != nil {
for _, v := range teamUids {
if tm := dbGetTeam(v); tm != nil {
ret = append(ret, *tm)
}
}
}
return ret
}
func dbGetTeam(id string) *Team {
var err error
var currJam string
if err = db.OpenDB(); err != nil {
return nil
}
defer db.CloseDB()
if currJam, err = dbGetCurrentJam(); err != nil {
return nil
}
teamPath := []string{"jams", currJam, "teams", id}
tm := new(Team)
if tm.Name, err = db.GetValue(teamPath, "name"); err != nil {
return nil
}
return tm
}
func dbGetTeamByName(nm string) *Team {
var err error
var currJam string
if err = db.OpenDB(); err != nil {
return nil
}
defer db.CloseDB()
if currJam, err = dbGetCurrentJam(); err != nil {
return nil
}
teamPath := []string{"jams", currJam, "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 dbDeleteTeam(id string) error {
var err error
var currJam string
if err = db.OpenDB(); err != nil {
return err
}
defer db.CloseDB()
if currJam, err = dbGetCurrentJam(); err != nil {
return err
}
teamPath := []string{"jams", currJam, "teams"}
return db.DeleteBucket(teamPath, id)
}

76
model_users.go Normal file
View File

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

View File

@ -3,41 +3,16 @@ package main
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/gorilla/sessions"
) )
func initPublicPage(w http.ResponseWriter, req *http.Request) *pageData { func initPublicPage(w http.ResponseWriter, req *http.Request) *pageData {
p := new(pageData) p := InitPageData(w, req)
// Get session
var err error
var s *sessions.Session
if s, err = sessionStore.Get(req, site.SessionName); err != nil {
http.Error(w, err.Error(), 500)
return p
}
p.session = new(pageSession)
p.session.session = s
p.session.req = req
p.session.w = w
p.Site = site
p.SubTitle = "GameJam Voting"
p.Stylesheets = make([]string, 0, 0)
p.Stylesheets = append(p.Stylesheets, "/assets/css/pure-min.css")
p.Stylesheets = append(p.Stylesheets, "/assets/font-awesome/css/font-awesome.min.css")
p.Stylesheets = append(p.Stylesheets, "/assets/css/gjvote.css")
p.HeaderScripts = make([]string, 0, 0)
p.HeaderScripts = append(p.HeaderScripts, "/assets/js/snack-min.js")
p.Scripts = make([]string, 0, 0)
p.Scripts = append(p.Scripts, "/assets/js/gjvote.js")
p.FlashMessage, p.FlashClass = p.session.getFlashMessage()
return p return p
} }
func handleMain(w http.ResponseWriter, req *http.Request) { func handleMain(w http.ResponseWriter, req *http.Request) {
page := initPublicPage(w, req) page := initPublicPage(w, req)
page.SubTitle = "Place your Vote!" page.SubTitle = "!"
for _, tmpl := range []string{ for _, tmpl := range []string{
"htmlheader.html", "htmlheader.html",
"main.html", "main.html",

View File

@ -0,0 +1,12 @@
<div class="center">
<form class="pure-form pure-form-aligned" action="/admin/teams/new/save" method="POST">
<fieldset>
<div class="pure-control-group">
<label for="teamname">Team Name</label>
<input id="teamname" name="teamname" type="text" placeholder="Team Name" value="">
</div>
<button type="submit" class="pure-button pure-button-primary">Add Team</button>
</fieldset>
</form>
</div>

View File

@ -0,0 +1,42 @@
<div class="center">
<form class="pure-form pure-form-aligned" action="/admin/teams/{{ .TemplateData.UUID }}/save" method="POST">
<fieldset>
<div class="pure-control-group">
<span>{{ .TemplateData.Name }}</span>
</div>
<div class="pure-control-group">
<label class="control-label" for="teamname">Team Name</label>
<input id="teamname" name="teamname" type="password" placeholder="Team Name">
</div>
<div class="pure-control-group reset-pull">
<a href="/admin/teams" class="pull-left space pure-button pure-button-plain">Cancel</a>
<button type="submit" class="pull-right space pure-button pure-button-primary">Update</button>
<button type="button" id="btnDeleteUser" class="pull-right space pure-button pure-button-error">Delete</button>
</div>
</fieldset>
</form>
</div>
<script>
snack.listener(
{node:document.getElementById('btnDeleteTeam'),event:'click'},
function() {
showModal({
title: 'Delete Team',
subtitle: '({{ .TemplateData.Name }} - {{ .TemplateData.UUID }})',
body: 'Are you sure? This cannot be undone.',
buttons: [{
title:'Cancel',
position:'left',
click: hideModal
},{
title:'Delete',
position:'right',
class: 'pure-button-error',
href: '/admin/teams/{{ .TemplateData.UUID }}/delete'
}]
});
}
);
</script>

View File

@ -0,0 +1,31 @@
<div class="bottom-space center">
<a id="btnAddTeam" class="pure-button pure-button-success" href="/admin/teams/new"><i class="fa fa-plus"></i> Add Team</a>
</div>
<table id="teams-table" class="hidden sortable pure-table pure-table-bordered center">
<thead>
<tr>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
{{ range $i, $v := .TemplateData.Teams }}
<tr>
<td>{{ $v.Name }}</td>
<td>
<a href="/admin/users/{{ $v.UUID }}/edit" class="pure-button pure-button-plain"><i class="fa fa-pencil"></i></a>
<a href="/admin/users/{{ $v.UUID }}/delete" class="pure-button pure-button-plain"><i class="fa fa-trash"></i></a>
</td>
</tr>
{{ end }}
</tbody>
</table>
<script>
snack.ready(function() {
var tableBody = document.querySelector("#teams-table>tbody");
if(tableBody.children.length>0) {
// Show the table
document.getElementById('teams-table').classList.remove('hidden');
}
});
</script>