Skip to content
This repository was archived by the owner on Mar 3, 2022. It is now read-only.

Commit 391165c

Browse files
Sameer Naikprydonius
Sameer Naik
authored andcommitted
comments: delete endpoint (#14)
1 parent 31b9193 commit 391165c

File tree

3 files changed

+175
-13
lines changed

3 files changed

+175
-13
lines changed

handler.go

+61-8
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ import (
3434
"gopkg.in/mgo.v2/bson"
3535
)
3636

37+
// Params a key-value map of path params
38+
type Params map[string]string
39+
40+
// WithParams can be used to wrap handlers to take an extra arg for path params
41+
type WithParams func(http.ResponseWriter, *http.Request, Params)
42+
43+
func (h WithParams) ServeHTTP(w http.ResponseWriter, req *http.Request) {
44+
vars := mux.Vars(req)
45+
h(w, req, vars)
46+
}
47+
3748
const itemCollection = "items"
3849

3950
type item struct {
@@ -152,14 +163,12 @@ func UpdateStar(w http.ResponseWriter, req *http.Request) {
152163
}
153164

154165
// GetComments returns a list of comments
155-
func GetComments(w http.ResponseWriter, req *http.Request) {
166+
func GetComments(w http.ResponseWriter, req *http.Request, params Params) {
156167
db, closer := dbSession.DB()
157168
defer closer()
158169

159-
vars := mux.Vars(req)
160-
itemId := vars["repo"] + "/" + vars["chartName"]
161-
162170
var it item
171+
itemId := params["repo"] + "/" + params["chartName"]
163172
if err := db.C(itemCollection).FindId(itemId).One(&it); err != nil {
164173
response.NewDataResponse([]int64{}).Write(w)
165174
return
@@ -174,13 +183,10 @@ func GetComments(w http.ResponseWriter, req *http.Request) {
174183
}
175184

176185
// CreateComment creates a comment and appends the comment to the item.Comments array
177-
func CreateComment(w http.ResponseWriter, req *http.Request) {
186+
func CreateComment(w http.ResponseWriter, req *http.Request, params Params) {
178187
db, closer := dbSession.DB()
179188
defer closer()
180189

181-
vars := mux.Vars(req)
182-
itemId := vars["repo"] + "/" + vars["chartName"]
183-
184190
currentUser, err := getCurrentUser(req)
185191
if err != nil {
186192
response.NewErrorResponse(http.StatusUnauthorized, "unauthorized").Write(w)
@@ -205,6 +211,7 @@ func CreateComment(w http.ResponseWriter, req *http.Request) {
205211
cm.Author = currentUser
206212

207213
var it item
214+
itemId := params["repo"] + "/" + params["chartName"]
208215
if err = db.C(itemCollection).FindId(itemId).One(&it); err != nil {
209216
// Create the item if inexistant
210217
it.Type = "chart"
@@ -232,6 +239,52 @@ func CreateComment(w http.ResponseWriter, req *http.Request) {
232239
response.NewDataResponse(cm).WithCode(http.StatusCreated).Write(w)
233240
}
234241

242+
// DeleteComment delete's an existing comment
243+
func DeleteComment(w http.ResponseWriter, req *http.Request, params Params) {
244+
db, closer := dbSession.DB()
245+
defer closer()
246+
247+
itemId := params["repo"] + "/" + params["chartName"]
248+
commentId := bson.ObjectIdHex(params["commentId"])
249+
250+
currentUser, err := getCurrentUser(req)
251+
if err != nil {
252+
response.NewErrorResponse(http.StatusUnauthorized, "unauthorized").Write(w)
253+
return
254+
}
255+
256+
var it item
257+
if err := db.C(itemCollection).FindId(itemId).One(&it); err != nil {
258+
response.NewErrorResponse(http.StatusNotFound, "comment not found").Write(w)
259+
return
260+
}
261+
262+
var cm comment
263+
for _, c := range it.Comments {
264+
if commentId == c.ID {
265+
cm = c
266+
break
267+
}
268+
}
269+
270+
if cm == (comment{}) {
271+
response.NewErrorResponse(http.StatusNotFound, "comment not found").Write(w)
272+
return
273+
}
274+
275+
// Users can only delete their own comments
276+
if cm.Author.ID != currentUser.ID {
277+
response.NewErrorResponse(http.StatusUnauthorized, "not authorized to delete this comment").Write(w)
278+
return
279+
}
280+
281+
if err = db.C(itemCollection).UpdateId(it.ID, bson.M{"$pull": bson.M{"comments": cm}}); err != nil {
282+
response.NewErrorResponse(http.StatusInternalServerError, "internal server error").Write(w)
283+
return
284+
}
285+
response.NewDataResponse(cm).WithCode(http.StatusAccepted).Write(w)
286+
}
287+
235288
type userClaims struct {
236289
*user
237290
Email string

handler_test.go

+111-3
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,11 @@ func TestGetComments(t *testing.T) {
217217
})
218218
w := httptest.NewRecorder()
219219
req := httptest.NewRequest("GET", "/v1/comments/stable/wordpress", nil)
220-
GetComments(w, req)
220+
params := Params{
221+
"repo": "stable",
222+
"chartName": "wordpress",
223+
}
224+
GetComments(w, req, params)
221225
assert.Equal(t, http.StatusOK, w.Code)
222226
var b body
223227
json.NewDecoder(w.Body).Decode(&b)
@@ -263,7 +267,11 @@ func TestCreateComment(t *testing.T) {
263267
}
264268
w := httptest.NewRecorder()
265269
req := httptest.NewRequest("POST", "/v1/comments/stable/wordpress", bytes.NewBuffer([]byte(tt.requestBody)))
266-
CreateComment(w, req)
270+
params := Params{
271+
"repo": "stable",
272+
"chartName": "wordpress",
273+
}
274+
CreateComment(w, req, params)
267275
assert.Equal(t, tt.wantCode, w.Code)
268276
})
269277
}
@@ -274,7 +282,107 @@ func TestCreateCommentUnauthorized(t *testing.T) {
274282
dbSession = testutil.NewMockSession(&m)
275283
w := httptest.NewRecorder()
276284
req := httptest.NewRequest("POST", "/v1/comments/stable/wordpress", nil)
277-
CreateComment(w, req)
285+
params := Params{
286+
"repo": "stable",
287+
"chartName": "wordpress",
288+
}
289+
CreateComment(w, req, params)
290+
assert.Equal(t, http.StatusUnauthorized, w.Code)
291+
}
292+
293+
func TestDeleteComment(t *testing.T) {
294+
var m mock.Mock
295+
dbSession = testutil.NewMockSession(&m)
296+
297+
currentUser := &user{ID: bson.NewObjectId(), Name: "Rick Sanchez", Email: "[email protected]"}
298+
oldGetCurrentUser := getCurrentUser
299+
getCurrentUser = func(_ *http.Request) (*user, error) { return currentUser, nil }
300+
defer func() { getCurrentUser = oldGetCurrentUser }()
301+
302+
commentId := getNewObjectID()
303+
oldGetNewObjectID := getNewObjectID
304+
getNewObjectID = func() bson.ObjectId { return commentId }
305+
defer func() { getNewObjectID = oldGetNewObjectID }()
306+
307+
commentTimestamp := getTimestamp()
308+
oldGetTimestamp := getTimestamp
309+
getTimestamp = func() time.Time { return commentTimestamp }
310+
defer func() { getTimestamp = oldGetTimestamp }()
311+
312+
m.On("One", &item{}).Return(nil).Run(func(args mock.Arguments) {
313+
*args.Get(0).(*item) = item{ID: "stable/wordpress", Type: "chart", Comments: []comment{
314+
comment{ID: bson.NewObjectId(), Text: "First comment", CreatedAt: getTimestamp(), Author: currentUser},
315+
comment{ID: commentId, Text: "Second comment", CreatedAt: commentTimestamp, Author: currentUser},
316+
}}
317+
})
318+
319+
tests := []struct {
320+
name string
321+
commentId string
322+
wantCode int
323+
}{
324+
{"does not exist", "5a0e9183833def3853088836", http.StatusNotFound},
325+
{"exists", commentId.Hex(), http.StatusAccepted},
326+
}
327+
328+
for _, tt := range tests {
329+
t.Run(tt.name, func(t *testing.T) {
330+
if tt.wantCode == http.StatusAccepted {
331+
m.On("UpdateId", "stable/wordpress", bson.M{"$pull": bson.M{"comments": comment{ID: commentId, Text: "Second comment", Author: currentUser, CreatedAt: commentTimestamp}}})
332+
}
333+
334+
w := httptest.NewRecorder()
335+
req := httptest.NewRequest("DELETE", "/v1/comments/stable/wordpress"+tt.commentId, nil)
336+
params := Params{
337+
"repo": "stable",
338+
"chartName": "wordpress",
339+
"commentId": tt.commentId,
340+
}
341+
DeleteComment(w, req, params)
342+
assert.Equal(t, tt.wantCode, w.Code)
343+
})
344+
}
345+
}
346+
347+
func TestDeleteCommentUnauthorized(t *testing.T) {
348+
var m mock.Mock
349+
dbSession = testutil.NewMockSession(&m)
350+
w := httptest.NewRecorder()
351+
req := httptest.NewRequest("DELETE", "/v1/comments/stable/wordpress/5a0e9183833def3853088836", nil)
352+
params := Params{
353+
"repo": "stable",
354+
"chartName": "wordpress",
355+
"commentId": "5a0e9183833def3853088836",
356+
}
357+
DeleteComment(w, req, params)
358+
assert.Equal(t, http.StatusUnauthorized, w.Code)
359+
}
360+
361+
func TestDeleteCommentCannotDeleteOtherUsersComments(t *testing.T) {
362+
var m mock.Mock
363+
dbSession = testutil.NewMockSession(&m)
364+
w := httptest.NewRecorder()
365+
366+
currentUser := &user{ID: bson.NewObjectId(), Name: "Rick Sanchez", Email: "[email protected]"}
367+
oldGetCurrentUser := getCurrentUser
368+
getCurrentUser = func(_ *http.Request) (*user, error) { return currentUser, nil }
369+
defer func() { getCurrentUser = oldGetCurrentUser }()
370+
371+
commentId := getNewObjectID()
372+
m.On("One", &item{}).Return(nil).Run(func(args mock.Arguments) {
373+
*args.Get(0).(*item) = item{ID: "stable/wordpress", Type: "chart", Comments: []comment{
374+
comment{ID: bson.NewObjectId(), Author: &user{ID: bson.NewObjectId()}},
375+
comment{ID: commentId, Author: &user{ID: bson.NewObjectId()}},
376+
}}
377+
})
378+
379+
req := httptest.NewRequest("DELETE", "/v1/comments/stable/wordpress/"+commentId.Hex(), nil)
380+
params := Params{
381+
"repo": "stable",
382+
"chartName": "wordpress",
383+
"commentId": commentId.Hex(),
384+
}
385+
DeleteComment(w, req, params)
278386
assert.Equal(t, http.StatusUnauthorized, w.Code)
279387
}
280388

main.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ func main() {
5353
apiv1 := r.PathPrefix("/v1").Subrouter()
5454
apiv1.Methods("GET").Path("/stars").HandlerFunc(GetStars)
5555
apiv1.Methods("PUT").Path("/stars").HandlerFunc(UpdateStar)
56-
apiv1.Methods("GET").Path("/comments/{repo}/{chartName}").HandlerFunc(GetComments)
57-
apiv1.Methods("POST").Path("/comments/{repo}/{chartName}").HandlerFunc(CreateComment)
56+
apiv1.Methods("GET").Path("/comments/{repo}/{chartName}").Handler(WithParams(GetComments))
57+
apiv1.Methods("POST").Path("/comments/{repo}/{chartName}").Handler(WithParams(CreateComment))
58+
apiv1.Methods("DELETE").Path("/comments/{repo}/{chartName}/{commentId}").Handler(WithParams(DeleteComment))
5859

5960
n := negroni.Classic()
6061
n.UseHandler(r)

0 commit comments

Comments
 (0)