2020-02-19 15:39:35 +01:00
|
|
|
import { ApiResponse, Client } from '@elastic/elasticsearch'
|
2020-02-13 11:49:03 +01:00
|
|
|
import { CONFIG } from '../initializers/constants'
|
2020-02-19 15:39:35 +01:00
|
|
|
import { logger } from './logger'
|
|
|
|
import { flatMap } from 'lodash'
|
|
|
|
import { IndexableDoc } from '../types/elastic-search.model'
|
2020-02-13 11:49:03 +01:00
|
|
|
|
2020-02-14 14:09:31 +01:00
|
|
|
const elasticSearch = new Client({ node: 'http://' + CONFIG.ELASTIC_SEARCH.HOSTNAME + ':' + CONFIG.ELASTIC_SEARCH.PORT })
|
2020-02-13 11:49:03 +01:00
|
|
|
|
2020-02-14 16:14:45 +01:00
|
|
|
function buildSort (value: string) {
|
2020-02-19 15:39:35 +01:00
|
|
|
let sortField: string
|
2020-02-14 16:14:45 +01:00
|
|
|
let direction: 'asc' | 'desc'
|
|
|
|
|
|
|
|
if (value.substring(0, 1) === '-') {
|
|
|
|
direction = 'desc'
|
2020-02-19 15:39:35 +01:00
|
|
|
sortField = value.substring(1)
|
2020-02-14 16:14:45 +01:00
|
|
|
} else {
|
|
|
|
direction = 'asc'
|
2020-02-19 15:39:35 +01:00
|
|
|
sortField = value
|
2020-02-14 16:14:45 +01:00
|
|
|
}
|
|
|
|
|
2020-02-19 15:39:35 +01:00
|
|
|
const field = sortField === 'match'
|
|
|
|
? '_score'
|
|
|
|
: sortField
|
|
|
|
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
[field]: { order: direction }
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildIndex (name: string, mapping: object) {
|
|
|
|
logger.info('Initialize %s Elastic Search index.', name)
|
|
|
|
|
|
|
|
return elasticSearch.indices.create({
|
|
|
|
index: name,
|
|
|
|
body: {
|
|
|
|
settings: {
|
|
|
|
number_of_shards: 1,
|
|
|
|
number_of_replicas: 1
|
|
|
|
},
|
|
|
|
mappings: {
|
|
|
|
properties: mapping
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}).catch(err => {
|
|
|
|
if (err.name === 'ResponseError' && err.meta?.body?.error.root_cause[0]?.type === 'resource_already_exists_exception') return
|
|
|
|
|
|
|
|
throw err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
async function indexDocuments <T extends IndexableDoc> (options: {
|
|
|
|
objects: T[]
|
|
|
|
formatter: (o: T) => any
|
|
|
|
replace: boolean
|
|
|
|
index: string
|
|
|
|
}) {
|
|
|
|
const { objects, formatter, replace, index } = options
|
|
|
|
|
|
|
|
const elIdIndex: { [elId: string]: T } = {}
|
|
|
|
|
|
|
|
for (const object of objects) {
|
|
|
|
elIdIndex[object.elasticSearchId] = object
|
|
|
|
}
|
|
|
|
|
|
|
|
const method = replace ? 'index' : 'update'
|
|
|
|
|
|
|
|
const body = flatMap(objects, v => {
|
|
|
|
const doc = formatter(v)
|
|
|
|
|
|
|
|
const options = replace
|
|
|
|
? doc
|
|
|
|
: { doc, doc_as_upsert: true }
|
|
|
|
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
[method]: {
|
|
|
|
_id: v.elasticSearchId,
|
|
|
|
_index: index
|
|
|
|
}
|
|
|
|
},
|
|
|
|
options
|
|
|
|
]
|
|
|
|
})
|
|
|
|
|
|
|
|
const result = await elasticSearch.bulk({
|
|
|
|
index,
|
|
|
|
body
|
|
|
|
})
|
|
|
|
|
|
|
|
const resultBody = result.body
|
|
|
|
|
|
|
|
if (resultBody.errors === true) {
|
|
|
|
const msg = 'Cannot insert data in elastic search.'
|
2020-05-28 16:32:49 +02:00
|
|
|
logger.error({ err: resultBody }, msg)
|
2020-02-19 15:39:35 +01:00
|
|
|
throw new Error(msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
const created: T[] = result.body.items
|
|
|
|
.map(i => i[method])
|
|
|
|
.filter(i => i.result === 'created')
|
|
|
|
.map(i => elIdIndex[i._id])
|
|
|
|
|
|
|
|
return { created }
|
|
|
|
}
|
|
|
|
|
|
|
|
function extractQueryResult (result: ApiResponse<any, any>) {
|
|
|
|
const hits = result.body.hits
|
|
|
|
|
2020-08-27 14:44:21 +02:00
|
|
|
return { total: hits.total.value, data: hits.hits.map(h => Object.assign(h._source, { score: h._score })) }
|
2020-02-14 16:14:45 +01:00
|
|
|
}
|
|
|
|
|
2020-02-13 11:49:03 +01:00
|
|
|
export {
|
2020-02-14 16:14:45 +01:00
|
|
|
elasticSearch,
|
2020-02-19 15:39:35 +01:00
|
|
|
indexDocuments,
|
|
|
|
buildSort,
|
|
|
|
extractQueryResult,
|
|
|
|
buildIndex
|
2020-02-13 11:49:03 +01:00
|
|
|
}
|