lib/routing.js

  1. /**
  2. * @file Set up Express, Handlebars and routes/views
  3. * @author Antonio Olmo Titos <a@olmo-titos.info>
  4. * @exports lib/routing
  5. */
  6. // Configuration:
  7. const SELF = require('../package'),
  8. CONFIG = require('../config'),
  9. ROUTES = require('./routes');
  10. // External packages:
  11. const EXPRESS_HANDLEBARS = require('express-handlebars'),
  12. BODY_PARSER = require('body-parser');
  13. // Internal packages:
  14. const AUTHENTICATION = require('./authentication'),
  15. PERSISTENCE = require('./persistence'),
  16. TIMEZONES = require('./timezones');
  17. /**
  18. * Generic handler for errors
  19. * @param {Object} req - Express request
  20. * @param {Object} res - Express response
  21. * @param {String} message - (optional) additional error message to display
  22. */
  23. const four_oh_four = function(req, res, message) {
  24. const context = buildContext(req, {title: 'uh?', message: message});
  25. res.render('404', context);
  26. };
  27. /**
  28. * Context baseline for Handlebars rendering
  29. * @param {Object} req - (optional) Express request
  30. * @param {Object} props - (optional) data (keys/properties) to add to the context
  31. * @returns {Object} a context to use in rendering
  32. */
  33. const buildContext = function(req, props) {
  34. const result = {
  35. version: SELF.version,
  36. debug: CONFIG.debug,
  37. };
  38. if (req) {
  39. result.known = AUTHENTICATION.isKnownUser(req);
  40. if (result.known)
  41. result.user = AUTHENTICATION.getUser(req);
  42. }
  43. for (var i in props)
  44. result[i] = props[i];
  45. return result;
  46. };
  47. /**
  48. * Set up views using templates and Express Handlebars
  49. * @param {Object} app - the <a href="http://expressjs.com/">Express</a> application
  50. */
  51. const setUp = function(app) {
  52. const HANDLEBARS = EXPRESS_HANDLEBARS.create({defaultLayout: 'main'});
  53. app.set('title', 'Eunomia');
  54. app.set('case sensitive routing', true);
  55. app.set('strict routing', false);
  56. app.set('view cache', !CONFIG.debug);
  57. app.set('view engine', 'hbs');
  58. app.engine('hbs', HANDLEBARS.engine);
  59. app.use(BODY_PARSER.urlencoded({extended: true}));
  60. // Standard pages:
  61. for (var i in ROUTES) {
  62. const route = ROUTES[i];
  63. app.get(i, function(req, res) {
  64. const context = buildContext(req, {title: route.title, special: route.special});
  65. if (route.list) {
  66. const list = PERSISTENCE.listEntities(route.list);
  67. list.then(function(rows) {
  68. context.list = rows;
  69. res.render(route.view, context);
  70. });
  71. list.catch(function() {
  72. // @TODO: handle errors here.
  73. });
  74. } else if (route.timezones) {
  75. context.list = TIMEZONES.tzContinents;
  76. res.render(route.view, context);
  77. } else
  78. res.render(route.view, context);
  79. });
  80. if (route.post)
  81. if ('login' === route.post)
  82. app.post(i, function(req, res) {
  83. const user = PERSISTENCE.findEntity(req.body.name);
  84. user.then(function(data) {
  85. if ('string' === typeof data) {
  86. // @TODO: handle this error.
  87. res.status(401).end();
  88. } else if (PERSISTENCE.TYPE_PERSON === data.type) {
  89. AUTHENTICATION.setUser(req, data.data);
  90. // res.send(`/${data.data.name}`);
  91. res.send('/');
  92. } else {
  93. // @TODO: handle this error.
  94. res.status(401).end();
  95. }
  96. });
  97. user.catch(function() {
  98. // @TODO: handle this error.
  99. res.status(401).end();
  100. });
  101. });
  102. else if ('signup' === route.post)
  103. app.post(i, function(req, res) {
  104. const newUser = req.body;
  105. if (TIMEZONES.timezoneExists(newUser.name))
  106. res.send('user ID not valid');
  107. else {
  108. // if (PEOPLE.createNewUser(newUser))
  109. // LOGGING.debug('ok');
  110. }
  111. const context = buildContext(req, {title: route.title});
  112. res.render(route.view, context);
  113. });
  114. }
  115. // Standard favicon:
  116. app.get('favicon.ico', function(req, res) {
  117. res.redirect('https://www.w3.org/2015/labs/favicon.ico');
  118. });
  119. // Timezone pages:
  120. app.get('/:continent/:city', function(req, res) {
  121. const p1 = req.params.continent,
  122. p2 = req.params.city,
  123. a1 = p1.replace(/_/g, '&nbsp;'),
  124. a2 = p2.replace(/_/g, '&nbsp;'),
  125. tz = `${p1}/${p2}`,
  126. title = `${a1}&nbsp;/&nbsp;${a2}`;
  127. var found = TIMEZONES.tzContinents[p1];
  128. if (found)
  129. found = found[p2];
  130. else
  131. found = undefined;
  132. if (found) {
  133. const context = buildContext(req, {title: title, tz: tz});
  134. TIMEZONES.processTimezone(context);
  135. res.render('timezone', context);
  136. } else
  137. four_oh_four(req, res, 'TZ not found');
  138. });
  139. // /me:
  140. app.get('/me', function(req, res) {
  141. if (AUTHENTICATION.isKnownUser(req)) {
  142. const id = AUTHENTICATION.getUser(req).name;
  143. res.redirect(`/${id}`);
  144. } else
  145. res.redirect('/login');
  146. });
  147. // /logout:
  148. app.get('/logout', function(req, res) {
  149. if (AUTHENTICATION.isKnownUser(req))
  150. AUTHENTICATION.logOut(req);
  151. res.redirect('/');
  152. });
  153. // Entity pages:
  154. app.get('/:id', function(req, res) {
  155. const context = buildContext(req);
  156. const result = PERSISTENCE.findEntity(req.params.id);
  157. result.then(function(data) {
  158. if ('string' === typeof data)
  159. four_oh_four(req, res, data);
  160. else {
  161. context.title = data.data.name;
  162. context.item = data.data;
  163. if (PERSISTENCE.TYPE_PERSON === data.type)
  164. res.render('person', context);
  165. else if (PERSISTENCE.TYPE_MEETING === data.type)
  166. res.render('meeting', context);
  167. else if (PERSISTENCE.TYPE_LOCATION === data.type)
  168. res.render('location', context);
  169. else
  170. res.send(data);
  171. }
  172. });
  173. result.catch(function() {
  174. four_oh_four(req, res);
  175. });
  176. });
  177. };
  178. // Export stuff:
  179. exports.setUp = setUp;