aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRubén Beltrán del Río <ben@nsovocal.com>2017-01-31 00:49:26 -0600
committerGitHub <noreply@github.com>2017-01-31 00:49:26 -0600
commit9b1f50fefe021578113b8f5935084fe4fed110b2 (patch)
tree98805583b7c5041f16b195de823c5524f4de7672 /lib
parenta6ccda0fbc4df683f9568d85eb22b21684d2a0bd (diff)
Add Comments (#4)
* Preserve whitespace in posts * Create comments handler * Use comments handler * Update paw * Add comment components * Style comments * Remove unnecessary code from app * Fix linter warning * Add key to handle children (thx @javierbyte) * Add toggling to the form * Return all keys always * Correct missing semicolons * Restore redirect * Render posts conditionally * Set poll time to 1s * Set post frequency to 2s * Add polling to comments * Correct typo in error logging * Clear intervals and reduce polling * Stops polling if it's being destroyed * Update changelog
Diffstat (limited to 'lib')
-rw-r--r--lib/dasein.js14
-rw-r--r--lib/handlers/comments.js140
-rw-r--r--lib/handlers/posts.js14
3 files changed, 162 insertions, 6 deletions
diff --git a/lib/dasein.js b/lib/dasein.js
index 3a537bd..5b8486a 100644
--- a/lib/dasein.js
+++ b/lib/dasein.js
@@ -10,6 +10,7 @@ const Path = require('path');
const AuthHandler = require('./handlers/auth');
const PostsHandler = require('./handlers/posts');
+const CommentsHandler = require('./handlers/comments');
const internals = {};
@@ -90,6 +91,7 @@ module.exports = internals.Dasein = class Dasein {
this._initializeAuthRoutes();
this._initializePostsRoutes();
+ this._initializeCommentsRoutes();
this._app.use(function * () {
@@ -125,6 +127,18 @@ module.exports = internals.Dasein = class Dasein {
}
+ // Initialize routes for comments
+
+ _initializeCommentsRoutes() {
+
+ const commentsHandler = new CommentsHandler({
+ ttl: this.ttl,
+ redis: this.redis
+ });
+ this._app.use(KoaRoute.get('/api/posts/:postId/comments', commentsHandler.findAll()));
+ this._app.use(KoaRoute.post('/api/posts/:postId/comments', commentsHandler.create()));
+ }
+
// Starts listening
_startServer() {
diff --git a/lib/handlers/comments.js b/lib/handlers/comments.js
new file mode 100644
index 0000000..14bd137
--- /dev/null
+++ b/lib/handlers/comments.js
@@ -0,0 +1,140 @@
+'use strict';
+
+const Joi = require('joi');
+const Pify = require('pify');
+const Redis = require('redis');
+const UUID = require('uuid/v4');
+
+const internals = {};
+
+internals.kPostsPrefix = 'posts';
+internals.kCommentsPrefix = 'comments';
+internals.kMaxCommentSize = 255;
+
+internals.kCommentsSchema = Joi.object().keys({
+ uuid: Joi.string().required(),
+ content: Joi.string().max(internals.kMaxCommentSize).required(),
+ timestamp: Joi.number().integer().required(),
+ userId: Joi.string().required(),
+ userName: Joi.string().required(),
+ userImage: Joi.string().required()
+});
+
+/**
+ * Handles the HTTP requests for comment related operations
+ *
+ * @class CommentsHandler
+ * @param {Dasein.tConfiguration} config The configuration to
+ * initialize.
+ */
+module.exports = internals.CommentsHandler = class CommentsHandler {
+ constructor(config) {
+
+ this._ttl = config.ttl;
+ this._redis = Redis.createClient(config.redis);
+
+ // Log an error if it happens.
+ this._redis.on('error', (err) => {
+
+ console.error(err);
+ });
+ }
+
+ /**
+ * Fetches all available comments
+ *
+ * @function findAll
+ * @memberof CommentsHandler
+ * @instance
+ * @return {generator} a koa compatible handler generator function
+ */
+ findAll() {
+
+ const self = this;
+
+ return function * (postId) {
+
+ if (!this.state.user) {
+ return this.throw('Unauthorized', 401);
+ }
+
+ const scan = Pify(self._redis.scan.bind(self._redis));
+ const hgetall = Pify(self._redis.hgetall.bind(self._redis));
+
+ const commentsKey = `${internals.kCommentsPrefix}:${postId}:*`;
+ let keys = [];
+ let nextCursor = 0;
+ let currentKeys = null;
+
+ do {
+ [nextCursor, currentKeys] = yield scan(nextCursor || 0, 'MATCH', commentsKey);
+ keys = keys.concat(currentKeys);
+ } while (nextCursor > 0);
+
+ const comments = yield keys.map((key) => hgetall(key));
+
+ this.body = comments.sort((a, b) => a.timestamp - b.timestamp);
+ };
+ }
+
+ /**
+ * Creates a comment
+ *
+ * @function create
+ * @memberof CommentsHandler
+ * @instance
+ * @return {generator} a koa compatible handler generator function
+ */
+ create() {
+
+ const self = this;
+
+ return function * (postId) {
+
+ if (!this.state.user) {
+ return this.throw('Unauthorized', 401);
+ }
+
+ const hmset = Pify(self._redis.hmset.bind(self._redis));
+ const hgetall = Pify(self._redis.hgetall.bind(self._redis));
+ const expire = Pify(self._redis.expire.bind(self._redis));
+
+ const uuid = UUID();
+ const timestamp = Date.now();
+ const user = this.state.user;
+
+ const postKey = `${internals.kPostsPrefix}:${postId}`;
+ const commentKey = `${internals.kCommentsPrefix}:${postId}:${uuid}`;
+
+ const comment = {
+ uuid,
+ content: this.request.body.content,
+ timestamp,
+ userId: user.screen_name,
+ userName: user.name,
+ userImage: user.profile_image_url_https
+ };
+
+ yield self._validate(comment).catch((err) => {
+
+ this.throw(err.message, 422);
+ });
+
+ yield hmset(commentKey, comment);
+ yield expire(commentKey, self._ttl * 100); // this is me being lazy :(
+ // comments will last at most 100 bumps
+ // but will disappear eventually
+ yield expire(postKey, self._ttl); // bumps the parent comment TTL
+
+ this.body = yield hgetall(commentKey);
+ };
+ }
+
+ // Validates the comment schema
+
+ _validate(comment) {
+
+ const validate = Pify(Joi.validate.bind(Joi));
+ return validate(comment, internals.kCommentsSchema);
+ }
+};
diff --git a/lib/handlers/posts.js b/lib/handlers/posts.js
index 425fb5e..b5e4f0e 100644
--- a/lib/handlers/posts.js
+++ b/lib/handlers/posts.js
@@ -60,16 +60,18 @@ module.exports = internals.PostsHandler = class PostsHandler {
const scan = Pify(self._redis.scan.bind(self._redis));
const hgetall = Pify(self._redis.hgetall.bind(self._redis));
- const cursor = parseInt(this.request.query.cursor) || 0;
- const [nextCursor, keys] = yield scan(cursor, 'MATCH', `${internals.kPostsPrefix}:*`);
+ let keys = [];
+ let nextCursor = 0;
+ let currentKeys = null;
- if (nextCursor > 0) {
- this.append('Link', `<${this.request.origin}${this.request.path}?cursor=${nextCursor}>; rel="next"`);
- }
+ do {
+ [nextCursor, currentKeys] = yield scan(nextCursor || 0, 'MATCH', `${internals.kPostsPrefix}:*`);
+ keys = keys.concat(currentKeys);
+ } while (nextCursor > 0);
const posts = yield keys.map((key) => hgetall(key));
- this.body = posts;
+ this.body = posts.sort((a, b) => b.timestamp - a.timestamp);
};
}