Skip to content

Commit 1633d92

Browse files
facundobatistajelmer
authored andcommitted
New 'latest' endpoint to serve latest comments. Fixes isso-comments#556. (isso-comments#610)
New 'latest' endpoint to serve latest comments.
1 parent 1de7588 commit 1633d92

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed

docs/docs/configuration/server.rst

+4
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ gravatar-url
110110
Url for gravatar images. The "{}" is where the email hash will be placed.
111111
Defaults to "https://www.gravatar.com/avatar/{}?d=identicon"
112112

113+
latest-enabled
114+
If True it will enable the ``/latest`` endpoint. Optional, defaults
115+
to False.
116+
113117

114118

115119
.. _CORS: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS

docs/docs/extras/api.rst

+13
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ plain :
8383
pass plain=1 to get the raw comment text, defaults to 0.
8484

8585

86+
Get the latest N comments for all threads:
87+
88+
.. code-block:: text
89+
90+
GET /latest?limit=N
91+
92+
The N parameter limits how many of the latest comments to retrieve; it's
93+
mandatory, and must be an integer greater than 0.
94+
95+
This endpoint needs to be enabled in the configuration (see the
96+
``latest-enabled`` option in the ``general`` section).
97+
98+
8699
Create comment
87100
--------------
88101

isso/tests/test_comments.py

+43
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def setUp(self):
3333
conf.set("general", "dbpath", self.path)
3434
conf.set("guard", "enabled", "off")
3535
conf.set("hash", "algorithm", "none")
36+
conf.set("general", "latest-enabled", "true")
3637
self.conf = conf
3738

3839
class App(Isso, core.Mixin):
@@ -451,6 +452,48 @@ def testPreview(self):
451452
self.assertEqual(
452453
rv["text"], '<p>This is <strong>mark</strong><em>down</em></p>')
453454

455+
def testLatestOk(self):
456+
# load some comments in a mix of posts
457+
saved = []
458+
for idx, post_id in enumerate([1, 2, 2, 1, 2, 1, 3, 1, 4, 2, 3, 4, 1, 2]):
459+
text = 'text-{}'.format(idx)
460+
post_uri = 'test-{}'.format(post_id)
461+
self.post('/new?uri=' + post_uri, data=json.dumps({'text': text}))
462+
saved.append((post_uri, text))
463+
464+
response = self.get('/latest?limit=5')
465+
self.assertEqual(response.status_code, 200)
466+
467+
body = loads(response.data)
468+
expected_items = saved[-5:] # latest 5
469+
for reply, expected in zip(body, expected_items):
470+
expected_uri, expected_text = expected
471+
self.assertIn(expected_text, reply['text'])
472+
self.assertEqual(expected_uri, reply['uri'])
473+
474+
def testLatestWithoutLimit(self):
475+
response = self.get('/latest')
476+
self.assertEqual(response.status_code, 400)
477+
478+
def testLatestBadLimitNaN(self):
479+
response = self.get('/latest?limit=WAT')
480+
self.assertEqual(response.status_code, 400)
481+
482+
def testLatestBadLimitNegative(self):
483+
response = self.get('/latest?limit=-12')
484+
self.assertEqual(response.status_code, 400)
485+
486+
def testLatestBadLimitZero(self):
487+
response = self.get('/latest?limit=0')
488+
self.assertEqual(response.status_code, 400)
489+
490+
def testLatestNotEnabled(self):
491+
# disable the endpoint
492+
self.conf.set("general", "latest-enabled", "false")
493+
494+
response = self.get('/latest?limit=5')
495+
self.assertEqual(response.status_code, 404)
496+
454497

455498
class TestModeratedComments(unittest.TestCase):
456499

isso/views/comments.py

+88
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import unicode_literals
44

