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 -}