From 9440565555b5cb88586f0e7597b71f3aacd33b9c Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Wed, 11 Oct 2017 18:03:27 -0500 Subject: [PATCH 1/6] Pushing up, done for the day --- admin_endpoints.go | 4 +- admin_games.go | 2 +- main.go | 68 ++++++++-------- model.go | 183 +++++++++++++++++------------------------- model_clients.go | 111 +++++++------------------- model_gamejam.go | 115 +++++++++++---------------- model_games.go | 103 +++++++++++++++++++++--- model_site.go | 62 --------------- model_sitedata.go | 112 ++++++++++++++++++++++++++ model_teams.go | 190 ++++++++++++++++++++++++++++++-------------- model_users.go | 49 ++++++------ model_votes.go | 64 ++++++++++++++- public_endpoints.go | 4 +- 13 files changed, 611 insertions(+), 456 deletions(-) delete mode 100644 model_site.go create mode 100644 model_sitedata.go diff --git a/admin_endpoints.go b/admin_endpoints.go index 52251c6..c901fe9 100644 --- a/admin_endpoints.go +++ b/admin_endpoints.go @@ -62,7 +62,7 @@ func handleAdminSetMode(w http.ResponseWriter, req *http.Request, page *pageData if err != nil { page.session.setFlashMessage("Invalid Mode: "+vars["id"], "error") } - if dbSetPublicSiteMode(newMode) != nil { + if db.setPublicSiteMode(newMode) != nil { page.session.setFlashMessage("Invalid Mode: "+vars["id"], "error") } redirect("/admin", w, req) @@ -74,7 +74,7 @@ func handleAdminSetAuthMode(w http.ResponseWriter, req *http.Request, page *page if err != nil { page.session.setFlashMessage("Invalid Authentication Mode: "+vars["id"], "error") } - if db.setAuthMode(newMode) != nil { + if db.site.setAuthMode(newMode) != nil { page.session.setFlashMessage("Invalid Authentication Mode: "+vars["id"], "error") } redirect("/admin", w, req) diff --git a/admin_games.go b/admin_games.go index 8fc755f..6003a94 100644 --- a/admin_games.go +++ b/admin_games.go @@ -33,7 +33,7 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) if tm != nil { switch vars["function"] { case "save": - gm := newGame(tm.UUID) + gm := db.newGame(tm.UUID) gm.Name = req.FormValue("gamename") gm.Link = req.FormValue("gamelink") gm.Description = req.FormValue("gamedesc") diff --git a/main.go b/main.go index 29dcd08..be09458 100644 --- a/main.go +++ b/main.go @@ -60,9 +60,14 @@ var sessionSecret = "JCOP5e8ohkTcOzcSMe74" var sessionStore = sessions.NewCookieStore([]byte(sessionSecret)) var site *siteData var r *mux.Router +var m *model func main() { - db = new(gjDatabase) + var err error + if m, err = NewModel(); err != nil { + errorExit("Unable to initialize Model: " + err.Error()) + } + loadConfig() site.save() initialize() @@ -70,13 +75,10 @@ func main() { r = mux.NewRouter() r.StrictSlash(true) - if site.DevMode { + if m.site.DevMode { fmt.Println("Operating in Development Mode") } - //s := http.StripPrefix("/assets/", http.FileServer(FS(site.DevMode))) - //http.Dir(site.ServerDir+"assets/"))) - //r.PathPrefix("/assets/").Handler(s) - r.PathPrefix("/assets/").Handler(http.FileServer(FS(site.DevMode))) + r.PathPrefix("/assets/").Handler(http.FileServer(FS(m.site.DevMode))) // Public Subrouter pub := r.PathPrefix("/").Subrouter() @@ -110,8 +112,6 @@ func main() { } func loadConfig() { - site = db.getSiteConfig() - if len(os.Args) > 1 { for _, v := range os.Args { key := v @@ -124,27 +124,27 @@ func loadConfig() { } switch key { case "-title": - site.Title = val - fmt.Print("Set site title: ", site.Title, "\n") + m.site.Title = val + fmt.Print("Set site title: ", m.site.Title, "\n") case "-port": var tryPort int var err error if tryPort, err = strconv.Atoi(val); err != nil { fmt.Print("Invalid port given: ", val, " (Must be an integer)\n") - tryPort = site.Port + tryPort = m.site.Port } // TODO: Make sure a valid port number is given - site.Port = tryPort + m.site.Port = tryPort case "-session-name": - site.SessionName = val + m.site.SessionName = val case "-server-dir": // TODO: Probably check if the given directory is valid - site.ServerDir = val + m.site.ServerDir = val case "-help", "-h", "-?": printHelp() done() case "-dev": - site.DevMode = true + m.site.DevMode = true case "-reset-defaults": resetToDefaults() done() @@ -154,10 +154,9 @@ func loadConfig() { } func initialize() { - // Check if the database has been created - assertError(db.initialize()) - - if !db.hasUser() { + // Test if we have an admin user first + if !m.hasUser() { + // Nope, create one reader := bufio.NewReader(os.Stdin) fmt.Println("Create new Admin user") fmt.Print("Email: ") @@ -175,29 +174,26 @@ func initialize() { fmt.Println("Entered Passwords don't match!") } } - assertError(db.updateUserPassword(email, string(pw1))) + assertError(m.updateUserPassword(email, string(pw1))) } - if !db.hasCurrentJam() { + + // Now test if the 'current jam' is named + if m.jam.Name == "" { reader := bufio.NewReader(os.Stdin) fmt.Println("Create New Game Jam") fmt.Print("GameJam Name: ") gjName, _ := reader.ReadString('\n') gjName = strings.TrimSpace(gjName) - if db.setCurrentJam(gjName) != nil { + if db.setJamName(gjName) != nil { fmt.Println("Error saving Current Jam") } } - jmNm, err := db.getCurrentJam() - if err == nil { + if m.jam.Name != "" { fmt.Println("Current Jam Name: " + jmNm) } else { - fmt.Println(err.Error()) + fmt.Println("No Jam Name Specified") } - - // Load all votes into memory - site.Votes = db.getAllVotes() - site.Teams = db.getAllTeams() } func loggingHandler(h http.Handler) http.Handler { @@ -261,7 +257,7 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData { } p.HideAdminMenu = true - if p.CurrentJam, err = db.getCurrentJam(); err != nil { + if p.CurrentJam = db.getJamName(); p.CurrentJam != "" { p.FlashMessage = "Error Loading Current GameJam: " + err.Error() p.FlashClass = "error" } @@ -274,7 +270,7 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData { // Public Mode p.PublicMode = db.getPublicSiteMode() // Authentication Mode - p.AuthMode = db.getAuthMode() + p.AuthMode = db.site.getAuthMode() return p } @@ -310,12 +306,12 @@ func redirect(url string, w http.ResponseWriter, req *http.Request) { } func resetToDefaults() { - def := NewSiteData() + def := NewSiteData(m) fmt.Println("Reset settings to defaults?") - fmt.Print(site.Title, " -> ", def.Title, "\n") - fmt.Print(site.Port, " -> ", def.Port, "\n") - fmt.Print(site.SessionName, " -> ", def.SessionName, "\n") - fmt.Print(site.ServerDir, " -> ", def.ServerDir, "\n") + fmt.Print(m.site.Title, " -> ", def.Title, "\n") + fmt.Print(m.site.Port, " -> ", def.Port, "\n") + fmt.Print(m.site.SessionName, " -> ", def.SessionName, "\n") + fmt.Print(m.site.ServerDir, " -> ", def.ServerDir, "\n") fmt.Println("Are you sure? (y/N): ") reader := bufio.NewReader(os.Stdin) conf, _ := reader.ReadString('\n') diff --git a/model.go b/model.go index 488f262..a878ac9 100644 --- a/model.go +++ b/model.go @@ -2,45 +2,66 @@ package main import ( "errors" - "strings" "github.com/br0xen/boltease" ) -// TODO: I don't think we need a global for this... -var db *currJamDb +// model stores the current jam in memory, and has the ability to access archived dbs +type model struct { + bolt *boltease.DB + dbOpened int + dbFileName string -// gjdb is the interface that works for the current jam database as well as all archive databases -type gjdb interface { - getDB() *boltease.DB - open() error - close() error - - getJamName() string - setJamName(nm string) + site *siteData // Configuration data for the site + jam *Gamejam // The currently active gamejam + clients []Client // Web clients that have connected to the server } +// Authentication Modes: Flags for which clients are able to vote const ( AuthModeAuthentication = iota AuthModeAll AuthModeError ) -// currJamDb also contains site configuration information -type currJamDb struct { - bolt *boltease.DB - dbOpened int +// Update Flags: Which parts of the model need to be updated +const ( + UpdateSiteData = iota + UpdateJamData +) + +func NewModel() (*model, error) { + var err error + m := new(model) + + m.dbFileName = DbName + if err = m.openDB(); err != nil { + return nil, errors.New("Unable to open DB: " + err.Error()) + } + defer m.closeDB() + + // Initialize the DB + if err = m.initDB(); err != nil { + return nil, errors.New("Unable to initialize DB: " + err.Error()) + } + + // Load the site data + m.site = m.LoadSiteData() + + // Load the jam data + m.jam = m.LoadCurrentJam() + + // Load web clients + m.clients = m.LoadAllClients() + + return &m, nil } -func (db *currJamDb) getDB() *boltease.DB { - return db.bolt -} - -func (db *currJamDb) open() error { - db.dbOpened += 1 +func (m *model) openDB() error { + m.dbOpened += 1 if db.dbOpened == 1 { var err error - db.bolt, err = boltease.Create(DbName, 0600, nil) + m.bolt, err = boltease.Create(m.dbFileName, 0600, nil) if err != nil { return err } @@ -48,112 +69,58 @@ func (db *currJamDb) open() error { return nil } -func (db *currJamDb) close() error { - db.dbOpened -= 1 - if db.dbOpened == 0 { - return db.bolt.CloseDB() +func (m *model) closeDB() error { + m.dbOpened -= 1 + if m.dbOpened == 0 { + return m.bolt.CloseDB() } return nil } -// initialize the 'current jam' database -func (db *currJamDb) initialize() error { +func (m *model) initDB() error { var err error - if err = db.open(); err != nil { + if err = m.openDB(); err != nil { return err } - defer db.close() + defer m.closeDB() // Create the path to the bucket to store admin users - if err := db.bolt.MkBucketPath([]string{"users"}); err != nil { + if err = m.bolt.MkBucketPath([]string{"users"}); err != nil { return err } - // Create the path to the bucket to store jam informations - if err := db.bolt.MkBucketPath([]string{"jam"}); err != nil { + // Create the path to the bucket to store the web clients + if err = m.bolt.MkBucketPath([]string{"clients"}); err != nil { + return err + } + // Create the path to the bucket to store the current jam & teams + if err = m.bolt.MkBucketPath([]string{"jam", "teams"}); err != nil { + return err + } + // Create the path to the bucket to store the list of archived jams + if err = m.bolt.MkBucketPath([]string{"archive"}); err != nil { return err } // Create the path to the bucket to store site config data - return db.bolt.MkBucketPath([]string{"site"}) + return m.bolt.MkBucketPath([]string{"site"}) } -func (db *currJamDb) getSiteConfig() *siteData { - var ret *siteData - def := NewSiteData() +// saveChanges saves any parts of the model that have been flagged as changed to the database +func (m *model) saveChanges() error { var err error - if err = db.open(); err != nil { - return def - } - defer db.close() - - ret = new(siteData) - siteConf := []string{"site"} - if ret.Title, err = db.bolt.GetValue(siteConf, "title"); err != nil { - ret.Title = def.Title - } - if ret.Port, err = db.bolt.GetInt(siteConf, "port"); err != nil { - ret.Port = def.Port - } - if ret.SessionName, err = db.bolt.GetValue(siteConf, "session-name"); err != nil { - ret.SessionName = def.SessionName - } - if ret.ServerDir, err = db.bolt.GetValue(siteConf, "server-dir"); err != nil { - ret.ServerDir = def.ServerDir - } - return ret -} - -func (db *currJamDb) setJamName(name string) error { - var err error - if err = db.open(); err != nil { + if err = m.openDB(); err != nil { return err } - defer db.close() + defer m.closeDB() - return db.bolt.SetValue([]string{"site"}, "current-jam", name) -} - -func (db *currJamDb) getJamName() string { - var ret string - var err error - if err = db.open(); err != nil { - return "", err + if m.site.needsSave() { + if err = m.site.saveToDB(); err != nil { + return err + } } - defer db.close() - - ret, err = db.bolt.GetValue([]string{"site"}, "current-jam") - - if err == nil && strings.TrimSpace(ret) == "" { - return ret, errors.New("No Jam Name Specified") + if m.jam.needsSave() { + if err = m.jam.saveToDB(); err != nil { + return err + } } - return ret, err -} - -func (db *currJamDb) getAuthMode() int { - if ret, err := db.bolt.GetInt([]string{"site"}, "auth-mode"); err != nil { - return AuthModeAuthentication - } else { - return ret - } -} - -func (db *currJamDb) setAuthMode(mode int) error { - if mode < 0 || mode >= AuthModeError { - return errors.New("Invalid site mode") - } - return db.bolt.SetInt([]string{"site"}, "auth-mode", mode) -} - -func (db *currJamDb) getPublicSiteMode() int { - if ret, err := db.bolt.GetInt([]string{"site"}, "public-mode"); err != nil { - return SiteModeWaiting - } else { - return ret - } -} - -func (db *currJamDb) setPublicSiteMode(mode int) error { - if mode < 0 || mode >= SiteModeError { - return errors.New("Invalid site mode") - } - return db.bolt.SetInt([]string{"site"}, "public-mode", mode) + return nil } diff --git a/model_clients.go b/model_clients.go index 4a0790a..2d60e9e 100644 --- a/model_clients.go +++ b/model_clients.go @@ -1,12 +1,15 @@ package main import ( - "fmt" "strconv" "strings" "time" ) +/** + * Client + * A client is a system that is connecting to the web server + */ type Client struct { UUID string Auth bool @@ -14,57 +17,53 @@ type Client struct { IP string } -func (db *currJamDb) getAllClients() []Client { - var ret []Client +// Load all clients +func (m *model) LoadAllClients() []Client { var err error - if err = db.open(); err != nil { - return ret + if err = m.openDB(); err != nil { + return err } - defer db.close() + defer m.closeDB() var clientUids []string - if clientUids, err = db.bolt.GetBucketList([]string{"clients"}); err != nil { - return ret + if clientUids, err = m.bolt.GetBucketList([]string{"clients"}); err != nil { + return err } for _, v := range clientUids { - if cl := db.getClient(v); cl != nil { - ret = append(ret, *cl) + if cl := m.LoadClient(v); cl != nil { + m.clients = append(m.clients, *cl) } } - return ret } -func (db *currJamDb) getClient(id string) *Client { +// Load a client from the DB and return it +func (m *model) LoadClient(clId string) *Client { var err error - if err = db.open(); err != nil { + if err = m.openDB(); err != nil { return nil } - defer db.close() + defer m.closeDB() cl := new(Client) cl.UUID = id - cl.Auth, _ = db.bolt.GetBool([]string{"clients", id}, "auth") - cl.Name, _ = db.bolt.GetValue([]string{"clients", id}, "name") - cl.IP, _ = db.bolt.GetValue([]string{"clients", id}, "ip") + cl.Auth, _ = m.bolt.GetBool([]string{"clients", id}, "auth") + cl.Name, _ = m.bolt.GetValue([]string{"clients", id}, "name") + cl.IP, _ = m.bolt.GetValue([]string{"clients", id}, "ip") return cl } -func (db *currJamDb) getClientByIp(ip string) *Client { - var err error - if err = db.open(); err != nil { - return nil - } - defer db.close() - - allClients := db.getAllClients() - for i := range allClients { - if allClients[i].IP == ip { - return &allClients[i] +func (m *model) getClientById(ip string) *Client { + for i := range m.clients { + if m.clients[i].IP == ip { + return &m.clients[i].IP } } return nil } +/** + * OLD FUNCTIONS + */ func (c *Client) save() error { var err error if err = db.open(); err != nil { @@ -81,62 +80,6 @@ func (c *Client) save() error { 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 { diff --git a/model_gamejam.go b/model_gamejam.go index 1c343af..82ef444 100644 --- a/model_gamejam.go +++ b/model_gamejam.go @@ -1,12 +1,11 @@ package main -import ( - "time" +import "time" - "github.com/br0xen/boltease" -) - -// Gamejam is specifically for an archived game jam +/** + * Gamejam + * Gamejam is the struct for any gamejam (current or archived) + */ type Gamejam struct { UUID string Name string @@ -14,74 +13,56 @@ type Gamejam struct { Teams []Team Votes []Vote - db *boltease.DB - dbOpened int + m *model + updates []string } -// Archived Gamejam data is stored in it's own file to keep things nice and organized -func (gj *Gamejam) openDB() error { - gj.dbOpened += 1 - if gj.dbOpened == 1 { - var err error - gj.db, err = boltease.Create(gj.UUID+".db", 0600, nil) - if err != nil { - return err - } - } - return nil +func NewGamejam(m *model) *Gamejam { + gj := new(Gamejam) + gj.m = m + return gj } -func (gj *Gamejam) closeDB() error { - gj.dbOpened -= 1 - if gj.dbOpened == 0 { - return gj.db.CloseDB() - } - return nil -} - -// archiveGameJam creates a separate gamejam file and populates it with the -// given name, teams, and votes -func archiveGamejam(nm string, teams []Team, votes []Vote) error { - // TODO - return nil -} - -// dbGetGamejam returns a gamejam with the given uuid -// or nil if it couldn't be found -func dbGetGamejam(id string) *Gamejam { - var err error - if err = openDatabase(); err != nil { - return nil - } - defer closeDatabase() - - ret := Gamejam{UUID: id} - // TODO: Load gamejam teams, other details - return ret -} - -// dbGetGamejamByName looks for a gamejam with the given name -// and returns it, or it returns nil if it couldn't find it -func dbGetGamejamByName(nm string) *Gamejam { - var err error - if err = openDatabase(); err != nil { +func (m *model) LoadCurrentJam() *Gamejam { + if err := m.openDB(); err != nil { return err } - defer closeDatabase() + defer m.closeDB() - var gjid string - if gjs, err = db.GetBucketList([]string{"gamejams"}); err == nil { - for _, v := range gjUids { - tstNm, _ := db.GetValue([]string{"gamejams", v}, "name") - if tstNm == nm { - // We've got it - gjid = v - break - } + var err error + jamPath := []string{"jam"} + gj := NewGamejam(m) + gj.Name, _ = m.bolt.GetValue(jamPath, "name") + + // Load all teams + gj.Teams = gj.LoadAllTeams() + + // Load all votes + gj.Votes = gj.LoadAllVotes() + + return gj +} + +func (gj *Gamejam) getTeamByUUID(uuid string) *Team { + for i := range gj.Teams { + if gj.Teams[i].UUID == uuid { + return &gj.Teams[i] } } - if gjid == "" { - return nil - } - return dbGetGamejam(gjid) + return nil +} + +func (gj *Gamejam) needsSave() bool { + return len(updates) > 0 +} + +func (gj *Gamejam) saveToDB() error { + if err := s.m.openDB(); err != nil { + return err + } + defer s.m.closeDB() + + for i := range updates { + // TODO: Save + } } diff --git a/model_games.go b/model_games.go index d0fddbf..6d325ae 100644 --- a/model_games.go +++ b/model_games.go @@ -2,6 +2,10 @@ package main import "errors" +/** + * Game + * A team's game, including links, description, and screenshots + */ type Game struct { Name string TeamId string @@ -18,8 +22,87 @@ type Screenshot struct { Filetype string } +// Load a team's game from the DB and return it +func (gj *Gamejam) LoadTeamGame(tmId string) *Game { + var err error + if err = gj.m.openDB(); err != nil { + return nil + } + defer gj.m.closeDB() + + gamePath := []string{"jam", "teams", tmId, "game"} + gm := new(Game) + gm.TeamId = tm.UUID + if gm.Name, err = gj.m.bolt.GetValue(gamePath, "name"); err != nil { + gm.Name = "" + } + if gm.Description, err = gj.m.bolt.GetValue(gamePath, "description"); err != nil { + gm.Description = "" + } + if gm.Link, err = gj.m.bolt.GetValue(gamePath, "link"); err != nil { + gm.Link = "" + } + // Now get the game screenshots + gm.Screenshots = gj.LoadTeamGameScreenshots(tmId) + + return &gm +} + +func (gj *Gamejam) LoadTeamGameScreenshots(tmId string) []Screenshot { + var err error + if err = gj.m.openDB(); err != nil { + return nil + } + defer gj.m.closeDB() + + var ret []Screenshot + ssBktPath := []string{"jam", "teams", tmId, "game", "screenshots"} + var ssIds []string + ssIds, _ = gj.m.bolt.GetBucketList(ssBktPath) + for _, v := range ssIds { + ssLd := gj.LoadTeamGameScreenshot(tmId, v) + if ssLd != nil { + ret = append(ret, ssLd) + } + } + return ret +} + +func (gj *Gamejam) LoadTeamGameScreenshot(tmId, ssId string) *Screenshot { + var err error + if err = gj.m.openDB(); err != nil { + return nil + } + defer gj.m.closeDB() + + var ret []Screenshot + ssPath := []string{"jam", "teams", tmId, "game", "screenshots", ssId} + ret := new(Screenshot) + ret.UUID = ssId + if ret.Description, err = gj.m.bolt.GetValue(ssPath, "description"); err != nil { + return nil + } + if ret.Image, err = gj.m.bolt.GetValue(ssPath, "image"); err != nil { + return nil + } + if ret.Thumbnail, err = gj.m.bolt.GetValue(ssPath, "thumbnail"); err != nil { + return nil + } + if ret.Thumbnail == "" { + ret.Thumbnail = ret.Image + } + if ret.Filetype, err = gj.m.bolt.GetValue(ssPath, "filetype"); err != nil { + return nil + } + return ret +} + +/** + * OLD FUNCTIONS + */ + // Create a new game object, must have a valid team id -func newGame(tmId string) *Game { +func (db *currJamDb) newGame(tmId string) *Game { var err error if err = db.open(); err != nil { return nil @@ -33,6 +116,15 @@ func newGame(tmId string) *Game { return &Game{TeamId: tmId} } +func (db *currJamDb) getAllGames() []Game { + var ret []Game + tms := db.getAllTeams() + for i := range tms { + ret = append(ret, *tms[i].getGame()) + } + return ret +} + func (gm *Game) save() error { var err error if err = db.open(); err != nil { @@ -67,12 +159,3 @@ func (gm *Game) save() error { return err } - -func (db *gjDatabase) getAllGames() []Game { - var ret []Game - tms := db.getAllTeams() - for i := range tms { - ret = append(ret, *tms[i].getGame()) - } - return ret -} diff --git a/model_site.go b/model_site.go deleted file mode 100644 index 2a0125f..0000000 --- a/model_site.go +++ /dev/null @@ -1,62 +0,0 @@ -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) -} diff --git a/model_sitedata.go b/model_sitedata.go new file mode 100644 index 0000000..5f0cdfe --- /dev/null +++ b/model_sitedata.go @@ -0,0 +1,112 @@ +package main + +/** + * SiteData + * Contains configuration for the website + */ +type siteData struct { + title string + port int + sessionName string + serverDir string + authMode int + publicMode int + + DevMode bool + Mode int + + m *model + changed bool +} + +// NewSiteData returns a siteData object with the default values +func NewSiteData(m *model) *siteData { + ret := new(siteData) + ret.Title = "ICT GameJam" + ret.Port = 8080 + ret.SessionName = "ict-gamejam" + ret.ServerDir = "./" + ret.m = m + return ret +} + +// Mode flags for how the site is currently running +const ( + SiteModeWaiting = iota + SiteModeVoting + SiteModeError +) + +// load the site data out of the database +// If fields don't exist in the DB, don't clobber what is already in s +func (s *siteData) loadFromDB() error { + if err := s.m.openDB(); err != nil { + return err + } + defer s.m.closeDB() + + siteConf := []string{"site"} + if title, err := s.m.bolt.GetValue(siteConf, "title"); err == nil { + s.Title = title + } + if port, err := s.m.bolt.GetInt(siteConf, "port"); err == nil { + s.Port = port + } + if sessionName, err = s.m.bolt.GetValue(siteConf, "session-name"); err == nil { + s.SessionName = sessionName + } + if serverDir, err = s.m.bolt.GetValue(siteConf, "server-dir"); err == nil { + s.ServerDir = serverDir + } + s.changed = false + return nil +} + +func (s *siteData) needsSave() bool { + return s.changed +} + +func (s *siteData) saveToDB() error { + if err := s.m.openDB(); err != nil { + return err + } + defer s.m.closeDB() + + siteConf := []string{"site"} + if err = s.m.bolt.SetValue(siteConf, "title", s.Title); err != nil { + return err + } + if err = s.m.bolt.SetInt(siteConf, "port", s.Port); err != nil { + return err + } + if err = s.m.bolt.SetValue(siteConf, "session-name", s.SessionName); err != nil { + return err + } + if err = s.m.bolt.SetValue(siteConf, "server-dir", s.ServerDir); err != nil { + return err + } + s.changed = false + return nil +} + +func (s *siteData) getAuthMode() int { + return s.authMode +} + +func (s *siteData) setAuthMode(mode int) { + if mode != s.authMode { + s.authMode = mode + s.changed = true + } +} + +func (s *siteData) getPublicMode() int { + return s.publicMode +} + +func (s *siteData) setPublicMode(mode int) { + if mode != s.publicMode { + s.publicMode = mode + s.changed = true + } +} diff --git a/model_teams.go b/model_teams.go index 5f95935..aa8844e 100644 --- a/model_teams.go +++ b/model_teams.go @@ -6,6 +6,9 @@ import ( "github.com/pborman/uuid" ) +/** + * Team + */ type Team struct { UUID string Name string @@ -13,8 +16,130 @@ type Team struct { Game *Game } -// newTeam creates a team with name nm and stores it in the DB -func (db *gjDatabase) newTeam(nm string) error { +// Create a team +func NewTeam(nm string) *Team { + return &Team{ + UUID: uuid.New(), + Name: nm, + } +} + +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 +} + +// LoadAllTeams loads all teams for the jam out of the database +func (gj *Gamejam) LoadAllTeams() []Team { + var err error + var ret []Team + if err = gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + + teamsPath := []string{"jam", "teams"} + if tmUUIDs, err = m.bolt.GetBucketList(mbrsPath); err != nil { + return ret + } + for _, v := range tmUUIDs { + tm := gj.LoadTeam(v) + if tm != nil { + ret = append(ret, tm) + } + } + return ret +} + +// Load a team out of the database +func (gj *Gamejam) LoadTeam(uuid string) *Team { + var err error + if err = gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + + // Team Data + tmPath := []string{"jam", "teams", uuid} + tm := new(Team) + tm.UUID = uuid + if tm.Name, err = gj.m.bolt.GetValue(tmPath, "name"); err != nil { + return nil + } + + // Team Members + tm.Members = gj.LoadTeamMembers(uuid) + + // Team Game + tm.Game = gj.LoadTeamGame(uuid) + +} + +// Load the members of a team from the DB and return them +func (gj *Gamejam) LoadTeamMembers(tmId string) []TeamMember { + var err error + var ret []TeamMember + if err = gj.m.openDB(); err != nil { + return ret + } + defer gj.m.closeDB() + + // Team Members + var memberUuids []string + mbrsPath := []string{"jam", "teams", tmId, "members"} + if memberUuids, err = gj.m.bolt.GetBucketList(mbrsPath); err == nil { + for _, v := range memberUuids { + mbr := gj.LoadTeamMember(tmId, v) + if mbr != nil { + ret = append(ret, mbr) + } + } + } + return ret +} + +// Load a team member from the DB and return it +func (gj *Gamejam) LoadTeamMember(tmId, mbrId string) *TeamMember { + var err error + if err = gj.m.openDB(); err != nil { + return nil + } + defer gj.m.closeDB() + + mbr := new(TeamMember) + mbr.UUID = v + teamMbrPath := append(mbrsPath, mbr.UUID) + // Name is the only required field + if mbr.Name, err = gj.m.bolt.GetValue(teamMbrPath, "name"); err != nil { + return nil + } + if mbr.SlackId, err = gj.m.bolt.GetValue(teamMbrPath, "slackid"); err != nil { + mbr.SlackId = "" + } + if mbr.Twitter, err = gj.m.bolt.GetValue(teamMbrPath, "twitter"); err != nil { + mbr.Twitter = "" + } + if mbr.Email, err = gj.m.bolt.GetValue(teamMbrPath, "email"); err != nil { + mbr.Email = "" + } + return mbr +} + +/** + * OLD FUNCTIONS + */ + +// NewTeam creates a team with name nm and stores it in the DB +func NewTeam(nm string) error { var err error if err = db.open(); err != nil { return err @@ -45,7 +170,7 @@ func (db *gjDatabase) newTeam(nm string) error { } // getTeam returns a team with the given id, or nil -func (db *gjDatabase) getTeam(id string) *Team { +func (db *currJamDb) getTeam(id string) *Team { var err error if err = db.open(); err != nil { return nil @@ -64,7 +189,7 @@ func (db *gjDatabase) getTeam(id string) *Team { } // This function returns the team for a specific member -func (db *gjDatabase) getTeamForMember(mbrid string) (*Team, error) { +func (db *currJamDb) getTeamForMember(mbrid string) (*Team, error) { var err error if err = db.open(); err != nil { return nil, err @@ -87,7 +212,7 @@ func (db *gjDatabase) getTeamForMember(mbrid string) (*Team, error) { } // getAllTeams returns all teams in the database -func (db *gjDatabase) getAllTeams() []Team { +func (db *currJamDb) getAllTeams() []Team { var ret []Team var err error if err = db.open(); err != nil { @@ -109,7 +234,7 @@ func (db *gjDatabase) getAllTeams() []Team { } // getTeamByName returns a team with the given name or nil -func (db *gjDatabase) getTeamByName(nm string) *Team { +func (db *currJamDb) getTeamByName(nm string) *Team { var err error if err = db.open(); err != nil { return nil @@ -212,45 +337,6 @@ func (tm *Team) saveScreenshot(ss *Screenshot) error { 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 { @@ -262,20 +348,6 @@ func (tm *Team) deleteScreenshot(ssId string) error { 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 { diff --git a/model_users.go b/model_users.go index bcd051c..f179600 100644 --- a/model_users.go +++ b/model_users.go @@ -2,46 +2,49 @@ package main import "golang.org/x/crypto/bcrypt" -// dbHasUser +// These are all model functions that have to do with users + // Returns true if there are any users in the database -func (db *gjDatabase) hasUser() bool { - return len(db.getAllUsers()) > 0 +func (m *model) hasUser() bool { + return len(m.getAllUsers()) > 0 } -func (db *gjDatabase) getAllUsers() []string { - if err := db.open(); err != nil { +func (m *model) getAllUsers() []string { + if err := m.openDB(); err != nil { return []string{} } - defer db.close() + defer m.closeDB() - usrs, err := db.bolt.GetBucketList([]string{"users"}) + usrs, err := m.bolt.GetBucketList([]string{"users"}) if err != nil { return []string{} } return usrs } -func (db *gjDatabase) isValidUserEmail(email string) bool { - if err := db.open(); err != nil { +// Is the given email one that is in our DB? +func (m *model) isValidUserEmail(email string) bool { + if err := m.openDB(); err != nil { return false } - defer db.close() + defer m.closeDB() usrPath := []string{"users", email} - _, err := db.bolt.GetValue(usrPath, "password") + _, err := m.bolt.GetValue(usrPath, "password") return err == nil } -func (db *gjDatabase) checkCredentials(email, pw string) error { +// Is the email and pw given valid? +func (m *model) checkCredentials(email, pw string) error { var err error - if err = db.open(); err != nil { + if err = m.openDB(); err != nil { return err } - defer db.close() + defer m.closeDB() var uPw string usrPath := []string{"users", email} - if uPw, err = db.bolt.GetValue(usrPath, "password"); err != nil { + if uPw, err = m.bolt.GetValue(usrPath, "password"); err != nil { return err } return bcrypt.CompareHashAndPassword([]byte(uPw), []byte(pw)) @@ -51,26 +54,26 @@ func (db *gjDatabase) checkCredentials(email, pw string) error { // 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 (db *gjDatabase) updateUserPassword(email, password string) error { +func (m *model) updateUserPassword(email, password string) error { cryptPw, cryptError := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if cryptError != nil { return cryptError } - if err := db.open(); err != nil { + if err := m.openDB(); err != nil { return err } - defer db.close() + defer m.closeDB() usrPath := []string{"users", email} - return db.bolt.SetValue(usrPath, "password", string(cryptPw)) + return m.bolt.SetValue(usrPath, "password", string(cryptPw)) } -func (db *gjDatabase) deleteUser(email string) error { +func (m *model) deleteUser(email string) error { var err error - if err = db.open(); err != nil { + if err = m.openDB(); err != nil { return err } - defer db.close() + defer m.closeDB() - return db.bolt.DeleteBucket([]string{"users"}, email) + return m.bolt.DeleteBucket([]string{"users"}, email) } diff --git a/model_votes.go b/model_votes.go index f5520e6..4cc0dd6 100644 --- a/model_votes.go +++ b/model_votes.go @@ -1,6 +1,9 @@ package main -import "time" +import ( + "strconv" + "time" +) // A Choice is a ranking of a game in a vote type GameChoice struct { @@ -15,7 +18,64 @@ type Vote struct { Choices []GameChoice } -func (db *gjDatabase) getAllVotes() []Vote { +// LoadAllVotes loads all votes for the jam out of the database +func (gj *Gamejam) LoadAllVotes() []Vote { + var ret []Vote + if err := gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + + votesPath := []string{"jam", "votes"} + if cliUUIDs, err = m.bolt.GetBucketList(votesPath); err != nil { + return ret + } + for _, cId := range cliUUIDs { + vtsPth := append(votesPath, cId) + if times, err := m.bolt.GetBucketList(vtsPth); err != nil { + // Error reading this bucket, move on to the next + continue + } + for _, t := range times { + vt := gj.LoadVote(cId, t) + if vt != nil { + ret = append(ret, vt) + } + } + } + return ret +} + +// Load a vote from the DB and return it +func (gj *Gamejam) LoadVote(clientId, tm string) *Vote { + var tm time.Time + if tm, err = time.Parse(time.RFC3339, t); err != nil { + return nil + } + vt := new(Vote) + vt.Timestamp = tm + vt.ClientId = cId + vtPth := append(vtsPth, t) + var choices []string + if choices, err = m.bolt.GetKeyList(vtPth); err != nil { + return nil + } + for _, v := range choices { + ch := new(GameChoices) + var rank int + if rank, err = strconv.Atoi(v); err == nil { + ch.Rank = rank + ch.Team, _ = m.bolt.GetValue(vtPth, v) + vt.Choices = append(vt.Choices, *ch) + } + } + return &vt +} + +/** + * OLD FUNCTIONS + */ +func (db *currJamDb) getAllVotes() []Vote { var ret []Vote var err error if err = db.open(); err != nil { diff --git a/public_endpoints.go b/public_endpoints.go index 5d31c4a..47de0d8 100644 --- a/public_endpoints.go +++ b/public_endpoints.go @@ -28,7 +28,7 @@ func handleMain(w http.ResponseWriter, req *http.Request) { func loadVotingPage(w http.ResponseWriter, req *http.Request) { page := initPublicPage(w, req) // Client authentication required - if (db.getAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { + if (db.site.getAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { page.show("unauthorized.html", w) return } @@ -55,7 +55,7 @@ func loadVotingPage(w http.ResponseWriter, req *http.Request) { func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) { page := initPublicPage(w, req) // Client authentication required - if (db.getAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { + if (db.site.getAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { page.show("unauthorized.html", w) return } From cdd0ce6a44e2aa65c3913512ece03711848b8412 Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Thu, 12 Oct 2017 08:51:53 -0500 Subject: [PATCH 2/6] Switching dev machines --- model_clients.go | 53 ++++++++------ model_gamejam.go | 11 +-- model_games.go | 182 +++++++++++++++++++++++++++++++---------------- model_teams.go | 164 ++++++++++++++---------------------------- 4 files changed, 214 insertions(+), 196 deletions(-) diff --git a/model_clients.go b/model_clients.go index 2d60e9e..c3d8af3 100644 --- a/model_clients.go +++ b/model_clients.go @@ -15,6 +15,15 @@ type Client struct { Auth bool Name string IP string + + mPath []string // The path in the DB to this client +} + +func NewClient(id string) *Client { + return &Client{ + UUID: id, + mPath: []string{"clients", id}, + } } // Load all clients @@ -26,7 +35,8 @@ func (m *model) LoadAllClients() []Client { defer m.closeDB() var clientUids []string - if clientUids, err = m.bolt.GetBucketList([]string{"clients"}); err != nil { + cliPath := []string{"clients"} + if clientUids, err = m.bolt.GetBucketList(cliPath); err != nil { return err } for _, v := range clientUids { @@ -44,11 +54,10 @@ func (m *model) LoadClient(clId string) *Client { } defer m.closeDB() - cl := new(Client) - cl.UUID = id - cl.Auth, _ = m.bolt.GetBool([]string{"clients", id}, "auth") - cl.Name, _ = m.bolt.GetValue([]string{"clients", id}, "name") - cl.IP, _ = m.bolt.GetValue([]string{"clients", id}, "ip") + cl := NewClient(clId) + cl.Auth, _ = m.bolt.GetBool(cl.mPath, "auth") + cl.Name, _ = m.bolt.GetValue(cl.mPath, "name") + cl.IP, _ = m.bolt.GetValue(cl.mPath, "ip") return cl } @@ -61,25 +70,25 @@ func (m *model) getClientById(ip string) *Client { return nil } +func (m *model) SaveClient(cl *Client) error { + var err error + if err = m.openDB(); err != nil { + return nil + } + defer m.closeDB() + + if err = db.bolt.SetBool(cl.mPath, "auth", c.Auth); err != nil { + return err + } + if err = db.bolt.SetValue(cl.mPath, "name", c.Name); err != nil { + return err + } + return db.bolt.SetValue(cl.mPath, "ip", c.IP) +} + /** * OLD FUNCTIONS */ -func (c *Client) save() error { - var err error - if err = db.open(); err != nil { - return nil - } - defer db.close() - - 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) saveVote(timestamp time.Time, votes []string) error { var err error if err = db.open(); err != nil { diff --git a/model_gamejam.go b/model_gamejam.go index 82ef444..56defb0 100644 --- a/model_gamejam.go +++ b/model_gamejam.go @@ -13,13 +13,15 @@ type Gamejam struct { Teams []Team Votes []Vote - m *model + m *model // The model that holds this gamejam's data + mPath []string // The path in the db to this gamejam updates []string } func NewGamejam(m *model) *Gamejam { gj := new(Gamejam) gj.m = m + gj.mPath = []string{"jam"} return gj } @@ -30,9 +32,8 @@ func (m *model) LoadCurrentJam() *Gamejam { defer m.closeDB() var err error - jamPath := []string{"jam"} gj := NewGamejam(m) - gj.Name, _ = m.bolt.GetValue(jamPath, "name") + gj.Name, _ = m.bolt.GetValue(gj.mPath, "name") // Load all teams gj.Teams = gj.LoadAllTeams() @@ -57,10 +58,10 @@ func (gj *Gamejam) needsSave() bool { } func (gj *Gamejam) saveToDB() error { - if err := s.m.openDB(); err != nil { + if err := gj.m.openDB(); err != nil { return err } - defer s.m.closeDB() + defer gj.m.closeDB() for i := range updates { // TODO: Save diff --git a/model_games.go b/model_games.go index 6d325ae..e03ca80 100644 --- a/model_games.go +++ b/model_games.go @@ -1,7 +1,5 @@ package main -import "errors" - /** * Game * A team's game, including links, description, and screenshots @@ -12,6 +10,25 @@ type Game struct { Link string Description string Screenshots []Screenshot + + mPath []string // The path in the DB to this game +} + +// Create a new game object +func NewGame(tmId string) *Game { + return &Game{ + TeamId: tmId, + mPath: []string{"jam", "teams", tmId, "game"}, + } +} + +func (gm *Game) GetScreenshot(ssId string) *Screenshot { + for _, ss := range gm.Screenshots { + if ss.UUID == ssId { + return ss + } + } + return nil } type Screenshot struct { @@ -20,6 +37,16 @@ type Screenshot struct { Image string Thumbnail string Filetype string + + mPath []string // The path in the DB to this screenshot +} + +// Create a Screenshot Object +func NewScreenshot(tmId, ssId string) *Screenshot { + return &Screenshot{ + UUID: ssId, + mPath: []string{"jam", "teams", tmId, "game", "screenshots", ssId}, + } } // Load a team's game from the DB and return it @@ -30,16 +57,14 @@ func (gj *Gamejam) LoadTeamGame(tmId string) *Game { } defer gj.m.closeDB() - gamePath := []string{"jam", "teams", tmId, "game"} - gm := new(Game) - gm.TeamId = tm.UUID - if gm.Name, err = gj.m.bolt.GetValue(gamePath, "name"); err != nil { + gm := NewGame(tmId) + if gm.Name, err = gj.m.bolt.GetValue(gm.mPath, "name"); err != nil { gm.Name = "" } - if gm.Description, err = gj.m.bolt.GetValue(gamePath, "description"); err != nil { + if gm.Description, err = gj.m.bolt.GetValue(gm.mPath, "description"); err != nil { gm.Description = "" } - if gm.Link, err = gj.m.bolt.GetValue(gamePath, "link"); err != nil { + if gm.Link, err = gj.m.bolt.GetValue(gm.mPath, "link"); err != nil { gm.Link = "" } // Now get the game screenshots @@ -48,6 +73,7 @@ func (gj *Gamejam) LoadTeamGame(tmId string) *Game { return &gm } +// Load a games screenshots from the DB func (gj *Gamejam) LoadTeamGameScreenshots(tmId string) []Screenshot { var err error if err = gj.m.openDB(); err != nil { @@ -56,7 +82,8 @@ func (gj *Gamejam) LoadTeamGameScreenshots(tmId string) []Screenshot { defer gj.m.closeDB() var ret []Screenshot - ssBktPath := []string{"jam", "teams", tmId, "game", "screenshots"} + gm := NewGame(tmId) + ssBktPath := append(gm.mPath, "screenshots") var ssIds []string ssIds, _ = gj.m.bolt.GetBucketList(ssBktPath) for _, v := range ssIds { @@ -68,6 +95,7 @@ func (gj *Gamejam) LoadTeamGameScreenshots(tmId string) []Screenshot { return ret } +// Load a screenshot from the DB func (gj *Gamejam) LoadTeamGameScreenshot(tmId, ssId string) *Screenshot { var err error if err = gj.m.openDB(); err != nil { @@ -75,87 +103,121 @@ func (gj *Gamejam) LoadTeamGameScreenshot(tmId, ssId string) *Screenshot { } defer gj.m.closeDB() - var ret []Screenshot - ssPath := []string{"jam", "teams", tmId, "game", "screenshots", ssId} - ret := new(Screenshot) - ret.UUID = ssId - if ret.Description, err = gj.m.bolt.GetValue(ssPath, "description"); err != nil { + ret := NewScreenshot(tmId, ssId) + if ret.Description, err = gj.m.bolt.GetValue(ret.mPath, "description"); err != nil { return nil } - if ret.Image, err = gj.m.bolt.GetValue(ssPath, "image"); err != nil { + if ret.Image, err = gj.m.bolt.GetValue(ret.mPath, "image"); err != nil { return nil } - if ret.Thumbnail, err = gj.m.bolt.GetValue(ssPath, "thumbnail"); err != nil { + if ret.Thumbnail, err = gj.m.bolt.GetValue(ret.mPath, "thumbnail"); err != nil { return nil } if ret.Thumbnail == "" { ret.Thumbnail = ret.Image } - if ret.Filetype, err = gj.m.bolt.GetValue(ssPath, "filetype"); err != nil { + if ret.Filetype, err = gj.m.bolt.GetValue(ret.mPath, "filetype"); err != nil { return nil } return ret } -/** - * OLD FUNCTIONS - */ - -// Create a new game object, must have a valid team id -func (db *currJamDb) newGame(tmId string) *Game { +// Save a game to the given model's DB +func (gj *Gamejam) SaveGame(gm *Game) error { + //func (gm *Game) save(m *model) error { var err error - if err = db.open(); err != nil { - return nil - } - defer db.close() - - tm := db.getTeam(tmId) - if tm == nil { - return nil - } - return &Game{TeamId: tmId} -} - -func (db *currJamDb) getAllGames() []Game { - var ret []Game - tms := db.getAllTeams() - for i := range tms { - ret = append(ret, *tms[i].getGame()) - } - return ret -} - -func (gm *Game) save() error { - var err error - if err = db.open(); err != nil { + if err = gj.m.openDB(); err != nil { return err } - defer db.close() + defer gj.m.closeDB() - tm := db.getTeam(gm.TeamId) - if tm == nil { - return errors.New("Invalid Team: " + gm.TeamId) - } - gamePath := []string{"teams", gm.TeamId, "game"} - if err := db.bolt.MkBucketPath(gamePath); err != nil { + /* + tm := gj.getTeam(gm.TeamId) + if tm == nil { + return errors.New("Invalid Team: " + gm.TeamId) + } + */ + if err := gj.m.bolt.MkBucketPath(gm.mPath); err != nil { return err } if gm.Name == "" { gm.Name = tm.Name + "'s Game" } - if err := db.bolt.SetValue(gamePath, "name", gm.Name); err != nil { + if err := gj.m.bolt.SetValue(gm.mPath, "name", gm.Name); err != nil { return err } - if err := db.bolt.SetValue(gamePath, "link", gm.Link); err != nil { + if err := gj.m.bolt.SetValue(gm.mPath, "link", gm.Link); err != nil { return err } - if err := db.bolt.SetValue(gamePath, "description", gm.Description); err != nil { + if err := gj.m.bolt.SetValue(gm.mPath, "description", gm.Description); err != nil { return err } - if err := db.bolt.MkBucketPath(append(gamePath, "screenshots")); err != nil { + if err := gj.m.bolt.MkBucketPath(append(gm.mPath, "screenshots")); err != nil { return err } - - return err + return gj.SaveScreenshots(gm) +} + +// Save all of the game's screenshots to the DB +// Remove screenshots from the DB that aren't in the game object +func (gj *Gamejam) SaveScreenshots(gm *Game) error { + var err error + if err = gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + + for _, ss := range gm.Screenshots { + if err = gj.SaveScreenshot(gm.TeamId, ss); err != nil { + return err + } + } + // Now remove unused screenshots + ssPath := append(gm.mPath, "screenshots") + if ssIds, err = gj.m.bolt.GetBucketList(ssPath); err != nil { + return err + } + for i := range ssIds { + if gm.GetScreenshot(ssIds[i]) == nil { + if err = gj.DeleteScreenshot(NewScreenshot(tm.TeamId, ssIds[i])); err != nil { + return err + } + } + } +} + +// Save a screenshot +func (gj *Gamejam) SaveScreenshot(tmId string, ss *Screenshot) error { + var err error + if err = gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + + if err = gj.m.bolt.MkBucketPath(ss.mPath); err != nil { + return err + } + if err = gj.m.bolt.SetValue(ss.mPath, "description", ss.Description); err != nil { + return err + } + if err = gj.m.bolt.SetValue(ss.mPath, "image", ss.Image); err != nil { + return err + } + if err = gj.m.bolt.SetValue(ss.mPath, "filetype", ss.Filetype); err != nil { + return err + } + return nil +} + +// Delete a screenshot +func (gj *Gamejam) DeleteScreenshot(ss *Screenshot) error { + var err error + if err = gj.m.openDB(); err != nil { + return nil + } + defer gj.m.closeDB() + + ssPath := ss.mPath[:len(ss.mPath)-1] + return gj.m.bolt.DeleteBucket(ssPath, ss.UUID) } diff --git a/model_teams.go b/model_teams.go index aa8844e..df98872 100644 --- a/model_teams.go +++ b/model_teams.go @@ -14,13 +14,15 @@ type Team struct { Name string Members []TeamMember Game *Game + + mPath []string // The path in the DB to this team } // Create a team -func NewTeam(nm string) *Team { +func NewTeam(id string) *Team { return &Team{ - UUID: uuid.New(), - Name: nm, + UUID: id, + mPath: []string{"jam", "teams", id}, } } @@ -32,10 +34,12 @@ type TeamMember struct { Email string } -// Create a new team member, only a name is required -func NewTeamMember(nm string) *TeamMember { - m := TeamMember{Name: nm} - return &m +// Create a new team member +func NewTeamMember(tmId, uId string) *TeamMember { + return &TeamMember{ + UUID: uId, + mPath: []string{"jam", "teams", tmId, "members", uId}, + } } // LoadAllTeams loads all teams for the jam out of the database @@ -47,7 +51,6 @@ func (gj *Gamejam) LoadAllTeams() []Team { } defer gj.m.closeDB() - teamsPath := []string{"jam", "teams"} if tmUUIDs, err = m.bolt.GetBucketList(mbrsPath); err != nil { return ret } @@ -69,10 +72,8 @@ func (gj *Gamejam) LoadTeam(uuid string) *Team { defer gj.m.closeDB() // Team Data - tmPath := []string{"jam", "teams", uuid} - tm := new(Team) - tm.UUID = uuid - if tm.Name, err = gj.m.bolt.GetValue(tmPath, "name"); err != nil { + tm := NewTeam(uuid) + if tm.Name, err = gj.m.bolt.GetValue(tm.mPath, "name"); err != nil { return nil } @@ -82,6 +83,7 @@ func (gj *Gamejam) LoadTeam(uuid string) *Team { // Team Game tm.Game = gj.LoadTeamGame(uuid) + return tm } // Load the members of a team from the DB and return them @@ -95,7 +97,8 @@ func (gj *Gamejam) LoadTeamMembers(tmId string) []TeamMember { // Team Members var memberUuids []string - mbrsPath := []string{"jam", "teams", tmId, "members"} + tm := NewTeam(tmId) + mbrsPath := append(tm.mPath, "members") if memberUuids, err = gj.m.bolt.GetBucketList(mbrsPath); err == nil { for _, v := range memberUuids { mbr := gj.LoadTeamMember(tmId, v) @@ -115,25 +118,57 @@ func (gj *Gamejam) LoadTeamMember(tmId, mbrId string) *TeamMember { } defer gj.m.closeDB() - mbr := new(TeamMember) - mbr.UUID = v - teamMbrPath := append(mbrsPath, mbr.UUID) + mbr := NewTeamMember(tmId, mbrId) // Name is the only required field - if mbr.Name, err = gj.m.bolt.GetValue(teamMbrPath, "name"); err != nil { + if mbr.Name, err = gj.m.bolt.GetValue(mbr.mPath, "name"); err != nil { return nil } - if mbr.SlackId, err = gj.m.bolt.GetValue(teamMbrPath, "slackid"); err != nil { + if mbr.SlackId, err = gj.m.bolt.GetValue(mbr.mPath, "slackid"); err != nil { mbr.SlackId = "" } - if mbr.Twitter, err = gj.m.bolt.GetValue(teamMbrPath, "twitter"); err != nil { + if mbr.Twitter, err = gj.m.bolt.GetValue(mbr.mPath, "twitter"); err != nil { mbr.Twitter = "" } - if mbr.Email, err = gj.m.bolt.GetValue(teamMbrPath, "email"); err != nil { + if mbr.Email, err = gj.m.bolt.GetValue(mbr.mPath, "email"); err != nil { mbr.Email = "" } return mbr } +func (gj *Gamejam) SaveTeam(tm *Team) error { + var err error + if err = gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + + // Save team data + if err = gj.m.bolt.SetValue(tm.mPath, "name"); err != nil { + return err + } + + // Save team members + for _, mbr := range tm.Members { + if err = gj.m.bolt.SetValue(mbr.mPath, "name", mbr.Name); err != nil { + return err + } + if err = gj.m.bolt.SetValue(mbr.mPath, "slackid", mbr.SlackId); err != nil { + return err + } + if err = gj.m.bolt.SetValue(mbr.mPath, "twitter", mbr.Twitter); err != nil { + return err + } + if err = gj.m.bolt.SetValue(mbr.mPath, "email", mbr.Email); err != nil { + return err + } + } + + // Save team game + if err = gj.m.bolt.SetValue(tm. + +} + + /** * OLD FUNCTIONS */ @@ -284,95 +319,6 @@ func (tm *Team) delete() error { 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) 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) -} - -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 err error From d1496007fb44f8df01d101cdd597d64db8aec688 Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Wed, 18 Oct 2017 17:18:12 -0500 Subject: [PATCH 3/6] Switching Dev Systems --- admin_clients.go | 27 ++--- admin_endpoints.go | 8 +- admin_games.go | 9 +- model.go | 12 +-- model_clients.go | 86 ++++++++++----- model_gamejam.go | 52 ++++++--- model_games.go | 33 ++++-- model_sitedata.go | 55 +++++++--- model_teams.go | 264 +++++++++------------------------------------ model_votes.go | 18 ---- 10 files changed, 234 insertions(+), 330 deletions(-) diff --git a/admin_clients.go b/admin_clients.go index 7e2c2c0..5c23aef 100644 --- a/admin_clients.go +++ b/admin_clients.go @@ -11,29 +11,28 @@ func handleAdminClients(w http.ResponseWriter, req *http.Request, page *pageData vars := mux.Vars(req) page.SubTitle = "Clients" clientId := vars["id"] - client := db.getClient(clientId) + client := m.GetClient(clientId) clientIp, _, _ := net.SplitHostPort(req.RemoteAddr) if clientId == "" { type clientsPageData struct { Clients []Client } - page.TemplateData = clientsPageData{Clients: db.getAllClients()} + page.TemplateData = clientsPageData{Clients: m.clients} page.SubTitle = "Clients" page.show("admin-clients.html", w) } else { switch vars["function"] { case "add": page.SubTitle = "Authenticate Client" - cli := db.getClient(clientId) - if cli.IP == "" { - cli.IP = clientIp + if client.IP == "" { + client.IP = clientIp } type actClientPageData struct { Id string Ip string Name string } - page.TemplateData = actClientPageData{Id: cli.UUID, Ip: cli.IP, Name: cli.Name} + page.TemplateData = actClientPageData{Id: client.UUID, Ip: client.IP, Name: client.Name} page.show("admin-activateclient.html", w) case "auth": email := req.FormValue("email") @@ -44,16 +43,13 @@ func handleAdminClients(w http.ResponseWriter, req *http.Request, page *pageData client.Name = clientName } client.IP = clientIp - client.save() + m.UpdateClient(client) if page.LoggedIn || doLogin(email, password) == nil { // Received a valid login // Authenticate the client client.Auth = true - if client.save() == nil { - page.session.setFlashMessage("Client Authenticated", "success") - } else { - page.session.setFlashMessage("Client Authentication Failed", "error") - } + m.UpdateClient(client) + page.session.setFlashMessage("Client Authenticated", "success") if page.LoggedIn { redirect("/admin/clients", w, req) } @@ -61,11 +57,8 @@ func handleAdminClients(w http.ResponseWriter, req *http.Request, page *pageData redirect("/", w, req) case "deauth": client.Auth = false - if client.save() == nil { - page.session.setFlashMessage("Client De-Authenticated", "success") - } else { - page.session.setFlashMessage("Client De-Authentication Failed", "success") - } + m.UpdateClient(client) + page.session.setFlashMessage("Client De-Authenticated", "success") redirect("/admin/clients", w, req) } } diff --git a/admin_endpoints.go b/admin_endpoints.go index c901fe9..dd26b18 100644 --- a/admin_endpoints.go +++ b/admin_endpoints.go @@ -62,8 +62,8 @@ func handleAdminSetMode(w http.ResponseWriter, req *http.Request, page *pageData if err != nil { page.session.setFlashMessage("Invalid Mode: "+vars["id"], "error") } - if db.setPublicSiteMode(newMode) != nil { - page.session.setFlashMessage("Invalid Mode: "+vars["id"], "error") + if err = m.site.SetPublicMode(newMode); err != nil { + page.session.setFlashMessage(err.Error(), "error") } redirect("/admin", w, req) } @@ -74,8 +74,8 @@ func handleAdminSetAuthMode(w http.ResponseWriter, req *http.Request, page *page if err != nil { page.session.setFlashMessage("Invalid Authentication Mode: "+vars["id"], "error") } - if db.site.setAuthMode(newMode) != nil { - page.session.setFlashMessage("Invalid Authentication Mode: "+vars["id"], "error") + if err = m.site.SetAuthMode(newMode); err != nil { + page.session.setFlashMessage(err.Error(), "error") } redirect("/admin", w, req) } diff --git a/admin_games.go b/admin_games.go index 6003a94..12cd918 100644 --- a/admin_games.go +++ b/admin_games.go @@ -20,24 +20,25 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) teamId := vars["id"] if teamId == "" { // Games List + // TODO: We should be able to just pass m.jam to the template instead of a custom struct type gamesPageData struct { Teams []Team } gpd := new(gamesPageData) - gpd.Teams = db.getAllTeams() + gpd.Teams = m.jam.Teams page.TemplateData = gpd page.SubTitle = "Games" page.show("admin-games.html", w) } else { - tm := db.getTeam(teamId) + tm := m.jam.GetTeamById(teamId) if tm != nil { switch vars["function"] { case "save": - gm := db.newGame(tm.UUID) + gm := NewGame(tm.UUID) gm.Name = req.FormValue("gamename") gm.Link = req.FormValue("gamelink") gm.Description = req.FormValue("gamedesc") - if err := gm.save(); err != nil { + if err := m.jam.UpdateGame(tm.UUID, gm); err != nil { page.session.setFlashMessage("Error updating game: "+err.Error(), "error") } else { page.session.setFlashMessage("Team game updated", "success") diff --git a/model.go b/model.go index a878ac9..2e038ad 100644 --- a/model.go +++ b/model.go @@ -17,13 +17,6 @@ type model struct { clients []Client // Web clients that have connected to the server } -// Authentication Modes: Flags for which clients are able to vote -const ( - AuthModeAuthentication = iota - AuthModeAll - AuthModeError -) - // Update Flags: Which parts of the model need to be updated const ( UpdateSiteData = iota @@ -122,5 +115,10 @@ func (m *model) saveChanges() error { return err } } + if m.clientsUpdated { + if err = m.SaveAllClients(); err != nil { + return err + } + } return nil } diff --git a/model_clients.go b/model_clients.go index c3d8af3..c7d5ee4 100644 --- a/model_clients.go +++ b/model_clients.go @@ -1,11 +1,5 @@ package main -import ( - "strconv" - "strings" - "time" -) - /** * Client * A client is a system that is connecting to the web server @@ -26,24 +20,31 @@ func NewClient(id string) *Client { } } -// Load all clients +/** + * DB Functions + * These are generally just called when the app starts up, or when the periodic 'save' runs + */ + +// Load all clients from the DB func (m *model) LoadAllClients() []Client { var err error + var ret []Client if err = m.openDB(); err != nil { - return err + return ret } defer m.closeDB() var clientUids []string cliPath := []string{"clients"} if clientUids, err = m.bolt.GetBucketList(cliPath); err != nil { - return err + return ret } for _, v := range clientUids { if cl := m.LoadClient(v); cl != nil { - m.clients = append(m.clients, *cl) + ret = append(ret, *cl) } } + return ret } // Load a client from the DB and return it @@ -61,15 +62,23 @@ func (m *model) LoadClient(clId string) *Client { return cl } -func (m *model) getClientById(ip string) *Client { - for i := range m.clients { - if m.clients[i].IP == ip { - return &m.clients[i].IP +// SaveAllClients saves all clients to the DB +func (m *model) SaveAllClients() error { + var err error + if err = m.openDB(); err != nil { + return nil + } + defer m.closeDB() + + for _, v := range m.clients { + if err = m.SaveClient(v); err != nil { + return err } } return nil } +// SaveClient saves a client to the DB func (m *model) SaveClient(cl *Client) error { var err error if err = m.openDB(); err != nil { @@ -87,20 +96,43 @@ func (m *model) SaveClient(cl *Client) error { } /** - * OLD FUNCTIONS + * In Memory functions + * This is generally how the app accesses client data */ -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 a client by it's UUID +func (m *model) GetClient(id string) *Client { + for i := range m.clients { + if m.clients[i].UUID == id { + return &m.clients[i] } } - return err + return nil +} + +// Return a client by it's IP address +func (m *model) GetClientByIp(ip string) *Client { + for i := range m.clients { + if m.clients[i].IP == ip { + return &m.clients[i] + } + } + return nil +} + +// Add/Update a client in the data model +func (m *model) UpdateClient(cl *Client) { + var found bool + for i := range m.clients { + if m.clients[i].UUID == cl.UUID { + found = true + m.clients[i].Auth = cl.Auth + m.clients[i].Name = cl.Name + m.clients[i].IP = cl.IP + } + } + if !found { + m.clients = append(m.clients, cl) + } + m.clientsUpdated = true } diff --git a/model_gamejam.go b/model_gamejam.go index 56defb0..1114178 100644 --- a/model_gamejam.go +++ b/model_gamejam.go @@ -15,7 +15,7 @@ type Gamejam struct { m *model // The model that holds this gamejam's data mPath []string // The path in the db to this gamejam - updates []string + changed bool // Flag to tell if we need to update the db } func NewGamejam(m *model) *Gamejam { @@ -25,6 +25,11 @@ func NewGamejam(m *model) *Gamejam { return gj } +/** + * DB Functions + * These are generally just called when the app starts up, or when the periodic 'save' runs + */ + func (m *model) LoadCurrentJam() *Gamejam { if err := m.openDB(); err != nil { return err @@ -44,6 +49,19 @@ func (m *model) LoadCurrentJam() *Gamejam { return gj } +// Save everything to the DB whether it's flagged as changed or not +func (gj *Gamejam) saveToDB() error { + if err := gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + +} + +/** + * In Memory functions + * This is generally how the app accesses client data + */ func (gj *Gamejam) getTeamByUUID(uuid string) *Team { for i := range gj.Teams { if gj.Teams[i].UUID == uuid { @@ -53,17 +71,27 @@ func (gj *Gamejam) getTeamByUUID(uuid string) *Team { return nil } -func (gj *Gamejam) needsSave() bool { - return len(updates) > 0 -} - -func (gj *Gamejam) saveToDB() error { - if err := gj.m.openDB(); err != nil { - return err +// Check if pth is already in updates, if not, add it +func (gj *Gamejam) NeedsUpdate(pth []string) { + var found bool + for _, v := range gj.updates { + if !(len(v) == len(pth)) { + continue + } + // The lengths are the same, do all elements match? + var nxt bool + for i := range pth { + if v[i] != pth[i] { + nxt = true + } + } + if !nxt { + // This pth is already in the 'updates' list + found = true + break + } } - defer gj.m.closeDB() - - for i := range updates { - // TODO: Save + if !found { + gj.updates = append(gj.updates, pth) } } diff --git a/model_games.go b/model_games.go index e03ca80..92290d3 100644 --- a/model_games.go +++ b/model_games.go @@ -1,5 +1,7 @@ package main +import "errors" + /** * Game * A team's game, including links, description, and screenshots @@ -49,6 +51,11 @@ func NewScreenshot(tmId, ssId string) *Screenshot { } } +/** + * DB Functions + * These are generally just called when the app starts up, or when the periodic 'save' runs + */ + // Load a team's game from the DB and return it func (gj *Gamejam) LoadTeamGame(tmId string) *Game { var err error @@ -122,21 +129,14 @@ func (gj *Gamejam) LoadTeamGameScreenshot(tmId, ssId string) *Screenshot { return ret } -// Save a game to the given model's DB +// Save a game to the DB func (gj *Gamejam) SaveGame(gm *Game) error { - //func (gm *Game) save(m *model) error { var err error if err = gj.m.openDB(); err != nil { return err } defer gj.m.closeDB() - /* - tm := gj.getTeam(gm.TeamId) - if tm == nil { - return errors.New("Invalid Team: " + gm.TeamId) - } - */ if err := gj.m.bolt.MkBucketPath(gm.mPath); err != nil { return err } @@ -221,3 +221,20 @@ func (gj *Gamejam) DeleteScreenshot(ss *Screenshot) error { ssPath := ss.mPath[:len(ss.mPath)-1] return gj.m.bolt.DeleteBucket(ssPath, ss.UUID) } + +/** + * In Memory functions + * This is generally how the app accesses client data + */ + +// Set the given team's game to gm +func (gj *Gamejam) UpdateGame(tmId string, gm *Game) error { + var found bool + tm := gj.GetTeamById(tmId) + if tm == nil { + return errors.New("Invalid team ID: " + gm.TeamId) + } + tm.Game = gm + gj.NeedsUpdate([]string{"team", tmId, "game"}) + return nil +} diff --git a/model_sitedata.go b/model_sitedata.go index 5f0cdfe..b9f2dbf 100644 --- a/model_sitedata.go +++ b/model_sitedata.go @@ -1,5 +1,7 @@ package main +import "strconv" + /** * SiteData * Contains configuration for the website @@ -16,6 +18,7 @@ type siteData struct { Mode int m *model + mPath []string // The path in the db to this site data changed bool } @@ -26,10 +29,18 @@ func NewSiteData(m *model) *siteData { ret.Port = 8080 ret.SessionName = "ict-gamejam" ret.ServerDir = "./" + ret.mPath = []string{"site"} ret.m = m return ret } +// Authentication Modes: Flags for which clients are able to vote +const ( + AuthModeAuthentication = iota + AuthModeAll + AuthModeError +) + // Mode flags for how the site is currently running const ( SiteModeWaiting = iota @@ -39,72 +50,82 @@ const ( // load the site data out of the database // If fields don't exist in the DB, don't clobber what is already in s -func (s *siteData) loadFromDB() error { +func (s *siteData) LoadFromDB() error { if err := s.m.openDB(); err != nil { return err } defer s.m.closeDB() - siteConf := []string{"site"} - if title, err := s.m.bolt.GetValue(siteConf, "title"); err == nil { + if title, err := s.m.bolt.GetValue(s.mPath, "title"); err == nil { s.Title = title } - if port, err := s.m.bolt.GetInt(siteConf, "port"); err == nil { + if port, err := s.m.bolt.GetInt(s.mPath, "port"); err == nil { s.Port = port } - if sessionName, err = s.m.bolt.GetValue(siteConf, "session-name"); err == nil { + if sessionName, err = s.m.bolt.GetValue(s.mPath, "session-name"); err == nil { s.SessionName = sessionName } - if serverDir, err = s.m.bolt.GetValue(siteConf, "server-dir"); err == nil { + if serverDir, err = s.m.bolt.GetValue(s.mPath, "server-dir"); err == nil { s.ServerDir = serverDir } s.changed = false return nil } -func (s *siteData) needsSave() bool { +// Return if the site data in memory has changed +func (s *siteData) NeedsSave() bool { return s.changed } -func (s *siteData) saveToDB() error { +// Save the site data into the DB +func (s *siteData) SaveToDB() error { if err := s.m.openDB(); err != nil { return err } defer s.m.closeDB() - siteConf := []string{"site"} - if err = s.m.bolt.SetValue(siteConf, "title", s.Title); err != nil { + if err = s.m.bolt.SetValue(s.mPath, "title", s.Title); err != nil { return err } - if err = s.m.bolt.SetInt(siteConf, "port", s.Port); err != nil { + if err = s.m.bolt.SetInt(s.mPath, "port", s.Port); err != nil { return err } - if err = s.m.bolt.SetValue(siteConf, "session-name", s.SessionName); err != nil { + if err = s.m.bolt.SetValue(s.mPath, "session-name", s.SessionName); err != nil { return err } - if err = s.m.bolt.SetValue(siteConf, "server-dir", s.ServerDir); err != nil { + if err = s.m.bolt.SetValue(s.mPath, "server-dir", s.ServerDir); err != nil { return err } s.changed = false return nil } -func (s *siteData) getAuthMode() int { +// Return the Auth Mode +func (s *siteData) GetAuthMode() int { return s.authMode } -func (s *siteData) setAuthMode(mode int) { +// Set the auth mode +func (s *siteData) SetAuthMode(mode int) error { + if mode < AuthModeAuthentication || mode >= AuthModeError { + return errors.Error("Invalid Authentication Mode: " + strconv.Itoa(mode)) + } if mode != s.authMode { s.authMode = mode s.changed = true } } -func (s *siteData) getPublicMode() int { +// Return the public site mode +func (s *siteData) GetPublicMode() int { return s.publicMode } -func (s *siteData) setPublicMode(mode int) { +// Set the public site mode +func (s *siteData) SetPublicMode(mode int) error { + if mode < SiteModeWaiting || mode >= SiteModeError { + return errors.Error("Invalid Public Mode: " + strconv.Itoa(mode)) + } if mode != s.publicMode { s.publicMode = mode s.changed = true diff --git a/model_teams.go b/model_teams.go index df98872..9f90f0c 100644 --- a/model_teams.go +++ b/model_teams.go @@ -1,10 +1,6 @@ package main -import ( - "errors" - - "github.com/pborman/uuid" -) +import "errors" /** * Team @@ -32,6 +28,8 @@ type TeamMember struct { SlackId string Twitter string Email string + + mPath []string // The path in the DB to this team member } // Create a new team member @@ -42,6 +40,11 @@ func NewTeamMember(tmId, uId string) *TeamMember { } } +/** + * DB Functions + * These are generally just called when the app starts up, or when the periodic 'save' runs + */ + // LoadAllTeams loads all teams for the jam out of the database func (gj *Gamejam) LoadAllTeams() []Team { var err error @@ -164,229 +167,58 @@ func (gj *Gamejam) SaveTeam(tm *Team) error { } // Save team game - if err = gj.m.bolt.SetValue(tm. - + return gj.SaveGame(gm) } +// Delete the team tm +func (gj *Gamejam) DeleteTeam(tm *Team) error { + var err error + if err = gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + + if len(tm.mPath) < 2 { + return errors.New("Invalid team path: " + string(tm.mPath)) + } + return gj.m.bolt.DeleteBucket(tm.mPath[:len(tm.mPath)-1], tm.UUID) +} + +// Delete the TeamMember mbr from Team tm +func (gj *Gamejam) DeleteTeamMember(tm *Team, mbr *TeamMember) error { + var err error + if err = gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + + if len(mbr.mPath) < 2 { + return errors.New("Invalid team path: " + string(tm.mPath)) + } + return gj.m.bolt.DeleteBucket(mbr.mPath[:len(mbr.mPath)-1], mbr.UUID) +} /** - * OLD FUNCTIONS + * In Memory functions + * This is generally how the app accesses data */ -// NewTeam creates a team with name nm and stores it in the DB -func NewTeam(nm string) error { - var err error - if err = db.open(); err != nil { - return err - } - defer db.close() - - // Generate a UUID - uuid := uuid.New() - teamPath := []string{"teams", uuid} - - if err := db.bolt.MkBucketPath(teamPath); err != nil { - return err - } - if err := db.bolt.SetValue(teamPath, "name", nm); err != nil { - return err - } - if err := db.bolt.MkBucketPath(append(teamPath, "members")); err != nil { - return err - } - gamePath := append(teamPath, "game") - if err := db.bolt.MkBucketPath(gamePath); err != nil { - return err - } - if err := db.bolt.SetValue(append(gamePath), "name", ""); err != nil { - return err - } - return db.bolt.MkBucketPath(append(gamePath, "screenshots")) -} - -// getTeam returns a team with the given id, or nil -func (db *currJamDb) getTeam(id string) *Team { - var err error - if err = db.open(); err != nil { - return nil - } - defer db.close() - - teamPath := []string{"teams", id} - tm := new(Team) - tm.UUID = id - if tm.Name, err = db.bolt.GetValue(teamPath, "name"); err != nil { - return nil - } - tm.Members = tm.getTeamMembers() - tm.Game = tm.getGame() - return tm -} - -// This function returns the team for a specific member -func (db *currJamDb) getTeamForMember(mbrid string) (*Team, error) { - var err error - if err = db.open(); err != nil { - return nil, err - } - defer db.close() - - teams := db.getAllTeams() - for i := range teams { - var tmMbrs []TeamMember - tmMbrs = teams[i].getTeamMembers() - if err == nil { - for j := range tmMbrs { - if tmMbrs[j].UUID == mbrid { - return &teams[i], nil - } - } - } - } - return nil, errors.New("Unable to find team member") -} - -// getAllTeams returns all teams in the database -func (db *currJamDb) 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 *currJamDb) 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) - } +// Find a team by it's ID +func (gj *Gamejam) GetTeamById(id string) *Team { + for i := range gj.Teams { + if gj.Teams[i].UUID == id { + return gj.Teams[i] } } 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) getTeamMembers() []TeamMember { - var ret []TeamMember - var err error - if err = db.open(); err != nil { - return ret - } - defer db.close() - - teamPath := []string{"teams", tm.UUID, "members"} - var memberUuids []string - if memberUuids, err = db.bolt.GetBucketList(teamPath); err == nil { - for _, v := range memberUuids { - var mbr *TeamMember - if mbr = tm.getTeamMember(v); mbr != nil { - ret = append(ret, *mbr) - } +// Find a team by name +func (gj *Gamejam) GetTeamByName(nm string) *Team { + for i := range gj.Teams { + if gj.Teams[i].Name == nm { + return gj.Teams[i] } } - 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 mbr.UUID == "" { - // It's really a new one - mbr.UUID = uuid.New() - } - - mbrPath := []string{"teams", tm.UUID, "members", mbr.UUID} - if db.bolt.SetValue(mbrPath, "name", mbr.Name) != nil { - return err - } - if db.bolt.SetValue(mbrPath, "slackid", mbr.SlackId) != nil { - return err - } - 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) -} diff --git a/model_votes.go b/model_votes.go index 4cc0dd6..cbc1c19 100644 --- a/model_votes.go +++ b/model_votes.go @@ -71,21 +71,3 @@ func (gj *Gamejam) LoadVote(clientId, tm string) *Vote { } return &vt } - -/** - * OLD FUNCTIONS - */ -func (db *currJamDb) getAllVotes() []Vote { - var ret []Vote - var err error - if err = db.open(); err != nil { - return ret - } - defer db.close() - - clients := db.getAllClients() - for _, cl := range clients { - ret = append(ret, cl.getVotes()...) - } - return ret -} From 1905f2c0092d4d253b8f3df04ca7095979152456 Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Thu, 19 Oct 2017 14:53:07 -0500 Subject: [PATCH 4/6] Compiling... But still work to do. --- .gitignore | 14 ++--- admin_archive.go | 2 +- admin_games.go | 37 +++++++---- admin_teams.go | 77 +++++++++++------------ admin_users.go | 18 +++--- admin_votes.go | 37 +++++------ main.go | 27 ++++----- model.go | 27 ++++++--- model_clients.go | 36 +++++++++-- model_gamejam.go | 77 ++++++++++------------- model_games.go | 117 +++++++++++++++++++++++++---------- model_sitedata.go | 26 +++++--- model_teams.go | 145 +++++++++++++++++++++++++++++++++++--------- model_users.go | 3 + model_votes.go | 106 ++++++++++++++++++++++++++------ page_session.go | 2 +- public_endpoints.go | 109 +++++++++++++++++++-------------- 17 files changed, 563 insertions(+), 297 deletions(-) diff --git a/.gitignore b/.gitignore index 2eacb04..a51d231 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ # Ignore the binaries -gjvote -gjvote.darwin64 -gjvote.linux386 -gjvote.linux64 -gjvote.linuxarm -gjvote.win386.exe -gjvote.win64.exe +ictgj-voting +ictgj-voting.darwin64 +ictgj-voting.linux386 +ictgj-voting.linux64 +ictgj-voting.linuxarm +ictgj-voting.win386.exe +ictgj-voting.win64.exe # Ignore the DBs *.db diff --git a/admin_archive.go b/admin_archive.go index 0a49456..e127b30 100644 --- a/admin_archive.go +++ b/admin_archive.go @@ -15,6 +15,6 @@ func handleAdminArchive(w http.ResponseWriter, req *http.Request, page *pageData type archivePageData struct { Gamejams []Gamejam } - apd := new(archivePageData) + //apd := new(archivePageData) } } diff --git a/admin_games.go b/admin_games.go index 12cd918..c9729ef 100644 --- a/admin_games.go +++ b/admin_games.go @@ -30,11 +30,15 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) page.SubTitle = "Games" page.show("admin-games.html", w) } else { - tm := m.jam.GetTeamById(teamId) + tm, _ := m.jam.GetTeamById(teamId) if tm != nil { switch vars["function"] { case "save": - gm := NewGame(tm.UUID) + var err error + var gm *Game + if gm, err = NewGame(tm.UUID); err != nil { + page.session.setFlashMessage("Error updating game: "+err.Error(), "error") + } gm.Name = req.FormValue("gamename") gm.Link = req.FormValue("gamelink") gm.Description = req.FormValue("gamedesc") @@ -50,8 +54,13 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) } redirect("/admin/teams/"+tm.UUID+"#game", w, req) case "screenshotdelete": + var ss *Screenshot + var err error ssid := vars["subid"] - if err := tm.deleteScreenshot(ssid); err != nil { + if ss, err = NewScreenshot(tm.UUID, ssid); err != nil { + page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error") + } + if err = m.jam.DeleteScreenshot(ss); err != nil { page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error") } redirect("/admin/teams/"+tm.UUID+"#game", w, req) @@ -65,6 +74,8 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) func saveScreenshots(tm *Team, req *http.Request) error { var err error + var ss *Screenshot + file, hdr, err := req.FormFile("newssfile") if err != nil { return err @@ -74,13 +85,13 @@ func saveScreenshots(tm *Team, req *http.Request) error { if len(hdr.Filename) > extIdx { fltp = hdr.Filename[extIdx+1:] } - m, _, err := image.Decode(file) + mI, _, err := image.Decode(file) buf := new(bytes.Buffer) // We convert everything to jpg - if err = jpeg.Encode(buf, m, nil); err != nil { + if err = jpeg.Encode(buf, mI, nil); err != nil { return errors.New("Unable to encode image") } - thm := resize.Resize(200, 0, m, resize.Lanczos3) + thm := resize.Resize(200, 0, mI, resize.Lanczos3) thmBuf := new(bytes.Buffer) var thmString string if fltp == "gif" { @@ -94,9 +105,13 @@ func saveScreenshots(tm *Team, req *http.Request) error { } thmString = base64.StdEncoding.EncodeToString(thmBuf.Bytes()) - return tm.saveScreenshot(&Screenshot{ - Image: base64.StdEncoding.EncodeToString(buf.Bytes()), - Thumbnail: thmString, - Filetype: fltp, - }) + if ss, err = NewScreenshot(tm.UUID, ""); err != nil { + return err + } + + ss.Image = base64.StdEncoding.EncodeToString(buf.Bytes()) + ss.Thumbnail = thmString + ss.Filetype = fltp + + return m.jam.SaveScreenshot(ss) } diff --git a/admin_teams.go b/admin_teams.go index d2000f0..7de428c 100644 --- a/admin_teams.go +++ b/admin_teams.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "net/http" _ "image/gif" @@ -10,10 +11,6 @@ import ( "github.com/gorilla/mux" ) -func refreshTeamsInMemory() { - site.Teams = db.getAllTeams() -} - func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData) { vars := mux.Vars(req) page.SubTitle = "Teams" @@ -23,17 +20,11 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData) switch vars["function"] { case "save": name := req.FormValue("teamname") - if db.getTeamByName(name) != nil { - // A team with that name already exists - page.session.setFlashMessage("A team with the name "+name+" already exists!", "error") - } else { - if err := db.newTeam(name); err != nil { - page.session.setFlashMessage(err.Error(), "error") - } else { - page.session.setFlashMessage("Team "+name+" created!", "success") - } + tm := NewTeam("") + tm.Name = name + if err := m.jam.AddTeam(tm); err != nil { + page.session.setFlashMessage("Error adding team: "+err.Error(), "error") } - refreshTeamsInMemory() redirect("/admin/teams", w, req) default: page.SubTitle = "Add New Team" @@ -41,57 +32,57 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData) } } else if teamId != "" { // Functions for existing team - tm := db.getTeam(teamId) + tm, _ := m.jam.GetTeamById(teamId) if tm != nil { switch vars["function"] { case "save": - tm.UUID = teamId tm.Name = req.FormValue("teamname") - if err := tm.save(); err != nil { - page.session.setFlashMessage("Error updating team: "+err.Error(), "error") - } else { - page.session.setFlashMessage("Team Updated!", "success") - } - refreshTeamsInMemory() + page.session.setFlashMessage("Team Updated!", "success") redirect("/admin/teams", w, req) case "delete": var err error - if err = tm.delete(); err != nil { - page.session.setFlashMessage("Error deleting team: "+err.Error(), "error") + if err = m.jam.RemoveTeamById(teamId); err != nil { + page.session.setFlashMessage("Error removing team: "+err.Error(), "error") } else { - page.session.setFlashMessage("Team "+tm.Name+" Deleted", "success") + page.session.setFlashMessage("Team "+tm.Name+" Removed", "success") } - refreshTeamsInMemory() redirect("/admin/teams", w, req) case "savemember": mbrName := req.FormValue("newmembername") - mbr := newTeamMember(mbrName) - mbr.SlackId = req.FormValue("newmemberslackid") - mbr.Twitter = req.FormValue("newmembertwitter") - mbr.Email = req.FormValue("newmemberemail") - if err := tm.updateTeamMember(mbr); err != nil { + mbr, err := NewTeamMember(tm.UUID, "") + if err == nil { + mbr.SlackId = req.FormValue("newmemberslackid") + mbr.Twitter = req.FormValue("newmembertwitter") + mbr.Email = req.FormValue("newmemberemail") + } + if err := tm.AddTeamMember(mbr); err != nil { page.session.setFlashMessage("Error adding team member: "+err.Error(), "error") } else { page.session.setFlashMessage(mbrName+" added to team!", "success") } - refreshTeamsInMemory() redirect("/admin/teams/"+teamId+"#members", w, req) case "deletemember": - m := tm.getTeamMember(req.FormValue("memberid")) - if m != nil { - 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") - } - refreshTeamsInMemory() + var err error + var mbr *TeamMember + if mbr, err = tm.GetTeamMemberById(req.FormValue("memberid")); err != nil { + fmt.Println("Error removing team member: " + err.Error()) + page.session.setFlashMessage("Error deleting team member", "error") + redirect("/admin/teams/"+teamId+"#members", w, req) + } + if err = tm.RemoveTeamMemberById(mbr.UUID); err != nil { + fmt.Println("Error removing team member: " + err.Error()) + page.session.setFlashMessage("Error deleting team member", "error") } else { - page.session.setFlashMessage("Couldn't find team member to delete", "error") + page.session.setFlashMessage(mbr.Name+" deleted from team", "success") } redirect("/admin/teams/"+teamId+"#members", w, req) default: page.SubTitle = "Edit Team" - t := db.getTeam(teamId) + t, err := m.jam.GetTeamById(teamId) + if err != nil { + page.session.setFlashMessage("Error loading team: "+err.Error(), "error") + redirect("/admin/teams", w, req) + } page.TemplateData = t page.show("admin-editteam.html", w) } @@ -104,7 +95,7 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData) type teamsPageData struct { Teams []Team } - page.TemplateData = teamsPageData{Teams: db.getAllTeams()} + page.TemplateData = m.jam page.SubTitle = "Teams" page.show("admin-teams.html", w) } diff --git a/admin_users.go b/admin_users.go index f4bdcea..1768f62 100644 --- a/admin_users.go +++ b/admin_users.go @@ -29,7 +29,7 @@ func handleAdminDoLogin(w http.ResponseWriter, req *http.Request) { // If it can't, it returns an error func doLogin(email, password string) error { if strings.TrimSpace(email) != "" && strings.TrimSpace(password) != "" { - return db.checkCredentials(email, password) + return m.checkCredentials(email, password) } return errors.New("Invalid Credentials") } @@ -53,12 +53,12 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData) switch vars["function"] { case "save": email = req.FormValue("email") - if db.isValidUserEmail(email) { + if m.isValidUserEmail(email) { // User already exists page.session.setFlashMessage("A user with email address "+email+" already exists!", "error") } else { password := req.FormValue("password") - if err := db.updateUserPassword(email, string(password)); err != nil { + if err := m.updateUserPassword(email, string(password)); err != nil { page.session.setFlashMessage(err.Error(), "error") } else { page.session.setFlashMessage("User "+email+" created!", "success") @@ -73,10 +73,10 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData) switch vars["function"] { case "save": var err error - if db.isValidUserEmail(email) { + if m.isValidUserEmail(email) { password := req.FormValue("password") if password != "" { - if err = db.updateUserPassword(email, password); err != nil { + if err = m.updateUserPassword(email, password); err != nil { page.session.setFlashMessage(err.Error(), "error") } else { page.session.setFlashMessage("User "+email+" created!", "success") @@ -86,8 +86,8 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData) } case "delete": var err error - if db.isValidUserEmail(email) { - if err = db.deleteUser(email); err != nil { + if m.isValidUserEmail(email) { + if err = m.deleteUser(email); err != nil { page.session.setFlashMessage(err.Error(), "error") } else { 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) default: page.SubTitle = "Edit Admin User" - if !db.isValidUserEmail(email) { + if !m.isValidUserEmail(email) { page.session.setFlashMessage("Couldn't find the requested user, please try again.", "error") redirect("/admin/users", w, req) } @@ -107,7 +107,7 @@ func handleAdminUsers(w http.ResponseWriter, req *http.Request, page *pageData) type usersPageData struct { Users []string } - page.TemplateData = usersPageData{Users: db.getAllUsers()} + page.TemplateData = usersPageData{Users: m.getAllUsers()} page.SubTitle = "Admin Users" page.show("admin-users.html", w) diff --git a/admin_votes.go b/admin_votes.go index 63ef05f..582ba50 100644 --- a/admin_votes.go +++ b/admin_votes.go @@ -23,22 +23,22 @@ func getCondorcetResult() []Ranking { } var allPairs []teamPair var ret []Ranking - for i := 0; i < len(site.Teams); i++ { - for j := i + 1; j < len(site.Teams); j++ { + for i := 0; i < len(m.jam.Teams); i++ { + for j := i + 1; j < len(m.jam.Teams); j++ { // For each pairing find a winner - winner, pct, _ := findWinnerBetweenTeams(&site.Teams[i], &site.Teams[j]) + winner, pct, _ := findWinnerBetweenTeams(&m.jam.Teams[i], &m.jam.Teams[j]) newPair := new(teamPair) if winner != nil { newPair.winner = winner - if winner.UUID == site.Teams[i].UUID { - newPair.loser = &site.Teams[j] + if winner.UUID == m.jam.Teams[i].UUID { + newPair.loser = &m.jam.Teams[j] } else { - newPair.loser = &site.Teams[i] + newPair.loser = &m.jam.Teams[i] } newPair.majority = pct } else { - newPair.winner = &site.Teams[i] - newPair.loser = &site.Teams[j] + newPair.winner = &m.jam.Teams[i] + newPair.loser = &m.jam.Teams[j] newPair.majority = 50 } allPairs = append(allPairs, *newPair) @@ -46,8 +46,8 @@ func getCondorcetResult() []Ranking { } // initialize map of team wins teamWins := make(map[string]int) - for i := range site.Teams { - teamWins[site.Teams[i].UUID] = 0 + for i := range m.jam.Teams { + teamWins[m.jam.Teams[i].UUID] = 0 } // Figure out how many wins each team has for i := range allPairs { @@ -72,7 +72,10 @@ func getCondorcetResult() []Ranking { nR := new(Ranking) nR.Rank = currRank for i := range rankedWins[topWins] { - nR.Teams = append(nR.Teams, *site.getTeamByUUID(rankedWins[topWins][i])) + tm, _ := m.jam.GetTeamById(rankedWins[topWins][i]) + if tm != nil { + nR.Teams = append(nR.Teams, *tm) + } } ret = append(ret, *nR) delete(rankedWins, topWins) @@ -99,7 +102,7 @@ func uuidIsInRankingSlice(uuid string, sl []Ranking) bool { func findWinnerBetweenTeams(tm1, tm2 *Team) (*Team, float32, error) { // tally gets incremented for a tm1 win, decremented for a tm2 win var tm1votes, tm2votes float32 - for _, v := range site.Votes { + for _, v := range m.jam.Votes { for _, chc := range v.Choices { if chc.Team == tm1.UUID { tm1votes++ @@ -138,12 +141,12 @@ func handleAdminVotes(w http.ResponseWriter, req *http.Request, page *pageData) Results []Ranking } vpd := new(votePageData) - for i := range site.Votes { + for i := range m.jam.Votes { v := new(vpdVote) - v.Timestamp = site.Votes[i].Timestamp.Format(time.RFC3339) - v.ClientId = site.Votes[i].ClientId - for _, choice := range site.Votes[i].Choices { - for _, fndTm := range site.Teams { + v.Timestamp = m.jam.Votes[i].Timestamp.Format(time.RFC3339) + v.ClientId = m.jam.Votes[i].ClientId + for _, choice := range m.jam.Votes[i].Choices { + for _, fndTm := range m.jam.Teams { if fndTm.UUID == choice.Team { v.Choices = append(v.Choices, fndTm) break diff --git a/main.go b/main.go index be09458..652e691 100644 --- a/main.go +++ b/main.go @@ -69,7 +69,9 @@ func main() { } loadConfig() - site.save() + if err = site.SaveToDB(); err != nil { + errorExit("Unable to save site config to DB: " + err.Error()) + } initialize() r = mux.NewRouter() @@ -184,13 +186,11 @@ func initialize() { fmt.Print("GameJam Name: ") gjName, _ := reader.ReadString('\n') gjName = strings.TrimSpace(gjName) - if db.setJamName(gjName) != nil { - fmt.Println("Error saving Current Jam") - } + m.jam.Name = gjName } if m.jam.Name != "" { - fmt.Println("Current Jam Name: " + jmNm) + fmt.Println("Current Jam Name: " + m.jam.Name) } else { fmt.Println("No Jam Name Specified") } @@ -220,7 +220,7 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData { // First check if we're logged in userEmail, _ := p.session.getStringValue("email") // With a valid account - p.LoggedIn = db.isValidUserEmail(userEmail) + p.LoggedIn = m.isValidUserEmail(userEmail) p.Site = site p.SubTitle = "GameJam Voting" @@ -257,20 +257,15 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData { } p.HideAdminMenu = true - if p.CurrentJam = db.getJamName(); p.CurrentJam != "" { - p.FlashMessage = "Error Loading Current GameJam: " + err.Error() - p.FlashClass = "error" - } - p.ClientId = p.session.getClientId() - cl := db.getClient(p.ClientId) + cl := m.GetClient(p.ClientId) p.ClientIsAuth = cl.Auth p.ClientIsServer = clientIsServer(req) // Public Mode - p.PublicMode = db.getPublicSiteMode() + p.PublicMode = m.site.GetPublicMode() // Authentication Mode - p.AuthMode = db.site.getAuthMode() + p.AuthMode = m.site.GetAuthMode() return p } @@ -317,8 +312,8 @@ func resetToDefaults() { conf, _ := reader.ReadString('\n') conf = strings.ToUpper(strings.TrimSpace(conf)) if strings.HasPrefix(conf, "Y") { - if def.save() != nil { - errorExit("Error resetting to defaults") + if err := def.SaveToDB(); err != nil { + errorExit("Error resetting to defaults: " + err.Error()) } fmt.Println("Reset to defaults") } diff --git a/model.go b/model.go index 2e038ad..8f02588 100644 --- a/model.go +++ b/model.go @@ -15,6 +15,8 @@ type model struct { site *siteData // Configuration data for the site jam *Gamejam // The currently active gamejam clients []Client // Web clients that have connected to the server + + clientsUpdated bool } // Update Flags: Which parts of the model need to be updated @@ -39,20 +41,27 @@ func NewModel() (*model, error) { } // Load the site data - m.site = m.LoadSiteData() + m.site = NewSiteData(m) + if err = m.site.LoadFromDB(); err != nil { + // Error loading from the DB, set to defaults + def := NewSiteData(m) + m.site = def + } // Load the jam data - m.jam = m.LoadCurrentJam() + if m.jam, err = m.LoadCurrentJam(); err != nil { + return nil, errors.New("Unable to load current jam: " + err.Error()) + } // Load web clients m.clients = m.LoadAllClients() - return &m, nil + return m, nil } func (m *model) openDB() error { m.dbOpened += 1 - if db.dbOpened == 1 { + if m.dbOpened == 1 { var err error m.bolt, err = boltease.Create(m.dbFileName, 0600, nil) if err != nil { @@ -105,20 +114,22 @@ func (m *model) saveChanges() error { } defer m.closeDB() - if m.site.needsSave() { - if err = m.site.saveToDB(); err != nil { + if m.site.NeedsSave() { + if err = m.site.SaveToDB(); err != nil { return err } } - if m.jam.needsSave() { - if err = m.jam.saveToDB(); err != nil { + if m.jam.IsChanged { + if err = m.jam.SaveToDB(); err != nil { return err } + m.jam.IsChanged = false } if m.clientsUpdated { if err = m.SaveAllClients(); err != nil { return err } + m.clientsUpdated = false } return nil } diff --git a/model_clients.go b/model_clients.go index c7d5ee4..b525cd3 100644 --- a/model_clients.go +++ b/model_clients.go @@ -1,5 +1,11 @@ package main +import ( + "errors" + + "github.com/pborman/uuid" +) + /** * Client * A client is a system that is connecting to the web server @@ -14,12 +20,32 @@ type Client struct { } func NewClient(id string) *Client { + if id == "" { + id = uuid.New() + } return &Client{ UUID: id, mPath: []string{"clients", id}, } } +func (m *model) AddClient(cl *Client) error { + for i := range m.clients { + if m.clients[i].UUID == cl.UUID { + return errors.New("A client with that ID already exists") + } + if m.clients[i].IP == cl.IP { + return errors.New("A client with that IP already exists") + } + if m.clients[i].Name == cl.Name { + return errors.New("A client with that Name already exists") + } + } + m.clients = append(m.clients, *cl) + m.clientsUpdated = true + return nil +} + /** * DB Functions * These are generally just called when the app starts up, or when the periodic 'save' runs @@ -71,7 +97,7 @@ func (m *model) SaveAllClients() error { defer m.closeDB() for _, v := range m.clients { - if err = m.SaveClient(v); err != nil { + if err = m.SaveClient(&v); err != nil { return err } } @@ -86,13 +112,13 @@ func (m *model) SaveClient(cl *Client) error { } defer m.closeDB() - if err = db.bolt.SetBool(cl.mPath, "auth", c.Auth); err != nil { + if err = m.bolt.SetBool(cl.mPath, "auth", cl.Auth); err != nil { return err } - if err = db.bolt.SetValue(cl.mPath, "name", c.Name); err != nil { + if err = m.bolt.SetValue(cl.mPath, "name", cl.Name); err != nil { return err } - return db.bolt.SetValue(cl.mPath, "ip", c.IP) + return m.bolt.SetValue(cl.mPath, "ip", cl.IP) } /** @@ -132,7 +158,7 @@ func (m *model) UpdateClient(cl *Client) { } } if !found { - m.clients = append(m.clients, cl) + m.clients = append(m.clients, *cl) } m.clientsUpdated = true } diff --git a/model_gamejam.go b/model_gamejam.go index 1114178..7f27303 100644 --- a/model_gamejam.go +++ b/model_gamejam.go @@ -1,6 +1,10 @@ package main -import "time" +import ( + "errors" + "strings" + "time" +) /** * Gamejam @@ -13,9 +17,10 @@ type Gamejam struct { Teams []Team Votes []Vote - m *model // The model that holds this gamejam's data - mPath []string // The path in the db to this gamejam - changed bool // Flag to tell if we need to update the db + m *model // The model that holds this gamejam's data + mPath []string // The path in the db to this gamejam + + IsChanged bool // Flag to tell if we need to update the db } func NewGamejam(m *model) *Gamejam { @@ -30,13 +35,12 @@ func NewGamejam(m *model) *Gamejam { * These are generally just called when the app starts up, or when the periodic 'save' runs */ -func (m *model) LoadCurrentJam() *Gamejam { +func (m *model) LoadCurrentJam() (*Gamejam, error) { if err := m.openDB(); err != nil { - return err + return nil, err } defer m.closeDB() - var err error gj := NewGamejam(m) gj.Name, _ = m.bolt.GetValue(gj.mPath, "name") @@ -46,52 +50,37 @@ func (m *model) LoadCurrentJam() *Gamejam { // Load all votes gj.Votes = gj.LoadAllVotes() - return gj + return gj, nil } // Save everything to the DB whether it's flagged as changed or not -func (gj *Gamejam) saveToDB() error { +func (gj *Gamejam) SaveToDB() error { if err := gj.m.openDB(); err != nil { return err } defer gj.m.closeDB() -} - -/** - * In Memory functions - * This is generally how the app accesses client data - */ -func (gj *Gamejam) getTeamByUUID(uuid string) *Team { - for i := range gj.Teams { - if gj.Teams[i].UUID == uuid { - return &gj.Teams[i] + var errs []error + // Save all Teams + for _, tm := range gj.Teams { + if err := gj.SaveTeam(&tm); err != nil { + errs = append(errs, err) } } + + // Save all Votes + for _, vt := range gj.Votes { + if err := gj.SaveVote(&vt); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + var errTxt string + for i := range errs { + errTxt = errTxt + errs[i].Error() + "\n" + } + errTxt = strings.TrimSpace(errTxt) + return errors.New("Error(s) saving to DB: " + errTxt) + } return nil } - -// Check if pth is already in updates, if not, add it -func (gj *Gamejam) NeedsUpdate(pth []string) { - var found bool - for _, v := range gj.updates { - if !(len(v) == len(pth)) { - continue - } - // The lengths are the same, do all elements match? - var nxt bool - for i := range pth { - if v[i] != pth[i] { - nxt = true - } - } - if !nxt { - // This pth is already in the 'updates' list - found = true - break - } - } - if !found { - gj.updates = append(gj.updates, pth) - } -} diff --git a/model_games.go b/model_games.go index 92290d3..788b3b3 100644 --- a/model_games.go +++ b/model_games.go @@ -1,6 +1,10 @@ package main -import "errors" +import ( + "errors" + + "github.com/pborman/uuid" +) /** * Game @@ -17,19 +21,37 @@ type Game struct { } // Create a new game object -func NewGame(tmId string) *Game { +func NewGame(tmId string) (*Game, error) { + if tmId == "" { + return nil, errors.New("Team ID is required") + } return &Game{ TeamId: tmId, mPath: []string{"jam", "teams", tmId, "game"}, - } + }, nil } -func (gm *Game) GetScreenshot(ssId string) *Screenshot { +func (gm *Game) GetScreenshot(ssId string) (*Screenshot, error) { for _, ss := range gm.Screenshots { if ss.UUID == ssId { - return ss + return &ss, nil } } + return nil, errors.New("Invalid Id") +} + +func (gm *Game) RemoveScreenshot(ssId string) error { + idx := -1 + for i, ss := range gm.Screenshots { + if ss.UUID == ssId { + idx = i + return nil + } + } + if idx < 0 { + return errors.New("Invalid Id") + } + gm.Screenshots = append(gm.Screenshots[:idx], gm.Screenshots[idx+1:]...) return nil } @@ -44,11 +66,18 @@ type Screenshot struct { } // Create a Screenshot Object -func NewScreenshot(tmId, ssId string) *Screenshot { +func NewScreenshot(tmId, ssId string) (*Screenshot, error) { + if tmId == "" { + return nil, errors.New("Team ID is required") + } + if ssId == "" { + // Generate a new UUID + ssId = uuid.New() + } return &Screenshot{ UUID: ssId, mPath: []string{"jam", "teams", tmId, "game", "screenshots", ssId}, - } + }, nil } /** @@ -57,14 +86,17 @@ func NewScreenshot(tmId, ssId string) *Screenshot { */ // Load a team's game from the DB and return it -func (gj *Gamejam) LoadTeamGame(tmId string) *Game { +func (gj *Gamejam) LoadTeamGame(tmId string) (*Game, error) { var err error if err = gj.m.openDB(); err != nil { - return nil + return nil, err } defer gj.m.closeDB() - gm := NewGame(tmId) + gm, err := NewGame(tmId) + if err != nil { + return nil, err + } if gm.Name, err = gj.m.bolt.GetValue(gm.mPath, "name"); err != nil { gm.Name = "" } @@ -74,10 +106,11 @@ func (gj *Gamejam) LoadTeamGame(tmId string) *Game { if gm.Link, err = gj.m.bolt.GetValue(gm.mPath, "link"); err != nil { gm.Link = "" } + // Now get the game screenshots gm.Screenshots = gj.LoadTeamGameScreenshots(tmId) - return &gm + return gm, nil } // Load a games screenshots from the DB @@ -89,44 +122,50 @@ func (gj *Gamejam) LoadTeamGameScreenshots(tmId string) []Screenshot { defer gj.m.closeDB() var ret []Screenshot - gm := NewGame(tmId) + gm, err := NewGame(tmId) + if err != nil { + return ret + } ssBktPath := append(gm.mPath, "screenshots") var ssIds []string ssIds, _ = gj.m.bolt.GetBucketList(ssBktPath) for _, v := range ssIds { - ssLd := gj.LoadTeamGameScreenshot(tmId, v) + ssLd, _ := gj.LoadTeamGameScreenshot(tmId, v) if ssLd != nil { - ret = append(ret, ssLd) + ret = append(ret, *ssLd) } } return ret } // Load a screenshot from the DB -func (gj *Gamejam) LoadTeamGameScreenshot(tmId, ssId string) *Screenshot { +func (gj *Gamejam) LoadTeamGameScreenshot(tmId, ssId string) (*Screenshot, error) { var err error if err = gj.m.openDB(); err != nil { - return nil + return nil, err } defer gj.m.closeDB() - ret := NewScreenshot(tmId, ssId) + ret, err := NewScreenshot(tmId, ssId) + if err != nil { + return nil, err + } if ret.Description, err = gj.m.bolt.GetValue(ret.mPath, "description"); err != nil { - return nil + return nil, err } if ret.Image, err = gj.m.bolt.GetValue(ret.mPath, "image"); err != nil { - return nil + return nil, err } if ret.Thumbnail, err = gj.m.bolt.GetValue(ret.mPath, "thumbnail"); err != nil { - return nil + return nil, err } if ret.Thumbnail == "" { ret.Thumbnail = ret.Image } if ret.Filetype, err = gj.m.bolt.GetValue(ret.mPath, "filetype"); err != nil { - return nil + return nil, err } - return ret + return ret, err } // Save a game to the DB @@ -141,6 +180,10 @@ func (gj *Gamejam) SaveGame(gm *Game) error { return err } + var tm *Team + if tm, err = gj.GetTeamById(gm.TeamId); err != nil { + return err + } if gm.Name == "" { gm.Name = tm.Name + "'s Game" } @@ -169,26 +212,35 @@ func (gj *Gamejam) SaveScreenshots(gm *Game) error { defer gj.m.closeDB() for _, ss := range gm.Screenshots { - if err = gj.SaveScreenshot(gm.TeamId, ss); err != nil { + if err = gj.SaveScreenshot(&ss); err != nil { return err } } // Now remove unused screenshots ssPath := append(gm.mPath, "screenshots") + var ssIds []string if ssIds, err = gj.m.bolt.GetBucketList(ssPath); err != nil { return err } for i := range ssIds { - if gm.GetScreenshot(ssIds[i]) == nil { - if err = gj.DeleteScreenshot(NewScreenshot(tm.TeamId, ssIds[i])); err != nil { - return err - } + ss, _ := gm.GetScreenshot(ssIds[i]) + if ss != nil { + // A valid screenshot, next + continue + } + if ss, err = NewScreenshot(gm.TeamId, ssIds[i]); err != nil { + // Error building screenshot to delete... + continue + } + if err = gj.DeleteScreenshot(ss); err != nil { + return err } } + return nil } // Save a screenshot -func (gj *Gamejam) SaveScreenshot(tmId string, ss *Screenshot) error { +func (gj *Gamejam) SaveScreenshot(ss *Screenshot) error { var err error if err = gj.m.openDB(); err != nil { return err @@ -229,12 +281,11 @@ func (gj *Gamejam) DeleteScreenshot(ss *Screenshot) error { // Set the given team's game to gm func (gj *Gamejam) UpdateGame(tmId string, gm *Game) error { - var found bool - tm := gj.GetTeamById(tmId) - if tm == nil { - return errors.New("Invalid team ID: " + gm.TeamId) + tm, err := gj.GetTeamById(tmId) + if err != nil { + return errors.New("Error getting team: " + err.Error()) } tm.Game = gm - gj.NeedsUpdate([]string{"team", tmId, "game"}) + gj.IsChanged = true return nil } diff --git a/model_sitedata.go b/model_sitedata.go index b9f2dbf..2ca86c3 100644 --- a/model_sitedata.go +++ b/model_sitedata.go @@ -1,16 +1,19 @@ package main -import "strconv" +import ( + "errors" + "strconv" +) /** * SiteData * Contains configuration for the website */ type siteData struct { - title string - port int - sessionName string - serverDir string + Title string + Port int + SessionName string + ServerDir string authMode int publicMode int @@ -62,10 +65,10 @@ func (s *siteData) LoadFromDB() error { if port, err := s.m.bolt.GetInt(s.mPath, "port"); err == nil { s.Port = port } - if sessionName, err = s.m.bolt.GetValue(s.mPath, "session-name"); err == nil { + if sessionName, err := s.m.bolt.GetValue(s.mPath, "session-name"); err == nil { s.SessionName = sessionName } - if serverDir, err = s.m.bolt.GetValue(s.mPath, "server-dir"); err == nil { + if serverDir, err := s.m.bolt.GetValue(s.mPath, "server-dir"); err == nil { s.ServerDir = serverDir } s.changed = false @@ -79,7 +82,8 @@ func (s *siteData) NeedsSave() bool { // Save the site data into the DB func (s *siteData) SaveToDB() error { - if err := s.m.openDB(); err != nil { + var err error + if err = s.m.openDB(); err != nil { return err } defer s.m.closeDB() @@ -108,12 +112,13 @@ func (s *siteData) GetAuthMode() int { // Set the auth mode func (s *siteData) SetAuthMode(mode int) error { if mode < AuthModeAuthentication || mode >= AuthModeError { - return errors.Error("Invalid Authentication Mode: " + strconv.Itoa(mode)) + return errors.New("Invalid Authentication Mode: " + strconv.Itoa(mode)) } if mode != s.authMode { s.authMode = mode s.changed = true } + return nil } // Return the public site mode @@ -124,10 +129,11 @@ func (s *siteData) GetPublicMode() int { // Set the public site mode func (s *siteData) SetPublicMode(mode int) error { if mode < SiteModeWaiting || mode >= SiteModeError { - return errors.Error("Invalid Public Mode: " + strconv.Itoa(mode)) + return errors.New("Invalid Public Mode: " + strconv.Itoa(mode)) } if mode != s.publicMode { s.publicMode = mode s.changed = true } + return nil } diff --git a/model_teams.go b/model_teams.go index 9f90f0c..f2c6202 100644 --- a/model_teams.go +++ b/model_teams.go @@ -1,6 +1,10 @@ package main -import "errors" +import ( + "errors" + + "github.com/pborman/uuid" +) /** * Team @@ -16,12 +20,24 @@ type Team struct { // Create a team func NewTeam(id string) *Team { + if id == "" { + id = uuid.New() + } return &Team{ UUID: id, mPath: []string{"jam", "teams", id}, } } +func (gj *Gamejam) GetTeamById(id string) (*Team, error) { + for i := range gj.Teams { + if gj.Teams[i].UUID == id { + return &gj.Teams[i], nil + } + } + return nil, errors.New("Invalid Team Id given") +} + type TeamMember struct { UUID string Name string @@ -33,11 +49,53 @@ type TeamMember struct { } // Create a new team member -func NewTeamMember(tmId, uId string) *TeamMember { +func NewTeamMember(tmId, uId string) (*TeamMember, error) { + if tmId == "" { + return nil, errors.New("Team ID is required") + } + if uId == "" { + uId = uuid.New() + } return &TeamMember{ UUID: uId, mPath: []string{"jam", "teams", tmId, "members", uId}, + }, nil +} + +// AddTeamMember adds a new team member +func (tm *Team) AddTeamMember(mbr *TeamMember) error { + lkup, _ := tm.GetTeamMemberById(mbr.UUID) + if lkup != nil { + return errors.New("A Team Member with that Id already exists") } + tm.Members = append(tm.Members, *mbr) + return nil +} + +// GetTeamMemberById returns a member with the given uuid +// or an error if it couldn't find it +func (tm *Team) GetTeamMemberById(uuid string) (*TeamMember, error) { + for i := range tm.Members { + if tm.Members[i].UUID == uuid { + return &tm.Members[i], nil + } + } + return nil, errors.New("Invalid Team Member Id given") +} + +func (tm *Team) RemoveTeamMemberById(id string) error { + idx := -1 + for i := range tm.Members { + if tm.Members[i].UUID == id { + idx = i + break + } + } + if idx < 0 { + return errors.New("Invalid Team Member ID given") + } + tm.Members = append(tm.Members[:idx], tm.Members[idx+1:]...) + return nil } /** @@ -50,43 +108,47 @@ func (gj *Gamejam) LoadAllTeams() []Team { var err error var ret []Team if err = gj.m.openDB(); err != nil { - return err + return ret } defer gj.m.closeDB() - if tmUUIDs, err = m.bolt.GetBucketList(mbrsPath); err != nil { + var tmUUIDs []string + tmsPath := append(gj.mPath, "teams") + if tmUUIDs, err = m.bolt.GetBucketList(tmsPath); err != nil { return ret } for _, v := range tmUUIDs { - tm := gj.LoadTeam(v) + tm, _ := gj.LoadTeam(v) if tm != nil { - ret = append(ret, tm) + ret = append(ret, *tm) } } return ret } // Load a team out of the database -func (gj *Gamejam) LoadTeam(uuid string) *Team { +func (gj *Gamejam) LoadTeam(uuid string) (*Team, error) { var err error if err = gj.m.openDB(); err != nil { - return err + return nil, err } defer gj.m.closeDB() // Team Data tm := NewTeam(uuid) if tm.Name, err = gj.m.bolt.GetValue(tm.mPath, "name"); err != nil { - return nil + return nil, errors.New("Error loading team: " + err.Error()) } // Team Members tm.Members = gj.LoadTeamMembers(uuid) // Team Game - tm.Game = gj.LoadTeamGame(uuid) + if tm.Game, err = gj.LoadTeamGame(uuid); err != nil { + return nil, errors.New("Error loading team game: " + err.Error()) + } - return tm + return tm, nil } // Load the members of a team from the DB and return them @@ -104,9 +166,9 @@ func (gj *Gamejam) LoadTeamMembers(tmId string) []TeamMember { mbrsPath := append(tm.mPath, "members") if memberUuids, err = gj.m.bolt.GetBucketList(mbrsPath); err == nil { for _, v := range memberUuids { - mbr := gj.LoadTeamMember(tmId, v) + mbr, _ := gj.LoadTeamMember(tmId, v) if mbr != nil { - ret = append(ret, mbr) + ret = append(ret, *mbr) } } } @@ -114,17 +176,20 @@ func (gj *Gamejam) LoadTeamMembers(tmId string) []TeamMember { } // Load a team member from the DB and return it -func (gj *Gamejam) LoadTeamMember(tmId, mbrId string) *TeamMember { +func (gj *Gamejam) LoadTeamMember(tmId, mbrId string) (*TeamMember, error) { var err error if err = gj.m.openDB(); err != nil { - return nil + return nil, err } defer gj.m.closeDB() - mbr := NewTeamMember(tmId, mbrId) + mbr, err := NewTeamMember(tmId, mbrId) + if err != nil { + return nil, errors.New("Error loading team member: " + err.Error()) + } // Name is the only required field if mbr.Name, err = gj.m.bolt.GetValue(mbr.mPath, "name"); err != nil { - return nil + return nil, errors.New("Error loading team member: " + err.Error()) } if mbr.SlackId, err = gj.m.bolt.GetValue(mbr.mPath, "slackid"); err != nil { mbr.SlackId = "" @@ -135,7 +200,7 @@ func (gj *Gamejam) LoadTeamMember(tmId, mbrId string) *TeamMember { if mbr.Email, err = gj.m.bolt.GetValue(mbr.mPath, "email"); err != nil { mbr.Email = "" } - return mbr + return mbr, nil } func (gj *Gamejam) SaveTeam(tm *Team) error { @@ -146,7 +211,7 @@ func (gj *Gamejam) SaveTeam(tm *Team) error { defer gj.m.closeDB() // Save team data - if err = gj.m.bolt.SetValue(tm.mPath, "name"); err != nil { + if err = gj.m.bolt.SetValue(tm.mPath, "name", tm.Name); err != nil { return err } @@ -167,10 +232,12 @@ func (gj *Gamejam) SaveTeam(tm *Team) error { } // Save team game - return gj.SaveGame(gm) + return gj.SaveGame(tm.Game) } // Delete the team tm +// TODO: Deletes should be done all at once when syncing memory to the DB +/* func (gj *Gamejam) DeleteTeam(tm *Team) error { var err error if err = gj.m.openDB(); err != nil { @@ -183,8 +250,11 @@ func (gj *Gamejam) DeleteTeam(tm *Team) error { } return gj.m.bolt.DeleteBucket(tm.mPath[:len(tm.mPath)-1], tm.UUID) } +*/ // Delete the TeamMember mbr from Team tm +// TODO: Deletes should be done all at once when syncing memory to the DB +/* func (gj *Gamejam) DeleteTeamMember(tm *Team, mbr *TeamMember) error { var err error if err = gj.m.openDB(); err != nil { @@ -197,28 +267,47 @@ func (gj *Gamejam) DeleteTeamMember(tm *Team, mbr *TeamMember) error { } return gj.m.bolt.DeleteBucket(mbr.mPath[:len(mbr.mPath)-1], mbr.UUID) } +*/ /** * In Memory functions * This is generally how the app accesses data */ -// Find a team by it's ID -func (gj *Gamejam) GetTeamById(id string) *Team { - for i := range gj.Teams { - if gj.Teams[i].UUID == id { - return gj.Teams[i] - } +// Add a team +func (gj *Gamejam) AddTeam(tm *Team) error { + if _, err := gj.GetTeamById(tm.UUID); err != nil { + return errors.New("A team with that ID already exists") } + if _, err := gj.GetTeamByName(tm.Name); err != nil { + return errors.New("A team with that Name already exists") + } + gj.Teams = append(gj.Teams, *tm) return nil } // Find a team by name -func (gj *Gamejam) GetTeamByName(nm string) *Team { +func (gj *Gamejam) GetTeamByName(nm string) (*Team, error) { for i := range gj.Teams { if gj.Teams[i].Name == nm { - return gj.Teams[i] + return &gj.Teams[i], nil } } + return nil, errors.New("Invalid team name given") +} + +// Remove a team by id +func (gj *Gamejam) RemoveTeamById(id string) error { + idx := -1 + for i := range gj.Teams { + if gj.Teams[i].UUID == id { + idx = i + break + } + } + if idx == -1 { + return errors.New("Invalid Team ID given") + } + gj.Teams = append(gj.Teams[:idx], gj.Teams[idx+1:]...) return nil } diff --git a/model_users.go b/model_users.go index f179600..17df447 100644 --- a/model_users.go +++ b/model_users.go @@ -3,6 +3,9 @@ package main import "golang.org/x/crypto/bcrypt" // These are all model functions that have to do with users +// Unlike gamejam functions, we manipulate the DB directly +// We want to make sure that we always use the most up-to-date user +// information. // Returns true if there are any users in the database func (m *model) hasUser() bool { diff --git a/model_votes.go b/model_votes.go index cbc1c19..cccad93 100644 --- a/model_votes.go +++ b/model_votes.go @@ -1,6 +1,7 @@ package main import ( + "errors" "strconv" "time" ) @@ -16,30 +17,87 @@ type Vote struct { Timestamp time.Time ClientId string // UUID of client Choices []GameChoice + + mPath []string // The path in the DB to this team } +func NewVote(clId string, tm time.Time) (*Vote, error) { + if clId == "" { + return nil, errors.New("Client ID is required") + } + if tm.IsZero() { + tm = time.Now() + } + + vt := new(Vote) + vt.mPath = []string{"jam", "votes", clId, tm.Format(time.RFC3339)} + return vt, nil +} + +func (vt *Vote) SetChoices(ch []string) error { + // Clear any previous choices from this vote + vt.Choices = []GameChoice{} + for i, v := range ch { + vt.Choices = append(vt.Choices, GameChoice{Rank: i, Team: v}) + } + return nil +} + +func (gj *Gamejam) GetVoteWithTimeString(clId, ts string) (*Vote, error) { + timestamp, err := time.Parse(time.RFC3339, ts) + if err != nil { + return nil, err + } + return gj.GetVote(clId, timestamp) +} + +func (gj *Gamejam) GetVote(clId string, ts time.Time) (*Vote, error) { + for _, v := range gj.Votes { + if v.ClientId == clId && v.Timestamp == ts { + return &v, nil + } + } + return nil, errors.New("Couldn't find requested vote") +} + +func (gj *Gamejam) AddVote(vt *Vote) error { + // Make sure that this isn't a duplicate + if _, err := gj.GetVote(vt.ClientId, vt.Timestamp); err == nil { + return errors.New("Duplicate Vote") + } + gj.Votes = append(gj.Votes, *vt) + return nil +} + +/** + * DB Functions + * These are generally just called when the app starts up or when the periodic 'save' runs + */ + // LoadAllVotes loads all votes for the jam out of the database func (gj *Gamejam) LoadAllVotes() []Vote { + var err error var ret []Vote - if err := gj.m.openDB(); err != nil { - return err + if err = gj.m.openDB(); err != nil { + return ret } defer gj.m.closeDB() votesPath := []string{"jam", "votes"} + var cliUUIDs []string if cliUUIDs, err = m.bolt.GetBucketList(votesPath); err != nil { return ret } for _, cId := range cliUUIDs { vtsPth := append(votesPath, cId) - if times, err := m.bolt.GetBucketList(vtsPth); err != nil { + var times []string + if times, err = m.bolt.GetBucketList(vtsPth); err != nil { // Error reading this bucket, move on to the next continue } for _, t := range times { - vt := gj.LoadVote(cId, t) - if vt != nil { - ret = append(ret, vt) + if vt, err := gj.LoadVote(cId, t); err == nil { + ret = append(ret, *vt) } } } @@ -47,27 +105,41 @@ func (gj *Gamejam) LoadAllVotes() []Vote { } // Load a vote from the DB and return it -func (gj *Gamejam) LoadVote(clientId, tm string) *Vote { +func (gj *Gamejam) LoadVote(clientId, t string) (*Vote, error) { var tm time.Time + var err error if tm, err = time.Parse(time.RFC3339, t); err != nil { - return nil + return nil, errors.New("Error loading vote: " + err.Error()) + } + vt, err := NewVote(clientId, tm) + if err != nil { + return nil, errors.New("Error creating vote: " + err.Error()) } - vt := new(Vote) - vt.Timestamp = tm - vt.ClientId = cId - vtPth := append(vtsPth, t) var choices []string - if choices, err = m.bolt.GetKeyList(vtPth); err != nil { - return nil + if choices, err = m.bolt.GetKeyList(vt.mPath); err != nil { + return nil, errors.New("Error creating vote: " + err.Error()) } for _, v := range choices { - ch := new(GameChoices) + ch := new(GameChoice) var rank int if rank, err = strconv.Atoi(v); err == nil { ch.Rank = rank - ch.Team, _ = m.bolt.GetValue(vtPth, v) + ch.Team, _ = m.bolt.GetValue(vt.mPath, v) vt.Choices = append(vt.Choices, *ch) } } - return &vt + return vt, nil +} + +func (gj *Gamejam) SaveVote(vt *Vote) error { + var err error + if err = gj.m.openDB(); err != nil { + return err + } + defer gj.m.closeDB() + + for _, v := range vt.Choices { + m.bolt.SetValue(vt.mPath, strconv.Itoa(v.Rank), v.Team) + } + return nil } diff --git a/page_session.go b/page_session.go index 99c99ff..ba3e04d 100644 --- a/page_session.go +++ b/page_session.go @@ -43,7 +43,7 @@ func (p *pageSession) getClientId() string { fmt.Println(" Client IP:" + clientIp) if clientIp != "127.0.0.1" { fmt.Println(" Pulling data by IP") - cli = db.getClientByIp(clientIp) + cli = m.GetClientByIp(clientIp) } if cli != nil { clientId = cli.UUID diff --git a/public_endpoints.go b/public_endpoints.go index 47de0d8..2173726 100644 --- a/public_endpoints.go +++ b/public_endpoints.go @@ -2,6 +2,7 @@ package main import ( "encoding/base64" + "fmt" "math/rand" "net/http" "strings" @@ -17,7 +18,7 @@ func initPublicPage(w http.ResponseWriter, req *http.Request) *pageData { func handleMain(w http.ResponseWriter, req *http.Request) { page := initPublicPage(w, req) - if db.getPublicSiteMode() == SiteModeWaiting { + if m.site.GetPublicMode() == SiteModeWaiting { page.SubTitle = "" page.show("public-waiting.html", w) } else { @@ -28,7 +29,7 @@ func handleMain(w http.ResponseWriter, req *http.Request) { func loadVotingPage(w http.ResponseWriter, req *http.Request) { page := initPublicPage(w, req) // Client authentication required - if (db.site.getAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { + if (m.site.GetAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { page.show("unauthorized.html", w) return } @@ -37,7 +38,7 @@ func loadVotingPage(w http.ResponseWriter, req *http.Request) { Timestamp string } vpd := new(votingPageData) - tms := db.getAllTeams() + tms := m.jam.Teams // Randomize the team list rand.Seed(time.Now().Unix()) @@ -55,7 +56,7 @@ func loadVotingPage(w http.ResponseWriter, req *http.Request) { func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) { page := initPublicPage(w, req) // Client authentication required - if (db.site.getAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { + if (m.site.GetAuthMode() == AuthModeAuthentication) && !page.ClientIsAuth { page.show("unauthorized.html", w) return } @@ -66,23 +67,37 @@ func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) { ts := req.FormValue("timestamp") timestamp, err := time.Parse(time.RFC3339, ts) if err != nil { - page.session.setFlashMessage("Error parsing timestamp: "+ts, "error") + page.session.setFlashMessage("Error creating vote", "error") + fmt.Println("Error parsing timestamp: " + ts) redirect("/", w, req) } - client := db.getClient(page.ClientId) - if _, err := client.getVote(timestamp); err == nil { + client := m.GetClient(page.ClientId) + + // voteSlice is an ordered string slice of the voters preferences + voteCSV := req.FormValue("uservote") + voteSlice := strings.Split(voteCSV, ",") + + if _, err = m.jam.GetVote(client.UUID, 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 := client.saveVote(timestamp, voteSlice); err != nil { - page.session.setFlashMessage("Error Saving Vote: "+err.Error(), "error") + + var vt *Vote + if vt, err = NewVote(client.UUID, timestamp); err != nil { + fmt.Println("Error creating vote: " + err.Error()) + page.session.setFlashMessage("Error creating vote", "error") + redirect("/", w, req) } - if newVote, err := client.getVote(timestamp); err == nil { - site.Votes = append(site.Votes, *newVote) + if err = vt.SetChoices(voteSlice); err != nil { + fmt.Println("Error creating vote: " + err.Error()) + page.session.setFlashMessage("Error creating vote", "error") + redirect("/", w, req) + } + if err := m.jam.AddVote(vt); err != nil { + fmt.Println("Error adding vote: " + err.Error()) + page.session.setFlashMessage("Error creating vote", "error") + redirect("/", w, req) } page.session.setFlashMessage("Vote Saved!", "success large fading") redirect("/", w, req) @@ -91,19 +106,22 @@ func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) { func handleThumbnailRequest(w http.ResponseWriter, req *http.Request) { // Thumbnail requests are open even without client authentication vars := mux.Vars(req) - tm := db.getTeam(vars["teamid"]) - if tm == nil { + tm, err := m.jam.GetTeamById(vars["teamid"]) + if err != nil { + fmt.Println("handleThumbnailRequest: " + err.Error()) http.Error(w, "Couldn't find image", 404) return } - ss := tm.getScreenshot(vars["imageid"]) - if ss == nil { + ss, err := tm.Game.GetScreenshot(vars["imageid"]) + if err != nil { + fmt.Println("handleThumbnailRequest: " + err.Error()) http.Error(w, "Couldn't find image", 404) return } w.Header().Set("Content-Type", "image/"+ss.Filetype) dat, err := base64.StdEncoding.DecodeString(ss.Thumbnail) if err != nil { + fmt.Println("handleThumbnailRequest: " + err.Error()) http.Error(w, "Couldn't find image", 404) return } @@ -113,19 +131,22 @@ func handleThumbnailRequest(w http.ResponseWriter, req *http.Request) { func handleImageRequest(w http.ResponseWriter, req *http.Request) { // Image requests are open even without client authentication vars := mux.Vars(req) - tm := db.getTeam(vars["teamid"]) - if tm == nil { + tm, err := m.jam.GetTeamById(vars["teamid"]) + if err != nil { + fmt.Println("handleImageRequest: " + err.Error()) http.Error(w, "Couldn't find image", 404) return } - ss := tm.getScreenshot(vars["imageid"]) - if ss == nil { + ss, err := tm.Game.GetScreenshot(vars["imageid"]) + if err != nil { + fmt.Println("handleImageRequest: " + err.Error()) http.Error(w, "Couldn't find image", 404) return } w.Header().Set("Content-Type", "image/"+ss.Filetype) dat, err := base64.StdEncoding.DecodeString(ss.Image) if err != nil { + fmt.Println("handleImageRequest: " + err.Error()) http.Error(w, "Couldn't find image", 404) return } @@ -134,15 +155,15 @@ func handleImageRequest(w http.ResponseWriter, req *http.Request) { func handleTeamMgmtRequest(w http.ResponseWriter, req *http.Request) { // Team Management pages are open even without client authentication - if db.getPublicSiteMode() == SiteModeVoting { + if m.site.GetPublicMode() == SiteModeVoting { redirect("/", w, req) } page := initPublicPage(w, req) vars := mux.Vars(req) page.SubTitle = "Team Details" teamId := vars["id"] - tm := db.getTeam(teamId) - if tm != nil { + tm, err := m.jam.GetTeamById(teamId) + if err == nil { // Team self-management functions switch vars["function"] { case "": @@ -150,41 +171,35 @@ func handleTeamMgmtRequest(w http.ResponseWriter, req *http.Request) { page.TemplateData = tm page.show("public-teammgmt.html", w) case "savemember": - m := newTeamMember(req.FormValue("newmembername")) + m, err := NewTeamMember(tm.UUID, "") + if err != nil { + page.session.setFlashMessage("Error adding team member: "+err.Error(), "error") + redirect("/team/"+tm.UUID+"#members", w, req) + } + m.Name = req.FormValue("newmembername") m.SlackId = req.FormValue("newmemberslackid") m.Twitter = req.FormValue("newmembertwitter") m.Email = req.FormValue("newmemberemail") - if err := tm.updateTeamMember(m); err != nil { + if err := tm.AddTeamMember(m); err != nil { page.session.setFlashMessage("Error adding team member: "+err.Error(), "error") } else { page.session.setFlashMessage(m.Name+" added to team!", "success") } - refreshTeamsInMemory() redirect("/team/"+tm.UUID+"#members", w, req) case "deletemember": mbrId := req.FormValue("memberid") - m := tm.getTeamMember(mbrId) - if m != nil { - 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") - } + err := tm.RemoveTeamMemberById(mbrId) + if err != nil { + page.session.setFlashMessage("Error deleting team member: "+err.Error(), "error") } else { - page.session.setFlashMessage("Couldn't find member to delete", "error") + page.session.setFlashMessage("Team member removed", "success") } - refreshTeamsInMemory() redirect("/team/"+tm.UUID, w, req) case "savegame": - gm := newGame(tm.UUID) - gm.Name = req.FormValue("gamename") - 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") - } else { - page.session.setFlashMessage("Team game updated", "success") - } + tm.Game.Name = req.FormValue("gamename") + tm.Game.Link = req.FormValue("gamelink") + tm.Game.Description = req.FormValue("gamedesc") + page.session.setFlashMessage("Team game updated", "success") redirect("/team/"+tm.UUID, w, req) case "screenshotupload": if err := saveScreenshots(tm, req); err != nil { @@ -193,7 +208,7 @@ func handleTeamMgmtRequest(w http.ResponseWriter, req *http.Request) { redirect("/team/"+tm.UUID, w, req) case "screenshotdelete": ssid := vars["subid"] - if err := tm.deleteScreenshot(ssid); err != nil { + if err := tm.Game.RemoveScreenshot(ssid); err != nil { page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error") } redirect("/team/"+tm.UUID, w, req) From 0850550be4da4896910de5f462b12a9ed05a647c Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Sat, 21 Oct 2017 17:47:01 -0500 Subject: [PATCH 5/6] Getting things going --- admin_clients.go | 5 ++++- admin_teams.go | 1 + assets.go | 18 +++++++++--------- main.go | 24 ++++++++++++++---------- model_clients.go | 12 ++++++------ model_gamejam.go | 3 +++ model_sitedata.go | 7 ++++--- model_teams.go | 11 ++++++++--- model_votes.go | 4 ++-- page_session.go | 4 +++- public_endpoints.go | 5 ++++- templates/admin-votes.html | 2 +- 12 files changed, 59 insertions(+), 37 deletions(-) diff --git a/admin_clients.go b/admin_clients.go index 5c23aef..641fa78 100644 --- a/admin_clients.go +++ b/admin_clients.go @@ -11,7 +11,10 @@ func handleAdminClients(w http.ResponseWriter, req *http.Request, page *pageData vars := mux.Vars(req) page.SubTitle = "Clients" clientId := vars["id"] - client := m.GetClient(clientId) + client, err := m.GetClient(clientId) + if err != nil { + client = NewClient(clientId) + } clientIp, _, _ := net.SplitHostPort(req.RemoteAddr) if clientId == "" { type clientsPageData struct { diff --git a/admin_teams.go b/admin_teams.go index 7de428c..795a0f6 100644 --- a/admin_teams.go +++ b/admin_teams.go @@ -51,6 +51,7 @@ func handleAdminTeams(w http.ResponseWriter, req *http.Request, page *pageData) mbrName := req.FormValue("newmembername") mbr, err := NewTeamMember(tm.UUID, "") if err == nil { + mbr.Name = mbrName mbr.SlackId = req.FormValue("newmemberslackid") mbr.Twitter = req.FormValue("newmembertwitter") mbr.Email = req.FormValue("newmemberemail") diff --git a/assets.go b/assets.go index 4fb2c8c..703575c 100644 --- a/assets.go +++ b/assets.go @@ -12113,16 +12113,16 @@ XzHfrrS2uzLN6JsOkTvo3Iny7fxf2KYCxs3I30osvf4OAAD//yR+jxHtAwAA "/templates/admin-votes.html": { local: "templates/admin-votes.html", - size: 837, - modtime: 1506689947, + size: 848, + modtime: 1508506407, compressed: ` -H4sIAAAJbogA/3xSTW/cIBC9768YWXusbam9RRip2lxy6SFd9Y7NJIuKwYJZS1WU/94Bm5Av5cTM4w3z -5g1CmxUmq2IcmoDxaim2k3ekjMPQyIO4fJenawjoCO63e9Ezdnh6gqDcI8LRfIPjCjcDdGecF6sIbxWp -bmfD8/MBoLIp0SnzjytXqHmnZBJD98r9ZeQmp7R2v9SMnIsxQC+3p9DpVFMj0fMYLJbUaBGMHprVE8Y2 -502ZL/qwEZZrwPZ92I4+aAyoYeJh8/AAgi6otMzyBIUtyLA8mxkjqXkRPWevLk7WJLfubt9fpMmMe4wV -5yi/mZC9j6DR63+yGPK1xz+t/ZMGLQ6+kajlZuiL0ORiT/ojY1N8pz8h7CEn3takCpuSsqms83TxZqpy -9kprcp+p7rJn6PVj+xpfKvrarAoqZr2tYLQYJujBe/pkWbr8AIsPxN/B27goNzQ/miTMooPutyHsipdw -9qQs5PRjfz63PhykjyMP/wMAAP//x7l0qEUDAAA= +H4sIAAAJbogA/3xSPW/cMAzd/SsI48b6DLRbIAsoLkuWDsGhu2wxOaGyZEg8A0WQ/15KtqPcRzOJfHoU +Hx8ltJlhsCrGrg4Yz5ZiM3hHyjgMtazE6bs8nENAR/C83IuWsertDYJyrwg78w12Mzx0sD/iOFlF+KhI +7Vc2vL9XAIVNiU6Zv5u5Qo0rJZMYelbuDyMPOaV5/0uNyLnoA7RyeQqdTjUlEi2PwWJJ9RbB6K6ePWFs +cl5v80UfFsJ0Dthch03vg8aAGgYeNg8PIOiESsssT1BYggzLoxkxkhon0XL26eJgTXLr6fH6Ik1m3Gss +OEf5zYSsfQT1Xv+VmyFfe/zT2t9p0M3BC4laLoZ+CE0utqRvGYviJ32HsIaceFuSImxIyoZtnYeTN0OR +s1Zak/sMZZctQ58fW9f4UdGWZkXQZtZlBaObYYJevKc7y9LbD7D4QvwdvI2Tcl39o07CLLr/2wpHT8pC +Tm+l8Lm05CD9IVn9CwAA//8Mc47HUAMAAA== `, }, diff --git a/main.go b/main.go index 652e691..584da71 100644 --- a/main.go +++ b/main.go @@ -58,7 +58,6 @@ type menuItem struct { var sessionSecret = "JCOP5e8ohkTcOzcSMe74" var sessionStore = sessions.NewCookieStore([]byte(sessionSecret)) -var site *siteData var r *mux.Router var m *model @@ -69,7 +68,7 @@ func main() { } loadConfig() - if err = site.SaveToDB(); err != nil { + if err = m.site.SaveToDB(); err != nil { errorExit("Unable to save site config to DB: " + err.Error()) } initialize() @@ -109,8 +108,8 @@ func main() { chain := alice.New(loggingHandler).Then(r) - fmt.Printf("Listening on port %d\n", site.Port) - log.Fatal(http.ListenAndServe("127.0.0.1:"+strconv.Itoa(site.Port), chain)) + fmt.Printf("Listening on port %d\n", m.site.Port) + log.Fatal(http.ListenAndServe("127.0.0.1:"+strconv.Itoa(m.site.Port), chain)) } func loadConfig() { @@ -187,6 +186,7 @@ func initialize() { gjName, _ := reader.ReadString('\n') gjName = strings.TrimSpace(gjName) m.jam.Name = gjName + assertError(m.jam.SaveToDB()) } if m.jam.Name != "" { @@ -201,14 +201,14 @@ func loggingHandler(h http.Handler) http.Handler { } func InitPageData(w http.ResponseWriter, req *http.Request) *pageData { - if site.DevMode { + if m.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 { + if s, err = sessionStore.Get(req, m.site.SessionName); err != nil { http.Error(w, err.Error(), 500) return p } @@ -222,7 +222,7 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData { // With a valid account p.LoggedIn = m.isValidUserEmail(userEmail) - p.Site = site + p.Site = m.site p.SubTitle = "GameJam Voting" p.Stylesheets = make([]string, 0, 0) p.Stylesheets = append(p.Stylesheets, "/assets/vendor/css/pure-min.css") @@ -258,7 +258,11 @@ func InitPageData(w http.ResponseWriter, req *http.Request) *pageData { p.HideAdminMenu = true p.ClientId = p.session.getClientId() - cl := m.GetClient(p.ClientId) + var cl *Client + if cl, err = m.GetClient(p.ClientId); err != nil { + // A new client + cl = NewClient(p.ClientId) + } p.ClientIsAuth = cl.Auth p.ClientIsServer = clientIsServer(req) @@ -290,8 +294,8 @@ func (p *pageData) show(tmplName string, w http.ResponseWriter) error { // Spit out a template func outputTemplate(tmplName string, tmplData interface{}, w http.ResponseWriter) error { n := "/templates/" + tmplName - l := template.Must(template.New("layout").Parse(FSMustString(site.DevMode, n))) - t := template.Must(l.Parse(FSMustString(site.DevMode, n))) + l := template.Must(template.New("layout").Parse(FSMustString(m.site.DevMode, n))) + t := template.Must(l.Parse(FSMustString(m.site.DevMode, n))) return t.Execute(w, tmplData) } diff --git a/model_clients.go b/model_clients.go index b525cd3..9191f0e 100644 --- a/model_clients.go +++ b/model_clients.go @@ -127,23 +127,23 @@ func (m *model) SaveClient(cl *Client) error { */ // Return a client by it's UUID -func (m *model) GetClient(id string) *Client { +func (m *model) GetClient(id string) (*Client, error) { for i := range m.clients { if m.clients[i].UUID == id { - return &m.clients[i] + return &m.clients[i], nil } } - return nil + return nil, errors.New("Invalid Id") } // Return a client by it's IP address -func (m *model) GetClientByIp(ip string) *Client { +func (m *model) GetClientByIp(ip string) (*Client, error) { for i := range m.clients { if m.clients[i].IP == ip { - return &m.clients[i] + return &m.clients[i], nil } } - return nil + return nil, errors.New("Invalid Ip") } // Add/Update a client in the data model diff --git a/model_gamejam.go b/model_gamejam.go index 7f27303..a8a028f 100644 --- a/model_gamejam.go +++ b/model_gamejam.go @@ -61,6 +61,9 @@ func (gj *Gamejam) SaveToDB() error { defer gj.m.closeDB() var errs []error + if err := gj.m.bolt.SetValue(gj.mPath, "name", gj.Name); err != nil { + errs = append(errs, err) + } // Save all Teams for _, tm := range gj.Teams { if err := gj.SaveTeam(&tm); err != nil { diff --git a/model_sitedata.go b/model_sitedata.go index 2ca86c3..4d7467c 100644 --- a/model_sitedata.go +++ b/model_sitedata.go @@ -3,6 +3,7 @@ package main import ( "errors" "strconv" + "strings" ) /** @@ -59,16 +60,16 @@ func (s *siteData) LoadFromDB() error { } defer s.m.closeDB() - if title, err := s.m.bolt.GetValue(s.mPath, "title"); err == nil { + if title, _ := s.m.bolt.GetValue(s.mPath, "title"); strings.TrimSpace(title) != "" { s.Title = title } if port, err := s.m.bolt.GetInt(s.mPath, "port"); err == nil { s.Port = port } - if sessionName, err := s.m.bolt.GetValue(s.mPath, "session-name"); err == nil { + if sessionName, _ := s.m.bolt.GetValue(s.mPath, "session-name"); strings.TrimSpace(sessionName) != "" { s.SessionName = sessionName } - if serverDir, err := s.m.bolt.GetValue(s.mPath, "server-dir"); err == nil { + if serverDir, _ := s.m.bolt.GetValue(s.mPath, "server-dir"); strings.TrimSpace(serverDir) != "" { s.ServerDir = serverDir } s.changed = false diff --git a/model_teams.go b/model_teams.go index f2c6202..97851c7 100644 --- a/model_teams.go +++ b/model_teams.go @@ -2,6 +2,7 @@ package main import ( "errors" + "fmt" "github.com/pborman/uuid" ) @@ -23,8 +24,11 @@ func NewTeam(id string) *Team { if id == "" { id = uuid.New() } + // Create an emtpy game for the team + gm, _ := NewGame(id) return &Team{ UUID: id, + Game: gm, mPath: []string{"jam", "teams", id}, } } @@ -114,7 +118,8 @@ func (gj *Gamejam) LoadAllTeams() []Team { var tmUUIDs []string tmsPath := append(gj.mPath, "teams") - if tmUUIDs, err = m.bolt.GetBucketList(tmsPath); err != nil { + if tmUUIDs, err = gj.m.bolt.GetBucketList(tmsPath); err != nil { + fmt.Println(err.Error()) return ret } for _, v := range tmUUIDs { @@ -276,10 +281,10 @@ func (gj *Gamejam) DeleteTeamMember(tm *Team, mbr *TeamMember) error { // Add a team func (gj *Gamejam) AddTeam(tm *Team) error { - if _, err := gj.GetTeamById(tm.UUID); err != nil { + if _, err := gj.GetTeamById(tm.UUID); err == nil { return errors.New("A team with that ID already exists") } - if _, err := gj.GetTeamByName(tm.Name); err != nil { + if _, err := gj.GetTeamByName(tm.Name); err == nil { return errors.New("A team with that Name already exists") } gj.Teams = append(gj.Teams, *tm) diff --git a/model_votes.go b/model_votes.go index cccad93..644040a 100644 --- a/model_votes.go +++ b/model_votes.go @@ -85,13 +85,13 @@ func (gj *Gamejam) LoadAllVotes() []Vote { votesPath := []string{"jam", "votes"} var cliUUIDs []string - if cliUUIDs, err = m.bolt.GetBucketList(votesPath); err != nil { + if cliUUIDs, err = gj.m.bolt.GetBucketList(votesPath); err != nil { return ret } for _, cId := range cliUUIDs { vtsPth := append(votesPath, cId) var times []string - if times, err = m.bolt.GetBucketList(vtsPth); err != nil { + if times, err = gj.m.bolt.GetBucketList(vtsPth); err != nil { // Error reading this bucket, move on to the next continue } diff --git a/page_session.go b/page_session.go index ba3e04d..215f421 100644 --- a/page_session.go +++ b/page_session.go @@ -43,7 +43,9 @@ func (p *pageSession) getClientId() string { fmt.Println(" Client IP:" + clientIp) if clientIp != "127.0.0.1" { fmt.Println(" Pulling data by IP") - cli = m.GetClientByIp(clientIp) + if cli, err = m.GetClientByIp(clientIp); err != nil { + cli = NewClient(clientId) + } } if cli != nil { clientId = cli.UUID diff --git a/public_endpoints.go b/public_endpoints.go index 2173726..e4a8131 100644 --- a/public_endpoints.go +++ b/public_endpoints.go @@ -71,7 +71,10 @@ func handlePublicSaveVote(w http.ResponseWriter, req *http.Request) { fmt.Println("Error parsing timestamp: " + ts) redirect("/", w, req) } - client := m.GetClient(page.ClientId) + client, err := m.GetClient(page.ClientId) + if err != nil { + client = NewClient(page.ClientId) + } // voteSlice is an ordered string slice of the voters preferences voteCSV := req.FormValue("uservote") diff --git a/templates/admin-votes.html b/templates/admin-votes.html index d7e750c..203e447 100644 --- a/templates/admin-votes.html +++ b/templates/admin-votes.html @@ -31,7 +31,7 @@ - {{ len .Site.Votes }} Total Votes + {{ len .TemplateData.AllVotes }} Total Votes From 84cba4d3403c2eb6fb74b9c7961257b472518572 Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Tue, 23 Jan 2018 14:55:52 -0600 Subject: [PATCH 6/6] Running out of memory now Save to DB happens on timed interval and on shutdown --- admin_games.go | 63 +++++++++++++++++++++++++++++++-------------- main.go | 23 +++++++++++++++++ model.go | 32 +++++++++++++---------- model_games.go | 3 ++- model_votes.go | 18 ++++++++----- public_endpoints.go | 20 ++++++++++++-- 6 files changed, 116 insertions(+), 43 deletions(-) diff --git a/admin_games.go b/admin_games.go index c9729ef..2485333 100644 --- a/admin_games.go +++ b/admin_games.go @@ -20,13 +20,7 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) teamId := vars["id"] if teamId == "" { // Games List - // TODO: We should be able to just pass m.jam to the template instead of a custom struct - type gamesPageData struct { - Teams []Team - } - gpd := new(gamesPageData) - gpd.Teams = m.jam.Teams - page.TemplateData = gpd + page.TemplateData = m.jam page.SubTitle = "Games" page.show("admin-games.html", w) } else { @@ -48,22 +42,50 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) page.session.setFlashMessage("Team game updated", "success") } redirect("/admin/teams/"+tm.UUID+"#game", w, req) + case "screenshotupload": - if err := saveScreenshots(tm, req); err != nil { + var ss *Screenshot + tm, err := m.jam.GetTeamById(tm.UUID) + if err != nil { page.session.setFlashMessage("Error updating game: "+err.Error(), "error") + redirect("/admin/teams/"+tm.UUID+"#game", w, req) + } + ss, err = ssFromRequest(tm, req) + if err != nil { + page.session.setFlashMessage("Error updating game: "+err.Error(), "error") + redirect("/admin/teams/"+tm.UUID+"#game", w, req) + } + gm := tm.Game + gm.Screenshots = append(gm.Screenshots, *ss) + if err = m.jam.UpdateGame(tm.UUID, gm); err != nil { + page.session.setFlashMessage("Error updating game: "+err.Error(), "error") + } else { + page.session.setFlashMessage("Screenshot Uploaded", "success") } redirect("/admin/teams/"+tm.UUID+"#game", w, req) + case "screenshotdelete": - var ss *Screenshot var err error ssid := vars["subid"] - if ss, err = NewScreenshot(tm.UUID, ssid); err != nil { - page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error") + tm, err := m.jam.GetTeamById(tm.UUID) + if err != nil { + page.session.setFlashMessage("Error updating game: "+err.Error(), "error") + redirect("/admin/teams/"+tm.UUID+"#game", w, req) + break } - if err = m.jam.DeleteScreenshot(ss); err != nil { - page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error") + gm := tm.Game + if err = gm.RemoveScreenshot(ssid); err != nil { + page.session.setFlashMessage("Error updating game: "+err.Error(), "error") + redirect("/admin/teams/"+tm.UUID+"#game", w, req) + break + } + if err = m.jam.UpdateGame(tm.UUID, gm); err != nil { + page.session.setFlashMessage("Error updating game: "+err.Error(), "error") + } else { + page.session.setFlashMessage("Screenshot Removed", "success") } redirect("/admin/teams/"+tm.UUID+"#game", w, req) + } } else { page.session.setFlashMessage("Not a valid team id", "error") @@ -72,13 +94,13 @@ func handleAdminGames(w http.ResponseWriter, req *http.Request, page *pageData) } } -func saveScreenshots(tm *Team, req *http.Request) error { +func ssFromRequest(tm *Team, req *http.Request) (*Screenshot, error) { var err error var ss *Screenshot file, hdr, err := req.FormFile("newssfile") if err != nil { - return err + return nil, err } extIdx := strings.LastIndex(hdr.Filename, ".") fltp := "png" @@ -89,29 +111,30 @@ func saveScreenshots(tm *Team, req *http.Request) error { buf := new(bytes.Buffer) // We convert everything to jpg if err = jpeg.Encode(buf, mI, nil); err != nil { - return errors.New("Unable to encode image") + return nil, errors.New("Unable to encode image") } thm := resize.Resize(200, 0, mI, resize.Lanczos3) thmBuf := new(bytes.Buffer) var thmString string if fltp == "gif" { if err = gif.Encode(thmBuf, thm, nil); err != nil { - return errors.New("Unable to encode image") + return nil, errors.New("Unable to encode image") } } else { if err = jpeg.Encode(thmBuf, thm, nil); err != nil { - return errors.New("Unable to encode image") + return nil, errors.New("Unable to encode image") } } thmString = base64.StdEncoding.EncodeToString(thmBuf.Bytes()) if ss, err = NewScreenshot(tm.UUID, ""); err != nil { - return err + return nil, err } ss.Image = base64.StdEncoding.EncodeToString(buf.Bytes()) ss.Thumbnail = thmString ss.Filetype = fltp - return m.jam.SaveScreenshot(ss) + return ss, nil + //return m.jam.SaveScreenshot(ss) } diff --git a/main.go b/main.go index 584da71..3cc889d 100644 --- a/main.go +++ b/main.go @@ -9,8 +9,11 @@ import ( "log" "net/http" "os" + "os/signal" "strconv" "strings" + "syscall" + "time" "golang.org/x/crypto/ssh/terminal" @@ -71,6 +74,15 @@ func main() { if err = m.site.SaveToDB(); err != nil { errorExit("Unable to save site config to DB: " + err.Error()) } + + // Save changes to the DB every 5 minutes + go func() { + for { + m.saveChanges() + time.Sleep(5 * time.Minute) + } + }() + initialize() r = mux.NewRouter() @@ -108,6 +120,17 @@ func main() { chain := alice.New(loggingHandler).Then(r) + // Set up a channel to intercept Ctrl+C for graceful shutdowns + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + // Save the changes when the app quits + fmt.Println("\nFinishing up...") + m.saveChanges() + os.Exit(0) + }() + fmt.Printf("Listening on port %d\n", m.site.Port) log.Fatal(http.ListenAndServe("127.0.0.1:"+strconv.Itoa(m.site.Port), chain)) } diff --git a/model.go b/model.go index 8f02588..88606d8 100644 --- a/model.go +++ b/model.go @@ -2,6 +2,7 @@ package main import ( "errors" + "fmt" "github.com/br0xen/boltease" ) @@ -114,22 +115,25 @@ func (m *model) saveChanges() error { } defer m.closeDB() - if m.site.NeedsSave() { - if err = m.site.SaveToDB(); err != nil { - return err - } + //if m.site.NeedsSave() { + fmt.Println("Saving Site data to DB") + if err = m.site.SaveToDB(); err != nil { + return err } - if m.jam.IsChanged { - if err = m.jam.SaveToDB(); err != nil { - return err - } - m.jam.IsChanged = false + //} + //if m.jam.IsChanged { + fmt.Println("Saving Jam data to DB") + if err = m.jam.SaveToDB(); err != nil { + return err } - if m.clientsUpdated { - if err = m.SaveAllClients(); err != nil { - return err - } - m.clientsUpdated = false + m.jam.IsChanged = false + //} + //if m.clientsUpdated { + fmt.Println("Saving Client data to DB") + if err = m.SaveAllClients(); err != nil { + return err } + m.clientsUpdated = false + //} return nil } diff --git a/model_games.go b/model_games.go index 788b3b3..888b5de 100644 --- a/model_games.go +++ b/model_games.go @@ -2,6 +2,7 @@ package main import ( "errors" + "fmt" "github.com/pborman/uuid" ) @@ -45,12 +46,12 @@ func (gm *Game) RemoveScreenshot(ssId string) error { for i, ss := range gm.Screenshots { if ss.UUID == ssId { idx = i - return nil } } if idx < 0 { return errors.New("Invalid Id") } + fmt.Print("Removing Screenshot (", ssId, ") (IDX:", idx, ")\n") gm.Screenshots = append(gm.Screenshots[:idx], gm.Screenshots[idx+1:]...) return nil } diff --git a/model_votes.go b/model_votes.go index 644040a..677036c 100644 --- a/model_votes.go +++ b/model_votes.go @@ -2,6 +2,7 @@ package main import ( "errors" + "fmt" "strconv" "time" ) @@ -30,7 +31,10 @@ func NewVote(clId string, tm time.Time) (*Vote, error) { } vt := new(Vote) + + vt.Timestamp = tm vt.mPath = []string{"jam", "votes", clId, tm.Format(time.RFC3339)} + return vt, nil } @@ -52,9 +56,9 @@ func (gj *Gamejam) GetVoteWithTimeString(clId, ts string) (*Vote, error) { } func (gj *Gamejam) GetVote(clId string, ts time.Time) (*Vote, error) { - for _, v := range gj.Votes { - if v.ClientId == clId && v.Timestamp == ts { - return &v, nil + for i := range gj.Votes { + if gj.Votes[i].ClientId == clId && gj.Votes[i].Timestamp == ts { + return &gj.Votes[i], nil } } return nil, errors.New("Couldn't find requested vote") @@ -96,6 +100,7 @@ func (gj *Gamejam) LoadAllVotes() []Vote { continue } for _, t := range times { + fmt.Println("Loading Vote", cId, t) if vt, err := gj.LoadVote(cId, t); err == nil { ret = append(ret, *vt) } @@ -116,7 +121,7 @@ func (gj *Gamejam) LoadVote(clientId, t string) (*Vote, error) { return nil, errors.New("Error creating vote: " + err.Error()) } var choices []string - if choices, err = m.bolt.GetKeyList(vt.mPath); err != nil { + if choices, err = gj.m.bolt.GetKeyList(vt.mPath); err != nil { return nil, errors.New("Error creating vote: " + err.Error()) } for _, v := range choices { @@ -124,8 +129,9 @@ func (gj *Gamejam) LoadVote(clientId, t string) (*Vote, error) { var rank int if rank, err = strconv.Atoi(v); err == nil { ch.Rank = rank - ch.Team, _ = m.bolt.GetValue(vt.mPath, v) - vt.Choices = append(vt.Choices, *ch) + if ch.Team, err = gj.m.bolt.GetValue(vt.mPath, v); err == nil { + vt.Choices = append(vt.Choices, *ch) + } } } return vt, nil diff --git a/public_endpoints.go b/public_endpoints.go index e4a8131..52f98e1 100644 --- a/public_endpoints.go +++ b/public_endpoints.go @@ -38,7 +38,8 @@ func loadVotingPage(w http.ResponseWriter, req *http.Request) { Timestamp string } vpd := new(votingPageData) - tms := m.jam.Teams + tms := make([]Team, len(m.jam.Teams)) + copy(tms, m.jam.Teams) // Randomize the team list rand.Seed(time.Now().Unix()) @@ -173,6 +174,7 @@ func handleTeamMgmtRequest(w http.ResponseWriter, req *http.Request) { page.SubTitle = "Team Management" page.TemplateData = tm page.show("public-teammgmt.html", w) + case "savemember": m, err := NewTeamMember(tm.UUID, "") if err != nil { @@ -189,6 +191,7 @@ func handleTeamMgmtRequest(w http.ResponseWriter, req *http.Request) { page.session.setFlashMessage(m.Name+" added to team!", "success") } redirect("/team/"+tm.UUID+"#members", w, req) + case "deletemember": mbrId := req.FormValue("memberid") err := tm.RemoveTeamMemberById(mbrId) @@ -198,23 +201,36 @@ func handleTeamMgmtRequest(w http.ResponseWriter, req *http.Request) { page.session.setFlashMessage("Team member removed", "success") } redirect("/team/"+tm.UUID, w, req) + case "savegame": tm.Game.Name = req.FormValue("gamename") tm.Game.Link = req.FormValue("gamelink") tm.Game.Description = req.FormValue("gamedesc") page.session.setFlashMessage("Team game updated", "success") redirect("/team/"+tm.UUID, w, req) + case "screenshotupload": - if err := saveScreenshots(tm, req); err != nil { + ss, err := ssFromRequest(tm, req) + if err != nil { page.session.setFlashMessage("Error updating game: "+err.Error(), "error") + redirect("/team/"+tm.UUID, w, req) + } + gm := tm.Game + gm.Screenshots = append(gm.Screenshots, *ss) + if err = m.jam.UpdateGame(tm.UUID, gm); err != nil { + page.session.setFlashMessage("Error updating game: "+err.Error(), "error") + } else { + page.session.setFlashMessage("Screenshot Uploaded", "success") } redirect("/team/"+tm.UUID, w, req) + case "screenshotdelete": ssid := vars["subid"] if err := tm.Game.RemoveScreenshot(ssid); err != nil { page.session.setFlashMessage("Error deleting screenshot: "+err.Error(), "error") } redirect("/team/"+tm.UUID, w, req) + } } else { http.Error(w, "Page Not Found", 404)