engine.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. const _ = require('lodash')
  2. const { SearchService, QueryType } = require('azure-search-client')
  3. const request = require('request-promise')
  4. const { pipeline } = require('stream')
  5. module.exports = {
  6. async activate() {
  7. // not used
  8. },
  9. async deactivate() {
  10. // not used
  11. },
  12. /**
  13. * INIT
  14. */
  15. async init() {
  16. WIKI.logger.info(`(SEARCH/AZURE) Initializing...`)
  17. this.client = new SearchService(this.config.serviceName, this.config.adminKey)
  18. // -> Create Search Index
  19. const indexes = await this.client.indexes.list()
  20. if (!_.find(_.get(indexes, 'result.value', []), ['name', this.config.indexName])) {
  21. WIKI.logger.info(`(SEARCH/AWS) Creating index...`)
  22. await this.client.indexes.create({
  23. name: this.config.indexName,
  24. fields: [
  25. {
  26. name: 'id',
  27. type: 'Edm.String',
  28. key: true,
  29. searchable: false
  30. },
  31. {
  32. name: 'locale',
  33. type: 'Edm.String',
  34. searchable: false
  35. },
  36. {
  37. name: 'path',
  38. type: 'Edm.String',
  39. searchable: false
  40. },
  41. {
  42. name: 'title',
  43. type: 'Edm.String',
  44. searchable: true
  45. },
  46. {
  47. name: 'description',
  48. type: 'Edm.String',
  49. searchable: true
  50. },
  51. {
  52. name: 'content',
  53. type: 'Edm.String',
  54. searchable: true
  55. }
  56. ],
  57. scoringProfiles: [
  58. {
  59. name: 'fieldWeights',
  60. text: {
  61. weights: {
  62. title: 4,
  63. description: 3,
  64. content: 1
  65. }
  66. }
  67. }
  68. ],
  69. suggesters: [
  70. {
  71. name: 'suggestions',
  72. searchMode: 'analyzingInfixMatching',
  73. sourceFields: ['title', 'description', 'content']
  74. }
  75. ],
  76. })
  77. }
  78. WIKI.logger.info(`(SEARCH/AZURE) Initialization completed.`)
  79. },
  80. /**
  81. * QUERY
  82. *
  83. * @param {String} q Query
  84. * @param {Object} opts Additional options
  85. */
  86. async query(q, opts) {
  87. try {
  88. let suggestions = []
  89. const results = await this.client.indexes.use(this.config.indexName).search({
  90. count: true,
  91. scoringProfile: 'fieldWeights',
  92. search: q,
  93. select: 'id, locale, path, title, description',
  94. queryType: QueryType.simple,
  95. top: 50
  96. })
  97. if (results.result.value.length < 5) {
  98. // Using plain request, not yet available in library...
  99. try {
  100. const suggestResults = await request({
  101. uri: `https://${this.config.serviceName}.search.windows.net/indexes/${this.config.indexName}/docs/autocomplete`,
  102. method: 'post',
  103. qs: {
  104. 'api-version': '2017-11-11-Preview'
  105. },
  106. headers: {
  107. 'api-key': this.config.adminKey,
  108. 'Content-Type': 'application/json'
  109. },
  110. json: true,
  111. body: {
  112. autocompleteMode: 'oneTermWithContext',
  113. search: q,
  114. suggesterName: 'suggestions'
  115. }
  116. })
  117. suggestions = suggestResults.value.map(s => s.queryPlusText)
  118. } catch (err) {
  119. WIKI.logger.warn('Search Engine suggestion failure: ', err)
  120. }
  121. }
  122. return {
  123. results: results.result.value,
  124. suggestions,
  125. totalHits: results.result['@odata.count']
  126. }
  127. } catch (err) {
  128. WIKI.logger.warn('Search Engine Error:')
  129. WIKI.logger.warn(err)
  130. }
  131. },
  132. /**
  133. * CREATE
  134. *
  135. * @param {Object} page Page to create
  136. */
  137. async created(page) {
  138. await this.client.indexes.use(this.config.indexName).index([
  139. {
  140. id: page.hash,
  141. locale: page.localeCode,
  142. path: page.path,
  143. title: page.title,
  144. description: page.description,
  145. content: page.content
  146. }
  147. ])
  148. },
  149. /**
  150. * UPDATE
  151. *
  152. * @param {Object} page Page to update
  153. */
  154. async updated(page) {
  155. await this.client.indexes.use(this.config.indexName).index([
  156. {
  157. id: page.hash,
  158. locale: page.localeCode,
  159. path: page.path,
  160. title: page.title,
  161. description: page.description,
  162. content: page.content
  163. }
  164. ])
  165. },
  166. /**
  167. * DELETE
  168. *
  169. * @param {Object} page Page to delete
  170. */
  171. async deleted(page) {
  172. await this.client.indexes.use(this.config.indexName).index([
  173. {
  174. '@search.action': 'delete',
  175. id: page.hash
  176. }
  177. ])
  178. },
  179. /**
  180. * RENAME
  181. *
  182. * @param {Object} page Page to rename
  183. */
  184. async renamed(page) {
  185. await this.client.indexes.use(this.config.indexName).index([
  186. {
  187. '@search.action': 'delete',
  188. id: page.sourceHash
  189. }
  190. ])
  191. await this.client.indexes.use(this.config.indexName).index([
  192. {
  193. id: page.destinationHash,
  194. locale: page.localeCode,
  195. path: page.destinationPath,
  196. title: page.title,
  197. description: page.description,
  198. content: page.content
  199. }
  200. ])
  201. },
  202. /**
  203. * REBUILD INDEX
  204. */
  205. async rebuild() {
  206. WIKI.logger.info(`(SEARCH/AZURE) Rebuilding Index...`)
  207. await pipeline(
  208. WIKI.models.knex.column({ id: 'hash' }, 'path', { locale: 'localeCode' }, 'title', 'description', 'content').select().from('pages').where({
  209. isPublished: true,
  210. isPrivate: false
  211. }).stream(),
  212. this.client.indexes.use(this.config.indexName).createIndexingStream()
  213. )
  214. WIKI.logger.info(`(SEARCH/AZURE) Index rebuilt successfully.`)
  215. }
  216. }