New 'latest' endpoint to serve latest comments. Fixes #556. (#610)

New 'latest' endpoint to serve latest comments.
This commit is contained in:
Facundo Batista 2020-01-26 11:14:15 -03:00 committed by Jelmer Vernooij
parent 1de758887a
commit 1633d9261e
5 changed files with 152 additions and 0 deletions

View File

@ -110,6 +110,10 @@ gravatar-url
Url for gravatar images. The "{}" is where the email hash will be placed.
Defaults to "https://www.gravatar.com/avatar/{}?d=identicon"
latest-enabled
If True it will enable the ``/latest`` endpoint. Optional, defaults
to False.
.. _CORS: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS

View File

@ -83,6 +83,19 @@ plain :
pass plain=1 to get the raw comment text, defaults to 0.
Get the latest N comments for all threads:
.. code-block:: text
GET /latest?limit=N
The N parameter limits how many of the latest comments to retrieve; it's
mandatory, and must be an integer greater than 0.
This endpoint needs to be enabled in the configuration (see the
``latest-enabled`` option in the ``general`` section).
Create comment
--------------

View File

@ -33,6 +33,7 @@ class TestComments(unittest.TestCase):
conf.set("general", "dbpath", self.path)
conf.set("guard", "enabled", "off")
conf.set("hash", "algorithm", "none")
conf.set("general", "latest-enabled", "true")
self.conf = conf
class App(Isso, core.Mixin):
@ -451,6 +452,48 @@ class TestComments(unittest.TestCase):
self.assertEqual(
rv["text"], '<p>This is <strong>mark</strong><em>down</em></p>')
def testLatestOk(self):
# load some comments in a mix of posts
saved = []
for idx, post_id in enumerate([1, 2, 2, 1, 2, 1, 3, 1, 4, 2, 3, 4, 1, 2]):
text = 'text-{}'.format(idx)
post_uri = 'test-{}'.format(post_id)
self.post('/new?uri=' + post_uri, data=json.dumps({'text': text}))
saved.append((post_uri, text))
response = self.get('/latest?limit=5')
self.assertEqual(response.status_code, 200)
body = loads(response.data)
expected_items = saved[-5:] # latest 5
for reply, expected in zip(body, expected_items):
expected_uri, expected_text = expected
self.assertIn(expected_text, reply['text'])
self.assertEqual(expected_uri, reply['uri'])
def testLatestWithoutLimit(self):
response = self.get('/latest')
self.assertEqual(response.status_code, 400)
def testLatestBadLimitNaN(self):
response = self.get('/latest?limit=WAT')
self.assertEqual(response.status_code, 400)
def testLatestBadLimitNegative(self):
response = self.get('/latest?limit=-12')
self.assertEqual(response.status_code, 400)
def testLatestBadLimitZero(self):
response = self.get('/latest?limit=0')
self.assertEqual(response.status_code, 400)
def testLatestNotEnabled(self):
# disable the endpoint
self.conf.set("general", "latest-enabled", "false")
response = self.get('/latest?limit=5')
self.assertEqual(response.status_code, 404)
class TestModeratedComments(unittest.TestCase):

View File

@ -2,6 +2,7 @@
from __future__ import unicode_literals
import collections
import re
import time
import functools
@ -112,6 +113,7 @@ class API(object):
('count', ('GET', '/count')),
('counts', ('POST', '/count')),
('feed', ('GET', '/feed')),
('latest', ('GET', '/latest')),
('view', ('GET', '/id/<int:id>')),
('edit', ('PUT', '/id/<int:id>')),
('delete', ('DELETE', '/id/<int:id>')),
@ -1136,3 +1138,89 @@ class API(object):
counts=comment_mode_count,
order_by=order_by, asc=asc,
isso_host_script=isso_host_script)
"""
@api {get} /latest latest
@apiGroup Comment
@apiDescription
Get the latest comments from the system, no matter which thread
@apiParam {number} limit
The quantity of last comments to retrieve
@apiExample {curl} Get the latest 5 comments
curl 'https://comments.example.com/latest?limit=5'
@apiUse commentResponse
@apiSuccessExample Example result:
[
{
"website": null,
"uri": "/some",
"author": null,
"parent": null,
"created": 1464912312.123416,
"text": " &lt;p&gt;I want to use MySQL&lt;/p&gt;",
"dislikes": 0,
"modified": null,
"mode": 1,
"id": 3,
"likes": 1
},
{
"website": null,
"uri": "/other",
"author": null,
"parent": null,
"created": 1464914341.312426,
"text": " &lt;p&gt;I want to use MySQL&lt;/p&gt;",
"dislikes": 0,
"modified": null,
"mode": 1,
"id": 4,
"likes": 0
}
]
"""
def latest(self, environ, request):
# if the feature is not allowed, don't present the endpoint
if not self.conf.getboolean("latest-enabled"):
return NotFound()
# get and check the limit
bad_limit_msg = "Query parameter 'limit' is mandatory (integer, >0)"
try:
limit = int(request.args['limit'])
except (KeyError, ValueError):
return BadRequest(bad_limit_msg)
if limit <= 0:
return BadRequest(bad_limit_msg)
# retrieve the latest N comments from the DB
all_comments_gen = self.comments.fetchall(limit=None, order_by='created', mode='1')
comments = collections.deque(all_comments_gen, maxlen=limit)
# prepare a special set of fields (except text which is rendered specifically)
fields = {
'author',
'created',
'dislikes',
'id',
'likes',
'mode',
'modified',
'parent',
'text',
'uri',
'website',
}
# process the retrieved comments and build results
result = []
for comment in comments:
processed = {key: comment[key] for key in fields}
processed['text'] = self.isso.render(comment['text'])
result.append(processed)
return JSON(result, 200)

View File

@ -59,6 +59,10 @@ gravatar = false
# default url for gravatar. {} is where the hash will be placed
gravatar-url = https://www.gravatar.com/avatar/{}?d=identicon
# enable the "/latest" endpoint, that serves comment for multiple posts (not
# needing to previously know the posts URIs)
latest-enabled = false
[admin]
enabled = false