gerard2p/koaton

View on GitHub
src/support/KoatonRouter.js

Summary

Maintainability
B
4 hrs
Test Coverage
import * as Router from 'koa-router';
import * as passport from 'koa-passport';
import inflector from './inflector';
import * as path from 'upath';
import { RestModel } from './RestModel';
import { existsSync } from 'fs';
import debug from './debug';
 
let allroutes = {};
/**
* Reads the model that is requested based on the route
* and appends model to ctx.state
* @param {KoaContext} ctx
* @param {KoaNext} next
* @param {JSURL} ctx.model - reference attached. DEPRECATED.
* @param {JSURL} ctx.state.model - reference attached.
*/
async function findmodel (ctx, next) {
let pmodel = ctx.request.url.split('?')[0].split('/')[1];
ctx.model = ctx.db[pmodel];
ctx.state.model = ctx.state.db[pmodel];
await next();
}
/**
* Makes a route only accessible if user is logged in
* and appends model to ctx.state
* @param {KoaContext} ctx
* @param {KoaNext} next
* @return {Object} {status:401, body: null}
*/
async function protect (ctx, next) {
if (ctx.isAuthenticated()) {
await next();
} else {
await passport.authenticate('bearer', {
session: false
}, async function (err, user) {
// console.log(err, user);
/* istanbul ignore if */
if (err) {
throw err;
}
if (user === false) {
ctx.body = null;
ctx.status = 401;
} else {
ctx.state.user = user;
await next();
}
})(ctx);
}
}
 
