search.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /**
  2. * @swagger
  3. *
  4. * components:
  5. * schemas:
  6. * ElasticsearchResult:
  7. * description: Elasticsearch result v1
  8. * type: object
  9. * properties:
  10. * meta:
  11. * type: object
  12. * properties:
  13. * took:
  14. * type: number
  15. * description: Time Elasticsearch took to execute a search(milliseconds)
  16. * example: 34
  17. * total:
  18. * type: number
  19. * description: Number of documents matching search criteria
  20. * example: 2
  21. * results:
  22. * type: number
  23. * description: Actual array length of search results
  24. * example: 2
  25. *
  26. */
  27. module.exports = function(crowi, app) {
  28. // var debug = require('debug')('growi:routes:search')
  29. const Page = crowi.model('Page');
  30. const ApiResponse = require('../util/apiResponse');
  31. const ApiPaginate = require('../util/apiPaginate');
  32. const actions = {};
  33. const api = {};
  34. actions.searchPage = function(req, res) {
  35. const keyword = req.query.q || null;
  36. return res.render('search', {
  37. q: keyword,
  38. });
  39. };
  40. /**
  41. * @swagger
  42. *
  43. * /search:
  44. * get:
  45. * tags: [Search, CrowiCompatibles]
  46. * operationId: searchPages
  47. * summary: /search
  48. * description: Search pages
  49. * parameters:
  50. * - in: query
  51. * name: q
  52. * schema:
  53. * type: string
  54. * description: keyword
  55. * example: daily report
  56. * required: true
  57. * - in: query
  58. * name: path
  59. * schema:
  60. * $ref: '#/components/schemas/Page/properties/path'
  61. * - in: query
  62. * name: offset
  63. * schema:
  64. * $ref: '#/components/schemas/V1PaginateResult/properties/meta/properties/offset'
  65. * - in: query
  66. * name: limit
  67. * schema:
  68. * $ref: '#/components/schemas/V1PaginateResult/properties/meta/properties/limit'
  69. * responses:
  70. * 200:
  71. * description: Succeeded to get list of pages.
  72. * content:
  73. * application/json:
  74. * schema:
  75. * properties:
  76. * ok:
  77. * $ref: '#/components/schemas/V1Response/properties/ok'
  78. * meta:
  79. * $ref: '#/components/schemas/ElasticsearchResult/properties/meta'
  80. * totalCount:
  81. * type: integer
  82. * description: total count of pages
  83. * example: 35
  84. * data:
  85. * type: array
  86. * items:
  87. * $ref: '#/components/schemas/Page'
  88. * description: page list
  89. * 403:
  90. * $ref: '#/components/responses/403'
  91. * 500:
  92. * $ref: '#/components/responses/500'
  93. */
  94. /**
  95. * @api {get} /search search page
  96. * @apiName Search
  97. * @apiGroup Search
  98. *
  99. * @apiParam {String} q keyword
  100. * @apiParam {String} path
  101. * @apiParam {String} offset
  102. * @apiParam {String} limit
  103. */
  104. api.search = async function(req, res) {
  105. const user = req.user;
  106. const { q: keyword = null, type = null } = req.query;
  107. let paginateOpts;
  108. try {
  109. paginateOpts = ApiPaginate.parseOptionsForElasticSearch(req.query);
  110. }
  111. catch (e) {
  112. res.json(ApiResponse.error(e));
  113. }
  114. if (keyword === null || keyword === '') {
  115. return res.json(ApiResponse.error('keyword should not empty.'));
  116. }
  117. const { searchService } = crowi;
  118. if (!searchService.isReachable) {
  119. return res.json(ApiResponse.error('SearchService is not reachable.'));
  120. }
  121. let userGroups = [];
  122. if (user != null) {
  123. const UserGroupRelation = crowi.model('UserGroupRelation');
  124. userGroups = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user);
  125. }
  126. const searchOpts = { ...paginateOpts, type };
  127. const result = {};
  128. try {
  129. const esResult = await searchService.searchKeyword(keyword, user, userGroups, searchOpts);
  130. // create score map for sorting
  131. // key: id , value: score
  132. const scoreMap = {};
  133. for (const esPage of esResult.data) {
  134. scoreMap[esPage._id] = esPage._score;
  135. }
  136. const ids = esResult.data.map((page) => { return page._id });
  137. const findResult = await Page.findListByPageIds(ids);
  138. // add tag data to result pages
  139. findResult.pages.map((page) => {
  140. const data = esResult.data.find((data) => { return page.id === data._id });
  141. page._doc.tags = data._source.tag_names;
  142. return page;
  143. });
  144. result.meta = esResult.meta;
  145. result.totalCount = findResult.totalCount;
  146. result.data = findResult.pages
  147. .map((page) => {
  148. page.bookmarkCount = (page._source && page._source.bookmark_count) || 0;
  149. return page;
  150. })
  151. .sort((page1, page2) => {
  152. // note: this do not consider NaN
  153. return scoreMap[page2._id] - scoreMap[page1._id];
  154. });
  155. }
  156. catch (err) {
  157. return res.json(ApiResponse.error(err));
  158. }
  159. return res.json(ApiResponse.success(result));
  160. };
  161. actions.api = api;
  162. return actions;
  163. };