/*------------------------------------*\
ROUTES
\*------------------------------------*/
/* jslint node: true */
/**
* Add route to express.
* @private
* @function loadRoutes
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
*/
function loadRoutes(NA) {
/* For each `webconfig.routes`. */
NA.forEach(NA.webconfig.routes, function (currentUrl) {
NA.request(currentUrl, NA.webconfig.routes);
});
}
/**
* Crawl all routes and execute each file with response requested by client after passed into `NA#controllers[].setRoutes` hook.
* @private
* @function initRoutes
* @memberOf NA#
* @this NA
*/
exports.initRoutes = function () {
var NA = this,
runIndexPage = function () {
/* Only if server was started and `index` is set to « true ». */
if (
/**
* Allow NodeAtlas to create a root page with a link for each routes if set to true.
* @public
* @alias index
* @type {boolean}
* @memberOf NA#webconfig
* @default false
*/
NA.webconfig.index) {
NA.indexPage();
}
};
if (typeof NA.controllers[NA.webconfig.controller] !== 'undefined' &&
typeof NA.controllers[NA.webconfig.controller].setRoutes !== 'undefined') {
/**
* Set all routes before the index was started.
* @function setRoutes
* @memberOf NA#controllers[]
* @this NA
* @param {NA~callback} next Next steps after routes are setted.
*/
NA.controllers[NA.webconfig.controller].setRoutes.call(NA, function () {
runIndexPage();
loadRoutes(NA);
NA.pageNotFound();
});
/* ...else, just continue. */
} else {
runIndexPage();
loadRoutes(NA);
NA.pageNotFound();
}
};
/**
* Create a « Overview » page to « / » url with all of page accessible via links.
* @private
* @function indexPage
* @memberOf NA#
* @this NA
*/
exports.indexPage = function () {
var NA = this,
url = NA.modules.url,
path = NA.modules.path,
opn = NA.modules.opn,
urlOutput = url.resolve(NA.webconfig.urlRoot, path.join(NA.webconfig.urlRelativeSubPath, ((typeof NA.configuration.browse === 'string') ? NA.configuration.browse : "")));
/* Create a new path to « / ». Erase the route to « / » defined into `routes`. */
NA.express.get(url.format(path.join("/", NA.webconfig.urlRelativeSubPath, "/")), function (request, response) {
var data = {},
matches = function (regex, matches) { return data[matches]; };
data.render = "";
/* List all routes... */
NA.forEach(NA.webconfig.routes, function (page) {
var routeParameters;
if (typeof page !== "string") {
routeParameters = page;
} else {
routeParameters = NA.webconfig.routes[page];
}
data.page = page;
if (routeParameters.url) {
data.page = routeParameters.url;
}
if (routeParameters.output !== false) {
data.page = decodeURIComponent(data.page);
data.render += NA.cliLabels.indexPage.line.replace(/%([\-a-zA-Z0-9_]+)%/g, matches);
}
});
/* ...and provide a page. */
response.writeHead(200, NA.cliLabels.indexPage.charsetAndType);
response.end(NA.cliLabels.indexPage.data.replace(/%([\-a-zA-Z0-9_]+)%/g, function (regex, matches) { return data[matches]; }));
});
/* Display index after all routes are setted. */
if (NA.configuration.browse) {
opn(urlOutput);
}
};
/**
* Set the public directory for asset like CSS/JS and media.
* @private
* @function initStatics
* @memberOf NA#
* @this NA
*/
exports.initStatics = function () {
var NA = this,
express = NA.modules.express,
path = NA.modules.path,
url = NA.modules.url,
staticOptions = { maxAge: 86400000 * 30 };
/* No Cache */
if (!NA.webconfig.cache) {
staticOptions.etag = false;
staticOptions.maxAge = 0;
staticOptions.lastModified = false;
}
/**
* Set information for static file from `assetsRelativePath`.
* @public
* @see http://expressjs.com/api.html#express.static for options.
* @alias staticOptions
* @type {string}
* @memberOf NA#webconfig
*/
staticOptions = NA.extend(staticOptions, NA.webconfig.staticOptions);
NA.express.use(NA.webconfig.urlRelativeSubPath, express.static(path.join(NA.serverPath, NA.webconfig.assetsRelativePath), staticOptions));
NA.forEach(NA.webconfig.statics, function (directory, directories) {
var virtual = directory,
real = directories[directory],
options;
if (NA.webconfig.statics instanceof Array) {
virtual = directory.virtual;
real = directory;
}
if (typeof real === "object") {
options = real.staticOptions || staticOptions;
real = real.path;
}
NA.express.use(url.format(path.join(NA.webconfig.urlRelativeSubPath, virtual)), express.static(path.join(NA.serverPath, real), options));
});
};
/**
* Affect support of GET, POST, UPDATE, DELETE and OPTIONS to a route.
* @private
* @function setSupport
* @memberOf NA~
* @param {boolean} support Type of support GET, POST, PUT, DELETE or OPTIONS.
* @param {boolean} path Instruction support for all page.
* @param {boolean} options Instruction support for current page.
* @return {boolean} if support is effective;
*/
function setSupport(support, common, specific) {
/* Manage GET / POST / PUT / DELETE / OPTIONS support for an url. */
if (common === false || common === "false") {
support = false;
}
if (common === true || common === "true") {
support = true;
}
if (specific === false || specific === "false") {
support = false;
}
if (specific === true || specific === "true") {
support = true;
}
return support;
}
/**
* Set parameters authorization.
* @private
* @function supportGet
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
* @param {Object} routeParameters Parameters set into `routes[<currentRoute>]`.
* @param {boolean} getSupport Represent initial state of support.
* @return {boolean} if support is effective;
*/
function supportGet(NA, routeParameters, getSupport) {
return setSupport(getSupport,
/**
* Allow you to avoid or authorize GET response for all page.
* @public
* @alias get
* @type {boolean}
* @memberOf NA#webconfig
* @default true
*/
NA.webconfig.get,
/**
* Allow you to avoid or authorize GET response for current page.
* @public
* @alias get
* @type {boolean}
* @memberOf NA#locals.routeParameters
* @default true
*/
routeParameters.get
);
}
/**
* Set parameters authorization.
* @private
* @function supportPost
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
* @param {Object} routeParameters Parameters set into `routes[<currentRoute>]`.
* @param {boolean} postSupport Represent initial state of support.
* @return {boolean} if support is effective;
*/
function supportPost(NA, routeParameters, postSupport) {
return setSupport(postSupport,
/**
* Allow you to avoid or authorize POST response for all page.
* @public
* @alias post
* @type {boolean}
* @memberOf NA#webconfig
* @default true
*/
NA.webconfig.post,
/**
* Allow you to avoid or authorize POST response for current page.
* @public
* @alias post
* @type {boolean}
* @memberOf NA#locals.routeParameters
* @default true
*/
routeParameters.post
);
}
/**
* Set parameters authorization.
* @private
* @function supportPut
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
* @param {Object} routeParameters Parameters set into `routes[<currentRoute>]`.
* @param {boolean} putSupport Represent initial state of support.
* @return {boolean} if support is effective;
*/
function supportPut(NA, routeParameters, putSupport) {
return setSupport(putSupport,
/**
* Allow you to avoid or authorize PUT response for all page.
* @public
* @alias put
* @type {boolean}
* @memberOf NA#webconfig
* @default false
*/
NA.webconfig.put,
/**
* Allow you to avoid or authorize PUT response for current page.
* @public
* @alias put
* @type {boolean}
* @memberOf NA#locals.routeParameters
* @default false
*/
routeParameters.put
);
}
/**
* Set parameters authorization.
* @private
* @function supportDelete
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
* @param {Object} routeParameters Parameters set into `routes[<currentRoute>]`.
* @param {boolean} deleteSupport Represent initial state of support.
* @return {boolean} if support is effective;
*/
function supportDelete(NA, routeParameters, deleteSupport) {
return setSupport(deleteSupport,
/**
* Allow you to avoid or authorize DELETE response for all page.
* @public
* @alias delete
* @type {boolean}
* @memberOf NA#webconfig
* @default false
*/
NA.webconfig.delete,
/**
* Allow you to avoid or authorize DELETE response for current page.
* @public
* @alias delete
* @type {boolean}
* @memberOf NA#locals.routeParameters
* @default false
*/
routeParameters.delete
);
}
/**
* Set parameters authorization.
* @private
* @function supportOptions
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
* @param {Object} routeParameters Parameters set into `routes[<currentRoute>]`.
* @param {boolean} optionsSupport Represent initial state of support.
* @return {boolean} if support is effective;
*/
function supportOptions(NA, routeParameters, optionsSupport) {
return setSupport(optionsSupport,
/**
* Allow you to avoid or authorize OPTIONS response for all page.
* @public
* @alias options
* @type {boolean}
* @memberOf NA#webconfig
* @default false
*/
NA.webconfig.options,
/**
* Allow you to avoid or authorize OPTIONS response for current page.
* @public
* @alias options
* @type {boolean}
* @memberOf NA#locals.routeParameters
* @default false
*/
routeParameters.options
);
}
/**
* Listen a specific request.
* @private
* @function request
* @memberOf NA#
* @this NA
* @param {string} path The url listening.
* @param {Object} options Options associate to this url.
*/
exports.request = function (path, options) {
var NA = this,
routeParameters,
getSupport = true,
postSupport = true,
putSupport = false,
deleteSupport = false,
optionsSupport = false,
objectPath;
/* Case of `path` is an object because `NA.webconfig.routes` is an array and not an object. */
if (typeof path === "object") {
routeParameters = path;
} else {
routeParameters = options[path];
}
/* Adding of subfolder before url listening. */
objectPath = NA.webconfig.urlRelativeSubPath + ((
/**
* If setted, replace « The url listening ». « The url listening. » become a « key » value.
* @public
* @alias url
* @type {string}
* @memberOf NA#locals.routeParameters
*/
routeParameters.url) ? routeParameters.url : path);
/* Manage GET / POST / PUT / DELETE / OPTIONS support for an url. */
getSupport = supportGet(NA, routeParameters, getSupport);
postSupport = supportPost(NA, routeParameters, postSupport);
putSupport = supportPut(NA, routeParameters, putSupport);
deleteSupport = supportDelete(NA, routeParameters, deleteSupport);
optionsSupport = supportOptions(NA, routeParameters, optionsSupport);
/* Ask for a page in GET, POST, PUT or DELETE. */
NA.requestRegex({
getSupport: getSupport,
postSupport: postSupport,
putSupport: putSupport,
deleteSupport: deleteSupport,
optionsSupport: optionsSupport
}, objectPath, options, path, routeParameters);
};
/**
* Listen a specific request (Regex Part).
* @private
* @function requestRegex
* @memberOf NA#
* @param {Object} support Contain all GET, POST, PUT, DELETE or OPTIONS capability.
* @param {boolean} support.getSupport This page can be requested by GET ?
* @param {boolean} support.postSupport This page can be requested by POST ?
* @param {boolean} support.putSupport This page can be requested by PUT ?
* @param {boolean} support.deleteSupport This page can be requested by DELETE ?
* @param {boolean} support.optionsSupport This page can be requested by OPTIONS ?
* @param {string} objectPath The list of Url match for obtain response.
* @param {Object} options Option associate to this url.
* @param {string} path The Url in routes' webconfig.
* @param {Object} routeParameters Parameters for this route.
*/
exports.requestRegex = function (support, objectPath, options, path, routeParameters) {
var NA = this;
/* Allow you to use regex into your url route... */
if (
/**
* Use RegExp expression as selector for route url If setted to true.
* Same if is a string but string represent option like "g".
* @public
* @alias regExp
* @type {string|boolean}
* @default false
* @memberOf NA#locals.routeParameters
*/
routeParameters.regExp
) {
/* ...with options... */
if (typeof routeParameters.regExp === 'string') {
objectPath = new RegExp(objectPath, routeParameters.regExp);
/* ...or not... */
} else {
objectPath = new RegExp(objectPath);
}
}
/* Ask for a page in GET / POST / PUT / DELETE / OPTIONS. */
NA.executeRequest(support, objectPath, options, path);
};
/**
* Prepare locals and headers.
* @private
* @function prepare
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
* @param {Object} path Key/Path for routes.
* @param {boolean} options Value(s) for routes.
* @return {function} Preparation middleware.
*/
function prepare(NA, path, options) {
return function (request, response, next) {
/* ...else execute render... */
NA.prepareResponse(path, options, request, response, function () {
next();
});
};
}
/**
* If page must be redirected.
* @private
* @function redirect
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
* @param {Object} routeParameters Parameters for route.
* @return {function} Preparation redirect.
*/
function redirect(NA, routeParameters) {
return function (request, response, next) {
/* Verify if route is a redirection... */
if (routeParameters.redirect && routeParameters.statusCode) {
/* ...and if is it, redirect... */
return NA.redirect(routeParameters, request, response);
}
next();
};
}
/**
* If page must be redirected.
* @private
* @function redirect
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
* @return {function} Preparation middleware.
*/
function response(NA) {
return function (request, response, next) {
/* ...else execute render... */
NA.changeVariationsCommon(response.locals, request, response, function () {
next();
});
};
}
/**
* Create Middleware function.
* @private
* @function getMiddlewares
* @memberOf NA~
* @param {NA} NA NodeAtlas instance.
* @param {Object} routeParameters Parameters for route.
* @return {function} Preparation middleware.
*/
function getMiddlewares(NA, routeParameters) {
var path = NA.modules.path,
middlewares = [];
function hasMiddlewaresFile(callback) {
if (
/**
* Allow you to set Express middleware for a specific route.
* @public
* @alias middlewares
* @type {string}
* @memberOf NA#locals.routeParameters
*/
routeParameters.middlewares) {
middlewares = [];
if (routeParameters.middlewares instanceof Array) {
routeParameters.middlewares.forEach(function (middleware) {
callback(require(path.join(NA.serverPath, NA.webconfig.middlewaresRelativePath, middleware)).bind(NA));
});
} else {
callback(require(path.join(NA.serverPath, NA.webconfig.middlewaresRelativePath, routeParameters.middlewares)).bind(NA));
}
}
}
hasMiddlewaresFile(function (file) {
var content;
try {
content = file();
} catch (e) {
content = "";
}
if (content instanceof Array) {
middlewares = middlewares.concat(content);
} else {
middlewares.push(file);
}
});
return function() { return middlewares; };
}
/**
* Ask for a page in GET, POST, UPDATE, DELETE or OPTIONS.
* @private
* @function executeRequest
* @memberOf NA#
* @param {Object} support Contain all GET, POST, PUT and DELETE capability.
* @param {boolean} support.getSupport This page can be requested by GET ?
* @param {boolean} support.postSupport This page can be requested by POST ?
* @param {boolean} support.putSupport This page can be requested by PUT ?
* @param {boolean} support.deleteSupport This page can be requested by DELETE ?
* @param {boolean} support.optionsSupport This page can be requested by OPTIONS ?
* @param {string} objectPath The list of Url match for obtain response.
* @param {Object} options Option associate to this url.
* @param {string} path The Url in routes' webconfig.
*/
exports.executeRequest = function (support, objectPath, options, path) {
var NA = this,
routeParameters,
middlewares;
/* Case of `path` is an object because `NA.webconfig.routes` is an array and not an object. */
if (typeof path === "object") {
routeParameters = path;
} else {
routeParameters = options[path];
}
middlewares = getMiddlewares(NA, routeParameters)();
/** Execute Get Request */
if (support.getSupport) {
NA.express.get(objectPath, prepare(NA, path, options), middlewares, redirect(NA, routeParameters), response(NA));
}
/** Execute Post Request */
if (support.postSupport) {
NA.express.post(objectPath, prepare(NA, path, options), middlewares, redirect(NA, routeParameters), response(NA));
}
/** Execute Put Request */
if (support.putSupport) {
NA.express.put(objectPath, prepare(NA, path, options), middlewares, redirect(NA, routeParameters), response(NA));
}
/** Execute Delete Request */
if (support.deleteSupport) {
NA.express.delete(objectPath, prepare(NA, path, options), middlewares, redirect(NA, routeParameters), response(NA));
}
/** Execute Options Request */
if (support.optionsSupport) {
NA.express.options(objectPath, prepare(NA, path, options), middlewares, redirect(NA, routeParameters), function (request, response) {
response.sendStatus(200);
});
}
};
/**
* Send HTML result to the client.
* @private
* @function sendResponse
* @memberOf NA#
* @param {Object} request Initial request.
* @param {Object} response Initial response.
* @param {string} data HTML DOM ready for sending.
* @param {NA~callback} next Next step after.
*/
exports.sendResponse = function (request, response, data) {
/* Set/Send body */
response.write(data);
response.end();
};
/**
* Redirect a page to an other page if option page is set for that.
* @private
* @function redirect
* @memberOf NA#
* @param {Object} routeParameters All information associate with the redirection.
* @param {Object} request Initial request.
* @param {Object} response Initial response.
*/
exports.redirect = function (routeParameters, request, response) {
var NA = this,
location,
path = NA.modules.path;
/* Re-inject param into redirected url if is replaced by regex. */
if (routeParameters.regExp) {
/**
* Represent route to redirect if current route matched.
* @public
* @alias redirect
* @type {string}
* @memberOf NA#locals.routeParameters
*/
location = routeParameters.redirect.replace(/\$([0-9]+)/g, function (regex, matches) { return request.params[matches]; });
/* Or by standard selector. */
} else {
location = routeParameters.redirect.replace(/\:([a-z0-9]+)/g, function (regex, matches) { return request.params[matches]; });
}
/* Set status and new location. */
response.writeHead(routeParameters.statusCode, {
Location: path.join(NA.webconfig.urlRelativeSubPath, location)
});
/* No more data. */
response.end();
};
/**
* Define a page to display when no url match in route or in directories provided by `NA#initStatics`.
* @private
* @function pageNotFound
* @memberOf NA#
* @this NA
*/
exports.pageNotFound = function () {
var NA = this,
pageNotFound,
key,
i,
/**
* Represent route to use if no route match in all route.
* @public
* @alias pageNotFound
* @type {string}
* @memberOf NA#webconfig
*/
pageNotFoundUrl = NA.webconfig.pageNotFound,
middlewares;
if (NA.webconfig.routes instanceof Array) {
for (i = 0; i < NA.webconfig.routes.length; i++) {
key = NA.webconfig.routes[i].key || NA.webconfig.routes[i].url;
if (NA.webconfig.pageNotFound && key === NA.webconfig.pageNotFound) {
pageNotFound = NA.webconfig.routes[i];
pageNotFoundUrl = NA.webconfig.routes[i];
}
}
} else if (NA.webconfig.pageNotFound && NA.webconfig.routes[NA.webconfig.pageNotFound]) {
pageNotFound = NA.webconfig.routes[NA.webconfig.pageNotFound];
}
if (pageNotFound) {
middlewares = getMiddlewares(NA, pageNotFound)();
/* Match all Get Request */
NA.express.all("*", prepare(NA, pageNotFoundUrl, NA.webconfig.routes), middlewares, redirect(NA, pageNotFound), response(NA));
}
};