You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
2.5 KiB

  1. "use strict";
  2. var Promise = require('bluebird'),
  3. _ = require('lodash'),
  4. path = require('path'),
  5. searchIndex = require('search-index'),
  6. stopWord = require('stopword');
  7. /**
  8. * Search Model
  9. */
  10. module.exports = {
  11. _si: null,
  12. /**
  13. * Initialize Search model
  14. *
  15. * @param {Object} appconfig The application config
  16. * @return {Object} Search model instance
  17. */
  18. init(appconfig) {
  19. let self = this;
  20. let dbPath = path.resolve(ROOTPATH, appconfig.datadir.db, 'search-index');
  21. searchIndex({
  22. deletable: true,
  23. fieldedSearch: true,
  24. indexPath: dbPath,
  25. logLevel: 'error',
  26. stopwords: stopWord.getStopwords(appconfig.lang).sort()
  27. }, (err, si) => {
  28. if(err) {
  29. winston.error('Failed to initialize search-index.', err);
  30. } else {
  31. self._si = Promise.promisifyAll(si);
  32. }
  33. });
  34. return self;
  35. },
  36. find(terms) {
  37. let self = this;
  38. terms = _.chain(terms)
  39. .deburr()
  40. .toLower()
  41. .trim()
  42. .replace(/[^a-z0-9 ]/g, '')
  43. .value();
  44. let arrTerms = _.chain(terms)
  45. .split(' ')
  46. .filter((f) => { return !_.isEmpty(f); })
  47. .value();
  48. return self._si.searchAsync({
  49. query: {
  50. AND: [{ '*': arrTerms }]
  51. },
  52. pageSize: 10
  53. }).get('hits').then((hits) => {
  54. if(hits.length < 5) {
  55. return self._si.matchAsync({
  56. beginsWith: terms,
  57. threshold: 3,
  58. limit: 5,
  59. type: 'simple'
  60. }).then((matches) => {
  61. return {
  62. match: hits,
  63. suggest: matches
  64. };
  65. });
  66. } else {
  67. return {
  68. match: hits,
  69. suggest: []
  70. };
  71. }
  72. });
  73. },
  74. /**
  75. * Add a document to the index
  76. *
  77. * @param {Object} content Document content
  78. * @return {Promise} Promise of the add operation
  79. */
  80. add(content) {
  81. let self = this;
  82. return self._si.addAsync({
  83. entryPath: content.entryPath,
  84. title: content.meta.title,
  85. subtitle: content.meta.subtitle || '',
  86. parent: content.parent.title || '',
  87. content: content.text || ''
  88. }, {
  89. fieldOptions: [{
  90. fieldName: 'entryPath',
  91. searchable: true,
  92. weight: 2
  93. },
  94. {
  95. fieldName: 'title',
  96. nGramLength: [1, 2],
  97. searchable: true,
  98. weight: 3
  99. },
  100. {
  101. fieldName: 'subtitle',
  102. searchable: true,
  103. weight: 1,
  104. store: false
  105. },
  106. {
  107. fieldName: 'subtitle',
  108. searchable: false,
  109. },
  110. {
  111. fieldName: 'content',
  112. searchable: true,
  113. weight: 0,
  114. store: false
  115. }]
  116. }).then(() => {
  117. winston.info('Entry ' + content.entryPath + ' added to index.');
  118. }).catch((err) => {
  119. winston.error(err);
  120. });
  121. }
  122. };