search.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. /**
  2. * Search
  3. */
  4. var elasticsearch = require('elasticsearch'),
  5. debug = require('debug')('crowi:lib:search');
  6. function SearchClient(crowi, esUri) {
  7. this.esUri = esUri;
  8. this.crowi = crowi;
  9. var uri = this.parseUri(this.esUri);
  10. this.host = uri.host;
  11. this.index_name = uri.index_name;
  12. this.client = new elasticsearch.Client({
  13. host: this.host,
  14. });
  15. this.mappingFile = crowi.resourceDir + 'search/mappings.json';
  16. //this.Page = crowi.model('Page');
  17. //this.Config = crowi.model('Config');
  18. //this.config = crowi.getConfig();
  19. }
  20. SearchClient.prototype.parseUri = function(uri) {
  21. if (!(m = uri.match(/^elasticsearch:\/\/([^:]+):([^\/]+)\/(.+)$/))) {
  22. throw new Error('Invalid ELASTICSEARCH_URI format. Should be elasticsearch://host:port/index_name');
  23. }
  24. return {
  25. host: m[1] + ':' + m[2],
  26. index_name: m[3],
  27. };
  28. };
  29. SearchClient.prototype.buildIndex = function(uri) {
  30. return this.client.indices.create({
  31. index: this.index_name,
  32. body: require(this.mappingFile)
  33. });
  34. };
  35. SearchClient.prototype.deleteIndex = function(uri) {
  36. return this.client.indices.delete({
  37. index: this.index_name,
  38. });
  39. };
  40. SearchClient.prototype.prepareBodyForUpdate = function(body, page) {
  41. if (!Array.isArray(body)) {
  42. throw new Error('Body must be an array.');
  43. }
  44. var command = {
  45. update: {
  46. _index: this.index_name,
  47. _type: 'pages',
  48. _id: page._id.toString(),
  49. }
  50. };
  51. var document = {
  52. path: page.path,
  53. body: page.revision.body,
  54. username: page.creator.username,
  55. comment_count: page.commentCount,
  56. like_count: page.liker.length || 0,
  57. updated_at: page.updatedAt,
  58. };
  59. body.push(command);
  60. body.push(document);
  61. };
  62. SearchClient.prototype.prepareBodyForCreate = function(body, page) {
  63. if (!Array.isArray(body)) {
  64. throw new Error('Body must be an array.');
  65. }
  66. var command = {
  67. index: {
  68. _index: this.index_name,
  69. _type: 'pages',
  70. _id: page._id.toString(),
  71. }
  72. };
  73. var document = {
  74. path: page.path,
  75. body: page.revision.body,
  76. username: page.creator.username,
  77. comment_count: page.commentCount,
  78. like_count: page.liker.length || 0,
  79. created_at: page.createdAt,
  80. updated_at: page.updatedAt,
  81. };
  82. body.push(command);
  83. body.push(document);
  84. };
  85. SearchClient.prototype.addPages = function(pages)
  86. {
  87. var self = this;
  88. var body = [];
  89. pages.map(function(page) {
  90. self.prepareBodyForCreate(body, page);
  91. });
  92. return this.client.bulk({
  93. body: body,
  94. });
  95. };
  96. SearchClient.prototype.updatePages = function(pages)
  97. {
  98. var self = this;
  99. var body = [];
  100. pages.map(function(page) {
  101. self.prepareBodyForUpdate(body, page);
  102. });
  103. return this.client.bulk({
  104. body: body,
  105. });
  106. };
  107. SearchClient.prototype.addAllPages = function()
  108. {
  109. var self = this;
  110. var offset = 0;
  111. var Page = this.crowi.model('Page');
  112. var stream = Page.getStreamOfFindAll();
  113. var body = [];
  114. return new Promise(function(resolve, reject) {
  115. stream.on('data', function (doc) {
  116. if (!doc.creator || !doc.revision) {
  117. debug('Skipped', doc.path);
  118. return ;
  119. }
  120. self.prepareBodyForCreate(body, doc);
  121. }).on('error', function (err) {
  122. // TODO: handle err
  123. debug('Error stream:', err);
  124. }).on('close', function () {
  125. // all done
  126. // 最後に送信
  127. self.client.bulk({ body: body, })
  128. .then(function(res) {
  129. debug('Reponse from es:', res);
  130. return resolve(res);
  131. }).catch(function(err) {
  132. debug('Err from es:', err);
  133. return reject(err);
  134. });
  135. });
  136. });
  137. };
  138. SearchClient.prototype.createSearchQuerySortedByUpdatedAt = function()
  139. {
  140. // default is only id field, sorted by updated_at
  141. return {
  142. index: this.index_name,
  143. type: 'pages',
  144. body: {
  145. fields: ['_id'],
  146. sort: [{ updated_at: { order: 'desc'}}],
  147. query: {}, // query
  148. }
  149. };
  150. };
  151. SearchClient.prototype.createSearchQuerySortedByScore = function()
  152. {
  153. // sort by score
  154. return {
  155. index: this.index_name,
  156. type: 'pages',
  157. body: {
  158. fields: ['_id'],
  159. sort: [ {_score: { order: 'desc'} }],
  160. query: {}, // query
  161. }
  162. };
  163. };
  164. SearchClient.prototype.searchSuggest = function(keyword)
  165. {
  166. var query = this.createSearchQuerySortedByScore();
  167. query.body.query = {
  168. bool: {
  169. must: [
  170. {
  171. bool: {
  172. should: [
  173. {
  174. match: {
  175. 'path.raw': {
  176. query: sprintf('*%s*', keyword),
  177. operator: 'or'
  178. }
  179. }
  180. },
  181. {
  182. match: {
  183. 'body.ja': {
  184. query: keyword,
  185. operator: 'or'
  186. }
  187. }
  188. }
  189. ]
  190. }
  191. }
  192. ]
  193. }
  194. };
  195. return this.client.search(query);
  196. };
  197. SearchClient.prototype.searchKeyword = function(keyword)
  198. {
  199. var query = this.createSearchQuerySortedByUpdatedAt();
  200. query.body.query = {
  201. bool: {
  202. must: [
  203. {
  204. bool: {
  205. should: [
  206. {
  207. match: {
  208. 'path.raw': {
  209. query: sprintf('*%s*', keyword),
  210. operator: 'or'
  211. }
  212. }
  213. },
  214. {
  215. match: {
  216. 'body.ja': {
  217. query: keyword,
  218. operator: 'or'
  219. }
  220. }
  221. }
  222. ]
  223. }
  224. }
  225. ]
  226. }
  227. };
  228. return this.client.search(query);
  229. };
  230. SearchClient.prototype.searchByPath = function(keyword, prefix)
  231. {
  232. var query = this.createSearchQuerySortedByUpdatedAt();
  233. };
  234. SearchClient.prototype.searchKeywordUnderPath = function(keyword, path)
  235. {
  236. var query = this.createSearchQuerySortedByUpdatedAt();
  237. };
  238. module.exports = SearchClient;
  239. /*
  240. lib.searchPageByKeyword = function(keyword) {
  241. var queryBody = {
  242. query: {
  243. bool: {
  244. should: [
  245. {term: { path: { term: keyword, boost: 2.0 } }},
  246. {term: { body: { term: keyword } }}
  247. ]
  248. }
  249. },
  250. highlight : { fields : { body : {} } },
  251. //sort: [{ updated: { order: "desc" } } ]
  252. };
  253. return client.search({
  254. index: index_name,
  255. body: queryBody
  256. });
  257. };
  258. */