/**
* Return the requested property recursive
* @private
* @param {Object} object
* @param {String} action
* @param {...String} rest
*/
const DeepGet = function (object, action, ...rest) {
if (action) {
return DeepGet(object[action], ...rest);
} else {
return object;
}
};
/**
* Creates a default handler for the route (render html view)
* @private
* @param {string} view - Default view name
* @param {string} engine - Default engine extension
* @return {async function(ctx: KoaContext, next: KoaNext)} renders the view
*/
const DefaultView = function (view, engine = 'html') {
return async function DefaultView (ctx, next) {
await ctx.render(`${view}.${engine}`);
};
};
/**
* This class helps to build routes for a specific subdomain
* @class KoatonRouter
* @param {String} subdomain
*/
export default class KoatonRouter {
/**
* @param {String} subdomain - subdomain reference for the router
*/
constructor (subdomain) {
/** path location of the router */
this.loc = '.';
/**
* subdomain name
* @type {String}
*/
this.subdomain = subdomain;
/**
* Router for public access
* @type {KoaRouter}
*/
this.public = new Router();
/**
* Router for private access
* @type {KoaRouter}
*/
this.secured = new Router();
this.secured.use(protect);
this.public.use(findmodel);
this.secured.use(findmodel);
}
/**
* Update the location of the router
* @param {String} location
*/
location (location) {
this.loc = location;
}
/**
* return all the routes defined in the app
* @return {Array(Object)}
*/
static AllRoutes (subdomain) {
return allroutes[subdomain];
}
/**
* find what is the best mathching action for the given router and binding
* @param {KoaRouter} - The router
* @param {String} binding - a dot separated action. example: 'school.get'
* @return {function(ctx: KoaContext, next: KoaNext)} handles the route
*/
static findAction (router, binding) {
if (existsSync(path.resolve('views', binding))) {
return DefaultView(...binding.split('.'));
}
let [controller, ...actions] = binding.split('.');
let content = null;
try {
content = require(ProyPath(router.loc, 'controllers', controller));
if (!content.default) {
console.warn(`${controller} controller does not export any default. This will not be supported`);
} else {
content = content.default;
}
} catch (err) {
debug(err);
}
let action = DeepGet(content, ...actions) || DefaultView(controller);
return action;
}
/**
* Append a method to a route in the specified verb.
* @private
* @param {Verb} method
* @param {String} url
* @param {String|function} binding='index' - You can use a string to refer to a controller or pass a function to work as handler
* @param {Boolean} [secured=false]
* @return {KoaRouter}
*/
Function `request` has 30 lines of code (exceeds 25 allowed). Consider refactoring.
Function `request` has a Cognitive Complexity of 9 (exceeds 5 allowed). Consider refactoring.
request (method, url, binding = 'index', secured = false) {
let Action;
if (typeof binding === 'boolean') {
secured = binding;
binding = 'index';
}
if (typeof binding !== 'function') {
Action = KoatonRouter.findAction(this, binding);
} else {
Action = binding;
}
let route = url.replace(/^\//, '').split('/');
let repl = url.split('/').map((r, i) => {
if (!r) return '/';
return `$${i}` + r.replace(/([^\.]*)(.*)/g, '$2');
}).join('/');
let exp = route.filter(r => !!r).map(r => {
if (r.indexOf(':') > -1) {
return '([^\\.]*)' + r.replace(/([^\.]*)(.*)/g, '$2').replace('*', '.*');
}
return `(${r})`.replace('*', '.*');
}).join('\\.').replace(/\\\.$/, '');
let nroute = route[0] || 'home';
allroutes[this.subdomain] = allroutes[this.subdomain] || [];
allroutes[this.subdomain].push([new RegExp(`^${exp || 'home'}$`), repl.replace(/\/+/g, '/')]);
(secured ? this.secured : this.public)[method](url, async (ctx, next) => {
ctx.state.route = nroute;
ctx.state.fullRoute = url;
await next();
}, Action);
return this;
}
/**
* Create a route for a GET request
* @param {String} url
* @param {String|function} binding='index' - You can use a string to refer to a controller or pass a function to work as handler
* @param {Boolean} [secured=false]
* @return {KoaRouter}
*/
get (...args) {
return this.request.bind(this, 'get')(...args);
}
/**
* Create a route for a POST request
* @param {String} url
* @param {String|function} binding='index' - You can use a string to refer to a controller or pass a function to work as handler
* @param {Boolean} [secured=false]
* @return {KoaRouter}
*/
post (...args) {
return this.request.bind(this, 'post')(...args);
}
/**
* Create a route for a DELETE request
* @param {String} url
* @param {String|function} binding='index' - You can use a string to refer to a controller or pass a function to work as handler
* @param {Boolean} [secured=false]
* @return {KoaRouter}
*/
delete (...args) {
return this.request.bind(this, 'delete')(...args);
}
/**
* Create a route for a PUT request
* @param {String} url
* @param {String|function} binding='index' - You can use a string to refer to a controller or pass a function to work as handler
* @param {Boolean} [secured=false]
* @return {KoaRouter}
*/
put (...args) {
return this.request.bind(this, 'put')(...args);
}
/**
* Creates a REST route, all the rules of this routes will be given the controller of the given route
* @param {String} url
* @param {String} model=url - If not model present it will try to match the url with a model
* @return {KoaRouter}
*/
Function `rest` has 33 lines of code (exceeds 25 allowed). Consider refactoring.
Function `rest` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.
rest (url, model) {
if (model === undefined) {
model = url;
url = undefined;
}
let controller;
controller = require(ProyPath(this.loc, 'controllers', model));
if (!controller.default) {
console.warn('controller must export a default variable; not exporting a default won\'t be supporter in v3');
} else {
controller = controller.default;
}
controller = Object.assign({
Name: model,
Namespace: '',
REST: false,
Pluralize: true
}, controller);
if (controller.REST) {
/* istanbul ignore else */
if (controller.Pluralize) {
controller.Name = inflector.pluralize(controller.Name);
}
let mountRoute = path.join('/', controller.Namespace, url || controller.Name, '/');
let options = controller.REST === 'public' ? {
get: 'public',
post: 'public',
put: 'public',
delete: 'public'
} : {};
let router = RestModel(options, mountRoute, controller.Name);
this.public.use(router.path, router.public.routes());
this.secured.use(router.path, router.private.routes());
}
return this;
}
}