I am writing an HTTP server in Go that interacts with a MongoDB database. The GET, POST and PUT routes all work perfectly as expected - PUT is the most similar to DELETE in its content and works - whereas DELETE doesn't.
My delete route when searching for the document will return mongo.ErrNoDocuments
even though the UUID definitely exists and the other routes (PUT and GET) work perfectly with the same UUID.
I am still new to Go so I am sure I am missing something. Any help is greatly appreciated
Here is the code:
DELETE route
/* DELETE /{uuid}
r.Body:
"accessKey" -> required
Deletes an existing Paste in the MongoDB database
*/
func (s *Server) deletePaste() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() {
log.Printf("%s %s [%v]\n",
r.Method,
r.URL.Path,
time.Since(start),
)
}()
uuidStr, _ := mux.Vars(r)["uuid"]
fmt.Println(uuidStr)
body := struct {
AccessKey string `json:"accessKey,omitempty"`
}{}
if err := decodeJSONBody(w, r, &body); err != nil {
var mr *badRequest
if errors.As(err, &mr) {
http.Error(w, mr.msg, mr.status)
} else {
log.Print(err.Error())
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return
}
coll := s.Client.Database("paste").Collection("files")
// Check document exists and accessKey is the same
var result bson.M
filter := bson.M{"uuid": uuidStr}
project := bson.M{
"_id": 0,
"accessKey": 1,
}
err := coll.FindOne(
context.TODO(),
filter,
options.FindOne().SetProjection(project),
).Decode(&result)
if err != nil {
if err == mongo.ErrNoDocuments {
http.Error(w, "No document found with that UUID", http.StatusBadRequest)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Check the sender can actually edit the paste
if body.AccessKey != result["accessKey"] {
http.Error(w, "Invalid access key", http.StatusUnauthorized)
return
}
// Delete matching document
//opts := options.Delete().SetHint(bson.D{{Key: "uuid", Value: 1}})
res, err := coll.DeleteOne(context.TODO(), filter, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if res.DeletedCount == 0 {
http.Error(w, "Error matching and deleting document", http.StatusInternalServerError)
return
}
response := make(map[string]string)
response["info"] = "Document deleted"
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
PUT route
/* PUT /{uuid}
r.Body:
"accessKey" -> required
"content" -> optional
"name" -> optional
"filetype" -> optional
"expiresIn" -> optional
^ At least one of the 4 optional fields must be updated
Updates an existing Paste in the MongoDB database and returns a JSON document
{
uuid: UUID,
name: String,
content: String,
filetype: String,
accessKey: String,
expiresAt: Date
}
*/
func (s *Server) updatePaste() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() {
log.Printf("%s %s [%v]\n",
r.Method,
r.URL.Path,
time.Since(start),
)
}()
uuidStr, _ := mux.Vars(r)["uuid"]
var paste Paste
var body PasteBody
if err := decodeJSONBody(w, r, &body); err != nil {
var mr *badRequest
if errors.As(err, &mr) {
http.Error(w, mr.msg, mr.status)
} else {
log.Print(err.Error())
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return
}
if err := paste.EditPaste(&body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
doc, err := toBsonDoc(&paste)
if err != nil {
log.Println(err)
http.Error(w, "Error converting request body to BSON document", http.StatusInternalServerError)
}
coll := s.Client.Database("pastes").Collection("files")
// Check document and provided accessKey match
var result bson.M
filter := bson.M{"uuid": uuidStr}
project := bson.M{
"_id": 0,
"accessKey": 1,
}
err = coll.FindOne(
context.TODO(),
filter,
options.FindOne().SetProjection(project),
).Decode(&result)
if err != nil {
if err == mongo.ErrNoDocuments {
http.Error(w, "No document found with that UUID", http.StatusBadRequest)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Check the sender can actually edit the paste
if paste.AccessKey != result["accessKey"] {
http.Error(w, "Invalid access key", http.StatusUnauthorized)
return
}
// Update document
filter = bson.M{"uuid": uuidStr}
update := bson.M{"$set": doc}
res, err := coll.UpdateOne(context.TODO(), filter, update)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if res.MatchedCount == 0 || res.ModifiedCount == 0 {
http.Error(w, "Error matching and updating document", http.StatusInternalServerError)
return
}
response := make(map[string]string)
response["uuid"] = uuidStr
response["expiresAt"] = paste.ExpiresAt.Time().String()
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
GET route
/* GET /{uuid}
Returns the Paste from the MongoDB database with the matching UUID in JSON
{
uuid: UUID,
name: String,
content: String,
filetype: String,
accessKey: String,
expires: Date
}
*/
func (s *Server) getPaste() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() {
log.Printf("%s %s [%v]\n",
r.Method,
r.URL.Path,
time.Since(start),
)
}()
uuidStr, _ := mux.Vars(r)["uuid"]
coll := s.Client.Database("pastes").Collection("files")
var result bson.M
filter := bson.M{"uuid": uuidStr}
project := bson.M{
"_id": 0,
"accessKey": 0,
"uuid": 0,
}
err := coll.FindOne(
context.TODO(),
filter,
options.FindOne().SetProjection(project),
).Decode(&result)
if err != nil {
if err == mongo.ErrNoDocuments {
http.Error(w, "No document found with that UUID", http.StatusBadRequest)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Convert to long date format
result["expiresAt"] = primitive.DateTime(result["expiresAt"].(primitive.DateTime)).Time().String()
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(result); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
EDIT:
@icza spotted my typo in using "paste"
not "pastes"
as the database name when finding the collection. Implementing global variables to hold these strings solved this from happening again.