diff --git a/isso/__init__.py b/isso/__init__.py index 854fe21..b6242e2 100644 --- a/isso/__init__.py +++ b/isso/__init__.py @@ -54,6 +54,7 @@ url_map = Map([ Rule('/', methods=['PUT', 'DELETE'], endpoint=views.comment.modify), Rule('/new', methods=['POST'], endpoint=views.comment.create), Rule('/like', methods=['POST'], endpoint=views.comment.like), + Rule('/count', methods=['GET'], endpoint=views.comment.count), Rule('/admin/', endpoint=views.admin.index) ]) diff --git a/isso/db.py b/isso/db.py index 154b767..95d7ca0 100644 --- a/isso/db.py +++ b/isso/db.py @@ -73,6 +73,13 @@ class Abstract: same ip address are ignored as well).""" return + @abc.abstractmethod + def count(self, path=None, mode=1): + """return count of comments for path (optional) and mode (defaults to + visible, not deleted or moderated comments).""" + return + + class SQLite(Abstract): """A basic :class:`Abstract` implementation using SQLite3. All comments share a single database. The tuple (id, path) acts as unique identifier @@ -213,6 +220,17 @@ class SQLite(Abstract): return likes + 1 + def count(self, path=None, mode=1): + + if path is not None: + with sqlite3.connect(self.dbpath) as con: + return con.execute("SELECT COUNT(*) FROM comments WHERE path=?" \ + + " AND (? | mode) = ?", (path, mode, mode)).fetchone() + + with sqlite3.connect(self.dbpath) as con: + return con.execute("SELECT COUNT(*) FROM comments WHERE" \ + + "(? | mode) = ?", (path, mode, mode)).fetchone() + def retrieve(self, path, mode=5): with sqlite3.connect(self.dbpath) as con: rv = con.execute("SELECT * FROM comments WHERE path=? AND (? | mode) = ?" \ diff --git a/isso/js/app/api.js b/isso/js/app/api.js index a950713..a751511 100644 --- a/isso/js/app/api.js +++ b/isso/js/app/api.js @@ -104,11 +104,22 @@ define(["lib/q"], function(Q) { }) } + var count = function(uri) { + return curl("GET", endpoint + "/count?" + qs({uri: uri}), null) + .then(function (rv) { + if (rv.status == 200) + return JSON.parse(rv.body) + + throw {status: rv.status, reason: rv.body}; + }) + } + return { endpoint: endpoint, create: create, remove: remove, - fetchall: fetchall + fetchall: fetchall, + count: count } }); \ No newline at end of file diff --git a/isso/js/app/count.js b/isso/js/app/count.js new file mode 100644 index 0000000..595c8c7 --- /dev/null +++ b/isso/js/app/count.js @@ -0,0 +1,16 @@ +define(["app/api", "lib/HTML"], function(api, HTML) { + return function() { + HTML.query("a").each(function(el, i, all) { + if (! el.href.match("#isso-thread$")) { + return; + }; + + var uri = el.href.match("^(.+)#isso-thread$")[1] + .replace(/^.*\/\/[^\/]+/, ''); + console.log(uri) + api.count(uri).then(function(rv) { + el.textContent = rv + (rv > 1 ? " Kommentare" : " Kommentar"); + }) + }); + } +}); \ No newline at end of file diff --git a/isso/js/build.count.js b/isso/js/build.count.js new file mode 100644 index 0000000..bf1d9b1 --- /dev/null +++ b/isso/js/build.count.js @@ -0,0 +1,7 @@ +({ + bseUrl: ".", + name: "lib/almond", + include: ['count'], + out: "count.min.js", + wrap: true +}) diff --git a/isso/js/build.js b/isso/js/build.embed.js similarity index 100% rename from isso/js/build.js rename to isso/js/build.embed.js diff --git a/isso/js/count.js b/isso/js/count.js new file mode 100644 index 0000000..420cc75 --- /dev/null +++ b/isso/js/count.js @@ -0,0 +1,5 @@ +require(["lib/ready", "app/count"], function(domready, count) { + domready(function() { + count(); + }) +}); diff --git a/isso/js/main.js b/isso/js/main.js index 0935925..50eec5d 100644 --- a/isso/js/main.js +++ b/isso/js/main.js @@ -1,5 +1,6 @@ -require(["lib/ready", "app/isso"], function(domready, isso) { +require(["lib/ready", "app/isso", "app/count"], function(domready, isso, count) { domready(function() { + count(); isso.init(); }) }); diff --git a/isso/static/style.css b/isso/static/isso.css similarity index 100% rename from isso/static/style.css rename to isso/static/isso.css diff --git a/isso/views/comment.py b/isso/views/comment.py index eef5342..8923a04 100644 --- a/isso/views/comment.py +++ b/isso/views/comment.py @@ -168,3 +168,14 @@ def approve(app, environ, request, path, id): app.db.activate(path, id) return Response(app.dumps(app.db.get(path, id)), 200, content_type='application/json') + + +@requires(str, 'uri') +def count(app, environ, request, uri): + + rv = app.db.count(uri, mode=1)[0] + + if rv == 0: + abort(404) + + return Response(json.dumps(rv), 200, content_type='application/json') diff --git a/specs/test_comment.py b/specs/test_comment.py index 40bc6a4..e7174ab 100644 --- a/specs/test_comment.py +++ b/specs/test_comment.py @@ -201,3 +201,25 @@ class TestComments(unittest.TestCase): assert rv.keys() == [] + def testCounts(self): + + assert self.get('/count?uri=%2Fpath%2F').status_code == 404 + self.post('/new?uri=%2Fpath%2F', data=json.dumps({"text": "..."})) + + rv = self.get('/count?uri=%2Fpath%2F') + assert rv.status_code == 200 + assert json.loads(rv.data) == 1 + + for x in range(3): + self.post('/new?uri=%2Fpath%2F', data=json.dumps({"text": "..."})) + + rv = self.get('/count?uri=%2Fpath%2F') + assert rv.status_code == 200 + assert json.loads(rv.data) == 4 + + for x in range(4): + self.delete('/?uri=%%2Fpath%%2F&id=%i' % (x + 1)) + + rv = self.get('/count?uri=%2Fpath%2F') + assert rv.status_code == 404 +