5+
import collections
56
import re
67
import time
78
import functools
@@ -112,6 +113,7 @@ class API(object):
112113
('count', ('GET', '/count')),
113114
('counts', ('POST', '/count')),
114115
('feed', ('GET', '/feed')),
116+
('latest', ('GET', '/latest')),
115117
('view', ('GET', '/id/<int:id>')),
116118
('edit', ('PUT', '/id/<int:id>')),
117119
('delete', ('DELETE', '/id/<int:id>')),
@@ -1136,3 +1138,89 @@ def admin(self, env, req):
11361138
counts=comment_mode_count,
11371139
order_by=order_by, asc=asc,
11381140
isso_host_script=isso_host_script)
1141+
"""
1142+
@api {get} /latest latest
1143+
@apiGroup Comment
1144+
@apiDescription
1145+
Get the latest comments from the system, no matter which thread
1146+
1147+
@apiParam {number} limit
1148+
The quantity of last comments to retrieve
1149+
1150+
@apiExample {curl} Get the latest 5 comments
1151+
curl 'https://comments.example.com/latest?limit=5'
1152+
1153+
@apiUse commentResponse
1154+
1155+
@apiSuccessExample Example result:
1156+
[
1157+
{
1158+
"website": null,
1159+
"uri": "/some",
1160+
"author": null,
1161+
"parent": null,
1162+
"created": 1464912312.123416,
1163+
"text": " &lt;p&gt;I want to use MySQL&lt;/p&gt;",
1164+
"dislikes": 0,
1165+
"modified": null,
1166+
"mode": 1,
1167+
"id": 3,
1168+
"likes": 1
1169+
},
1170+
{
1171+
"website": null,
1172+
"uri": "/other",
1173+
"author": null,
1174+
"parent": null,
1175+
"created": 1464914341.312426,
1176+
"text": " &lt;p&gt;I want to use MySQL&lt;/p&gt;",
1177+
"dislikes": 0,
1178+
"modified": null,
1179+
"mode": 1,
1180+
"id": 4,
1181+
"likes": 0
1182+
}
1183+
]
1184+
"""
1185+
1186+
def latest(self, environ, request):
1187+
# if the feature is not allowed, don't present the endpoint
1188+
if not self.conf.getboolean("latest-enabled"):
1189+
return NotFound()
1190+
1191+
# get and check the limit
1192+
bad_limit_msg = "Query parameter 'limit' is mandatory (integer, >0)"
1193+
try:
1194+
limit = int(request.args['limit'])
1195+
except (KeyError, ValueError):
1196+
return BadRequest(bad_limit_msg)
1197+
if limit <= 0:
1198+
return BadRequest(bad_limit_msg)
1199+
1200+
# retrieve the latest N comments from the DB
1201+
all_comments_gen = self.comments.fetchall(limit=None, order_by='created', mode='1')
1202+
comments = collections.deque(all_comments_gen, maxlen=limit)
1203+
1204+
# prepare a special set of fields (except text which is rendered specifically)
1205+
fields = {
1206+
'author',
1207+
'created',
1208+
'dislikes',
1209+
'id',
1210+
'likes',
1211+
'mode',
1212+
'modified',
1213+
'parent',
1214+
'text',
1215+
'uri',
1216+
'website',
1217+
}
1218+
1219+
# process the retrieved comments and build results
1220+
result = []
1221+
for comment in comments:
1222+
processed = {key: comment[key] for key in fields}
1223+
processed['text'] = self.isso.render(comment['text'])
1224+
result.append(processed)
1225+
1226+
return JSON(result, 200)

share/isso.conf

+4
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ gravatar = false
5959
# default url for gravatar. {} is where the hash will be placed
6060
gravatar-url = https://www.gravatar.com/avatar/{}?d=identicon
6161

62+
# enable the "/latest" endpoint, that serves comment for multiple posts (not
63+
# needing to previously know the posts URIs)
64+
latest-enabled = false
65+
6266
[admin]
6367
enabled = false
6468

0 commit comments

Comments
 (0)