diff --git a/backend/app/rest/api/admin.go b/backend/app/rest/api/admin.go index 53be9e75..6f634e29 100644 --- a/backend/app/rest/api/admin.go +++ b/backend/app/rest/api/admin.go @@ -55,7 +55,7 @@ func (a *admin) deleteCommentCtrl(w http.ResponseWriter, r *http.Request) { err := a.dataService.Delete(locator, id, store.SoftDelete) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't delete comment") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't delete comment", rest.ErrInternal) return } a.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.SiteID, locator.URL, lastCommentsScope)) @@ -71,7 +71,7 @@ func (a *admin) deleteUserCtrl(w http.ResponseWriter, r *http.Request) { log.Printf("[INFO] delete all user comments for %s, site %s", userID, siteID) if err := a.dataService.DeleteUser(siteID, userID); err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't delete user") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't delete user", rest.ErrInternal) return } a.cache.Flush(cache.Flusher(siteID).Scopes(userID, siteID, lastCommentsScope)) @@ -88,7 +88,7 @@ func (a *admin) getUserInfoCtrl(w http.ResponseWriter, r *http.Request) { ucomments, err := a.dataService.User(siteID, userID, 1, 0) if err != nil || len(ucomments) == 0 { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get user info") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get user info", rest.ErrInternal) return } render.Status(r, http.StatusOK) @@ -103,7 +103,7 @@ func (a *admin) deleteMeRequestCtrl(w http.ResponseWriter, r *http.Request) { claims, err := a.authenticator.TokenService().Parse(token) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't process token") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't process token", rest.ErrActionRejected) return } @@ -111,19 +111,19 @@ func (a *admin) deleteMeRequestCtrl(w http.ResponseWriter, r *http.Request) { // deleteme set by deleteMeCtrl, this check just to make sure we not trying to delete with leaked token if !claims.User.BoolAttr("delete_me") { - rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("forbidden"), "can't use provided token") + rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("forbidden"), "can't use provided token", rest.ErrNoAccess) return } if err := a.dataService.DeleteUser(claims.Audience, claims.User.ID); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete user") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete user", rest.ErrNoAccess) return } if claims.User.Picture != "" && a.authenticator.AvatarProxy() != nil { avatartStore := a.authenticator.AvatarProxy().Store if err := avatartStore.Remove(path.Base(claims.User.Picture)); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete user's avatar") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete user's avatar", rest.ErrInternal) return } } @@ -147,7 +147,7 @@ func (a *admin) setBlockCtrl(w http.ResponseWriter, r *http.Request) { } if err := a.dataService.SetBlock(siteID, userID, blockStatus, ttl); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set blocking status") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set blocking status", rest.ErrActionRejected) return } a.cache.Flush(cache.Flusher(siteID).Scopes(userID, siteID, lastCommentsScope)) @@ -159,7 +159,7 @@ func (a *admin) blockedUsersCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") users, err := a.dataService.Blocked(siteID) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get blocked users") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get blocked users", rest.ErrSiteNotFound) return } render.JSON(w, r, users) @@ -178,13 +178,14 @@ func (a *admin) setReadOnlyCtrl(w http.ResponseWriter, r *http.Request) { // don't allow to reset ro for posts turned to ro by ReadOnlyAge if !roStatus { if info, e := a.dataService.Info(locator, a.readOnlyAge); e == nil && isRoByAge(info) { - rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "read-only due the age") + rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), + "read-only due the age", rest.ErrActionRejected) return } } if err := a.dataService.SetReadOnly(locator, roStatus); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set readonly status") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set readonly status", rest.ErrPostNotFound) return } a.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.URL, locator.SiteID)) @@ -198,7 +199,7 @@ func (a *admin) setTitleCtrl(w http.ResponseWriter, r *http.Request) { c, err := a.dataService.SetTitle(locator, id) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't set title") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't set title", rest.ErrInternal) return } log.Printf("[INFO] set comment's title %s to %q", id, c.PostTitle) @@ -215,7 +216,7 @@ func (a *admin) setVerifyCtrl(w http.ResponseWriter, r *http.Request) { verifyStatus := r.URL.Query().Get("verified") == "1" if err := a.dataService.SetVerified(siteID, userID, verifyStatus); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set verify status") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set verify status", rest.ErrActionRejected) return } a.cache.Flush(cache.Flusher(siteID).Scopes(siteID, userID)) @@ -230,7 +231,7 @@ func (a *admin) setPinCtrl(w http.ResponseWriter, r *http.Request) { pinStatus := r.URL.Query().Get("pin") == "1" if err := a.dataService.SetPin(locator, commentID, pinStatus); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set pin status") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set pin status", rest.ErrActionRejected) return } a.cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.URL)) diff --git a/backend/app/rest/api/migrator.go b/backend/app/rest/api/migrator.go index b909e531..64ae6597 100644 --- a/backend/app/rest/api/migrator.go +++ b/backend/app/rest/api/migrator.go @@ -56,13 +56,14 @@ func (m *Migrator) importCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") if m.isBusy(siteID) { - rest.SendErrorJSON(w, r, http.StatusConflict, errors.New("already running"), "import rejected") + rest.SendErrorJSON(w, r, http.StatusConflict, errors.New("already running"), + "import rejected", rest.ErrActionRejected) return } tmpfile, err := m.saveTemp(r.Body) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't save request to temp file") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't save request to temp file", rest.ErrInternal) return } @@ -78,25 +79,26 @@ func (m *Migrator) importFormCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") if m.isBusy(siteID) { - rest.SendErrorJSON(w, r, http.StatusConflict, errors.New("already running"), "import rejected") + rest.SendErrorJSON(w, r, http.StatusConflict, errors.New("already running"), + "import rejected", rest.ErrActionRejected) return } if err := r.ParseMultipartForm(20 * 1024 * 1024); err != nil { // 20M max memory, if bigger will make a file - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't parse multipart form") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't parse multipart form", rest.ErrDecode) return } file, _, err := r.FormFile("file") if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't get import from the request") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't get import file from the request", rest.ErrInternal) return } defer func() { _ = file.Close() }() tmpfile, err := m.saveTemp(file) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't save request to temp file") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't save request to temp file", rest.ErrInternal) return } @@ -155,7 +157,7 @@ func (m *Migrator) exportCtrl(w http.ResponseWriter, r *http.Request) { } if _, err := m.NativeExporter.Export(writer, siteID); err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "export failed") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "export failed", rest.ErrInternal) return } } diff --git a/backend/app/rest/api/rest_private.go b/backend/app/rest/api/rest_private.go index 132b60ba..add6fd9d 100644 --- a/backend/app/rest/api/rest_private.go +++ b/backend/app/rest/api/rest_private.go @@ -28,7 +28,7 @@ func (s *Rest) createCommentCtrl(w http.ResponseWriter, r *http.Request) { comment := store.Comment{} if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, hardBodyLimit), &comment); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't bind comment") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't bind comment", rest.ErrDecode) return } @@ -40,36 +40,36 @@ func (s *Rest) createCommentCtrl(w http.ResponseWriter, r *http.Request) { comment.Orig = comment.Text // original comment text, prior to md render if err := s.DataService.ValidateComment(&comment); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "invalid comment") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "invalid comment", rest.ErrCommentValidation) return } comment = s.CommentFormatter.Format(comment) // check if user blocked if s.adminService.checkBlocked(comment.Locator.SiteID, comment.User) { - rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "user blocked") + rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "user blocked", rest.ErrUserBlocked) return } if s.isReadOnly(comment.Locator) { - rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "old post, read-only") + rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "old post, read-only", rest.ErrReadOnly) return } id, err := s.DataService.Create(comment) if err == service.ErrRestrictedWordsFound { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "invalid comment") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "invalid comment", rest.ErrCommentValidation) return } if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't save comment") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't save comment", rest.ErrInternal) return } // DataService modifies comment finalComment, err := s.DataService.Get(comment.Locator, id) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't load created comment") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't load created comment", rest.ErrInternal) return } s.Cache.Flush(cache.Flusher(comment.Locator.SiteID). @@ -95,7 +95,7 @@ func (s *Rest) updateCommentCtrl(w http.ResponseWriter, r *http.Request) { }{} if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, hardBodyLimit), &edit); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't bind comment") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't bind comment", rest.ErrDecode) return } @@ -108,12 +108,13 @@ func (s *Rest) updateCommentCtrl(w http.ResponseWriter, r *http.Request) { var currComment store.Comment var err error if currComment, err = s.DataService.Get(locator, id); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't find comment") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't find comment", rest.ErrCommentNotFound) return } if currComment.User.ID != user.ID { - rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "can not edit comments for other users") + rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), + "can not edit comments for other users", rest.ErrNoAccess) return } @@ -126,11 +127,18 @@ func (s *Rest) updateCommentCtrl(w http.ResponseWriter, r *http.Request) { res, err := s.DataService.EditComment(locator, id, editReq) if err == service.ErrRestrictedWordsFound { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "invalid comment") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "invalid comment", rest.ErrCommentValidation) return } if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't update comment") + code := rest.ErrCommentRejected + switch { + case strings.HasPrefix(err.Error(), "too late to edit"): + code = rest.ErrCommentEditExpired + case strings.HasPrefix(err.Error(), "parent comment with reply can't be edited"): + code = rest.ErrCommentEditChanged + } + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't update comment", code) return } @@ -158,19 +166,30 @@ func (s *Rest) voteCtrl(w http.ResponseWriter, r *http.Request) { vote := r.URL.Query().Get("vote") == "1" if s.isReadOnly(locator) { - rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "old post, read-only") + rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "old post, read-only", rest.ErrReadOnly) return } // check if user blocked if s.adminService.checkBlocked(locator.SiteID, user) { - rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "user blocked") + rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("rejected"), "user blocked", rest.ErrUserBlocked) return } comment, err := s.DataService.Vote(locator, id, user.ID, vote) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't vote for comment") + code := rest.ErrVoteRejected + switch { + case strings.Contains(err.Error(), "can not vote for his own comment"): + code = rest.ErrVoteSelf + case strings.Contains(err.Error(), "already voted for"): + code = rest.ErrVoteDbl + case strings.Contains(err.Error(), "maximum number of votes exceeded for comment"): + code = rest.ErrVoteMax + case strings.Contains(err.Error(), "minimal score reached for comment"): + code = rest.ErrVoteMinScore + } + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't vote for comment", code) return } s.Cache.Flush(cache.Flusher(locator.SiteID).Scopes(locator.URL, comment.User.ID)) @@ -183,7 +202,7 @@ func (s *Rest) userAllDataCtrl(w http.ResponseWriter, r *http.Request) { user := rest.MustGetUserInfo(r) userB, err := json.Marshal(&user) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't marshal user info") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't marshal user info", rest.ErrInternal) return } @@ -211,12 +230,12 @@ func (s *Rest) userAllDataCtrl(w http.ResponseWriter, r *http.Request) { for i := 0; i < 100; i++ { comments, err := s.DataService.User(siteID, user.ID, 100, i*100) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't get user comments") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't get user comments", rest.ErrInternal) return } b, err := json.Marshal(comments) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't marshal user comments") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't marshal user comments", rest.ErrInternal) return } @@ -228,7 +247,7 @@ func (s *Rest) userAllDataCtrl(w http.ResponseWriter, r *http.Request) { merr = multierror.Append(merr, write([]byte(`}`))) if merr.(*multierror.Error).ErrorOrNil() != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, merr, "can't write user info") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, merr, "can't write user info", rest.ErrInternal) return } @@ -258,7 +277,7 @@ func (s *Rest) deleteMeCtrl(w http.ResponseWriter, r *http.Request) { tokenStr, err := s.Authenticator.TokenService().Token(claims) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't make token") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't make token", rest.ErrInternal) return } diff --git a/backend/app/rest/api/rest_private_test.go b/backend/app/rest/api/rest_private_test.go index 5cdf3556..2f14461b 100644 --- a/backend/app/rest/api/rest_private_test.go +++ b/backend/app/rest/api/rest_private_test.go @@ -289,7 +289,7 @@ func TestRest_UpdateNotOwner(t *testing.T) { body, err := ioutil.ReadAll(b.Body) assert.Nil(t, err) assert.Equal(t, 403, b.StatusCode, string(body), "update from non-owner") - assert.Equal(t, `{"details":"can not edit comments for other users","error":"rejected"}`+"\n", string(body)) + assert.Equal(t, `{"code":3,"details":"can not edit comments for other users","error":"rejected"}`+"\n", string(body)) client = http.Client{} req, err = http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id1+ diff --git a/backend/app/rest/api/rest_public.go b/backend/app/rest/api/rest_public.go index e91b5fbd..99e60a9c 100644 --- a/backend/app/rest/api/rest_public.go +++ b/backend/app/rest/api/rest_public.go @@ -56,7 +56,7 @@ func (s *Rest) findCommentsCtrl(w http.ResponseWriter, r *http.Request) { }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't find comments") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't find comments", rest.ErrCommentNotFound) return } @@ -69,19 +69,19 @@ func (s *Rest) findCommentsCtrl(w http.ResponseWriter, r *http.Request) { func (s *Rest) previewCommentCtrl(w http.ResponseWriter, r *http.Request) { comment := store.Comment{} if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, hardBodyLimit), &comment); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't bind comment") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't bind comment", rest.ErrDecode) return } user, err := rest.GetUserInfo(r) if err != nil { // this not suppose to happen (handled by Auth), just dbl-check - rest.SendErrorJSON(w, r, http.StatusUnauthorized, err, "can't get user info") + rest.SendErrorJSON(w, r, http.StatusUnauthorized, err, "can't get user info", rest.ErrNoAccess) return } comment.User = user comment.Orig = comment.Text if err = s.DataService.ValidateComment(&comment); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "invalid comment") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "invalid comment", rest.ErrCommentValidation) return } @@ -104,7 +104,7 @@ func (s *Rest) infoCtrl(w http.ResponseWriter, r *http.Request) { }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get post info") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get post info", rest.ErrPostNotFound) return } @@ -136,7 +136,7 @@ func (s *Rest) lastCommentsCtrl(w http.ResponseWriter, r *http.Request) { }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't get last comments") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't get last comments", rest.ErrInternal) return } @@ -156,7 +156,7 @@ func (s *Rest) commentByIDCtrl(w http.ResponseWriter, r *http.Request) { comment, err := s.DataService.Get(store.Locator{SiteID: siteID, URL: url}, id) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get comment by id") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get comment by id", rest.ErrCommentNotFound) return } comment = s.adminService.alterComments([]store.Comment{comment}, r)[0] @@ -202,7 +202,7 @@ func (s *Rest) findUserCommentsCtrl(w http.ResponseWriter, r *http.Request) { }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get comment by user id") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get comment by user id", rest.ErrCommentNotFound) return } @@ -257,7 +257,7 @@ func (s *Rest) countCtrl(w http.ResponseWriter, r *http.Request) { locator := store.Locator{SiteID: r.URL.Query().Get("site"), URL: r.URL.Query().Get("url")} count, err := s.DataService.Count(locator) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get count") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get count", rest.ErrPostNotFound) return } render.JSON(w, r, R.JSON{"count": count, "locator": locator}) @@ -268,7 +268,7 @@ func (s *Rest) countMultiCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") posts := []string{} if err := render.DecodeJSON(http.MaxBytesReader(w, r.Body, hardBodyLimit), &posts); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get list of posts from request") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get list of posts from request", rest.ErrSiteNotFound) return } @@ -276,7 +276,7 @@ func (s *Rest) countMultiCtrl(w http.ResponseWriter, r *http.Request) { k := URLKey(r) + strings.Join(posts, ",") hasher := sha1.New() if _, err := hasher.Write([]byte(k)); err != nil { - rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't make sha1 for list of urls") + rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't make sha1 for list of urls", rest.ErrInternal) return } sha := base64.URLEncoding.EncodeToString(hasher.Sum(nil)) @@ -290,7 +290,7 @@ func (s *Rest) countMultiCtrl(w http.ResponseWriter, r *http.Request) { }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get counts for "+siteID) + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get counts for "+siteID, rest.ErrSiteNotFound) return } @@ -322,7 +322,7 @@ func (s *Rest) listCtrl(w http.ResponseWriter, r *http.Request) { }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get list of comments for "+siteID) + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get list of comments for "+siteID, rest.ErrSiteNotFound) return } diff --git a/backend/app/rest/api/rss.go b/backend/app/rest/api/rss.go index c46d4c74..1573befc 100644 --- a/backend/app/rest/api/rss.go +++ b/backend/app/rest/api/rss.go @@ -50,7 +50,7 @@ func (s *Rest) rssPostCommentsCtrl(w http.ResponseWriter, r *http.Request) { }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't find comments") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't find comments", rest.ErrPostNotFound) return } @@ -83,7 +83,7 @@ func (s *Rest) rssSiteCommentsCtrl(w http.ResponseWriter, r *http.Request) { }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get last comments") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get last comments", rest.ErrSiteNotFound) return } @@ -135,7 +135,7 @@ func (s *Rest) rssRepliesCtrl(w http.ResponseWriter, r *http.Request) { }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get replies") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get replies", rest.ErrSiteNotFound) return } diff --git a/backend/app/rest/httperrors.go b/backend/app/rest/httperrors.go index e4db2a00..48a815ec 100644 --- a/backend/app/rest/httperrors.go +++ b/backend/app/rest/httperrors.go @@ -9,16 +9,39 @@ import ( "github.com/go-chi/render" log "github.com/go-pkgz/lgr" + "github.com/go-pkgz/rest" +) + +const ( + ErrInternal = 0 // any internal error + ErrCommentNotFound = 1 // can't find comment + ErrDecode = 2 // failed to unmarshal incoming request + ErrNoAccess = 3 // rejected by auth + ErrCommentValidation = 4 // validation failed + ErrPostNotFound = 5 // can't find post + ErrSiteNotFound = 6 // can't find site + ErrUserBlocked = 7 // user blocked + ErrReadOnly = 8 // write failed on read only + ErrCommentRejected = 9 // general error on rejected comment change + ErrCommentEditExpired = 10 // too late for edit + ErrCommentEditChanged = 11 // parent commend changed + ErrVoteRejected = 12 // general error on vote rejected + ErrVoteSelf = 13 // vote for own comment + ErrVoteDbl = 14 // already voted for the comment + ErrVoteMax = 15 // too many votes for the comment + ErrVoteMinScore = 16 // min score reached for the comment + ErrActionRejected = 17 // general error for rejected actions + ErrAssetNotFound = 18 // requested file not found ) // SendErrorJSON makes {error: blah, details: blah} json body and responds with error code -func SendErrorJSON(w http.ResponseWriter, r *http.Request, code int, err error, details string) { - log.Printf("[DEBUG] %s", errDetailsMsg(r, code, err, details)) - render.Status(r, code) - render.JSON(w, r, map[string]interface{}{"error": err.Error(), "details": details}) +func SendErrorJSON(w http.ResponseWriter, r *http.Request, httpStatusCode int, err error, details string, errCode int) { + log.Printf("[DEBUG] %s", errDetailsMsg(r, httpStatusCode, err, details, errCode)) + render.Status(r, httpStatusCode) + render.JSON(w, r, rest.JSON{"error": err.Error(), "details": details, "code": errCode}) } -func errDetailsMsg(r *http.Request, code int, err error, details string) string { +func errDetailsMsg(r *http.Request, httpStatusCode int, err error, details string, errCode int) string { uinfoStr := "" if user, e := GetUserInfo(r); e == nil { uinfoStr = user.Name + "/" + user.ID + " - " @@ -40,5 +63,6 @@ func errDetailsMsg(r *http.Request, code int, err error, details string) string if pos := strings.Index(remoteIP, ":"); pos >= 0 { remoteIP = remoteIP[:pos] } - return fmt.Sprintf("%s - %v - %d - %s%s - %s%s", details, err, code, uinfoStr, remoteIP, q, srcFileInfo) + return fmt.Sprintf("%s - %v - %d (%d) - %s%s - %s%s", + details, err, httpStatusCode, errCode, uinfoStr, remoteIP, q, srcFileInfo) } diff --git a/backend/app/rest/httperrors_test.go b/backend/app/rest/httperrors_test.go index 64356b34..f0fceec1 100644 --- a/backend/app/rest/httperrors_test.go +++ b/backend/app/rest/httperrors_test.go @@ -17,7 +17,7 @@ func TestSendErrorJSON(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/error" { t.Log("http err request", r.URL) - SendErrorJSON(w, r, 500, errors.New("error 500"), "error details 123456") + SendErrorJSON(w, r, 500, errors.New("error 500"), "error details 123456", 123) return } w.WriteHeader(404) @@ -33,7 +33,7 @@ func TestSendErrorJSON(t *testing.T) { require.Nil(t, err) assert.Equal(t, 500, resp.StatusCode) - assert.Equal(t, `{"details":"error details 123456","error":"error 500"}`+"\n", string(body)) + assert.Equal(t, `{"code":123,"details":"error details 123456","error":"error 500"}`+"\n", string(body)) } func TestErrorDetailsMsg(t *testing.T) { @@ -41,8 +41,8 @@ func TestErrorDetailsMsg(t *testing.T) { req, err := http.NewRequest("GET", "https://example.com/test?k1=v1&k2=v2", nil) require.Nil(t, err) req.RemoteAddr = "1.2.3.4" - msg := errDetailsMsg(req, 500, errors.New("error 500"), "error details 123456") - assert.Equal(t, "error details 123456 - error 500 - 500 - 1.2.3.4 - https://example.com/test?k1=v1&k2=v2 [caused by app/rest/httperrors_test.go:47 rest.TestErrorDetailsMsg]", msg) + msg := errDetailsMsg(req, 500, errors.New("error 500"), "error details 123456", 123) + assert.Equal(t, "error details 123456 - error 500 - 500 (123) - 1.2.3.4 - https://example.com/test?k1=v1&k2=v2 [caused by app/rest/httperrors_test.go:47 rest.TestErrorDetailsMsg]", msg) } callerFn() } @@ -53,8 +53,8 @@ func TestErrorDetailsMsgWithUser(t *testing.T) { req.RemoteAddr = "127.0.0.1:1234" req = SetUserInfo(req, store.User{Name: "test", ID: "id"}) require.Nil(t, err) - msg := errDetailsMsg(req, 500, errors.New("error 500"), "error details 123456") - assert.Equal(t, "error details 123456 - error 500 - 500 - test/id - 127.0.0.1 - https://example.com/test?k1=v1&k2=v2 [caused by app/rest/httperrors_test.go:59 rest.TestErrorDetailsMsgWithUser]", msg) + msg := errDetailsMsg(req, 500, errors.New("error 500"), "error details 123456", 34567) + assert.Equal(t, "error details 123456 - error 500 - 500 (34567) - test/id - 127.0.0.1 - https://example.com/test?k1=v1&k2=v2 [caused by app/rest/httperrors_test.go:59 rest.TestErrorDetailsMsgWithUser]", msg) } callerFn() } diff --git a/backend/app/rest/proxy/image.go b/backend/app/rest/proxy/image.go index 5ad0b805..d6247e7a 100644 --- a/backend/app/rest/proxy/image.go +++ b/backend/app/rest/proxy/image.go @@ -49,7 +49,7 @@ func (p Image) Routes() chi.Router { router.Get("/", func(w http.ResponseWriter, r *http.Request) { src, err := base64.URLEncoding.DecodeString(r.URL.Query().Get("src")) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't decode image url") + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't decode image url", rest.ErrDecode) return } @@ -73,7 +73,7 @@ func (p Image) Routes() chi.Router { return e }) if err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get image "+string(src)) + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get image "+string(src), rest.ErrAssetNotFound) return } defer func() {