diff options
| author | Rubén Beltrán del Río <ben@nsovocal.com> | 2017-01-30 02:47:14 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-01-30 02:47:14 -0600 |
| commit | a6ccda0fbc4df683f9568d85eb22b21684d2a0bd (patch) | |
| tree | 967e98f2d8bf6718858da1fbc279987208b25e06 /lib/handlers | |
| parent | 287fa13b3e600b2340895a5463a288bf08101bb5 (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.js | 73 | ||||
| -rw-r--r-- | lib/handlers/posts.js | 177 |
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); + } +}; |