feat: search + basic engine (wip)
This commit is contained in:
@@ -45,6 +45,8 @@ defaults:
|
||||
maxAge: 600
|
||||
methods: 'GET,POST'
|
||||
origin: true
|
||||
search:
|
||||
maxHits: 100
|
||||
localeNamespaces:
|
||||
- admin
|
||||
- auth
|
||||
|
@@ -69,6 +69,7 @@ module.exports = {
|
||||
await WIKI.models.storage.refreshTargetsFromDisk()
|
||||
|
||||
await WIKI.auth.activateStrategies()
|
||||
await WIKI.models.searchEngines.initEngine()
|
||||
await WIKI.models.storage.initTargets()
|
||||
WIKI.scheduler.start()
|
||||
},
|
||||
|
@@ -16,7 +16,18 @@ module.exports = {
|
||||
offsetPage: args.offsetPage || 0,
|
||||
offsetSize: args.offsetSize || 100
|
||||
})
|
||||
}
|
||||
},
|
||||
async search (obj, args, context) {
|
||||
if (WIKI.data.searchEngine) {
|
||||
return WIKI.data.searchEngine.query(args.query, args)
|
||||
} else {
|
||||
return {
|
||||
results: [],
|
||||
suggestions: [],
|
||||
totalHits: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
PageMutation: {
|
||||
async create(obj, args, context) {
|
||||
|
@@ -20,6 +20,12 @@ type PageQuery {
|
||||
offsetPage: Int
|
||||
offsetSize: Int
|
||||
): PageHistoryResult @auth(requires: ["manage:system", "read:pages"])
|
||||
|
||||
search(
|
||||
query: String!
|
||||
path: String
|
||||
locale: String
|
||||
): PageSearchResponse! @auth(requires: ["manage:system", "read:pages"])
|
||||
}
|
||||
|
||||
# -----------------------------------------------
|
||||
@@ -88,3 +94,17 @@ type PageHistoryResult {
|
||||
trail: [PageHistory]
|
||||
total: Int!
|
||||
}
|
||||
|
||||
type PageSearchResponse {
|
||||
results: [PageSearchResult]!
|
||||
suggestions: [String]!
|
||||
totalHits: Int!
|
||||
}
|
||||
|
||||
type PageSearchResult {
|
||||
id: Int!
|
||||
title: String!
|
||||
description: String!
|
||||
path: String!
|
||||
locale: String!
|
||||
}
|
||||
|
@@ -95,6 +95,19 @@ module.exports = class SearchEngine extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
static async initEngine() {
|
||||
const searchEngine = await WIKI.models.searchEngines.query().findOne('isEnabled', true)
|
||||
if (searchEngine) {
|
||||
WIKI.data.searchEngine = require(`../modules/search/${searchEngine.key}/engine`)
|
||||
WIKI.data.searchEngine.config = searchEngine.config
|
||||
try {
|
||||
await WIKI.data.searchEngine.init()
|
||||
} catch (err) {
|
||||
WIKI.logger.warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async pageEvent({ event, page }) {
|
||||
const searchEngines = await WIKI.models.storage.query().where('isEnabled', true)
|
||||
if (searchEngines && searchEngines.length > 0) {
|
||||
|
@@ -4,4 +4,20 @@ description: Algolia is a powerful search-as-a-service solution, made easy to us
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/algolia.svg
|
||||
website: https://www.algolia.com/
|
||||
props: {}
|
||||
props:
|
||||
appId:
|
||||
type: String
|
||||
title: App ID
|
||||
hint: Your Algolia Application ID, found under API Keys
|
||||
order: 1
|
||||
apiKey:
|
||||
type: String
|
||||
title: Admin API Key
|
||||
hint: Your Algolia Admin API Key, found under API Keys.
|
||||
order: 2
|
||||
indexName:
|
||||
type: String
|
||||
title: Index Name
|
||||
hint: The name of the index you created under Indices.
|
||||
default: wiki
|
||||
order: 3
|
||||
|
@@ -1,6 +1,6 @@
|
||||
key: db
|
||||
title: Database (built-in)
|
||||
description: Default database-based search engine.
|
||||
title: Database - Basic
|
||||
description: Default basic database-based search engine.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/database.svg
|
||||
website: https://www.requarks.io/
|
||||
|
@@ -1,26 +1,121 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = {
|
||||
activate() {
|
||||
|
||||
// not used
|
||||
},
|
||||
deactivate() {
|
||||
|
||||
// not used
|
||||
},
|
||||
query() {
|
||||
|
||||
/**
|
||||
* INIT
|
||||
*/
|
||||
init() {
|
||||
// not used
|
||||
},
|
||||
created() {
|
||||
|
||||
/**
|
||||
* SUGGEST
|
||||
*
|
||||
* @param {String} q Query
|
||||
* @param {Object} opts Additional options
|
||||
*/
|
||||
async suggest(q, opts) {
|
||||
const results = await WIKI.models.pages.query()
|
||||
.column('title')
|
||||
.where(builder => {
|
||||
builder.where('isPublished', true)
|
||||
if (opts.locale) {
|
||||
builder.andWhere('locale', opts.locale)
|
||||
}
|
||||
if (opts.path) {
|
||||
builder.andWhere('path', 'like', `${opts.path}%`)
|
||||
}
|
||||
builder.andWhere('title', 'like', `%${q}%`)
|
||||
})
|
||||
.limit(10)
|
||||
return _.uniq(_.filter(_.flatten(results.map(r => r.title.split(' '))), w => w.indexOf(q) >= 0))
|
||||
},
|
||||
updated() {
|
||||
/**
|
||||
* QUERY
|
||||
*
|
||||
* @param {String} q Query
|
||||
* @param {Object} opts Additional options
|
||||
*/
|
||||
async query(q, opts) {
|
||||
const results = await WIKI.models.pages.query()
|
||||
.column('id', 'title', 'description', 'path', 'localeCode as locale')
|
||||
.where(builder => {
|
||||
builder.where('isPublished', true)
|
||||
if (opts.locale) {
|
||||
builder.andWhere('localeCode', opts.locale)
|
||||
}
|
||||
if (opts.path) {
|
||||
builder.andWhere('path', 'like', `${opts.path}%`)
|
||||
}
|
||||
// TODO: Add user permissions filtering
|
||||
builder.andWhere(builder => {
|
||||
switch(WIKI.config.db.type) {
|
||||
case 'postgres':
|
||||
builder.where('title', 'ILIKE', `%${q}%`)
|
||||
builder.orWhere('description', 'ILIKE', `%${q}%`)
|
||||
break
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
builder.whereRaw(`title LIKE '%?%' COLLATE utf8_general_ci`, [q])
|
||||
builder.orWhereRaw(`description LIKE '%?%' COLLATE utf8_general_ci`, [q])
|
||||
break
|
||||
|
||||
// TODO: MSSQL handling
|
||||
default:
|
||||
builder.where('title', 'LIKE', `%${q}%`)
|
||||
builder.orWhere('description', 'LIKE', `%${q}%`)
|
||||
break
|
||||
}
|
||||
})
|
||||
})
|
||||
.limit(WIKI.config.search.maxHits)
|
||||
return {
|
||||
results,
|
||||
suggestions: [],
|
||||
totalHits: results.length
|
||||
}
|
||||
},
|
||||
deleted() {
|
||||
|
||||
/**
|
||||
* CREATE
|
||||
*
|
||||
* @param {Object} page Page to create
|
||||
*/
|
||||
async created(page) {
|
||||
// not used
|
||||
},
|
||||
renamed() {
|
||||
|
||||
/**
|
||||
* UPDATE
|
||||
*
|
||||
* @param {Object} page Page to update
|
||||
*/
|
||||
async updated(page) {
|
||||
// not used
|
||||
},
|
||||
rebuild() {
|
||||
|
||||
/**
|
||||
* DELETE
|
||||
*
|
||||
* @param {Object} page Page to delete
|
||||
*/
|
||||
async deleted(page) {
|
||||
// not used
|
||||
},
|
||||
/**
|
||||
* RENAME
|
||||
*
|
||||
* @param {Object} page Page to rename
|
||||
*/
|
||||
async renamed(page) {
|
||||
// not used
|
||||
},
|
||||
/**
|
||||
* REBUILD INDEX
|
||||
*/
|
||||
async rebuild() {
|
||||
// not used
|
||||
}
|
||||
}
|
||||
|
@@ -4,4 +4,41 @@ description: Elasticsearch is a distributed, RESTful search and analytics engine
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/elasticsearch.svg
|
||||
website: https://www.elastic.co/products/elasticsearch
|
||||
props: {}
|
||||
props:
|
||||
apiVersion:
|
||||
type: String
|
||||
title: API Version
|
||||
hint: Should match the version of the Elasticsearch nodes you are connecting to
|
||||
order: 1
|
||||
enum:
|
||||
- '6.6'
|
||||
- '6.5'
|
||||
- '6.4'
|
||||
- '6.3'
|
||||
default: '6.6'
|
||||
host:
|
||||
type: String
|
||||
title: Host(s)
|
||||
hint: Comma-separated list of Elasticsearch hosts to connect to
|
||||
order: 2
|
||||
user:
|
||||
type: String
|
||||
title: Username
|
||||
order: 3
|
||||
pass:
|
||||
type: String
|
||||
title: Password
|
||||
order: 4
|
||||
sniff:
|
||||
type: Boolean
|
||||
title: Sniff on start
|
||||
hint: 'Should Wiki.js attempt to detect the rest of the cluster on first connect? (Default: off)'
|
||||
default: false
|
||||
order: 5
|
||||
sniffInterval:
|
||||
type: Number
|
||||
title: Sniff Interval
|
||||
hint: '0 = disabled, Interval in seconds to check for updated list of nodes in cluster. (Default: 0)'
|
||||
order: 6
|
||||
default: 0
|
||||
|
||||
|
30
server/modules/search/postgres/definition.yml
Normal file
30
server/modules/search/postgres/definition.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
key: postgres
|
||||
title: Database - PostgreSQL
|
||||
description: Advanced PostgreSQL-based search engine.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/postgresql.svg
|
||||
website: https://www.requarks.io/
|
||||
props:
|
||||
dictLanguage:
|
||||
type: String
|
||||
title: Dictionnary Language
|
||||
hint: Language to use when creating and querying text search vectors.
|
||||
default: english
|
||||
enum:
|
||||
- simple
|
||||
- danish
|
||||
- dutch
|
||||
- english
|
||||
- finnish
|
||||
- french
|
||||
- german
|
||||
- hungarian
|
||||
- italian
|
||||
- norwegian
|
||||
- portuguese
|
||||
- romanian
|
||||
- russian
|
||||
- spanish
|
||||
- swedish
|
||||
- turkish
|
||||
order: 1
|
72
server/modules/search/postgres/engine.js
Normal file
72
server/modules/search/postgres/engine.js
Normal file
@@ -0,0 +1,72 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = {
|
||||
activate() {
|
||||
// not used
|
||||
},
|
||||
deactivate() {
|
||||
// not used
|
||||
},
|
||||
/**
|
||||
* INIT
|
||||
*/
|
||||
init() {
|
||||
// not used
|
||||
},
|
||||
/**
|
||||
* SUGGEST
|
||||
*
|
||||
* @param {String} q Query
|
||||
* @param {Object} opts Additional options
|
||||
*/
|
||||
async suggest(q, opts) {
|
||||
|
||||
},
|
||||
/**
|
||||
* QUERY
|
||||
*
|
||||
* @param {String} q Query
|
||||
* @param {Object} opts Additional options
|
||||
*/
|
||||
async query(q, opts) {
|
||||
|
||||
},
|
||||
/**
|
||||
* CREATE
|
||||
*
|
||||
* @param {Object} page Page to create
|
||||
*/
|
||||
async created(page) {
|
||||
// not used
|
||||
},
|
||||
/**
|
||||
* UPDATE
|
||||
*
|
||||
* @param {Object} page Page to update
|
||||
*/
|
||||
async updated(page) {
|
||||
// not used
|
||||
},
|
||||
/**
|
||||
* DELETE
|
||||
*
|
||||
* @param {Object} page Page to delete
|
||||
*/
|
||||
async deleted(page) {
|
||||
// not used
|
||||
},
|
||||
/**
|
||||
* RENAME
|
||||
*
|
||||
* @param {Object} page Page to rename
|
||||
*/
|
||||
async renamed(page) {
|
||||
// not used
|
||||
},
|
||||
/**
|
||||
* REBUILD INDEX
|
||||
*/
|
||||
async rebuild() {
|
||||
// not used
|
||||
}
|
||||
}
|
@@ -4,4 +4,31 @@ description: Solr is the popular, blazing-fast, open source enterprise search pl
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/solr.svg
|
||||
website: http://lucene.apache.org/solr/
|
||||
props: {}
|
||||
props:
|
||||
host:
|
||||
type: String
|
||||
title: Host
|
||||
hint: Host of the Solr server (e.g. 12.34.56.78 or solr.example.com)
|
||||
default: solr
|
||||
order: 1
|
||||
port:
|
||||
type: Number
|
||||
title: Port
|
||||
hint: Port of the Solr server
|
||||
default: 8983
|
||||
order: 2
|
||||
core:
|
||||
type: String
|
||||
title: Core
|
||||
hint: Core name (e.g. wiki)
|
||||
default: wiki
|
||||
order: 3
|
||||
protocol:
|
||||
type: String
|
||||
title: Protocol
|
||||
hint: Request protocol
|
||||
default: http
|
||||
enum:
|
||||
- http
|
||||
- https
|
||||
order: 4
|
||||
|
Reference in New Issue
Block a user