From 9ed692aa107d3856be0e58333ec9c3c0c2cdc2ae Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Tue, 10 Jan 2017 08:08:45 -0500 Subject: [PATCH] Add Groups --- attendees.go | 20 +- db.go | 1 + errors.go | 1 + groups.go | 243 ++++++++++++++++++++++++ js/actions/GroupActions.js | 135 +++++++++++++ js/components/LunchApp.js | 19 +- js/components/NewGroupModal.js | 119 ++++++++++++ js/components/NewUserModal.js | 70 +++++-- js/components/TopBar.js | 12 +- js/constants/GroupConstants.js | 10 + js/containers/LunchAppContainer.js | 2 + js/containers/NewGroupModalContainer.js | 20 ++ js/containers/NewUserModalContainer.js | 4 +- js/models.js | 43 ++++- js/reducers/GroupReducer.js | 24 +++ js/reducers/LunchReducer.js | 2 + main.go | 1 + reports.go | 24 +-- suggestions.go | 24 +-- users.go | 28 ++- 20 files changed, 740 insertions(+), 62 deletions(-) create mode 100644 groups.go create mode 100644 js/actions/GroupActions.js create mode 100644 js/components/NewGroupModal.js create mode 100644 js/constants/GroupConstants.js create mode 100644 js/containers/NewGroupModalContainer.js create mode 100644 js/reducers/GroupReducer.js diff --git a/attendees.go b/attendees.go index e592183..55000b5 100644 --- a/attendees.go +++ b/attendees.go @@ -11,7 +11,7 @@ import ( type Attendee struct { AttendeeId int64 - UserId int64 `json:"-"` + GroupId int64 `json:"-"` Name string Date time.Time `json:"-"` } @@ -66,10 +66,10 @@ func (aeu AttendeeInUseError) Error() string { return "Attendee in use (by suggestion)" } -func GetAttendees(userid int64, date time.Time) (*[]*Attendee, error) { +func GetAttendees(groupid int64, date time.Time) (*[]*Attendee, error) { var attendees []*Attendee - _, err := DB.Select(&attendees, "SELECT * from attendees WHERE UserId=? AND Date=?", userid, date) + _, err := DB.Select(&attendees, "SELECT * from attendees WHERE GroupId=? AND Date=?", groupid, date) if err != nil { return nil, err } @@ -105,7 +105,7 @@ func InsertAttendee(a *Attendee) error { return err } - existing, err := transaction.SelectInt("SELECT count(*) from attendees where UserId=? AND Name=? AND Date=?", a.UserId, a.Name, a.Date) + existing, err := transaction.SelectInt("SELECT count(*) from attendees where GroupId=? AND Name=? AND Date=?", a.GroupId, a.Name, a.Date) if err != nil { transaction.Rollback() return err @@ -130,10 +130,10 @@ func InsertAttendee(a *Attendee) error { return nil } -func GetAttendee(attendeeid int64, userid int64, date time.Time) (*Attendee, error) { +func GetAttendee(attendeeid int64, groupid int64, date time.Time) (*Attendee, error) { var a Attendee - err := DB.SelectOne(&a, "SELECT * from attendees where UserId=? AND AttendeeId=? AND Date=?", userid, attendeeid, date) + err := DB.SelectOne(&a, "SELECT * from attendees where GroupId=? AND AttendeeId=? AND Date=?", groupid, attendeeid, date) if err != nil { return nil, err } @@ -147,7 +147,7 @@ func DeleteAttendee(a *Attendee) error { } // Ensure attendee isn't used in any suggestions - suggestions, err := GetAttendeesSuggestions(transaction, a.UserId, a.Date, a.AttendeeId) + suggestions, err := GetAttendeesSuggestions(transaction, a.GroupId, a.Date, a.AttendeeId) if err != nil { transaction.Rollback() return err @@ -199,7 +199,7 @@ func AttendeeHandler(w http.ResponseWriter, r *http.Request) { return } attendee.AttendeeId = -1 - attendee.UserId = user.UserId + attendee.GroupId = user.GroupId attendee.Date = today err = InsertAttendee(&attendee) @@ -223,7 +223,7 @@ func AttendeeHandler(w http.ResponseWriter, r *http.Request) { } else if r.Method == "GET" { var al AttendeeList - attendees, err := GetAttendees(user.UserId, today) + attendees, err := GetAttendees(user.GroupId, today) if err != nil { WriteError(w, 999 /*Internal Error*/) log.Print(err) @@ -244,7 +244,7 @@ func AttendeeHandler(w http.ResponseWriter, r *http.Request) { return } - attendee, err := GetAttendee(attendeeid, user.UserId, today) + attendee, err := GetAttendee(attendeeid, user.GroupId, today) if err != nil { WriteError(w, 3 /*Invalid Request*/) return diff --git a/db.go b/db.go index 4f1afa9..c631c63 100644 --- a/db.go +++ b/db.go @@ -17,6 +17,7 @@ func initDB() *gorp.DbMap { dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} dbmap.AddTableWithName(User{}, "users").SetKeys(true, "UserId") + dbmap.AddTableWithName(Group{}, "groups").SetKeys(true, "GroupId") dbmap.AddTableWithName(Session{}, "sessions").SetKeys(true, "SessionId") dbmap.AddTableWithName(Attendee{}, "attendees").SetKeys(true, "AttendeeId") dbmap.AddTableWithName(Suggestion{}, "suggestions").SetKeys(true, "SuggestionId") diff --git a/errors.go b/errors.go index ae7c35a..91265a0 100644 --- a/errors.go +++ b/errors.go @@ -20,6 +20,7 @@ var error_codes = map[int]string{ 6: "Suggestion Exists", 7: "Attendee In Use", 8: "Suggestion Not Last", + 9: "Group Exists", 999: "Internal Error", } diff --git a/groups.go b/groups.go new file mode 100644 index 0000000..e85e6ad --- /dev/null +++ b/groups.go @@ -0,0 +1,243 @@ +package main + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strings" +) + +type Group struct { + GroupId int64 + Name string + Password string `db:"-"` + PasswordHash string `json:"-"` +} + +type GroupList struct { + Groups *[]*Group `json:"groups"` +} + +type GroupExistsError struct{} + +func (gee GroupExistsError) Error() string { + return "Group exists" +} + +func (g *Group) Write(w http.ResponseWriter) error { + enc := json.NewEncoder(w) + return enc.Encode(g) +} + +func (g *Group) Read(json_str string) error { + dec := json.NewDecoder(strings.NewReader(json_str)) + return dec.Decode(g) +} + +func (gl *GroupList) Write(w http.ResponseWriter) error { + enc := json.NewEncoder(w) + return enc.Encode(gl) +} + +func (g *Group) HashPassword() { + password_hasher := sha256.New() + io.WriteString(password_hasher, g.Password) + g.PasswordHash = fmt.Sprintf("%x", password_hasher.Sum(nil)) + g.Password = "" +} + +func GetGroup(groupid int64) (*Group, error) { + var g Group + + err := DB.SelectOne(&g, "SELECT * from groups where GroupId=?", groupid) + if err != nil { + return nil, err + } + return &g, nil +} + +func ValidGroup(groupid int64, password string) (bool, error) { + var test Group + test.Password = password + test.HashPassword() + + group, err := GetGroup(groupid) + if err != nil { + return false, err + } else { + return group.PasswordHash == test.PasswordHash, nil + } +} + +func GetGroups() (*[]*Group, error) { + var groups []*Group + + _, err := DB.Select(&groups, "SELECT * from groups") + if err != nil { + return nil, err + } + return &groups, nil +} + +func GetGroupByName(groupname string) (*Group, error) { + var g Group + + err := DB.SelectOne(&g, "SELECT * from groups where Name=?", groupname) + if err != nil { + return nil, err + } + return &g, nil +} + +func InsertGroup(g *Group) error { + transaction, err := DB.Begin() + if err != nil { + return err + } + + existing, err := transaction.SelectInt("SELECT count(*) from groups where Name=?", g.Name) + if err != nil { + transaction.Rollback() + return err + } + if existing > 0 { + transaction.Rollback() + return GroupExistsError{} + } + + err = transaction.Insert(g) + if err != nil { + transaction.Rollback() + return err + } + + err = transaction.Commit() + if err != nil { + transaction.Rollback() + return err + } + + return nil +} + +func GroupHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == "POST" { + group_json := r.PostFormValue("group") + if group_json == "" { + WriteError(w, 3 /*Invalid Request*/) + return + } + + var group Group + err := group.Read(group_json) + if err != nil { + WriteError(w, 3 /*Invalid Request*/) + return + } + group.GroupId = -1 + group.HashPassword() + + err = InsertGroup(&group) + if err != nil { + if _, ok := err.(GroupExistsError); ok { + WriteError(w, 9 /*Group Exists*/) + } else { + WriteError(w, 999 /*Internal Error*/) + log.Print(err) + } + return + } + + w.WriteHeader(201 /*Created*/) + err = group.Write(w) + if err != nil { + WriteError(w, 999 /*Internal Error*/) + log.Print(err) + return + } + } else if r.Method == "GET" { + var gl GroupList + + groups, err := GetGroups() + if err != nil { + WriteError(w, 999 /*Internal Error*/) + log.Print(err) + return + } + + gl.Groups = groups + err = (&gl).Write(w) + if err != nil { + WriteError(w, 999 /*Internal Error*/) + log.Print(err) + return + } + } else if r.Method == "PUT" { + user, err := GetUserFromSession(r) + if err != nil { + WriteError(w, 1 /*Not Signed In*/) + return + } + + groupid, err := GetURLID(r.URL.Path) + if err != nil { + WriteError(w, 3 /*Invalid Request*/) + return + } + + if groupid != user.GroupId { + WriteError(w, 2 /*Unauthorized Access*/) + return + } + + group_json := r.PostFormValue("group") + if group_json == "" { + WriteError(w, 3 /*Invalid Request*/) + return + } + + group, err := GetGroup(groupid) + if err != nil { + WriteError(w, 999 /*Internal Error*/) + log.Print(err) + return + } + + // Save old PWHash in case the new password is bogus + old_pwhash := group.PasswordHash + + err = group.Read(group_json) + if err != nil || group.GroupId != groupid { + WriteError(w, 3 /*Invalid Request*/) + return + } + + // If the user didn't create a new password for the group, keep the old one + if group.Password != BogusPassword { + group.HashPassword() + } else { + group.Password = "" + group.PasswordHash = old_pwhash + } + + count, err := DB.Update(group) + if count != 1 || err != nil { + WriteError(w, 999 /*Internal Error*/) + log.Print(err) + return + } + + err = group.Write(w) + if err != nil { + WriteError(w, 999 /*Internal Error*/) + log.Print(err) + return + } + } else { /* DELETE not implemented*/ + WriteError(w, 3 /*Invalid Request*/) + return + } +} diff --git a/js/actions/GroupActions.js b/js/actions/GroupActions.js new file mode 100644 index 0000000..56770bc --- /dev/null +++ b/js/actions/GroupActions.js @@ -0,0 +1,135 @@ +var GroupConstants = require('../constants/GroupConstants'); + +var ErrorActions = require('./ErrorActions'); + +var models = require('../models'); +var Group = models.Group; +var Error = models.Error; + +function createGroup(group) { + return { + type: GroupConstants.CREATE_GROUP, + group: group + } +} + +function groupCreated(group) { + return { + type: GroupConstants.GROUP_CREATED, + group: group + } +} + +function fetchGroups() { + return { + type: GroupConstants.FETCH_GROUPS + } +} + +function groupsFetched(groups) { + return { + type: GroupConstants.GROUPS_FETCHED, + groups: groups + } +} + +function updateGroup(group) { + return { + type: GroupConstants.UPDATE_GROUP, + group: group + } +} + +function groupUpdated(group) { + return { + type: GroupConstants.GROUP_UPDATED, + group: group + } +} + +function fetchAll() { + return function (dispatch) { + dispatch(fetchGroups()); + + $.ajax({ + type: "GET", + dataType: "json", + url: "group/", + success: function(data, status, jqXHR) { + var e = new Error(); + e.fromJSON(data); + if (e.isError()) { + dispatch(ErrorActions.serverError(e)); + } else { + dispatch(groupsFetched(data.groups.map(function(json) { + var g = new Group(); + g.fromJSON(json); + return g; + }))); + } + }, + error: function(jqXHR, status, error) { + dispatch(ErrorActions.ajaxError(error)); + } + }); + }; +} + +function create(group) { + return function(dispatch) { + dispatch(createGroup(group)); + $.ajax({ + type: "POST", + dataType: "json", + url: "group/", + data: {group: group.toJSON()}, + success: function(data, status, jqXHR) { + var e = new Error(); + e.fromJSON(data); + if (e.isError()) { + dispatch(ErrorActions.serverError(e)); + } else { + var u = new Group(); + u.fromJSON(data); + dispatch(groupCreated(u)); + } + }, + error: function(jqXHR, status, error) { + dispatch(ErrorActions.ajaxError(error)); + } + }); + }; +} + +function update(group) { + return function (dispatch) { + dispatch(updateGroup()); + + $.ajax({ + type: "PUT", + dataType: "json", + url: "group/"+group.GroupId+"/", + data: {group: group.toJSON()}, + success: function(data, status, jqXHR) { + var e = new Error(); + e.fromJSON(data); + if (e.isError()) { + dispatch(ErrorActions.serverError(e)); + } else { + var g = new Group(); + g.fromJSON(data); + dispatch(groupUpdated(u)); + } + }, + error: function(jqXHR, status, error) { + dispatch(ErrorActions.ajaxError(error)); + } + }); + }; +} + +module.exports = { + fetchAll: fetchAll, + create: create, + update: update +}; diff --git a/js/components/LunchApp.js b/js/components/LunchApp.js index f4cc2e9..cdb06a4 100644 --- a/js/components/LunchApp.js +++ b/js/components/LunchApp.js @@ -10,18 +10,21 @@ var TopBarContainer = require('../containers/TopBarContainer'); var RecordLunchContainer = require('../containers/RecordLunchContainer'); var AccountSettingsModalContainer = require('../containers/AccountSettingsModalContainer'); var LunchStatsContainer = require('../containers/LunchStatsContainer'); -var NewUserModalContainer = require('../containers//NewUserModalContainer'); +var NewUserModalContainer = require('../containers/NewUserModalContainer'); +var NewGroupModalContainer = require('../containers/NewGroupModalContainer'); module.exports = React.createClass({ displayName: "LunchApp", getInitialState: function() { return { showNewUserModal: false, + showNewGroupModal: false, showAccountSettingsModal: false }; }, componentDidMount: function() { this.props.tryResumingSession(); + this.props.fetchGroups(); }, handleAccountSettings: function() { this.setState({showAccountSettingsModal: true}); @@ -41,6 +44,15 @@ module.exports = React.createClass({ handleNewUserCanceled: function() { this.setState({showNewUserModal: false}); }, + handleCreateNewGroup: function() { + this.setState({showNewGroupModal: true}); + }, + handleNewGroupCreated: function() { + this.setState({showNewGroupModal: false}); + }, + handleNewGroupCanceled: function() { + this.setState({showNewGroupModal: false}); + }, render: function() { var mainContent; if (this.props.user.isUser()) @@ -65,12 +77,17 @@ module.exports = React.createClass({
{mainContent} + = 10) + return "success"; + else if (this.state.password.length >= 6) + return "warning"; + else + return "error"; + }, + confirmPasswordValidationState: function() { + if (this.state.confirm_password.length > 0) { + if (this.state.confirm_password == this.state.password) + return "success"; + else + return "error"; + } + }, + handleCancel: function() { + if (this.props.onCancel != null) + this.props.onCancel(); + }, + handleChange: function() { + this.setState({ + name: ReactDOM.findDOMNode(this.refs.name).value, + password: ReactDOM.findDOMNode(this.refs.password).value, + confirm_password: ReactDOM.findDOMNode(this.refs.confirm_password).value + }); + }, + handleSubmit: function(e) { + var g = new Group(); + var error = ""; + e.preventDefault(); + + g.Name = this.state.name; + g.Password = this.state.password; + if (g.Password != this.state.confirm_password) { + this.setState({error: "Error: passwords do not match"}); + return; + } + + this.props.createNewGroup(g); + if (this.props.onSubmit != null) + this.props.onSubmit(g); + }, + render: function() { + return ( + + + Create New Group + + + {this.state.error} +
+ + Name + + + + + + Group Password + + + + + + + Confirm Password + + + + + +
+
+ + + + + + +
+ ); + } +}); diff --git a/js/components/NewUserModal.js b/js/components/NewUserModal.js index 75a1f39..173c484 100644 --- a/js/components/NewUserModal.js +++ b/js/components/NewUserModal.js @@ -11,9 +11,10 @@ var Col = ReactBootstrap.Col; var Button = ReactBootstrap.Button; var ButtonGroup = ReactBootstrap.ButtonGroup; +var DropdownList = require('react-widgets').DropdownList; + var models = require('../models'); var User = models.User; -var Error = models.Error; module.exports = React.createClass({ displayName: "NewUserModal", @@ -21,21 +22,19 @@ module.exports = React.createClass({ return {error: "", name: "", username: "", + group: null, + group_password: "", email: "", password: "", - confirm_password: "", - passwordChanged: false, - initial_password: ""}; + confirm_password: ""}; }, passwordValidationState: function() { - if (this.state.passwordChanged) { - if (this.state.password.length >= 10) - return "success"; - else if (this.state.password.length >= 6) - return "warning"; - else - return "error"; - } + if (this.state.password.length >= 10) + return "success"; + else if (this.state.password.length >= 6) + return "warning"; + else + return "error"; }, confirmPasswordValidationState: function() { if (this.state.confirm_password.length > 0) { @@ -45,16 +44,21 @@ module.exports = React.createClass({ return "error"; } }, + groupPasswordValidationState: function() { + if (this.state.group_password.length >= 1) + return "success"; + else + return "error"; + }, handleCancel: function() { if (this.props.onCancel != null) this.props.onCancel(); }, handleChange: function() { - if (ReactDOM.findDOMNode(this.refs.password).value != this.state.initial_password) - this.setState({passwordChanged: true}); this.setState({ name: ReactDOM.findDOMNode(this.refs.name).value, username: ReactDOM.findDOMNode(this.refs.username).value, + group_password: ReactDOM.findDOMNode(this.refs.group_password).value, email: ReactDOM.findDOMNode(this.refs.email).value, password: ReactDOM.findDOMNode(this.refs.password).value, confirm_password: ReactDOM.findDOMNode(this.refs.confirm_password).value @@ -67,10 +71,16 @@ module.exports = React.createClass({ u.Name = this.state.name; u.Username = this.state.username; + if (!this.state.group) { + this.setState({error: "Error: No group specified!"}); + return; + } + u.GroupId = this.state.group.GroupId; + u.GroupPassword = this.state.group_password; u.Email = this.state.email; u.Password = this.state.password; if (u.Password != this.state.confirm_password) { - this.setState({error: "Error: password do not match"}); + this.setState({error: "Error: passwords do not match"}); return; } @@ -79,13 +89,17 @@ module.exports = React.createClass({ this.props.onSubmit(u); }, render: function() { + var groupList = []; + for (var groupIdx in this.props.groups) + groupList.push(this.props.groups[groupIdx]); + return ( - Create New user + Create New User - {this.state.error} + {this.state.error}
Name @@ -105,6 +119,28 @@ module.exports = React.createClass({ ref="username"/> + + Group + + this.setState({group})} + messages={{'emptyList': "There are no groups yet, please create one first"}}/> + + + + Group Password + + + + + Email diff --git a/js/components/TopBar.js b/js/components/TopBar.js index dcc58df..ef97bd4 100644 --- a/js/components/TopBar.js +++ b/js/components/TopBar.js @@ -35,12 +35,20 @@ const LoginBar = React.createClass({ e.preventDefault(); this.props.onCreateNewUser(); }, + handleNewGroupSubmit: function(e) { + e.preventDefault(); + this.props.onCreateNewGroup(); + }, render: function() { return ( - + + + + @@ -102,7 +110,7 @@ module.exports = React.createClass({ var barContents; var errorAlert; if (!this.props.user.isUser()) - barContents = ; + barContents = ; else barContents = ; if (this.props.error.isError()) diff --git a/js/constants/GroupConstants.js b/js/constants/GroupConstants.js new file mode 100644 index 0000000..d2307f1 --- /dev/null +++ b/js/constants/GroupConstants.js @@ -0,0 +1,10 @@ +var keyMirror = require('keymirror'); + +module.exports = keyMirror({ + CREATE_GROUP: null, + GROUP_CREATED: null, + FETCH_GROUPS: null, + GROUPS_FETCHED: null, + UPDATE_GROUP: null, + GROUP_UPDATED: null +}); diff --git a/js/containers/LunchAppContainer.js b/js/containers/LunchAppContainer.js index a16d63d..ebb44ea 100644 --- a/js/containers/LunchAppContainer.js +++ b/js/containers/LunchAppContainer.js @@ -1,6 +1,7 @@ var connect = require('react-redux').connect; var UserActions = require('../actions/UserActions'); +var GroupActions = require('../actions/GroupActions'); var LunchApp = require('../components/LunchApp'); @@ -13,6 +14,7 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { tryResumingSession: function() {dispatch(UserActions.tryResumingSession())}, + fetchGroups: function() {dispatch(GroupActions.fetchAll())} } } diff --git a/js/containers/NewGroupModalContainer.js b/js/containers/NewGroupModalContainer.js new file mode 100644 index 0000000..a3f2a79 --- /dev/null +++ b/js/containers/NewGroupModalContainer.js @@ -0,0 +1,20 @@ +var connect = require('react-redux').connect; + +var GroupActions = require('../actions/GroupActions'); + +var NewGroupModal = require('../components/NewGroupModal'); + +function mapStateToProps(state) { + return {} +} + +function mapDispatchToProps(dispatch) { + return { + createNewGroup: function(group) {dispatch(GroupActions.create(group))} + } +} + +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(NewGroupModal) diff --git a/js/containers/NewUserModalContainer.js b/js/containers/NewUserModalContainer.js index 4b7f8d9..e3adc1a 100644 --- a/js/containers/NewUserModalContainer.js +++ b/js/containers/NewUserModalContainer.js @@ -5,7 +5,9 @@ var UserActions = require('../actions/UserActions'); var NewUserModal = require('../components/NewUserModal'); function mapStateToProps(state) { - return {} + return { + groups: state.groups + } } function mapDispatchToProps(dispatch) { diff --git a/js/models.js b/js/models.js index a65728e..e9becdb 100644 --- a/js/models.js +++ b/js/models.js @@ -10,6 +10,8 @@ function getJSONObj(json_input) { function User() { this.UserId = -1; + this.GroupId = -1; + this.GroupPassword = ""; this.Name = ""; this.Username = ""; this.Password = ""; @@ -19,6 +21,8 @@ function User() { User.prototype.toJSON = function() { var json_obj = {}; json_obj.UserId = this.UserId; + json_obj.GroupId = this.GroupId; + json_obj.GroupPassword = this.GroupPassword; json_obj.Name = this.Name; json_obj.Username = this.Username; json_obj.Password = this.Password; @@ -31,6 +35,10 @@ User.prototype.fromJSON = function(json_input) { if (json_obj.hasOwnProperty("UserId")) this.UserId = json_obj.UserId; + if (json_obj.hasOwnProperty("GroupId")) + this.GroupId = json_obj.GroupId; + if (json_obj.hasOwnProperty("GroupPassword")) + this.GroupPassword = json_obj.GroupPassword; if (json_obj.hasOwnProperty("Name")) this.Name = json_obj.Name; if (json_obj.hasOwnProperty("Username")) @@ -43,10 +51,42 @@ User.prototype.fromJSON = function(json_input) { User.prototype.isUser = function() { var empty_user = new User(); - return this.UserId != empty_user.UserId || + return this.UserId != empty_user.UserId && + this.GroupId != empty_user.GroupId && this.Username != empty_user.Username; } +function Group() { + this.GroupId = -1; + this.Name = ""; + this.Password = ""; +} + +Group.prototype.toJSON = function() { + var json_obj = {}; + json_obj.GroupId = this.GroupId; + json_obj.Name = this.Name; + json_obj.Password = this.Password; + return JSON.stringify(json_obj); +} + +Group.prototype.fromJSON = function(json_input) { + var json_obj = getJSONObj(json_input); + + if (json_obj.hasOwnProperty("GroupId")) + this.GroupId = json_obj.GroupId; + if (json_obj.hasOwnProperty("Name")) + this.Name = json_obj.Name; + if (json_obj.hasOwnProperty("Password")) + this.Password = json_obj.Password; +} + +Group.prototype.isGroup = function() { + var empty_group = new Group(); + return this.GroupId != empty_group.GroupId && + this.Name != empty_group.Name; +} + function Session() { this.SessionId = -1; this.UserId = -1; @@ -253,6 +293,7 @@ module.exports = models = { // Classes User: User, + Group: Group, Session: Session, Error: Error, Attendee: Attendee, diff --git a/js/reducers/GroupReducer.js b/js/reducers/GroupReducer.js new file mode 100644 index 0000000..94bab16 --- /dev/null +++ b/js/reducers/GroupReducer.js @@ -0,0 +1,24 @@ +var assign = require('object-assign'); + +var GroupConstants = require('../constants/GroupConstants'); + +module.exports = function(state = {}, action) { + switch (action.type) { + case GroupConstants.GROUPS_FETCHED: + var groups = {}; + for (var i = 0; i < action.groups.length; i++) { + var group = action.groups[i]; + groups[group.GroupId] = group; + } + return groups; + case GroupConstants.GROUP_CREATED: + case GroupConstants.GROUP_UPDATED: + var group = action.group; + var groups = assign({}, state, { + [group.GroupId]: group + }); + return groups; + default: + return state; + } +}; diff --git a/js/reducers/LunchReducer.js b/js/reducers/LunchReducer.js index cd42ac3..86235da 100644 --- a/js/reducers/LunchReducer.js +++ b/js/reducers/LunchReducer.js @@ -2,6 +2,7 @@ var Redux = require('redux'); var UserReducer = require('./UserReducer'); var SessionReducer = require('./SessionReducer'); +var GroupReducer = require('./GroupReducer'); var AttendeeReducer = require('./AttendeeReducer'); var PopularAttendeeReducer = require('./PopularAttendeeReducer'); var SuggestionReducer = require('./SuggestionReducer'); @@ -12,6 +13,7 @@ var ErrorReducer = require('./ErrorReducer'); module.exports = Redux.combineReducers({ user: UserReducer, session: SessionReducer, + groups: GroupReducer, attendees: AttendeeReducer, popularAttendees: PopularAttendeeReducer, suggestions: SuggestionReducer, diff --git a/main.go b/main.go index 2bd58e8..63e9761 100644 --- a/main.go +++ b/main.go @@ -65,6 +65,7 @@ func main() { servemux.HandleFunc("/static/", staticHandler) servemux.HandleFunc("/session/", SessionHandler) servemux.HandleFunc("/user/", UserHandler) + servemux.HandleFunc("/group/", GroupHandler) servemux.HandleFunc("/attendee/", AttendeeHandler) servemux.HandleFunc("/popularattendees/", PopularAttendeeHandler) servemux.HandleFunc("/suggestion/", SuggestionHandler) diff --git a/reports.go b/reports.go index b85e501..7f2dda9 100644 --- a/reports.go +++ b/reports.go @@ -58,10 +58,10 @@ func (r *Report) FromSummedSuggestionList(suggestions *[]*Suggestion) { } } -func GetAllSuggestions(userid int64) (*[]*Suggestion, error) { +func GetAllSuggestions(groupid int64) (*[]*Suggestion, error) { var suggestions []*Suggestion - _, err := DB.Select(&suggestions, "SELECT * from suggestions WHERE UserId=?", userid) + _, err := DB.Select(&suggestions, "SELECT * from suggestions WHERE GroupId=?", groupid) if err != nil { return nil, err } @@ -69,10 +69,10 @@ func GetAllSuggestions(userid int64) (*[]*Suggestion, error) { return &suggestions, nil } -func GetVetoedSuggestions(userid int64) (*[]*Suggestion, error) { +func GetVetoedSuggestions(groupid int64) (*[]*Suggestion, error) { var suggestions []*Suggestion - _, err := DB.Select(&suggestions, "SELECT suggestions.* FROM suggestions INNER JOIN suggestions AS s2 WHERE suggestions.UserId=? AND s2.UserId=? AND suggestions.Date=s2.Date AND s2.VetoingId=suggestions.SuggestionId", userid, userid) + _, err := DB.Select(&suggestions, "SELECT suggestions.* FROM suggestions INNER JOIN suggestions AS s2 WHERE suggestions.GroupId=? AND s2.GroupId=? AND suggestions.Date=s2.Date AND s2.VetoingId=suggestions.SuggestionId", groupid, groupid) if err != nil { return nil, err } @@ -80,10 +80,10 @@ func GetVetoedSuggestions(userid int64) (*[]*Suggestion, error) { return &suggestions, nil } -func GetNonVetoedSuggestions(userid int64) (*[]*Suggestion, error) { +func GetNonVetoedSuggestions(groupid int64) (*[]*Suggestion, error) { var suggestions []*Suggestion - _, err := DB.Select(&suggestions, "SELECT suggestions.* FROM suggestions LEFT OUTER JOIN suggestions AS s2 ON suggestions.SuggestionId=s2.VetoingId WHERE s2.SuggestionId IS NULL AND suggestions.UserId=?;", userid) + _, err := DB.Select(&suggestions, "SELECT suggestions.* FROM suggestions LEFT OUTER JOIN suggestions AS s2 ON suggestions.SuggestionId=s2.VetoingId WHERE s2.SuggestionId IS NULL AND suggestions.GroupId=?;", groupid) if err != nil { return nil, err } @@ -91,10 +91,10 @@ func GetNonVetoedSuggestions(userid int64) (*[]*Suggestion, error) { return &suggestions, nil } -func GetAllAttendees(userid int64) (*[]*Attendee, error) { +func GetAllAttendees(groupid int64) (*[]*Attendee, error) { var attendees []*Attendee - _, err := DB.Select(&attendees, "SELECT * from attendees WHERE UserId=?", userid) + _, err := DB.Select(&attendees, "SELECT * from attendees WHERE GroupId=?", groupid) if err != nil { return nil, err } @@ -118,7 +118,7 @@ func ReportHandler(w http.ResponseWriter, r *http.Request) { if reportid == "non-vetoed-suggestions" { report.Title = "Non-Vetoed Suggestions" - suggestions, err := GetNonVetoedSuggestions(user.UserId) + suggestions, err := GetNonVetoedSuggestions(user.GroupId) if err != nil { WriteError(w, 999 /*Internal Error*/) log.Print(err) @@ -127,7 +127,7 @@ func ReportHandler(w http.ResponseWriter, r *http.Request) { report.FromSummedSuggestionList(suggestions) } else if reportid == "vetoed-suggestions" { report.Title = "Vetoed Suggestions" - suggestions, err := GetVetoedSuggestions(user.UserId) + suggestions, err := GetVetoedSuggestions(user.GroupId) if err != nil { WriteError(w, 999 /*Internal Error*/) log.Print(err) @@ -136,7 +136,7 @@ func ReportHandler(w http.ResponseWriter, r *http.Request) { report.FromSummedSuggestionList(suggestions) } else if reportid == "suggestions" { report.Title = "Suggestion Frequency" - suggestions, err := GetAllSuggestions(user.UserId) + suggestions, err := GetAllSuggestions(user.GroupId) if err != nil { WriteError(w, 999 /*Internal Error*/) log.Print(err) @@ -145,7 +145,7 @@ func ReportHandler(w http.ResponseWriter, r *http.Request) { report.FromSummedSuggestionList(suggestions) } else if reportid == "attendees" { report.Title = "Attendee Frequency" - attendees, err := GetAllAttendees(user.UserId) + attendees, err := GetAllAttendees(user.GroupId) if err != nil { WriteError(w, 999 /*Internal Error*/) log.Print(err) diff --git a/suggestions.go b/suggestions.go index e4ca90b..9708e1f 100644 --- a/suggestions.go +++ b/suggestions.go @@ -14,7 +14,7 @@ type Suggestion struct { SuggestionId int64 VetoingId int64 // -1 for initial suggestion AttendeeId int64 - UserId int64 `json:"-"` + GroupId int64 `json:"-"` RestaurantName string Date time.Time `json:"-"` } @@ -68,30 +68,30 @@ func (sl *PopularSuggestionList) Write(w http.ResponseWriter) error { return enc.Encode(sl) } -func GetAttendeesSuggestions(transaction *gorp.Transaction, userid int64, date time.Time, attendeeid int64) (*[]*Suggestion, error) { +func GetAttendeesSuggestions(transaction *gorp.Transaction, groupid int64, date time.Time, attendeeid int64) (*[]*Suggestion, error) { var suggestions []*Suggestion - _, err := transaction.Select(&suggestions, "SELECT * from suggestions WHERE UserId=? AND Date=? AND AttendeeID=?", userid, date, attendeeid) + _, err := transaction.Select(&suggestions, "SELECT * from suggestions WHERE GroupId=? AND Date=? AND AttendeeID=?", groupid, date, attendeeid) if err != nil { return nil, err } return &suggestions, nil } -func GetSuggestions(userid int64, date time.Time) (*[]*Suggestion, error) { +func GetSuggestions(groupid int64, date time.Time) (*[]*Suggestion, error) { var suggestions []*Suggestion - _, err := DB.Select(&suggestions, "SELECT * from suggestions WHERE UserId=? AND Date=?", userid, date) + _, err := DB.Select(&suggestions, "SELECT * from suggestions WHERE GroupId=? AND Date=?", groupid, date) if err != nil { return nil, err } return &suggestions, nil } -func GetSuggestion(suggestionid int64, userid int64, date time.Time) (*Suggestion, error) { +func GetSuggestion(suggestionid int64, groupid int64, date time.Time) (*Suggestion, error) { var s Suggestion - err := DB.SelectOne(&s, "SELECT * from suggestions where UserId=? AND SuggestionId=? AND Date=?", userid, suggestionid, date) + err := DB.SelectOne(&s, "SELECT * from suggestions where GroupId=? AND SuggestionId=? AND Date=?", groupid, suggestionid, date) if err != nil { return nil, err } @@ -101,7 +101,7 @@ func GetSuggestion(suggestionid int64, userid int64, date time.Time) (*Suggestio func VetoedBy(transaction *gorp.Transaction, suggestion *Suggestion) (*[]*Suggestion, error) { var suggestions []*Suggestion - _, err := transaction.Select(&suggestions, "SELECT * from suggestions WHERE UserId=? AND Date=? AND VetoingId=?", suggestion.UserId, suggestion.Date, suggestion.SuggestionId) + _, err := transaction.Select(&suggestions, "SELECT * from suggestions WHERE GroupId=? AND Date=? AND VetoingId=?", suggestion.GroupId, suggestion.Date, suggestion.SuggestionId) if err != nil { return nil, err } @@ -136,7 +136,7 @@ func InsertSuggestion(s *Suggestion) error { return err } - existing, err := transaction.SelectInt("SELECT count(*) from suggestions where RestaurantName=? AND UserId=? AND Date=?", s.RestaurantName, s.UserId, s.Date) + existing, err := transaction.SelectInt("SELECT count(*) from suggestions where RestaurantName=? AND GroupId=? AND Date=?", s.RestaurantName, s.GroupId, s.Date) if err != nil { transaction.Rollback() return err @@ -220,7 +220,7 @@ func SuggestionHandler(w http.ResponseWriter, r *http.Request) { return } suggestion.SuggestionId = -1 - suggestion.UserId = user.UserId + suggestion.GroupId = user.GroupId suggestion.Date = today err = InsertSuggestion(&suggestion) @@ -244,7 +244,7 @@ func SuggestionHandler(w http.ResponseWriter, r *http.Request) { } else if r.Method == "GET" { var sl SuggestionList - suggestions, err := GetSuggestions(user.UserId, today) + suggestions, err := GetSuggestions(user.GroupId, today) if err != nil { WriteError(w, 999 /*Internal Error*/) log.Print(err) @@ -265,7 +265,7 @@ func SuggestionHandler(w http.ResponseWriter, r *http.Request) { return } - suggestion, err := GetSuggestion(suggestionid, user.UserId, today) + suggestion, err := GetSuggestion(suggestionid, user.GroupId, today) if err != nil { WriteError(w, 3 /*Invalid Request*/) return diff --git a/users.go b/users.go index 1799db2..422a6eb 100644 --- a/users.go +++ b/users.go @@ -11,12 +11,14 @@ import ( ) type User struct { - UserId int64 - Name string - Username string - Password string `db:"-"` - PasswordHash string `json:"-"` - Email string + UserId int64 + GroupId int64 + GroupPassword string `db:"-"` + Name string + Username string + Password string `db:"-"` + PasswordHash string `json:"-"` + Email string } const BogusPassword = "password" @@ -120,6 +122,15 @@ func UserHandler(w http.ResponseWriter, r *http.Request) { user.UserId = -1 user.HashPassword() + validgroup, err := ValidGroup(user.GroupId, user.GroupPassword) + if err != nil { + WriteError(w, 3 /*Invalid Request*/) + return + } else if !validgroup { + WriteError(w, 2 /*Unauthorized Access*/) + return + } + err = InsertUser(&user) if err != nil { if _, ok := err.(UserExistsError); ok { @@ -173,6 +184,9 @@ func UserHandler(w http.ResponseWriter, r *http.Request) { // Save old PWHash in case the new password is bogus old_pwhash := user.PasswordHash + // Save GroupId because we don't allow changing them + groupid := user.GroupId + err = user.Read(user_json) if err != nil || user.UserId != userid { WriteError(w, 3 /*Invalid Request*/) @@ -187,6 +201,8 @@ func UserHandler(w http.ResponseWriter, r *http.Request) { user.PasswordHash = old_pwhash } + user.GroupId = groupid //ensure this doesn't change + count, err := DB.Update(user) if count != 1 || err != nil { WriteError(w, 999 /*Internal Error*/)