search.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. let express = require('express')
  2. let router = express.Router()
  3. let { Post, Thread, User, Category, Sequelize } = require('../models')
  4. const Errors = require('../lib/errors')
  5. router.get('/thread', async (req, res, next) => {
  6. try {
  7. let searchString = req.query.q.trim()
  8. if(searchString.length < 2) {
  9. throw Errors.sequelizeValidation(Sequelize, {
  10. error: 'search string must be at least 2 characters',
  11. value: searchString
  12. })
  13. }
  14. //Offset is really the previously lowest id
  15. //(as a proxy for oldest thread of the previous search)
  16. //if there is no offset, just use a clause that will always be true
  17. //i.e. greater than 0 id
  18. let offset = +req.query.offset ? { $lt: +req.query.offset } : { $gt: 0 }
  19. let limit = 10
  20. /*
  21. Task is to find threads that either have the
  22. string in the title or in the content of the first post
  23. Method
  24. 1) Select first n items from each group (posts and threads), where n is the LIMIT,
  25. greater than id x, where x is previous OFFSET
  26. 2) Merge results from both, remove duplicates and sort
  27. 3) Select first n items from merged group
  28. 4) Set x as the last item from merged group
  29. */
  30. let threadTitles = await Thread.findAll({
  31. where: {
  32. name: { $like: '%' + searchString + '%' },
  33. id: offset
  34. },
  35. order: [ ['id', 'DESC'] ],
  36. include: [
  37. {
  38. model: Post,
  39. include: [{ model: User, attributes: { exclude: ['hash'] } }],
  40. where: {
  41. postNumber: 0
  42. }
  43. },
  44. { model: Category },
  45. { model: User, attributes: { exclude: ['hash'] } }
  46. ],
  47. limit
  48. })
  49. let threadPosts = await Thread.findAll({
  50. where: {
  51. id: offset
  52. },
  53. order: [ ['id', 'DESC'] ],
  54. include: [
  55. {
  56. model: Post,
  57. include: [{ model: User, attributes: { exclude: ['hash'] } }],
  58. where: {
  59. postNumber: 0,
  60. plainText: { $like: '%' + searchString + '%' }
  61. }
  62. },
  63. { model: Category },
  64. { model: User, attributes: { exclude: ['hash'] } }
  65. ],
  66. limit
  67. })
  68. let merged = [...threadTitles, ...threadPosts];
  69. let unique = [];
  70. merged.forEach(thread => {
  71. let includes = unique.filter(u => thread.id === u.id);
  72. if(!includes.length) unique.push(thread);
  73. });
  74. let sorted = unique
  75. .sort((a, b) => {
  76. return b.id - a.id;
  77. })
  78. .slice(0, limit);
  79. //To get latest post, find threads where
  80. //the post number is equal to the overal posts count
  81. //and the post number > 0 (i.e. there are replies)
  82. let whereClause = sorted.reduce((arr, thread) => {
  83. if(thread.postsCount > 1) {
  84. let clause = {
  85. $and: {
  86. ThreadId: thread.id,
  87. postNumber: thread.postsCount-1
  88. }
  89. }
  90. return [...arr, clause];
  91. } else {
  92. return arr;
  93. }
  94. }, []);
  95. let latestPosts = await Post.findAll({
  96. where: {
  97. $or: whereClause
  98. },
  99. order: [ ['ThreadId', 'DESC'] ],
  100. include: [{ model: User, attributes: { exclude: ['hash'] } }]
  101. })
  102. //Merge latest posts with threads array
  103. let ret = sorted.map(thread => {
  104. if(thread.postsCount > 1) {
  105. let post = latestPosts.filter(p => p.ThreadId === thread.id)[0];
  106. thread.Posts.push(post);
  107. }
  108. return thread;
  109. })
  110. res.json({
  111. threads: ret,
  112. offset: ret.length ? ret.slice(-1)[0].id : null,
  113. next: ret.length < limit ? null : limit
  114. })
  115. } catch (e) { next(e) }
  116. })
  117. router.get('/user', async (req, res, next) => {
  118. try {
  119. let searchString = req.query.q
  120. let offset = +req.query.offset || 0
  121. let limit = 10
  122. let users = await User.findAll({
  123. where: {
  124. username: { $like: '%' + searchString + '%' }
  125. },
  126. order: [ ['username', 'DESC'] ],
  127. attributes: { exclude: ['hash'] },
  128. limit,
  129. offset
  130. })
  131. res.json({
  132. users,
  133. offset: users.length < limit ? null : offset + limit,
  134. next: users.length < limit ? null : limit
  135. })
  136. } catch (e) { next(e) }
  137. })
  138. module.exports = router