diff --git a/backend/app/rest/api/rss.go b/backend/app/rest/api/rss.go index 8d673555..7da33f0b 100644 --- a/backend/app/rest/api/rss.go +++ b/backend/app/rest/api/rss.go @@ -15,6 +15,8 @@ import ( ) const maxRssItems = 20 +const maxLastForReply = 100 +const maxReplyMins = 30 * time.Minute // ui uses links like #remark42__comment- const uiNav = "#remark42__comment-" @@ -23,6 +25,7 @@ func (s *Rest) rssRoutes() chi.Router { router := chi.NewRouter() router.Get("/post", s.rssPostCommentsCtrl) router.Get("/site", s.rssSiteCommentsCtrl) + router.Get("/reply", s.rssRepliesCtrl) return router } @@ -88,6 +91,54 @@ func (s *Rest) rssSiteCommentsCtrl(w http.ResponseWriter, r *http.Request) { } } +// GET /rss/reply?user=userID&site=siteID +func (s *Rest) rssRepliesCtrl(w http.ResponseWriter, r *http.Request) { + userID := r.URL.Query().Get("user") + siteID := r.URL.Query().Get("site") + log.Printf("[DEBUG] get rss replies to user %s for site %s", userID, siteID) + + data, err := s.Cache.Get(cache.Key(cache.URLKey(r), siteID, userID), func() ([]byte, error) { + comments, e := s.DataService.Last(siteID, maxLastForReply) + if e != nil { + return nil, e + } + comments = s.adminService.alterComments(comments, r) + + replies := []store.Comment{} + for _, c := range comments { + if len(replies) > maxRssItems || c.Timestamp.Add(maxReplyMins).Before(time.Now()) { + break + } + if c.ParentID != "" && !c.Deleted && c.User.ID != userID { // not interested replies to yourself + pc, errP := s.DataService.Get(c.Locator, c.ParentID) + if errP != nil { + return nil, errP + } + if pc.User.ID == userID { + replies = append(replies, c) + } + } + } + + rss, e := s.toRssFeed(siteID, replies) + if e != nil { + return nil, e + } + return []byte(rss), e + }) + + if err != nil { + rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't get replies") + return + } + + w.Header().Set("Content-Type", "application/xml; charset=utf-8") + w.WriteHeader(http.StatusOK) + if _, err := w.Write(data); err != nil { + log.Printf("[WARN] failed to send response to %s, %s", r.RemoteAddr, err) + } +} + func (s *Rest) toRssFeed(url string, comments []store.Comment) (string, error) { lastCommentTS := time.Unix(0, 0) diff --git a/backend/app/rest/api/rss_test.go b/backend/app/rest/api/rss_test.go index 08d9181e..8894dd76 100644 --- a/backend/app/rest/api/rss_test.go +++ b/backend/app/rest/api/rss_test.go @@ -158,6 +158,83 @@ func TestServer_RssWithReply(t *testing.T) { assert.Equal(t, expected, res) } +func TestServer_RssReplies(t *testing.T) { + srv, ts := prep(t) + assert.NotNil(t, srv) + defer cleanup(ts) + + waitOnSecChange() + + pubDate := time.Now().Format(time.RFC1123Z) + + c1 := store.Comment{ + Text: "c1", + Locator: store.Locator{URL: "https://radio-t.com/blah1", SiteID: "radio-t"}, + User: store.User{ID: "user1", Name: "user1"}, + } + id1, err := srv.DataService.Create(c1) + assert.Nil(t, err) + c2 := store.Comment{ + Text: "reply to c1 from user2", + ParentID: id1, + Locator: store.Locator{URL: "https://radio-t.com/blah1", SiteID: "radio-t"}, + User: store.User{ID: "user2", Name: "user2"}, + } + id2, err := srv.DataService.Create(c2) + assert.Nil(t, err) + c3 := store.Comment{ + Text: "reply to c1 from user3", + ParentID: id1, + Locator: store.Locator{URL: "https://radio-t.com/blah1", SiteID: "radio-t"}, + User: store.User{ID: "user3", Name: "user3"}, + } + id3, err := srv.DataService.Create(c3) + assert.Nil(t, err) + c4 := store.Comment{ + Text: "reply to c2 from developer one", + ParentID: id2, + Locator: store.Locator{URL: "https://radio-t.com/blah1", SiteID: "radio-t"}, + } + addComment(t, c4, ts) + c5 := store.Comment{ + Text: "developer one", + Locator: store.Locator{URL: "https://radio-t.com/blah1", SiteID: "radio-t"}, + } + addComment(t, c5, ts) + + // replies to c1 (user1). Must be [c3, c2] + res, code := get(t, ts.URL+"/api/v1/rss/reply?user=user1&site=radio-t") + assert.Equal(t, 200, code) + t.Log(res) + expected := fmt.Sprintf(` + + Remark42 comments + radio-t + comment updates + %s + + user3 > user1 + https://radio-t.com/blah1#remark42__comment-%s + reply to c1 from user3 + user3 + %s + + + user2 > user1 + https://radio-t.com/blah1#remark42__comment-%s + reply to c1 from user2 + user2 + %s + + + `, pubDate, id3, pubDate, id2, pubDate) + expected, res = cleanRssFormatting(expected, res) + assert.Equal(t, expected, res) + + _, code = get(t, ts.URL+"/api/v1/rss/reply?user=user1&site=radio-t-bad") + assert.Equal(t, 400, code) +} + func waitOnSecChange() { for { if time.Now().Nanosecond() < 100000000 {