mirror of https://github.com/Requarks/wiki.git
28 changed files with 365 additions and 80 deletions
Unified View
Diff Options
-
1client/components/admin.vue
-
63client/components/admin/admin-search.vue
-
25client/components/common/nav-header.vue
-
4client/components/common/search-results.vue
-
1client/components/history.vue
-
1client/components/profile.vue
-
1client/components/source.vue
-
12client/graph/admin/search/search-mutation-rebuild-index.gql
-
4client/graph/admin/search/search-mutation-save-engines.gql
-
1client/graph/admin/search/search-query-engines.gql
-
2package.json
-
13server/graph/resolvers/search.js
-
2server/graph/schemas/page.graphql
-
5server/graph/schemas/search.graphql
-
4server/helpers/error.js
-
3server/models/pages.js
-
31server/models/searchEngines.js
-
1server/modules/search/algolia/definition.yml
-
1server/modules/search/aws/definition.yml
-
19server/modules/search/azure/definition.yml
-
217server/modules/search/azure/engine.js
-
1server/modules/search/db/definition.yml
-
1server/modules/search/elasticsearch/definition.yml
-
1server/modules/search/manticore/definition.yml
-
1server/modules/search/postgres/definition.yml
-
28server/modules/search/postgres/engine.js
-
1server/modules/search/solr/definition.yml
-
1server/modules/search/sphinx/definition.yml
@ -0,0 +1,12 @@ |
|||||
|
mutation { |
||||
|
search { |
||||
|
rebuildIndex { |
||||
|
responseResult { |
||||
|
succeeded |
||||
|
errorCode |
||||
|
slug |
||||
|
message |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,26 +1,213 @@ |
|||||
module.exports = { |
|
||||
activate() { |
|
||||
|
const _ = require('lodash') |
||||
|
const { SearchService, QueryType } = require('azure-search-client') |
||||
|
const request = require('request-promise') |
||||
|
const { pipeline } = require('stream') |
||||
|
|
||||
|
module.exports = { |
||||
|
async activate() { |
||||
|
// not used
|
||||
}, |
}, |
||||
deactivate() { |
|
||||
|
|
||||
|
async deactivate() { |
||||
|
// not used
|
||||
}, |
}, |
||||
query() { |
|
||||
|
/** |
||||
|
* INIT |
||||
|
*/ |
||||
|
async init() { |
||||
|
this.client = new SearchService(this.config.serviceName, this.config.adminKey) |
||||
|
|
||||
|
// -> Create Search Index
|
||||
|
const indexes = await this.client.indexes.list() |
||||
|
if (!_.find(_.get(indexes, 'result.value', []), ['name', this.config.indexName])) { |
||||
|
await this.client.indexes.create({ |
||||
|
name: this.config.indexName, |
||||
|
fields: [ |
||||
|
{ |
||||
|
name: 'id', |
||||
|
type: 'Edm.String', |
||||
|
key: true, |
||||
|
searchable: false |
||||
|
}, |
||||
|
{ |
||||
|
name: 'locale', |
||||
|
type: 'Edm.String', |
||||
|
searchable: false |
||||
|
}, |
||||
|
{ |
||||
|
name: 'path', |
||||
|
type: 'Edm.String', |
||||
|
searchable: false |
||||
|
}, |
||||
|
{ |
||||
|
name: 'title', |
||||
|
type: 'Edm.String', |
||||
|
searchable: true |
||||
|
}, |
||||
|
{ |
||||
|
name: 'description', |
||||
|
type: 'Edm.String', |
||||
|
searchable: true |
||||
|
}, |
||||
|
{ |
||||
|
name: 'content', |
||||
|
type: 'Edm.String', |
||||
|
searchable: true |
||||
|
} |
||||
|
], |
||||
|
scoringProfiles: [ |
||||
|
{ |
||||
|
name: 'fieldWeights', |
||||
|
text: { |
||||
|
weights: { |
||||
|
title: 4, |
||||
|
description: 3, |
||||
|
content: 1 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
], |
||||
|
suggesters: [ |
||||
|
{ |
||||
|
name: 'suggestions', |
||||
|
searchMode: 'analyzingInfixMatching', |
||||
|
sourceFields: ['title', 'description', 'content'] |
||||
|
} |
||||
|
], |
||||
|
}) |
||||
|
} |
||||
}, |
}, |
||||
created() { |
|
||||
|
|
||||
|
/** |
||||
|
* QUERY |
||||
|
* |
||||
|
* @param {String} q Query |
||||
|
* @param {Object} opts Additional options |
||||
|
*/ |
||||
|
async query(q, opts) { |
||||
|
try { |
||||
|
let suggestions = [] |
||||
|
const results = await this.client.indexes.use(this.config.indexName).search({ |
||||
|
count: true, |
||||
|
scoringProfile: 'fieldWeights', |
||||
|
search: q, |
||||
|
select: 'id, locale, path, title, description', |
||||
|
queryType: QueryType.simple, |
||||
|
top: 50 |
||||
|
}) |
||||
|
if (results.result.value.length < 5) { |
||||
|
// Using plain request, not yet available in library...
|
||||
|
try { |
||||
|
const suggestResults = await request({ |
||||
|
uri: `https://${this.config.serviceName}.search.windows.net/indexes/${this.config.indexName}/docs/autocomplete`, |
||||
|
method: 'post', |
||||
|
qs: { |
||||
|
'api-version': '2017-11-11-Preview' |
||||
|
}, |
||||
|
headers: { |
||||
|
'api-key': this.config.adminKey, |
||||
|
'Content-Type': 'application/json' |
||||
|
}, |
||||
|
json: true, |
||||
|
body: { |
||||
|
autocompleteMode: 'oneTermWithContext', |
||||
|
search: q, |
||||
|
suggesterName: 'suggestions' |
||||
|
} |
||||
|
}) |
||||
|
suggestions = suggestResults.value.map(s => s.queryPlusText) |
||||
|
} catch (err) { |
||||
|
WIKI.logger.warn('Search Engine suggestion failure: ', err) |
||||
|
} |
||||
|
} |
||||
|
return { |
||||
|
results: results.result.value, |
||||
|
suggestions, |
||||
|
totalHits: results.result['@odata.count'] |
||||
|
} |
||||
|
} catch (err) { |
||||
|
WIKI.logger.warn('Search Engine Error:') |
||||
|
WIKI.logger.warn(err) |
||||
|
} |
||||
}, |
}, |
||||
updated() { |
|
||||
|
|
||||
|
/** |
||||
|
* CREATE |
||||
|
* |
||||
|
* @param {Object} page Page to create |
||||
|
*/ |
||||
|
async created(page) { |
||||
|
await this.client.indexes.use(this.config.indexName).index([ |
||||
|
{ |
||||
|
id: page.hash, |
||||
|
locale: page.localeCode, |
||||
|
path: page.path, |
||||
|
title: page.title, |
||||
|
description: page.description, |
||||
|
content: page.content |
||||
|
} |
||||
|
]) |
||||
}, |
}, |
||||
deleted() { |
|
||||
|
|
||||
|
/** |
||||
|
* UPDATE |
||||
|
* |
||||
|
* @param {Object} page Page to update |
||||
|
*/ |
||||
|
async updated(page) { |
||||
|
await this.client.indexes.use(this.config.indexName).index([ |
||||
|
{ |
||||
|
id: page.hash, |
||||
|
locale: page.localeCode, |
||||
|
path: page.path, |
||||
|
title: page.title, |
||||
|
description: page.description, |
||||
|
content: page.content |
||||
|
} |
||||
|
]) |
||||
}, |
}, |
||||
renamed() { |
|
||||
|
|
||||
|
/** |
||||
|
* DELETE |
||||
|
* |
||||
|
* @param {Object} page Page to delete |
||||
|
*/ |
||||
|
async deleted(page) { |
||||
|
await this.client.indexes.use(this.config.indexName).index([ |
||||
|
{ |
||||
|
'@search.action': 'delete', |
||||
|
id: page.hash |
||||
|
} |
||||
|
]) |
||||
}, |
}, |
||||
rebuild() { |
|
||||
|
|
||||
|
/** |
||||
|
* RENAME |
||||
|
* |
||||
|
* @param {Object} page Page to rename |
||||
|
*/ |
||||
|
async renamed(page) { |
||||
|
await this.client.indexes.use(this.config.indexName).index([ |
||||
|
{ |
||||
|
'@search.action': 'delete', |
||||
|
id: page.sourceHash |
||||
|
} |
||||
|
]) |
||||
|
await this.client.indexes.use(this.config.indexName).index([ |
||||
|
{ |
||||
|
id: page.destinationHash, |
||||
|
locale: page.localeCode, |
||||
|
path: page.destinationPath, |
||||
|
title: page.title, |
||||
|
description: page.description, |
||||
|
content: page.content |
||||
|
} |
||||
|
]) |
||||
|
}, |
||||
|
/** |
||||
|
* REBUILD INDEX |
||||
|
*/ |
||||
|
async rebuild() { |
||||
|
await pipeline( |
||||
|
WIKI.models.knex.column({ id: 'hash' }, 'path', { locale: 'localeCode' }, 'title', 'description', 'content').select().from('pages').where({ |
||||
|
isPublished: true, |
||||
|
isPrivate: false |
||||
|
}).stream(), |
||||
|
this.client.indexes.use(this.config.indexName).createIndexingStream() |
||||
|
) |
||||
} |
} |
||||
} |
} |
Write
Preview
Loading…
Cancel
Save