import { Client } from '@elastic/elasticsearch' import { CONFIG } from '../initializers/constants' import { IndexableVideo } from '../types/video.model' import { Avatar } from '@shared/models/avatars/avatar.model' import { flatMap } from 'lodash' const client = new Client({ node: 'http://' + CONFIG.ELASTIC_SEARCH.HOSTNAME + ':' + CONFIG.ELASTIC_SEARCH.PORT }) function initVideosIndex () { return client.indices.create({ index: CONFIG.ELASTIC_SEARCH.INDEXES.VIDEOS, body: { settings: { number_of_shards: 1, number_of_replicas: 1 }, mappings: { properties: buildVideosMapping() } } }).catch(err => { if (err.name === 'ResponseError' && err.meta?.body?.error.root_cause[0]?.type === 'resource_already_exists_exception') return throw err }) } function indexVideos (videos: IndexableVideo[]) { const body = flatMap(videos, v => { return [ { update: { _id: v.elasticSearchId, _index: CONFIG.ELASTIC_SEARCH.INDEXES.VIDEOS } }, { doc: formatVideo(v), doc_as_upsert: true } ] }) return client.bulk({ index: CONFIG.ELASTIC_SEARCH.INDEXES.VIDEOS, body }) } function refreshVideosIndex () { return client.indices.refresh({ index: CONFIG.ELASTIC_SEARCH.INDEXES.VIDEOS }) } async function queryVideos (query: any) { const res = await client.search({ index: CONFIG.ELASTIC_SEARCH.INDEXES.VIDEOS, body: { query } }) return res.body.hits.hits } export { indexVideos, queryVideos, refreshVideosIndex, initVideosIndex } // ############################################################################ function formatVideo (v: IndexableVideo) { return { uuid: v.uuid, createdAt: v.createdAt, updatedAt: v.updatedAt, publishedAt: v.publishedAt, originallyPublishedAt: v.originallyPublishedAt, category: { id: v.category.id, label: v.category.label }, licence: { id: v.licence.id, label: v.licence.label }, language: { id: v.language.id, label: v.language.label }, privacy: { id: v.privacy.id, label: v.privacy.label }, name: v.name, description: v.description, duration: v.duration, thumbnailPath: v.thumbnailPath, previewPath: v.previewPath, embedPath: v.embedPath, views: v.views, likes: v.likes, dislikes: v.dislikes, nsfw: v.nsfw, host: v.host, account: { name: v.account.name, displayName: v.account.displayName, url: v.account.url, host: v.account.host, avatar: formatAvatar(v.account) }, channel: { name: v.channel.name, displayName: v.channel.displayName, url: v.channel.url, host: v.channel.host, avatar: formatAvatar(v.channel) } } } function formatAvatar (obj: { avatar?: Avatar }) { if (!obj.avatar) return null return { path: obj.avatar.path, createdAt: obj.avatar.createdAt, updatedAt: obj.avatar.updatedAt } } function buildChannelOrAccountMapping () { return { name: { type: 'text', fields: { raw: { type: 'keyword' } } }, displayName: { type: 'text' }, url: { type: 'keyword' }, host: { type: 'keyword' }, avatar: { properties: { path: { type: 'keyword' }, createdAt: { type: 'date' }, updatedAt: { type: 'date' } } } } } function buildVideosMapping () { return { uuid: { type: 'keyword' }, createdAt: { type: 'date' }, updatedAt: { type: 'date' }, publishedAt: { type: 'date' }, originallyPublishedAt: { type: 'date' }, category: { properties: { id: { type: 'keyword' }, label: { type: 'text' } } }, licence: { properties: { id: { type: 'keyword' }, label: { type: 'text' } } }, language: { properties: { id: { type: 'keyword' }, label: { type: 'text' } } }, privacy: { properties: { id: { type: 'keyword' }, label: { type: 'text' } } }, name: { type: 'text' }, description: { type: 'text' }, duration: { type: 'long' }, thumbnailPath: { type: 'keyword' }, previewPath: { type: 'keyword' }, embedPath: { type: 'keyword' }, views: { type: 'long' }, likes: { type: 'long' }, dislikes: { type: 'long' }, nsfw: { type: 'boolean' }, host: { type: 'keyword' }, account: { properties: buildChannelOrAccountMapping() }, channel: { properties: buildChannelOrAccountMapping() } } }