entries.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. "use strict";
  2. var Promise = require('bluebird'),
  3. path = require('path'),
  4. fs = Promise.promisifyAll(require("fs")),
  5. _ = require('lodash'),
  6. farmhash = require('farmhash'),
  7. BSONModule = require('bson'),
  8. BSON = new BSONModule.BSONPure.BSON();
  9. /**
  10. * Entries Model
  11. */
  12. module.exports = {
  13. _repoPath: 'repo',
  14. _cachePath: 'data/cache',
  15. /**
  16. * Initialize Entries model
  17. *
  18. * @param {Object} appconfig The application config
  19. * @return {Object} Entries model instance
  20. */
  21. init(appconfig) {
  22. let self = this;
  23. self._repoPath = appconfig.datadir.repo;
  24. self._cachePath = path.join(appconfig.datadir.db, 'cache');
  25. return self;
  26. },
  27. /**
  28. * Fetch an entry from cache, otherwise the original
  29. *
  30. * @param {String} entryPath The entry path
  31. * @return {Object} Page Data
  32. */
  33. fetch(entryPath) {
  34. let self = this;
  35. let cpath = path.join(self._cachePath, farmhash.fingerprint32(entryPath) + '.bson');
  36. return fs.statAsync(cpath).then((st) => {
  37. return st.isFile();
  38. }).catch((err) => {
  39. return false;
  40. }).then((isCache) => {
  41. if(isCache) {
  42. // Load from cache
  43. return fs.readFileAsync(cpath).then((contents) => {
  44. return BSON.deserialize(contents);
  45. }).catch((err) => {
  46. winston.error('Corrupted cache file. Deleting it...');
  47. fs.unlinkSync(cpath);
  48. return false;
  49. });
  50. } else {
  51. // Load original
  52. return self.fetchOriginal(entryPath);
  53. }
  54. });
  55. },
  56. /**
  57. * Fetches the original document entry
  58. *
  59. * @param {String} entryPath The entry path
  60. * @param {Object} options The options
  61. * @return {Object} Page data
  62. */
  63. fetchOriginal(entryPath, options) {
  64. let self = this;
  65. let fpath = path.join(self._repoPath, entryPath + '.md');
  66. let cpath = path.join(self._cachePath, farmhash.fingerprint32(entryPath) + '.bson');
  67. options = _.defaults(options, {
  68. parseMarkdown: true,
  69. parseMeta: true,
  70. parseTree: true,
  71. includeMarkdown: false,
  72. includeParentInfo: true,
  73. cache: true
  74. });
  75. return fs.statAsync(fpath).then((st) => {
  76. if(st.isFile()) {
  77. return fs.readFileAsync(fpath, 'utf8').then((contents) => {
  78. // Parse contents
  79. let pageData = {
  80. markdown: (options.includeMarkdown) ? contents : '',
  81. html: (options.parseMarkdown) ? mark.parseContent(contents) : '',
  82. meta: (options.parseMeta) ? mark.parseMeta(contents) : {},
  83. tree: (options.parseTree) ? mark.parseTree(contents) : []
  84. };
  85. if(!pageData.meta.title) {
  86. pageData.meta.title = _.startCase(entryPath);
  87. }
  88. pageData.meta.path = entryPath;
  89. // Get parent
  90. let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
  91. return (pageData.parent = parentData);
  92. }).catch((err) => {
  93. return (pageData.parent = false);
  94. }) : Promise.resolve(true);
  95. return parentPromise.then(() => {
  96. // Cache to disk
  97. if(options.cache) {
  98. let cacheData = BSON.serialize(pageData, false, false, false);
  99. return fs.writeFileAsync(cpath, cacheData).catch((err) => {
  100. winston.error('Unable to write to cache! Performance may be affected.');
  101. return true;
  102. });
  103. } else {
  104. return true;
  105. }
  106. }).return(pageData);
  107. });
  108. } else {
  109. return false;
  110. }
  111. });
  112. },
  113. /**
  114. * Parse raw url path and make it safe
  115. *
  116. * @param {String} urlPath The url path
  117. * @return {String} Safe entry path
  118. */
  119. parsePath(urlPath) {
  120. let wlist = new RegExp('[^a-z0-9/\-]','g');
  121. urlPath = _.toLower(urlPath).replace(wlist, '');
  122. if(urlPath === '/') {
  123. urlPath = 'home';
  124. }
  125. let urlParts = _.filter(_.split(urlPath, '/'), (p) => { return !_.isEmpty(p); });
  126. return _.join(urlParts, '/');
  127. },
  128. /**
  129. * Gets the parent information.
  130. *
  131. * @param {String} entryPath The entry path
  132. * @return {Object|False} The parent information.
  133. */
  134. getParentInfo(entryPath) {
  135. let self = this;
  136. if(_.includes(entryPath, '/')) {
  137. let parentParts = _.split(entryPath, '/');
  138. let parentPath = _.join(_.initial(parentParts),'/');
  139. let parentFile = _.last(parentParts);
  140. let fpath = path.join(self._repoPath, parentPath + '.md');
  141. return fs.statAsync(fpath).then((st) => {
  142. if(st.isFile()) {
  143. return fs.readFileAsync(fpath, 'utf8').then((contents) => {
  144. let pageMeta = mark.parseMeta(contents);
  145. return {
  146. path: parentPath,
  147. title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile),
  148. subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false
  149. };
  150. });
  151. } else {
  152. return Promise.reject(new Error('Parent entry is not a valid file.'));
  153. }
  154. });
  155. } else {
  156. return Promise.reject(new Error('Parent entry is root.'));
  157. }
  158. }
  159. };