Working on editing team games

This commit is contained in:
Brian Buller 2017-06-15 12:35:53 -05:00
parent 597623d71b
commit 1faf4b9aa1
11 changed files with 518 additions and 87 deletions

View File

@ -55,3 +55,31 @@ func handleAdminClients(w http.ResponseWriter, req *http.Request, page *pageData
}
}
}
func clientIsAuthenticated(cid string, req *http.Request) bool {
return clientIsServer(req) || dbClientIsAuth(cid)
}
func clientIsServer(req *http.Request) bool {
clientIp, _, _ := net.SplitHostPort(req.RemoteAddr)
ifaces, err := net.Interfaces()
if err == nil {
for _, i := range ifaces {
if addrs, err := i.Addrs(); err == nil {
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if clientIp == ip.String() {
return true
}
}
}
}
}
return false
}

View File

@ -1,8 +1,12 @@
package main
import (
"fmt"
"io"
"mime/multipart"
"net/http"
"strings"
"os"
"strconv"
"github.com/gorilla/mux"
)
@ -10,23 +14,63 @@ import (
func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) {
vars := mux.Vars(req)
page.SubTitle = "Games"
gameId := vars["id"]
teamId := req.FormValue("teamid")
if strings.TrimSpace(teamId) != "" {
page.session.setStringValue("teamid", teamId)
page.TeamID = teamId
}
if gameId == "new" {
teamId := vars["id"]
if teamId == "" {
// Games List
type gamesPageData struct {
Games []Game
}
page.TemplateData = gamesPageData{Games: dbGetAllGames()}
page.SubTitle = "Games"
page.show("admin-games.html", w)
} else {
switch vars["function"] {
case "save":
name := req.FormValue("gamename")
desc := req.FormValue("gamedesc")
if dbIsValidTeam(teamId) {
if dbEditTeamGame(teamId, name) != nil {
if err := dbUpdateTeamGame(teamId, name, desc); err != nil {
page.session.setFlashMessage("Error updating game: "+err.Error(), "error")
} else {
page.session.setFlashMessage("Team game updated", "success")
}
redirect("/admin/teams/"+teamId, w, req)
}
default:
page.SubTitle = "Add New Game"
page.show("admin-addgame.html", w)
case "screenshotupload":
if err := saveScreenshots(teamId, req); err != nil {
page.session.setFlashMessage("Error updating game: "+err.Error(), "error")
}
redirect("/admin/teams/"+teamId, w, req)
}
}
}
func saveScreenshots(teamId string, req *http.Request) error {
err := req.ParseMultipartForm((1 << 10) * 24)
if err != nil {
return err
}
for _, fheaders := range req.MultipartForm.File {
for _, hdr := range fheaders {
// open uploaded
var infile multipart.File
if infile, err = hdr.Open(); err != nil {
return err
}
// open destination
var outfile *os.File
if outfile, err = os.Create("./uploaded/" + hdr.Filename); err != nil {
return err
}
// 32K buffer copy
var written int64
if written, err = io.Copy(outfile, infile); err != nil {
return err
}
fmt.Println("uploaded file:" + hdr.Filename + ";length:" + strconv.Itoa(int(written)))
}
}
return nil
}

View File

@ -11,6 +11,7 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
page.SubTitle = "Teams"
teamId := vars["id"]
if teamId == "new" {
// Add a new team
switch vars["function"] {
case "save":
name := req.FormValue("teamname")
@ -30,6 +31,7 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
page.show("admin-addteam.html", w)
}
} else if teamId != "" {
// Functions for existing team
if dbIsValidTeam(teamId) {
switch vars["function"] {
case "save":
@ -44,8 +46,11 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
redirect("/admin/teams", w, req)
case "delete":
var err error
t := dbGetTeam(teamId)
if err = dbDeleteTeam(teamId); err != nil {
page.session.setFlashMessage("Error deleting team: "+err.Error(), "error")
} else {
page.session.setFlashMessage("Team "+t.Name+" Deleted", "success")
}
redirect("/admin/teams", w, req)
case "savemember":
@ -61,10 +66,11 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
redirect("/admin/teams/"+teamId, w, req)
case "deletemember":
mbrId := req.FormValue("memberid")
m, _ := dbGetTeamMember(teamId, mbrId)
if err := dbDeleteTeamMember(teamId, mbrId); err != nil {
page.session.setFlashMessage("Error deleting team member: "+err.Error(), "error")
} else {
page.session.setFlashMessage("Member deleted from team", "success")
page.session.setFlashMessage(m.Name+" deleted from team", "success")
}
redirect("/admin/teams/"+teamId, w, req)
default:
@ -78,10 +84,10 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData)
redirect("/admin/teams", w, req)
}
} else {
// Team List
type teamsPageData struct {
Teams []Team
}
page.TemplateData = teamsPageData{Teams: dbGetAllTeams()}
page.SubTitle = "Teams"
page.show("admin-teams.html", w)

View File

@ -17,6 +17,14 @@ div.content {
margin-left: 0;
}
input.file {
padding: .5em .6em;
display: inline-block;
border: 1px solid #ccc;
box-shadow: inset 0 1px 3px #ddd;
border-radius: 4px;
}
#menu {
width: 100%;
height: 40px;
@ -29,6 +37,14 @@ div.content {
display: inline-block;
}
#menu .pure-menu-nonlink {
color: #777;
padding: .5em 1em;
display: block;
text-decoration: none;
white-space: nowrap;
}
#menu .menu-button {
display: inline;
position: absolute;
@ -57,8 +73,87 @@ div.content {
background-color: #191818;
}
input.uuid-field {
width: 360px;
div.horizontal-scroll {
overflow-x: scroll;
}
img.thumbnail {
width: 100px;
height: 100px;
}
.thumbnail-container {
display: block;
height: 120px;
background-color: #EEE;
}
.padding {
padding: 5px;
}
.space {
margin: 5px;
}
.space-sides {
margin-left: 5px;
margin-right: 5px;
}
.big-space {
margin: 10px;
}
.left {
text-align: left;
}
.right {
text-align: right;
}
.center {
text-align: center;
}
.center-all {
text-align: center;
vertical-align: center;
margin: auto;
}
table.center td {
text-align: center;
}
table.padding td {
padding: 5px;
}
.pure-button-error {
background-color: #DD0000;
color: #FFFFFF;
}
#modal-overlay {
visibility: hidden;
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
text-align: center;
z-index: 1000;
background-color: rgba(0, 0, 0, 0.5);
}
#modal-overlay>div {
width: 500px;
margin: 100px auto;
background-color: #FFF;
border: 1px solid #000;
border-radius: 10px;
padding: 15px;
text-align: center;
}
@media (min-width: 40em) {
@ -92,6 +187,11 @@ input.uuid-field {
margin-left: 150px;
}
aside.flash.success {
background-color: #229af9;
color: #FFFFFF;
}
.content {
margin-left: 150px;
}

View File

@ -15,3 +15,51 @@ document.onkeydown = function(evt) {
toggleAdminPanel();
}
}
function showModal(options) {
var modal = document.getElementById('modal-overlay');
document.getElementById('modal-title').innerText = (options.title)?options.title:"";
document.getElementById('modal-subtitle').innerText = (options.subtitle)?options.subtitle:"";
if(options.body) {
document.getElementById('modal-body').innerText = options.body;
} else if(options.bodyNode) {
document.getElementById('modal-body').appendChild(options.bodyNode);
}
if(options.buttons) {
for(var i = 0; i < options.buttons.length; i++) {
var btn;
if(options.buttons[i].isSubmit) {
btn = document.createElement('submit');
} else {
btn = document.createElement('a');
}
options.buttons[i].title = (options.buttons[i].title==undefined)?'':options.buttons[i].title;
options.buttons[i].href = (options.buttons[i].href==undefined)?'#':options.buttons[i].href;
options.buttons[i].click = (options.buttons[i].click==undefined)?function(){}:options.buttons[i].click;
options.buttons[i].class = (options.buttons[i].class==undefined)?'':options.buttons[i].class;
options.buttons[i].position = (options.buttons[i].position==undefined)?'right':options.buttons[i].position;
btn.innerHTML = options.buttons[i].title;
btn.title = options.buttons[i].title;
btn.href = options.buttons[i].href;
btn.className = 'space pure-button '+options.buttons[i].class+' '+options.buttons[i].position;
snack.listener(
{node:btn, event:'click'},
options.buttons[i].click
);
document.getElementById('modal-buttons').appendChild(btn);
}
}
modal.style.visibility = 'visible';
}
function hideModal() {
var modal = document.getElementById('modal-overlay');
modal.style.visibility = 'hidden';
document.getElementById('modal-title').innerHTML = '';
document.getElementById('modal-body').innerHTML = '';
var buttonsDiv = document.getElementById('modal-buttons')
while(buttonsDiv.firstChild) {
buttonsDiv.removeChild(buttonsDiv.firstChild);
}
}

View File

@ -53,6 +53,7 @@ type pageData struct {
CurrentJam string
ClientID string
ClientIsAuth bool
ClientIsServer bool
TeamID string
TemplateData interface{}
@ -237,7 +238,8 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData {
}
p.ClientID = p.session.getClientID()
p.ClientIsAuth = dbClientIsAuth(p.ClientID)
p.ClientIsAuth = clientIsAuthenticated(p.ClientID, req)
p.ClientIsServer = clientIsServer(req)
p.TeamID, _ = p.session.getStringValue("teamid")
return p

View File

@ -1,12 +1,145 @@
package main
import "github.com/pborman/uuid"
import (
"errors"
"github.com/pborman/uuid"
)
type Game struct {
UUID *uuid.UUID
Name string
Name string
TeamId string
Description string
Screenshots []Screenshot
}
func dbIsValidGame(id string) bool {
return true
type Screenshot struct {
Description string
Image string
}
func dbUpdateTeamGame(teamId, name, desc string) error {
var err error
if err = openDatabase(); err != nil {
return err
}
defer closeDatabase()
// Make sure the team is valid
tm := dbGetTeam(teamId)
if tm == nil {
return errors.New("Invalid team")
}
gamePath := []string{"teams", teamId, "game"}
if err := db.MkBucketPath(gamePath); err != nil {
return err
}
if name == "" {
name = tm.Name + "'s Game"
}
if err := db.SetValue(gamePath, "name", name); err != nil {
return err
}
if err := db.SetValue(gamePath, "description", desc); err != nil {
return err
}
if err := db.MkBucketPath(append(gamePath, "screenshots")); err != nil {
return err
}
return err
}
func dbGetAllGames() []Game {
var ret []Game
tms := dbGetAllTeams()
for i := range tms {
ret = append(ret, *dbGetTeamGame(tms[i].UUID))
}
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 = ""
}
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)
if ret.Description, err = db.GetValue(ssPath, "description"); err != nil {
return nil
}
if ret.Image, err = db.GetValue(ssPath, "image"); 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
}
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)
}

