diff --git a/errors.go b/errors.go
index 0af9b8a..ae7c35a 100644
--- a/errors.go
+++ b/errors.go
@@ -12,14 +12,14 @@ type Error struct {
}
var error_codes = map[int]string{
- 1: "Not Signed In",
- 2: "Unauthorized Access",
- 3: "Invalid Request",
- 4: "User Exists",
- 5: "Attendee Exists",
- 6: "Suggestion Exists",
- 7: "Attendee In Use",
- // 5: "Connection Failed", //client-side error
+ 1: "Not Signed In",
+ 2: "Unauthorized Access",
+ 3: "Invalid Request",
+ 4: "User Exists",
+ 5: "Attendee Exists",
+ 6: "Suggestion Exists",
+ 7: "Attendee In Use",
+ 8: "Suggestion Not Last",
999: "Internal Error",
}
diff --git a/js/actions/SuggestionActions.js b/js/actions/SuggestionActions.js
index b8ac810..ab2b656 100644
--- a/js/actions/SuggestionActions.js
+++ b/js/actions/SuggestionActions.js
@@ -33,6 +33,19 @@ function suggestionCreated(suggestion) {
}
}
+function removeSuggestion() {
+ return {
+ type: SuggestionConstants.REMOVE_SUGGESTION
+ }
+}
+
+function suggestionRemoved(suggestionId) {
+ return {
+ type: SuggestionConstants.SUGGESTION_REMOVED,
+ suggestionId: suggestionId
+ }
+}
+
function fetchPopularSuggestions() {
return {
type: SuggestionConstants.FETCH_POPULAR_SUGGESTIONS
@@ -129,8 +142,33 @@ function fetchPopular() {
};
}
+function remove(suggestion) {
+ return function(dispatch) {
+ dispatch(removeSuggestion());
+
+ $.ajax({
+ type: "DELETE",
+ dataType: "json",
+ url: "suggestion/"+suggestion.SuggestionId+"/",
+ success: function(data, status, jqXHR) {
+ var e = new Error();
+ e.fromJSON(data);
+ if (e.isError()) {
+ ErrorActions.serverError(e);
+ } else {
+ dispatch(suggestionRemoved(suggestion.SuggestionId));
+ }
+ },
+ error: function(jqXHR, status, error) {
+ ErrorActions.ajaxError(e);
+ }
+ });
+ };
+}
+
module.exports = {
fetchAll: fetchAll,
create: create,
+ remove: remove,
fetchPopular: fetchPopular
};
diff --git a/js/components/NewSuggestion.js b/js/components/NewSuggestion.js
index c9b8ddf..b4a37f6 100644
--- a/js/components/NewSuggestion.js
+++ b/js/components/NewSuggestion.js
@@ -4,6 +4,7 @@ var ReactBootstrap = require('react-bootstrap');
var Grid = ReactBootstrap.Grid;
var Row = ReactBootstrap.Row;
var Col = ReactBootstrap.Col;
+var FormGroup = ReactBootstrap.FormGroup;
var ControlLabel = ReactBootstrap.ControlLabel;
var Button = ReactBootstrap.Button;
@@ -102,31 +103,38 @@ module.exports = React.createClass({
var attendeeList = this.getAttendeeList();
var buttonDisabled = this.state.attendee == null || !this.state.suggestion;
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Add Suggestion/Veto To:
+
+
+
+
+
+
+ Suggested By:
+
+
+
+
+
+
+
+
+
+
+
);
}
});
diff --git a/js/components/RecordLunch.js b/js/components/RecordLunch.js
index 8c9525a..05c1b77 100644
--- a/js/components/RecordLunch.js
+++ b/js/components/RecordLunch.js
@@ -6,6 +6,8 @@ var ControlLabel = ReactBootstrap.ControlLabel;
var Grid = ReactBootstrap.Grid;
var Row = ReactBootstrap.Row;
var Col = ReactBootstrap.Col;
+var Panel = ReactBootstrap.Panel;
+var Button = ReactBootstrap.Button;
var Multiselect = require('react-widgets').Multiselect;
@@ -55,22 +57,47 @@ module.exports = React.createClass({
var attendeeList = this.getAttendeeList();
var suggestionIds = Object.keys(this.props.suggestions);
- suggestionIds.sort(function(a, b){return parseInt(a, 10) - parseInt(b, 10);});
+ suggestionIds.sort(function(a, b){return parseInt(b, 10) - parseInt(a, 10);});
var suggestions = [];
for (var i in suggestionIds) {
var suggestion = this.props.suggestions[suggestionIds[i]];
- suggestions.push((
-
- {this.props.attendees[suggestion.AttendeeId].Name}
-
- {suggestion.RestaurantName}
-
- ));
+ var suggestorName = "Unknown";
+ if (this.props.attendees.hasOwnProperty(suggestion.AttendeeId))
+ suggestorName = this.props.attendees[suggestion.AttendeeId].Name;
+
+ if (i == 0) {
+ var self = this;
+ var popFunction = function(){
+ return function(){self.props.removeSuggestion(suggestion);};
+ }();
+ suggestions.push((
+
+ Current Suggestion:
+
+ {suggestion.RestaurantName} (by {suggestorName})
+
+
+
+
+
+ ));
+ } else {
+ suggestions.push((
+
+ {(suggestionIds.length - i).toString() + "."}
+
+ {suggestion.RestaurantName} (by {suggestorName})
+
+
+
+
+ ));
+ }
}
return (
);
}
diff --git a/js/constants/SuggestionConstants.js b/js/constants/SuggestionConstants.js
index fc96f40..002f84c 100644
--- a/js/constants/SuggestionConstants.js
+++ b/js/constants/SuggestionConstants.js
@@ -5,6 +5,8 @@ module.exports = keyMirror({
SUGGESTIONS_FETCHED: null,
CREATE_SUGGESTION: null,
SUGGESTION_CREATED: null,
+ REMOVE_SUGGESTION: null,
+ SUGGESTION_REMOVED: null,
FETCH_POPULAR_SUGGESTIONS: null,
POPULAR_SUGGESTIONS_FETCHED: null,
});
diff --git a/js/containers/RecordLunchContainer.js b/js/containers/RecordLunchContainer.js
index 755ff39..3914348 100644
--- a/js/containers/RecordLunchContainer.js
+++ b/js/containers/RecordLunchContainer.js
@@ -19,7 +19,8 @@ function mapDispatchToProps(dispatch) {
return {
createAttendee: function(attendee) {dispatch(AttendeeActions.create(attendee))},
removeAttendee: function(attendee) {dispatch(AttendeeActions.remove(attendee))},
- createSuggestion: function(suggestion) {dispatch(SuggestionActions.create(suggestion))}
+ createSuggestion: function(suggestion) {dispatch(SuggestionActions.create(suggestion))},
+ removeSuggestion: function(suggestion) {dispatch(SuggestionActions.remove(suggestion))}
}
}
diff --git a/js/reducers/SuggestionReducer.js b/js/reducers/SuggestionReducer.js
index 38731ed..e6af852 100644
--- a/js/reducers/SuggestionReducer.js
+++ b/js/reducers/SuggestionReducer.js
@@ -18,6 +18,10 @@ module.exports = function(state = {}, action) {
[suggestion.SuggestionId]: suggestion
});
return suggestions;
+ case SuggestionConstants.SUGGESTION_REMOVED:
+ var suggestions = assign({}, state);
+ delete suggestions[action.suggestionId];
+ return suggestions;
case UserConstants.USER_LOGGEDOUT:
return {};
default:
diff --git a/static/index.html b/static/index.html
index 078bb96..feb261d 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1,6 +1,8 @@
-
+
+
Lunch
+
diff --git a/static/stylesheet.css b/static/stylesheet.css
index b9b9d40..780fbb3 100644
--- a/static/stylesheet.css
+++ b/static/stylesheet.css
@@ -6,7 +6,7 @@ div#content {
width: 95%;
height: 100%;
min-width: 20em;
- max-width: 100em;
+ max-width: 50em;
margin: auto;
}
@@ -16,3 +16,8 @@ div.tab-content {
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
}
+
+/* Make panels inside row line up with other rows by eliminating padding */
+div.row div.panel div.panel-body {
+ padding: 15px 0 15px 0
+}
diff --git a/suggestions.go b/suggestions.go
index 418880d..e4ca90b 100644
--- a/suggestions.go
+++ b/suggestions.go
@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
+ "errors"
"gopkg.in/gorp.v1"
"log"
"net/http"
@@ -52,6 +53,11 @@ func (aeu SuggestionExistsError) Error() string {
return "Suggestion exists"
}
+type SuggestionNotLastError struct{}
+
+func (snle SuggestionNotLastError) Error() string {
+ return "Suggestion not last on the stack"
+}
func (s *PopularSuggestion) Write(w http.ResponseWriter) error {
enc := json.NewEncoder(w)
return enc.Encode(s)
@@ -82,6 +88,26 @@ func GetSuggestions(userid int64, date time.Time) (*[]*Suggestion, error) {
return &suggestions, nil
}
+func GetSuggestion(suggestionid int64, userid 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)
+ if err != nil {
+ return nil, err
+ }
+ return &s, nil
+}
+
+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)
+ if err != nil {
+ return nil, err
+ }
+ return &suggestions, nil
+}
+
func GetPopularSuggestions() (*[]*PopularSuggestion, error) {
var suggestions []*Suggestion
suggestionMap := make(map[string]int64)
@@ -135,6 +161,42 @@ func InsertSuggestion(s *Suggestion) error {
return nil
}
+func DeleteSuggestion(s *Suggestion) error {
+ transaction, err := DB.Begin()
+ if err != nil {
+ return err
+ }
+
+ // Ensure suggestion hasn't been vetoed
+ suggestions, err := VetoedBy(transaction, s)
+ if err != nil {
+ transaction.Rollback()
+ return err
+ }
+ if len(*suggestions) > 0 {
+ transaction.Rollback()
+ return SuggestionNotLastError{}
+ }
+
+ count, err := transaction.Delete(s)
+ if err != nil {
+ transaction.Rollback()
+ return err
+ }
+ if count != 1 {
+ transaction.Rollback()
+ return errors.New("Was going to delete more than one suggestion")
+ }
+
+ err = transaction.Commit()
+ if err != nil {
+ transaction.Rollback()
+ return err
+ }
+
+ return nil
+}
+
func SuggestionHandler(w http.ResponseWriter, r *http.Request) {
user, err := GetUserFromSession(r)
if err != nil {
@@ -196,8 +258,33 @@ func SuggestionHandler(w http.ResponseWriter, r *http.Request) {
log.Print(err)
return
}
+ } else if r.Method == "DELETE" {
+ suggestionid, err := GetURLID(r.URL.Path)
+ if err != nil {
+ WriteError(w, 3 /* Invalid Request */)
+ return
+ }
+
+ suggestion, err := GetSuggestion(suggestionid, user.UserId, today)
+ if err != nil {
+ WriteError(w, 3 /*Invalid Request*/)
+ return
+ }
+
+ err = DeleteSuggestion(suggestion)
+ if err != nil {
+ if _, ok := err.(SuggestionNotLastError); ok {
+ WriteError(w, 8 /*Suggestion Not Last*/)
+ } else {
+ WriteError(w, 999 /*Internal Error*/)
+ log.Print(err)
+ }
+ return
+ }
+
+ WriteSuccess(w)
} else {
- /* No PUT or DELETE */
+ /* No PUT */
WriteError(w, 3 /*Invalid Request*/)
return
}