engine.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. const _ = require('lodash')
  2. const tsquery = require('pg-tsquery')()
  3. module.exports = {
  4. async activate() {
  5. // not used
  6. },
  7. async deactivate() {
  8. // not used
  9. },
  10. /**
  11. * INIT
  12. */
  13. async init() {
  14. // -> Create Search Index
  15. const indexExists = await WIKI.models.knex.schema.hasTable('pagesVector')
  16. if (!indexExists) {
  17. await WIKI.models.knex.schema.createTable('pagesVector', table => {
  18. table.increments()
  19. table.string('path')
  20. table.string('locale')
  21. table.string('title')
  22. table.string('description')
  23. table.specificType('tokens', 'TSVECTOR')
  24. })
  25. }
  26. // -> Create Words Index
  27. const wordsExists = await WIKI.models.knex.schema.hasTable('pagesWords')
  28. if (!wordsExists) {
  29. await WIKI.models.knex.raw(`
  30. CREATE TABLE "pagesWords" AS SELECT word FROM ts_stat(
  31. 'SELECT to_tsvector(''simple'', pages."title") || to_tsvector(''simple'', pages."description") || to_tsvector(''simple'', pages."content") FROM pages WHERE pages."isPublished" AND NOT pages."isPrivate"'
  32. )`)
  33. await WIKI.models.knex.raw('CREATE EXTENSION IF NOT EXISTS pg_trgm')
  34. await WIKI.models.knex.raw(`CREATE INDEX "pageWords_idx" ON "pagesWords" USING GIN (word gin_trgm_ops)`)
  35. }
  36. },
  37. /**
  38. * QUERY
  39. *
  40. * @param {String} q Query
  41. * @param {Object} opts Additional options
  42. */
  43. async query(q, opts) {
  44. try {
  45. let suggestions = []
  46. const results = await WIKI.models.knex.raw(`
  47. SELECT id, path, locale, title, description
  48. FROM "pagesVector", to_tsquery(?) query
  49. WHERE query @@ "tokens"
  50. ORDER BY ts_rank(tokens, query) DESC
  51. `, [tsquery(q)])
  52. if (results.rows.length < 5) {
  53. const suggestResults = await WIKI.models.knex.raw(`SELECT word, word <-> ? AS rank FROM "pagesWords" WHERE similarity(word, ?) > 0.2 ORDER BY rank LIMIT 5;`, [q, q])
  54. suggestions = suggestResults.rows.map(r => r.word)
  55. }
  56. return {
  57. results: results.rows,
  58. suggestions,
  59. totalHits: results.rows.length
  60. }
  61. } catch (err) {
  62. WIKI.logger.warn('Search Engine Error:')
  63. WIKI.logger.warn(err)
  64. }
  65. },
  66. /**
  67. * CREATE
  68. *
  69. * @param {Object} page Page to create
  70. */
  71. async created(page) {
  72. await WIKI.models.knex.raw(`
  73. INSERT INTO "pagesVector" (path, locale, title, description, tokens) VALUES (
  74. '?', '?', '?', '?', (setweight(to_tsvector('${this.config.dictLanguage}', '?'), 'A') || setweight(to_tsvector('${this.config.dictLanguage}', '?'), 'B') || setweight(to_tsvector('${this.config.dictLanguage}', '?'), 'C'))
  75. )
  76. `, [page.path, page.locale, page.title, page.description, page.title, page.description, page.content])
  77. },
  78. /**
  79. * UPDATE
  80. *
  81. * @param {Object} page Page to update
  82. */
  83. async updated(page) {
  84. await WIKI.models.knex.raw(`
  85. UPDATE "pagesVector" SET
  86. title = '?',
  87. description = '?',
  88. tokens = (setweight(to_tsvector('${this.config.dictLanguage}', '?'), 'A') ||
  89. setweight(to_tsvector('${this.config.dictLanguage}', '?'), 'B') ||
  90. setweight(to_tsvector('${this.config.dictLanguage}', '?'), 'C'))
  91. WHERE path = '?' AND locale = '?' LIMIT 1
  92. `, [page.title, page.description, page.title, page.description, page.content, page.path, page.locale])
  93. },
  94. /**
  95. * DELETE
  96. *
  97. * @param {Object} page Page to delete
  98. */
  99. async deleted(page) {
  100. await WIKI.models.knex('pagesVector').where({
  101. locale: page.locale,
  102. path: page.path
  103. }).del().limit(1)
  104. },
  105. /**
  106. * RENAME
  107. *
  108. * @param {Object} page Page to rename
  109. */
  110. async renamed(page) {
  111. await WIKI.models.knex('pagesVector').where({
  112. locale: page.locale,
  113. path: page.sourcePath
  114. }).update({
  115. locale: page.locale,
  116. path: page.destinationPath
  117. }).limit(1)
  118. },
  119. /**
  120. * REBUILD INDEX
  121. */
  122. async rebuild() {
  123. await WIKI.models.knex('pagesVector').truncate()
  124. await WIKI.models.knex.raw(`
  125. INSERT INTO "pagesVector" (path, locale, title, description, "tokens")
  126. SELECT path, "localeCode" AS locale, title, description,
  127. (setweight(to_tsvector('${this.config.dictLanguage}', title), 'A') ||
  128. setweight(to_tsvector('${this.config.dictLanguage}', description), 'B') ||
  129. setweight(to_tsvector('${this.config.dictLanguage}', content), 'C')) AS tokens
  130. FROM "pages"
  131. WHERE pages."isPublished" AND NOT pages."isPrivate"`)
  132. }
  133. }