View File

@ -105,6 +105,7 @@ func dbGetTeam(id string) *Team {
return nil
}
tm.Members, _ = dbGetTeamMembers(id)
tm.Game = dbGetTeamGame(id)
return tm
}
@ -213,7 +214,6 @@ func dbGetTeamMembers(teamid string) ([]TeamMember, error) {
for _, v := range memberUuids {
var mbr *TeamMember
if mbr, err = dbGetTeamMember(teamid, v); err == nil {
fmt.Println("Finding Team Members", teamid, mbr.Name)
ret = append(ret, *mbr)
}
}

View File

@ -1,70 +1,126 @@
{{ $uuid := .TemplateData.UUID }}
<div class="center">
<form class="pure-form pure-form-aligned" action="/admin/teams/{{ .TemplateData.UUID }}/save" method="POST">
<div class="left">
<form class="pure-form pure-form-aligned" action="/admin/teams/{{ $uuid }}/save" method="POST">
<h3>Team Details</h3>
<fieldset>
<div class="left big-space">
<div class="pure-control-group">
<label class="control-label" for="teamname">Team Name</label>
<input id="teamname" name="teamname" value="{{ .TemplateData.Name }}" placeholder="Team Name">
</div>
</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 Team</button>
<button type="button" id="btnDeleteTeam" class="pull-right space pure-button pure-button-error">Delete Team</button>
</div>
</fieldset>
</form>
<hr />
</div>
<form class="pure-form pure-form-aligned" action="/admin/games/{{ $uuid }}/save" method="POST">
<fieldset>
<div class="pure-control-group">
<span>{{ .TemplateData.Name }}</span>
<div class="left big-space">
<h3>Team Game</h3>
<div class="pure-control-group">
<label class="control-label" for="gamename">Game Name</label>
<input id="gamename" name="gamename" value="{{ .TemplateData.Game.Name }}" placeholder="Game Name">
</div>
<div class="pure-control-group">
<label class="control-label" for="gamedesc">Description</label>
<textarea id="gamedesc" name="gamedesc" placeholder="Description...">{{ .TemplateData.Game.Description }}</textarea>
</div>
<div class="pure-control-group">
<label class="control-label">Screenshots</label>
<div class="center-all horizontal-scroll thumbnail-container">
{{ if not .TemplateData.Game.Screenshots }}
<a style="margin-top:40px;" class="center-all pure-button pure-button-primary" href="javascript:toggleUploadSSForm();">Upload Screenshot</a>
{{ else }}
{{ range $i, $v := .TemplateData.Game.Screenshots }}
<img class="thumbnail" alt="{{ $v.Description }}" src="{{ $v.Image }}" />
{{ end }}
{{ end }}
</div>
{{ if .TemplateData.Game.Screenshots }}
<div class="right">
<a id="toggleUploadSSFormBtn" class="pure-button pure-button-primary" href="javascript:toggleUploadSSForm();">Upload Screenshot</a>
</div>
{{ end }}
</div>
</div>
<div class="pure-control-group">
<label class="control-label" for="teamname">Team Name</label>
<input id="teamname" name="teamname" value="{{ .TemplateData.Name }}" 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>
<a href="/admin/teams/{{ $uuid }}" class="pull-left space pure-button pure-button-plain">Cancel</a>
<button type="submit" class="pull-right space pure-button pure-button-primary">Update Game</button>
</div>
</fieldset>
</form>
<h2>Members</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Slack ID</th>
<th>Twitter</th>
<th>Email</th>
<th>Edit</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{{ range $i, $v := .TemplateData.Members }}
<tr>
<td>{{ $v.Name }}</td>
<td>{{ $v.SlackId }}</td>
<td>{{ $v.Twitter }}</td>
<td>{{ $v.Email }}</td>
<td>
<a href="/admin/teams/{{ $v.UUID }}/edit" class="pure-button pure-button-plain"><i class="fa fa-pencil"></i></a>
</td>
<td>
<form action="/admin/teams/{{ $uuid }}/deletemember" method="POST">
<input type="hidden" name="memberid" value="{{ $v.UUID }}"/>
<button type="submit" class="pure-button pure-button-plain"><i class="fa fa-trash"></i></button>
</form>
</td>
</tr>
{{ end }}
<tr>
<td colspan="6" class="center">Add a new member</td>
</tr>
<tr>
<td colspan="6">
<form action="/admin/teams/{{ $uuid }}/savemember" method="POST">
<input id="newmembername" name="newmembername" value="" placeholder="Member Name" />
<hr />
<div class="left">
<h3>Team Members</h3>
<table class="center padding hide">
<thead>
<tr>
<th>Name</th>
<th>Slack ID</th>
<th>Twitter</th>
<th>Email</th>
<th>Edit</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{{ range $i, $v := .TemplateData.Members }}
<tr>
<td>{{ $v.Name }}</td>
<td>{{ $v.SlackId }}</td>
<td>{{ $v.Twitter }}</td>
<td>{{ $v.Email }}</td>
<td>
<a href="/admin/teams/{{ $v.UUID }}/edit" class="pure-button pure-button-plain"><i class="fa fa-pencil"></i></a>
</td>
<td>
<form action="/admin/teams/{{ $uuid }}/deletemember" method="POST">
<input type="hidden" name="memberid" value="{{ $v.UUID }}"/>
<button type="submit" class="pure-button pure-button-error"><i class="fa fa-trash"></i></button>
</form>
</td>
</tr>
{{ end }}
<tr>
<td colspan="6">Add a new member</td>
</tr>
<tr>
<td colspan="6" class="padding">
<form class="pure-form" action="/admin/teams/{{ $uuid }}/savemember" method="POST">
<div class="pure-control-group">
<input id="newmembername" name="newmembername" value="" placeholder="Member Name" autofocus />
<input id="newmemberslackid" name="newmemberslackid" value="" placeholder="@SlackID" />
<input id="newmembertwitter" name="newmembertwitter" value="" placeholder="@Twitter" />
<input id="newmemberemail" name="newmemberemail" value="" placeholder="user@email.com" />
<button type="submit" class="pull-right space pure-button pure-button-primary">Add</button>
</form>
</td>
</tr>
</tbody>
</table>
<button type="submit" class="pull-right space-sides pure-button pure-button-primary">Add</button>
</div>
</form>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pure-control-group reset-pull">
<a href="/admin/teams" class="pull-left space pure-button pure-button-plain">Cancel</a>
</div>
</div>
<div id="uploadscreenshotform" style="display:none;">
<h3>Upload Screenshot</h3>
<form class="pure-form pure-form-aligned" action="/admin/games/{{ $uuid }}/screenshotupload" method="POST" enctype="multipart/form-data">
<div class="pure-control-group" style="margin-bottom:50px;">
<input class="file" type="file" name="newssfile" multiple>
</div>
<a href="javascript:hideModal();" class="pull-left space-sides pure-button">Cancel</a>
<button type="submit" class="pull-right space-sides pure-button pure-button-primary">Add</button>
</form>
</div>
<script>
snack.listener(
@ -72,7 +128,7 @@
function() {
showModal({
title: 'Delete Team',
subtitle: '({{ .TemplateData.Name }} - {{ $uuid}})',
subtitle: '({{ .TemplateData.Name }})',
body: 'Are you sure? This cannot be undone.',
buttons: [{
title:'Cancel',
@ -87,4 +143,14 @@
});
}
);
function toggleUploadSSForm() {
var uploadForm = document.getElementById('uploadscreenshotform');
showModal({
title: 'Upload Screenshot',
subtitle: '({{ .TemplateData.Name }})',
bodyNode: uploadForm
});
uploadForm.style.display="block";
document.getElementById('modal-body').style.height='165px';
}
</script>

View File

@ -12,11 +12,15 @@
</li>
{{ end }}
</ul>
{{if .ClientIsAuth}}
<a href="/admin/clients/{{.ClientID}}/remove" class="pure-menu-link"><i class="fa fa-key"></i> DeAuth Client</a>
{{else}}
{{ if .ClientIsAuth }}
{{ if .ClientIsServer }}
<span class="pure-menu-nonlink"><i class="fa fa-server"></i> Server Mode</span>
{{ else }}
<a href="/admin/clients/{{.ClientID}}/remove" class="pure-menu-link"><i class="fa fa-key"></i> DeAuth Client</a>
{{ end }}
{{ else }}
<a href="/admin/clients/{{.ClientID}}/add" class="pure-menu-link"><i class="fa fa-key"></i> Auth Client</a>
{{end}}
{{ end }}
<ul class="pure-menu-list menu-bottom">
{{ range $k, $v := .BottomMenu }}
<li class="pure-menu-item">

View File

@ -1,13 +1,13 @@
<script>
var clientID = "{{.ClientID}}";
</script>
<aside class="flash center {{.FlashClass}}">
{{.FlashMessage}}
</aside>
<div class="content">
<aside class="flash center {{.FlashClass}}">
{{.FlashMessage}}
</aside>
<div class="header">
devICT Game Jam - {{.CurrentJam}}
</div>
<div class="header-menu">
<h1>{{.SubTitle}}</h1>
<h2>{{.SubTitle}}</h2>
</div>