aboutsummaryrefslogtreecommitdiff
path: root/lib/handlers
diff options
context:
space:
mode:
authorRubén Beltrán del Río <ben@nsovocal.com>2017-01-30 02:47:14 -0600
committerGitHub <noreply@github.com>2017-01-30 02:47:14 -0600
commita6ccda0fbc4df683f9568d85eb22b21684d2a0bd (patch)
tree967e98f2d8bf6718858da1fbc279987208b25e06 /lib/handlers
parent287fa13b3e600b2340895a5463a288bf08101bb5 (diff)
Create and Show Posts (#3)
* 📝 Update changelog (retroactive) SORRY * Add JSDoc * Document twitter helper * Add posts handler * Remove cookies, set routes under /api * Update readme for clearer callback instructions * Redirect callback to login with token * Add dasein paw helper * Remove unused hostname * Remove hostname, add expiration to config * Rename expiration ttl * Send TTL to posts handler * Add redis dependency * Add redis config to config file * Add DASEIN_REDIS_HOST to env.dist * Correct redis config structure * Add redis db to compose * Add redis to posts handler * Update dasein paw file * Git add uuid * Add first iteration of post handlers * Ignore docs in linter * Ignore generated assets * Ignore docs and assets * Add frontend dependencies + more koa * Add post creation to backend * Adjust readme to show actual callback route * Add npm build as part of docker process * Configure babel * Update paw * Add webpack config * Add frontend code * List globals
Diffstat (limited to 'lib/handlers')
-rw-r--r--lib/handlers/auth.js73
-rw-r--r--lib/handlers/posts.js177
2 files changed, 221 insertions, 29 deletions
diff --git a/lib/handlers/auth.js b/lib/handlers/auth.js
index d16e15d..1db9494 100644
--- a/lib/handlers/auth.js
+++ b/lib/handlers/auth.js
@@ -1,41 +1,63 @@
'use strict';
const Co = require('co');
-const TwitterHelper = require('../twitter_helper');
const JsonWebToken = require('jsonwebtoken');
const Pify = require('pify');
+const TwitterHelper = require('../twitter_helper');
const internals = {};
internals.kRedirectUrl = 'https://api.twitter.com/oauth/authenticate?oauth_token=';
-internals.kMainLocation = '/';
+internals.kLoginRedirect = '/login';
internals.signJsonWebToken = Pify(JsonWebToken.sign);
+/**
+ * Handles the HTTP requests for auth related operations.
+ *
+ * @class AuthHandler
+ * @param {Dasein.tConfiguration} config The configuration to
+ * initialize.
+ */
module.exports = internals.AuthHandler = class AuthHandler {
constructor(config) {
this._twitterHelper = new TwitterHelper(config.twitter);
this._jwtConfig = config.jwt;
- this._hostname = config.hostname;
}
+ /**
+ * Triggers the twitter login flow. Redirects to twitter's oauth
+ * request page
+ *
+ * @function login
+ * @memberof AuthHandler
+ * @instance
+ * @return {generator} a koa compatible handler generator function
+ */
login() {
const twitterHelper = this._twitterHelper;
return function *handleLogin() {
- if (this.state.user) {
- return this.redirect(internals.kMainLocation);
- }
-
const requestToken = yield twitterHelper.getRequestToken();
- this.redirect(`${internals.kRedirectUrl}${requestToken.oAuthToken}`);
+ const loginUrl = `${internals.kRedirectUrl}${requestToken.oAuthToken}`;
+
+ this.body = { loginUrl };
};
}
+ /**
+ * Handles twitter's callback. Fetches the oAuth Verifier, attempts to
+ * obtain a user object and responds with the JWT
+ *
+ * @function callback
+ * @memberof AuthHandler
+ * @instance
+ * @return {generator} a koa compatible handler generator function
+ */
callback() {
const self = this;
@@ -46,8 +68,8 @@ module.exports = internals.AuthHandler = class AuthHandler {
return this.throw(401);
}
- const oAuthToken = this.request.query.oauth_token;
- const oAuthVerifier = this.request.query.oauth_verifier;
+ const oAuthToken = this.request.body.oAuthToken;
+ const oAuthVerifier = this.request.body.oAuthVerifier;
let user;
try {
@@ -59,25 +81,23 @@ module.exports = internals.AuthHandler = class AuthHandler {
return this.throw(401);
}
- yield self._setJWT(user, this);
-
- this.redirect(internals.kMainLocation);
- };
- }
-
- logout() {
+ const expiresAt = Date.now() + self._jwtConfig.duration * 1000;
- const self = this;
+ const token = yield self._getToken(user);
- return function * () {
+ const response = {
+ expiresAt,
+ user,
+ token
+ };
- this.cookies.set(self._jwtConfig.cookieName, null);
- this.redirect(internals.kMainLocation);
+ this.body = response;
};
}
- // Sets a JSON Web Token Cookie
- _setJWT(payload, context) {
+ // Generates a JSON Web Token
+
+ _getToken(payload) {
const self = this;
@@ -87,12 +107,7 @@ module.exports = internals.AuthHandler = class AuthHandler {
expiresIn: self._jwtConfig.duration
});
- context.cookies.set(self._jwtConfig.cookieName, token, {
- maxAge: self._jwtConfig.duration * 1000,
- signed: true,
- domain: self._hostname,
- overwrite: true
- });
+ return token;
});
}
};
diff --git a/lib/handlers/posts.js b/lib/handlers/posts.js
new file mode 100644
index 0000000..425fb5e
--- /dev/null
+++ b/lib/handlers/posts.js
@@ -0,0 +1,177 @@
+'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.kMaxPostSize = 255;
+
+internals.kPostsSchema = Joi.object().keys({
+ uuid: Joi.string().required(),
+ content: Joi.string().max(internals.kMaxPostSize).required(),
+ timestamp: Joi.number().integer().required(),
+ userId: Joi.string().required(),
+ userName: Joi.string().required(),
+ userImage: Joi.string().required()
+});
+
+/**
+ * Handles the HTTP requests for posts related operations
+ *
+ * @class PostsHandler
+ * @param {Dasein.tConfiguration} config The configuration to
+ * initialize.
+ */
+module.exports = internals.PostsHandler = class PostsHandler {
+ 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 posts
+ *
+ * @function findAll
+ * @memberof PostsHandler
+ * @instance
+ * @return {generator} a koa compatible handler generator function
+ */
+ findAll() {
+
+ const self = this;
+
+ return function * () {
+
+ 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 cursor = parseInt(this.request.query.cursor) || 0;
+ const [nextCursor, keys] = yield scan(cursor, 'MATCH', `${internals.kPostsPrefix}:*`);
+
+ if (nextCursor > 0) {
+ this.append('Link', `<${this.request.origin}${this.request.path}?cursor=${nextCursor}>; rel="next"`);
+ }
+
+ const posts = yield keys.map((key) => hgetall(key));
+
+ this.body = posts;
+ };
+ }
+
+ /**
+ * Fetches a single post
+ *
+ * @function find
+ * @memberof PostsHandler
+ * @instance
+ * @return {generator} a koa compatible handler generator function
+ */
+ find() {
+
+ const self = this;
+
+ return function * (uuid) {
+
+ if (!this.state.user) {
+ return this.throw('Unauthorized', 401);
+ }
+
+ const hgetall = Pify(self._redis.hgetall.bind(self._redis));
+
+ const postKey = `${internals.kPostsPrefix}:${uuid}`;
+
+ const post = yield hgetall(postKey);
+
+ if (!post) {
+ this.throw('Post not found', 404);
+ }
+
+ this.body = post;
+ };
+ }
+
+ /**
+ * Creates a post
+ *
+ * @function create
+ * @memberof PostsHandler
+ * @instance
+ * @return {generator} a koa compatible handler generator function
+ */
+ create() {
+
+ const self = this;
+
+ return function * () {
+
+ 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}:${uuid}`;
+
+ const post = {
+ uuid,
+ content: this.request.body.content,
+ timestamp,
+ userId: user.screen_name,
+ userName: user.name,
+ userImage: user.profile_image_url_https
+ };
+
+ yield self._validate(post).catch((err) => {
+
+ this.throw(err.message, 422);
+ });
+
+ yield hmset(postKey, post);
+ yield expire(postKey, self._ttl);
+
+ this.body = yield hgetall(postKey);
+ };
+ }
+
+ /**
+ * Deletes a post
+ *
+ * @function delete
+ * @memberof PostsHandler
+ * @instance
+ * @return {generator} a koa compatible handler generator function
+ */
+ delete() {
+
+ return function * () {};
+ }
+
+ // Validates the post schema
+
+ _validate(post) {
+
+ const validate = Pify(Joi.validate.bind(Joi));
+ return validate(post, internals.kPostsSchema);
+ }
+};