we-core
Version:
We.js is a node.js framework for build real time applications, sites or blogs!
933 lines (844 loc) • 28 kB
JavaScript
var _ = require('lodash');
var uploader = require('./uploader');
var weCorePath = require('path').resolve(__dirname, '../', '../');
var cors = require('cors');
var Alias = require('./Alias');
var mime = require('mime');
// absolute url regex tester
var absoluteUrlRegex = new RegExp('^(?:[a-z]+:)?//', 'i');
var Router = function routerPrototype(we) {
this.we = we;
var router = this;
this.routeMap = {};
// resources tree
this.resources = {};
// resources salved by name
this.resourcesByName = {};
this.resourcesSort = [];
this.search = require('./search');
this.title = require('./title');
this.alias = new Alias(we);
this.metatag = require('./metatag');
this.breadcrumb = require('./breadcrumb');
router.singleRecordActions = [
'findOne', 'update', 'destroy', 'updateAttribute',
'deleteAttribute', 'addRecord', 'removeRecord', 'getRecord', 'edit', 'delete'
];
we.hooks.on('we:router:request:after:load:context', [
function runTitleMiddleware(data, done) {
// only run on html response
if (!data.req.accepts('html')) return done();
router.title.middleware(data.req, data.res, done);
},
function runMetatagMiddleware(data, done) {
// only run on html response
if (!data.req.accepts('html')) return done();
router.metatag.middleware(data.req, data.res, done);
},
function runBreadcrumbMiddleware(data, done) {
// only run on html response
if (!data.req.accepts('html')) return done();
router.breadcrumb.middleware(data.req, data.res, done);
}
]);
}
/**
* Bind one we.js route
*
* This function bind context loader, acl, upload and controller middlewares
*
* @param {Object} we we.js object
* @param {String} route route like "get /route"
* @param {Object} config route configs
*/
Router.prototype.bindRoute = function bindRoute (app, route, config, groupRouter){
var method, path;
// set responseType based in extension
var extension = app.router.splitExtensionFromURL(route);
if (extension) {
// check if is valid this extension and is one of acceptable extensions
if (app.config.responseTypes.indexOf(extension) >-1) {
config.responseType = extension;
// update route for allow accesss without extension
route = route.replace('.' + extension, '');
} else {
extension = null;
}
}
// parse method and url
var r = route.split(' ');
if (r.length > 1) {
method = r[0];
path = r[1];
} else {
method = 'get';
path = r[0];
}
if (config.method) method = config.method;
var actionFunction = '';
// search for the controller action to bind
if (
app.controllers[config.controller] &&
app.controllers[config.controller][config.action]
) {
actionFunction = app.controllers[config.controller][config.action];
} else {
return app.log.warn('app.router.bindRoute: Unknow controller or action:', path, config);
}
if (!groupRouter) groupRouter = app.express;
if (config.search) {
// save param names
config.searchParams = Object.keys(config.search);
}
var middlewares = [
// CORS middleware per route
cors( (config.CORS || app.config.security.CORS) ),
// body we.js parser
app.router.parseBody.bind({ config: config }),
// bind context loader
app.router.contextLoader.bind({ config: config }),
// bind acl middleware
app.acl.canMiddleware.bind({ config: config })
];
/**
* Use this event to change we.js route middlewares
* @type {Event}
*/
app.events.emit('router:add:acl:middleware', {
we: app, middlewares: middlewares, config: config
});
// bind upload if have upload config and after ACL check
if (config.upload)
middlewares.push(uploader(config.upload));
/**
* Use this event to change we.js route middlewares
* @type {Event}
*/
app.events.emit('router:before:set:controller:middleware', {we: app, middlewares: middlewares, config: config});
// bind contoller
middlewares.push(actionFunction);
groupRouter[method](path, middlewares);
var mapName = config.name;
if (!mapName)
mapName = config.controller + '.' + config.action;
if (!app.router.routeMap[method]) app.router.routeMap[method] = {};
// map get routes for use with link-to
app.router.routeMap[method][mapName] = {
map: app.router.parseRouteToMap(path),
config: config
};
app.log.silly('Route bind:', method ,path);
}
/**
* Set resource, findAll, find, create, edit and delete routes
*
* @param {Object} opts route options
* @todo make it simpler
*/
Router.prototype.bindResource = function bindResource(opts) {
var we = this.we;
var router = this;
// valid route options ...
if (!opts.name) throw new Error('Resource name is required in bind resource');
// get related Model
var Model = we.db.models[opts.name];
if (!Model) throw new Error('Resource Model not found and is required in bind resource');
// set default options
_.defaults(opts, {
tplFolder: weCorePath + '/server/templates/',
routeId: ':'+ opts.name +'Id',
namePrefix: '',
templateFolderPrefix: '',
itemTitleHandler: 'i18n',
rootRoute: '/' + opts.name
});
if (opts.namespace) opts.rootRoute = opts.namespace + opts.rootRoute;
if (opts.parent) {
if (!router.resourcesByName[opts.parent]) {
throw new Error('Parent route not found: '+opts.parent+' <- '+ opts.name);
}
if (router.resourcesByName[opts.parent].itemRoute) {
opts.rootRoute = router.resourcesByName[opts.parent].itemRoute + opts.rootRoute;
} else {
opts.rootRoute = router.resourcesByName[opts.parent].itemRoute + opts.rootRoute;
}
}
if (!opts.itemRoute) opts.itemRoute = opts.rootRoute+'/'+opts.routeId;
var cfg = {
controller: ( opts.controller || opts.name ),
model: ( opts.model || opts.name )
};
if (Model.options.titleField) opts.itemTitleHandler = 'recordField';
// run all route resources bindders in router.resrouce
// this allows plugins to extend with extra resource paths
for(var r in router.resourceBinder) {
router.resourceBinder[r](we, cfg, opts, Model);
}
// set crud permissions
['find', 'create', 'update', 'delete'].forEach(function(ai){
if (!we.config.permissions[ai+'_'+cfg.model]) {
we.config.permissions[ai+'_'+cfg.model] = {
'title': ai+'_'+cfg.model
}
}
});
// after bind this resource routes, bind subRoutes
router.bindSubRouteResources(opts, we);
}
Router.prototype.bindSubRouteResources = function bindSubRouteResources(opts, we) {
if (opts.subRoutes) {
for (var resource in opts.subRoutes) {
we.router.bindResource(opts.subRoutes[resource]);
}
}
}
// variable to add resource bindders
Router.prototype.resourceBinder = {};
/**
* Bind resrouce create routes
*
* @param {Object} we
* @param {Object} cfg
* @param {Object} opts
* @param {Object} Model
*/
Router.prototype.resourceBinder.bindCreateResource = function bindCreateResource(we, cfg, opts) {
// save get create page
we.routes['get '+opts.rootRoute+'/create'] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
layoutName: opts.layoutName, // null = default layout
name: opts.namePrefix + opts.name + '.create',
action: 'create',
controller: cfg.controller,
model: cfg.model,
template: opts.templateFolderPrefix + opts.name + '/create',
fallbackTemplate: opts.tplFolder + 'default/create.hbs',
permission: 'create_' + opts.name,
titleHandler: 'i18n',
titleI18n: opts.name + '.create',
breadcrumbHandler: 'create'
},
opts.create,
we.routes['get '+opts.rootRoute+'/create'] || {}
);
// set post create page
we.routes['post '+opts.rootRoute+'/create'] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
layoutName: opts.layoutName, // null = default layout
action: 'create',
controller: cfg.controller,
model: cfg.model,
template: opts.templateFolderPrefix + opts.name + '/create',
fallbackTemplate: opts.tplFolder + 'default/create.hbs',
permission: 'create_' + opts.name,
titleHandler: 'i18n',
titleI18n: opts.name + '.create',
breadcrumbHandler: 'create'
},
opts.create,
we.routes['post '+opts.rootRoute+'/create'] || {}
);
// set post create on list for APIS
we.routes['post '+opts.rootRoute] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
action: 'create',
controller: cfg.controller,
model: cfg.model,
permission: 'create_' + opts.name,
breadcrumbHandler: 'create'
},
opts.create,
we.routes['post '+opts.rootRoute] || {}
);
}
/**
* Bind resource find routes
*
* @param {Object} we
* @param {Object} cfg
* @param {Object} opts
* @param {Object} Model
*/
Router.prototype.resourceBinder.bindGetResource = function bindGetResource(we, cfg, opts, Model) {
// set findAll
we.routes['get ' + opts.rootRoute] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
layoutName: opts.layoutName, // null = default layout
name: opts.namePrefix + opts.name + '.find',
action: 'find',
controller: cfg.controller,
model: cfg.model,
template: opts.templateFolderPrefix + opts.name + '/find',
fallbackTemplate: opts.tplFolder + 'default/find.hbs',
permission: 'find_' + opts.name,
titleHandler: 'i18n',
titleI18n: opts.name + '.find',
routeQuery: opts.routeQuery,
// default search
search: {
// since search is avaible in findAll by default
since: {
parser: 'since',
target: {
type: 'field',
field: 'createdAt'
}
}
},
breadcrumbHandler: 'find'
},
opts.findAll,
we.routes['get ' + opts.rootRoute] || {}
);
// set findOne
we.routes['get '+opts.itemRoute] = _.merge(
{
layoutName: opts.layoutName, // null = default layout
resourceName: opts.namePrefix+opts.name,
name: opts.namePrefix + opts.name + '.findOne',
action: 'findOne',
controller: cfg.controller,
model: cfg.model,
template: opts.templateFolderPrefix + opts.name + '/findOne',
fallbackTemplate: opts.tplFolder + 'default/findOne.hbs',
permission: 'find_' + opts.name,
titleHandler: opts.itemTitleHandler,
titleField: Model.options.titleField,
titleI18n: opts.name + '.findOne',
breadcrumbHandler: 'findOne'
},
opts.findOne,
we.routes['get '+opts.itemRoute] || {}
);
}
/**
* Bind resource update routes
*
* @param {Object} we
* @param {Object} cfg
* @param {Object} opts
* @param {Object} Model
*/
Router.prototype.resourceBinder.bindUpdateResource = function bindUpdateResource(we, cfg, opts, Model) {
// set get edit page
we.routes['get '+opts.itemRoute+'/edit'] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
name: opts.namePrefix + opts.name + '.edit',
layoutName: opts.layoutName, // null = default layout
action: 'edit',
controller: cfg.controller,
model: cfg.model,
template: opts.templateFolderPrefix + opts.name + '/edit',
fallbackTemplate: opts.tplFolder + 'default/edit.hbs',
permission: 'update_' + opts.name,
titleHandler: opts.itemTitleHandler,
titleField: Model.options.titleField,
titleI18n: opts.name + '.edit',
breadcrumbHandler: 'edit'
},
opts.edit,
we.routes['get '+opts.itemRoute+'/edit'] || {}
);
// set post edit page
we.routes['post '+opts.itemRoute+'/edit'] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
action: 'edit',
layoutName: opts.layoutName, // null = default layout
controller: cfg.controller,
model: cfg.model,
template: opts.templateFolderPrefix + opts.name + '/edit',
fallbackTemplate: opts.tplFolder + 'default/edit.hbs',
permission: 'update_' + opts.name,
titleHandler: opts.itemTitleHandler,
titleField: Model.options.titleField,
titleI18n: opts.name + '.edit',
breadcrumbHandler: 'edit'
},
opts.edit,
we.routes['post '+opts.itemRoute+'/edit'] || {}
);
// set put update for APIS
we.routes['put '+opts.itemRoute] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
action: 'edit',
controller: cfg.controller,
model: cfg.model,
permission: 'update_' + opts.name
},
opts.edit,
we.routes['put '+opts.itemRoute] || {}
);
// set patch update for APIS
we.routes['patch '+opts.itemRoute] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
action: 'edit',
controller: cfg.controller,
model: cfg.model,
permission: 'update_' + opts.name
},
opts.edit,
we.routes['patch '+opts.itemRoute] || {}
);
}
/**
* Bind resource delete routes
*
* @param {Object} we
* @param {Object} cfg
* @param {Object} opts
* @param {Object} Model
*/
Router.prototype.resourceBinder.bindDeleteResource = function bindDeleteResource(we, cfg, opts, Model) {
// set get delete page
we.routes['get '+opts.itemRoute+'/delete'] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
name: opts.namePrefix + opts.name + '.delete',
action: 'delete',
layoutName: opts.layoutName, // null = default layout
controller: cfg.controller,
model: cfg.model,
template: opts.templateFolderPrefix + opts.name + '/delete',
fallbackTemplate: opts.tplFolder + 'default/delete.hbs',
permission: 'delete_' + opts.name,
titleHandler: opts.itemTitleHandler,
titleField: Model.options.titleField,
titleI18n: opts.name + '.delete',
breadcrumbHandler: 'delete'
},
opts.delete,
we.routes['get '+opts.itemRoute+'/delete'] || {}
);
// set post delete page
we.routes['post '+opts.itemRoute+'/delete'] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
action: 'delete',
layoutName: opts.layoutName, // null = default layout
controller: cfg.controller,
model: cfg.model,
template: opts.templateFolderPrefix + opts.name + '/delete',
fallbackTemplate: opts.tplFolder + 'default/delete.hbs',
permission: 'delete_' + opts.name,
titleHandler: opts.itemTitleHandler,
titleField: Model.options.titleField,
titleI18n: opts.name + '.delete',
breadcrumbHandler: 'delete'
},
opts.delete,
we.routes['post '+opts.itemRoute+'/delete'] || {}
);
// bind delete for APIS
we.routes['delete '+opts.itemRoute] = _.merge(
{
resourceName: opts.namePrefix+opts.name,
action: 'delete',
controller: cfg.controller,
model: cfg.model,
permission: 'delete_' + opts.name
},
opts.delete,
we.routes['delete '+opts.itemRoute] || {}
);
}
/**
* Check if one url is absolute or relative
*
* @param {String} str url to check
* @return {Boolean} Returns true for absolute and false to relative
*/
Router.prototype.isAbsoluteUrl = function isAbsoluteUrl(str) {
return absoluteUrlRegex.test(str);
}
/**
* Parse route url to map
* Identify route params
*
* @param {String} route
* @return {Array}
*/
Router.prototype.parseRouteToMap = function parseRouteToMap(route) {
return route.split('/').map(function(r, i){
if(r[0] === ':'){
return { name: r, i: i };
} else {
return r;
}
});
}
/**
* Url builder function helper, converts one route name to url
*
* @param {String} name route name
* @param {Array} params array of params for use to build the route
* @return {String} Route url or path
*/
Router.prototype.urlTo = function urlTo (name, params){
var we = this.we;
var url = '';
var route = {};
if (we.router.routeMap.get[name]) {
route = we.router.routeMap.get[name];
var mapI = 0;
for (var i = 0; i < route.map.length; i++) {
// skip empty path parts linke // and fist /
if (!route.map[i]) continue;
if (typeof route.map[i] == 'string') {
url += '/' + route.map[i];
} else if (params && params[mapI]){
url += '/' + params[mapI];
mapI++;
} else {
we.log.warn('Invalid or undefined argument: ' + params +' ', route.map[i],'>', params[mapI], mapI, name);
}
}
} else {
we.log.verbose('Route map not found for urlTo: ' + name);
}
if (route.map && route.map.length && !url) url = '/';
return url;
}
/**
* Return a path url for given model record
*
* @param {String} modelName record model name
* @param {Object} record record
* @return {String} the path
*/
Router.prototype.pathToModel = function pathToModel(record) {
return record.urlPath();
}
/**
* Context loader middleware, run after others router middleware
* and preload related record data for use in acl and controller
*/
Router.prototype.contextLoader = function contextLoader(req, res, next) {
var router = req.we.router;
var hooks = req.we.hooks;
// only load context one time per request
if (req.weContextLoaded) return next();
// save current user reference for use in template
res.locals.currentUser = req.user;
// get route configs
var config = this.config;
// merge context var with route configs
_.merge(res.locals, config);
// save all params values as array for router.urlTo
req.paramsArray = _.toArray(req.params);
// set accept headers based in config.responseType, this overrides req.query.responseType
if (config.responseType) req.headers.accept = mime.lookup(config.responseType);
hooks.trigger('we:router:request:before:load:context', {
req: req, res: res
}, function runTheContextLoader(err) {
if (err) return res.serverError(err);
// set redirectTo
res.locals.redirectTo = req.we.utils.getRedirectUrl(req, res);
router.parseQuery(req, res, function(err, query) {
res.locals.query = query;
// skip record load if dont find model config
if (!res.locals.model ) {
return router.contextLoaded.bind({
req: req, res: res, next: next, hooks: hooks
})();
}
// set model class
res.locals.Model = req.we.db.models[res.locals.model];
// set id if exists and not is set
if (!res.locals.id) {
if (req.params[res.locals.model + 'Id']) {
res.locals.id = req.params[res.locals.model + 'Id'];
} else if (req.params.id) {
res.locals.id = req.params.id;
}
}
// set teaserTemplateName
res.locals.teaserTemplateName = res.locals.model + '/teaser';
// set load current record flag for single records requests
res.locals.loadCurrentRecord = router.needLoadCurrentModel(req, res);
// default template
if (!res.locals.template)
res.locals.template = res.locals.model + '/' + res.locals.action;
// run model context loader if exists
if (
req.we.db.models[res.locals.model] &&
typeof req.we.db.models[res.locals.model].contextLoader == 'function'
) {
req.we.db.models[res.locals.model].contextLoader(req, res, function afterRunModelContextLoader(err) {
return router.contextLoaded.bind({
req: req, res: res, next: next, hooks: hooks
})(err);
});
} else {
return router.contextLoaded.bind({
req: req, res: res, next: next, hooks: hooks
})();
}
});
});
}
/**
* contextLoaded , function how runs after contextLoader
* @param {Object} err if context loader returns error
*/
Router.prototype.contextLoaded = function contextLoaded(err) {
this.req.weContextLoaded = true;
if (err) return this.next(err);
// router.resourceCacheMiddleware(req, res, function() {
this.hooks.trigger('we:router:request:after:load:context', {
req: this.req, res: this.res
}, function afterRunAfterLoadContextHook(err) {
if (err) return this.res.serverError(err);
if (!this.req.user) return this.next();
// preload user roles
return this.req.user.getRoles()
.then(function afterGetUserRoles(result) {
this.next(null, result);
}.bind(this));
}.bind(this));
// });
}
/**
* Check resource cache
* @param {[type]} req [description]
* @param {[type]} res [description]
* @param {Function} next [description]
* @return {[type]} [description]
*/
Router.prototype.resourceCacheMiddleware = function resourceCacheMiddleware(req, res, next) {
// check modified cache
if (
!req.we.config.cache.skipResourceCache &&
req.we.config.cache.resourceCacheActions.indexOf(res.locals.action) > -1 &&
res.locals.data &&
res.locals.data.updatedAt
) {
if (req.header('If-Modified-Since') == res.locals.data.updatedAt.toUTCString()) {
// if not modified, return a 304 with empty response
return res.status(304).end();
} else {
res.setHeader('Last-Modified', res.locals.data.updatedAt.toUTCString());
}
}
next();
};
/**
* Add one route after 404 middleware for bind routes without
* server restart
*/
Router.prototype.liveBindRoute = function bindRoute(we, route, config) {
var allRoutes = we.express._router.stack;
we.router.bindRoute(we, route, config);
// push this new route to after 404 and error middlewares
allRoutes.splice(allRoutes.length-3, 0, allRoutes.splice(allRoutes.length-1, 1)[0]);
}
/**
* Check if need to load current model
*
* @param {Object} req express request
* @param {Object} res express response
* @return {Boolean}
*/
Router.prototype.needLoadCurrentModel = function needLoadCurrentModel(req, res) {
if (res.locals.loadRecord === false) return false;
if (res.locals.loadRecord === true) return true;
if (!res.locals.action) return false;
if ( this.singleRecordActions.indexOf( res.locals.action ) >-1 ) {
return true;
}
}
/**
* Parse req.query for current request
*
* @param {Object} req express request
* @param {Object} res express response
* @param {Function} next Callback
*/
Router.prototype.parseQuery = function parseQuery(req, res, next) {
var we = req.we;
var query = {
// set subQuery to false by default
subQuery: false,
where: {}
};
if (res.locals.skipParseQuery) return next(null, query);
// resolve query limit and offset
if (res.locals.query && res.locals.query.limit) {
// this allows to set query.limit in routes
query.limit = res.locals.query.limit;
} else if (req.query.limit) {
if (req.query.limit < we.config.queryMaxLimit) {
query.limit = req.query.limit;
} else {
query.limit = we.config.queryMaxLimit;
}
} else {
query.limit = we.config.queryDefaultLimit;
}
query.limit = Number(query.limit);
if (req.query.page && Number(req.query.page) ) {
query.offset = query.limit*(req.query.page-1);
query.page = req.query.page;
} else {
query.offset = 0;
query.page = 1;
}
if (req.query.order) {
query.order = res.locals.model + '.' +req.query.order;
} else if (req.query.sort) {
query.order = res.locals.model + '.' +req.query.sort;
} else {
// default order
query.order = [['createdAt', 'DESC']];
}
// set default query include
// query.include = [{ all: true, required: false}];
this.addRequestNx1Assocs(req, res, query);
// query where ex req.query.where
if (req.we.config.enableQueryWhere) this.parseQueryWhere(req, res, query);
// we.js query search route config
this.parseQuerySearch(req, res, query);
return next(null, query);
}
/**
* Parse request query where
*
* @param {Object} req express.js request
* @param {Object} res express.js response
* @param {Object} query query object to save results
*/
Router.prototype.parseQueryWhere = function parseQueryWhere(req, res, query) {
// parse where
try {
if (req.query.where) query.where = JSON.parse( req.query.where );
} catch(e) {
req.we.log.warn('req.query.where have a invalid format', req.query.where);
return res.badRequest();
}
// override url query with router query
if (res.locals.routeQuery) _.merge(query.where, res.locals.routeQuery);
// parse associations
if (res.locals.model && req.we.db.models[res.locals.model]) {
if (req.we.db.models[res.locals.model].associations) {
var assocFields = Object.keys(req.we.db.models[res.locals.model].associations);
var identifier;
for (var i = assocFields.length - 1; i >= 0; i--) {
identifier = req.we.db.models[res.locals.model].associations[assocFields[i]].identifier;
// parse in body params
if (req.body && req.body[assocFields[i]]) {
req.body[identifier] = req.body[assocFields[i]];
}
// parse in where
if (query.where && query.where[assocFields[i]]) {
query.where[identifier] = query.where[assocFields[i]];
}
}
}
}
}
/**
* Parse request query search
*
* @param {Object} req express.js request
* @param {Object} res express.js response
* @param {Object} query query object to save results
*/
Router.prototype.parseQuerySearch = function parseQuerySearch(req, res, query) {
// parse query search
if (res.locals.search) {
for (var sName in res.locals.search) {
if (req.query[sName] || res.locals.search[sName].runIfNull) {
this.search.targets[res.locals.search[sName].target.type](
sName, res.locals.search[sName], req.query[sName], query, req, res
)
}
}
}
}
/**
* Parse body requests
*
* @param {Object} req express request
* @param {Object} res express response
* @param {Function} next callback
*/
Router.prototype.parseBody = function parseBody(req ,res, next) {
if (req.we.config.updateMethods.indexOf(req.method) >-1 ) {
for(var p in req.body) {
if (req.body[p] === '' ) {
// change empty string body params to null
req.body[p] = null;
} else if (req.body[p] === 'false' ) {
// change false strings to boolean false value
req.body[p] = false;
}
}
if (req.body.setAlias) {
// check if can model setAlias
if (!req.we.acl.canStatic('setAlias', req.userRoleNames)) {
delete req.body.setAlias;
}
}
}
next();
}
/**
* Add request N x 1 associations in current request query.include for use in query
* @param {Object} req express.js request
* @param {Object} res express.js response
*/
Router.prototype.addRequestNx1Assocs = function addRequestNx1Assocs(req, res, query) {
if (
res.locals.model &&
req.we.db.modelsConfigs[res.locals.model] &&
req.we.db.modelsConfigs[res.locals.model].associations
) {
query.include = [];
var assocs = req.we.db.modelsConfigs[res.locals.model].associations;
for(var name in assocs) {
if (assocs[name].type == 'belongsTo') {
query.include.push({
model: req.we.db.models[assocs[name].model],
as: name,
required: false
})
}
}
if (!query.include.length) delete query.include;
}
}
/**
* Helper function for get and split extension from url
*
* @param {String} url
* @return {String} extension or null
*/
Router.prototype.splitExtensionFromURL = function splitExtensionFromURL (url){
var path = url.split('?')[0];
var urlParts = path.split('/');
// get file name and extension
var nameAndExt = urlParts[urlParts.length-1].split('.');
// this path dont have one extension
if (nameAndExt.length < 2) return null;
// get extension
return nameAndExt[nameAndExt.length-1];
}
/**
* Check if url is public folder url
*
* @param {String} url
* @return {Boolean}
*/
Router.prototype.isPublicFolder = function isPublicFolder (url){
return url.startsWith('/public') ||
url == '/robots.txt' ||
url == '/favicon.ico';
}
module.exports = Router;