@vegan-friendly/strapi-plugin-elasticsearch 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,8 +13,8 @@ const instance = axios_1.default.create({
13
13
  });
14
14
  instance.interceptors.request.use(async (config) => {
15
15
  config.headers = {
16
- Authorization: `Bearer ${helper_plugin_1.auth.getToken()}`,
17
- Accept: 'application/json',
16
+ 'Authorization': `Bearer ${helper_plugin_1.auth.getToken()}`,
17
+ 'Accept': 'application/json',
18
18
  'Content-Type': 'application/json',
19
19
  };
20
20
  return config;
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegan-friendly/strapi-plugin-elasticsearch",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.2.0",
4
4
  "description": "A Strapi plugin to enable using Elasticsearch with Strapi CMS.",
5
5
  "homepage": "https://github.com/vegan-friendly/strapi-plugin-elasticsearch",
6
6
  "strapi": {
@@ -37,7 +37,8 @@
37
37
  "dependencies": {
38
38
  "@elastic/elasticsearch": "^8.9.0",
39
39
  "@strapi/design-system": "^1.19.0",
40
- "markdown-to-txt": "^2.0.1"
40
+ "markdown-to-txt": "^2.0.1",
41
+ "pretty-ms": "^9.2.0"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@strapi/strapi": "^4.0.0"
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- console.log('strapi-plugin-elasticsearch : 00 Initializing strapi-plugin-elasticsearch plugin.');
4
3
  exports.default = async ({ strapi }) => {
5
4
  const pluginConfig = await strapi.config.get('plugin.elasticsearch');
6
5
  const configureIndexingService = strapi.plugins['elasticsearch'].services.configureIndexing;
@@ -8,8 +7,8 @@ exports.default = async ({ strapi }) => {
8
7
  const esInterface = strapi.plugins['elasticsearch'].services.esInterface;
9
8
  const indexer = strapi.plugins['elasticsearch'].services.indexer;
10
9
  const helper = strapi.plugins['elasticsearch'].services.helper;
10
+ const virtualCollectionIndexer = strapi.plugins['elasticsearch'].services.virtualCollectionsIndexer;
11
11
  try {
12
- console.log('strapi-plugin-elasticsearch 1: Initializing strapi-plugin-elasticsearch plugin.');
13
12
  await configureIndexingService.initializeStrapiElasticsearch();
14
13
  if (!Object.keys(pluginConfig).includes('indexingCronSchedule'))
15
14
  console.warn('The plugin strapi-plugin-elasticsearch is enabled but the indexingCronSchedule is not configured.');
@@ -116,6 +115,33 @@ exports.default = async ({ strapi }) => {
116
115
  }
117
116
  }
118
117
  });
118
+ // Register virtual collections //
119
+ const registry = strapi.service('plugin::elasticsearch.virtualCollectionsRegistry');
120
+ // Setup lifecycle hooks
121
+ const virtualCollections = registry.getAll();
122
+ // Create a set of all collections that need hooks
123
+ const collectionsToHook = new Set();
124
+ virtualCollections.forEach((collection) => {
125
+ collection.triggers.forEach((trigger) => {
126
+ collectionsToHook.add(trigger.collection);
127
+ });
128
+ });
129
+ // Setup hooks for each collection
130
+ collectionsToHook.forEach((collectionUID) => {
131
+ strapi.log.info(`Setting up Elasticsearch lifecycle hooks for collection: ${collectionUID}`);
132
+ strapi.db.lifecycles.subscribe({
133
+ models: [collectionUID],
134
+ afterCreate: async (event) => {
135
+ await virtualCollectionIndexer.handleTriggerEvent(event);
136
+ },
137
+ afterUpdate: async (event) => {
138
+ await virtualCollectionIndexer.handleTriggerEvent(event);
139
+ },
140
+ afterDelete: async (event) => {
141
+ await virtualCollectionIndexer.handleTriggerEvent(event);
142
+ },
143
+ });
144
+ });
119
145
  configureIndexingService.markInitialized();
120
146
  }
121
147
  catch (err) {
@@ -1,3 +1,4 @@
1
+ export * from './types';
1
2
  declare const _default: {
2
3
  register: ({ strapi }: {
3
4
  strapi: any;
@@ -145,34 +146,11 @@ declare const _default: {
145
146
  };
146
147
  esInterface: ({ strapi }: {
147
148
  strapi: any;
148
- }) => {
149
- initializeSearchEngine({ host, uname, password, cert }: {
150
- host: any;
151
- uname: any;
152
- password: any;
153
- cert: any;
154
- }): Promise<void>;
155
- createIndex(indexName: any): Promise<void>;
156
- deleteIndex(indexName: any): Promise<void>;
157
- attachAliasToIndex(indexName: any): Promise<void>;
158
- checkESConnection(): Promise<boolean>;
159
- indexDataToSpecificIndex({ itemId, itemData }: {
160
- itemId: any;
161
- itemData: any;
162
- }, iName: any): Promise<void>;
163
- indexData({ itemId, itemData }: {
164
- itemId: any;
165
- itemData: any;
166
- }): Promise<void>;
167
- removeItemFromIndex({ itemId }: {
168
- itemId: any;
169
- }): Promise<void>;
170
- searchData(searchQuery: any): Promise<any>;
171
- };
149
+ }) => import("./types").EsInterfaceService;
172
150
  indexer: ({ strapi }: {
173
151
  strapi: any;
174
152
  }) => {
175
- rebuildIndex(): Promise<boolean | undefined>;
153
+ rebuildIndex(): Promise<boolean>;
176
154
  indexCollection(collectionName: any, indexName?: null): Promise<boolean>;
177
155
  indexPendingData(): Promise<boolean>;
178
156
  };
@@ -223,6 +201,12 @@ declare const _default: {
223
201
  from: any;
224
202
  }): any;
225
203
  };
204
+ virtualCollectionsRegistry: ({ strapi }: {
205
+ strapi: any;
206
+ }) => import("./types").VirtualCollectionsRegistryService;
207
+ virtualCollectionsIndexer: ({ strapi }: {
208
+ strapi: any;
209
+ }) => import("./types").VirtualCollectionsIndexerService;
226
210
  };
227
211
  contentTypes: {
228
212
  task: {
@@ -1,4 +1,18 @@
1
1
  'use strict';
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
18
  };
@@ -13,6 +27,7 @@ const routes_1 = __importDefault(require("./routes"));
13
27
  const middlewares_1 = __importDefault(require("./middlewares"));
14
28
  const policies_1 = __importDefault(require("./policies"));
15
29
  const services_1 = __importDefault(require("./services"));
30
+ __exportStar(require("./types"), exports);
16
31
  exports.default = {
17
32
  register: register_1.default,
18
33
  bootstrap: bootstrap_1.default,
@@ -124,7 +124,7 @@ exports.default = ({ strapi }) => ({
124
124
  },
125
125
  async importContentConfig({ config }) {
126
126
  const pluginStore = getPluginStore();
127
- const settings = await pluginStore.get({ key: 'configsettings' });
127
+ const settings = (await pluginStore.get({ key: 'configsettings' }));
128
128
  if (settings) {
129
129
  const objSettings = JSON.parse(settings);
130
130
  objSettings['contentConfig'] = JSON.parse(config);
@@ -144,7 +144,7 @@ exports.default = ({ strapi }) => ({
144
144
  },
145
145
  async setContentConfig({ collection, config }) {
146
146
  const pluginStore = getPluginStore();
147
- const settings = await pluginStore.get({ key: 'configsettings' });
147
+ const settings = (await pluginStore.get({ key: 'configsettings' }));
148
148
  if (settings) {
149
149
  const objSettings = JSON.parse(settings);
150
150
  if (Object.keys(objSettings).includes('contentConfig')) {
@@ -1,27 +1,5 @@
1
+ import { EsInterfaceService } from '../types';
1
2
  declare const _default: ({ strapi }: {
2
3
  strapi: any;
3
- }) => {
4
- initializeSearchEngine({ host, uname, password, cert }: {
5
- host: any;
6
- uname: any;
7
- password: any;
8
- cert: any;
9
- }): Promise<void>;
10
- createIndex(indexName: any): Promise<void>;
11
- deleteIndex(indexName: any): Promise<void>;
12
- attachAliasToIndex(indexName: any): Promise<void>;
13
- checkESConnection(): Promise<boolean>;
14
- indexDataToSpecificIndex({ itemId, itemData }: {
15
- itemId: any;
16
- itemData: any;
17
- }, iName: any): Promise<void>;
18
- indexData({ itemId, itemData }: {
19
- itemId: any;
20
- itemData: any;
21
- }): Promise<void>;
22
- removeItemFromIndex({ itemId }: {
23
- itemId: any;
24
- }): Promise<void>;
25
- searchData(searchQuery: any): Promise<any>;
26
- };
4
+ }) => EsInterfaceService;
27
5
  export default _default;
@@ -18,7 +18,7 @@ exports.default = ({ strapi }) => ({
18
18
  });
19
19
  }
20
20
  catch (err) {
21
- if (err.message.includes('ECONNREFUSED')) {
21
+ if (err?.message?.includes('ECONNREFUSED')) {
22
22
  console.error('strapi-plugin-elasticsearch : Connection to ElasticSearch at ', host, ' refused.');
23
23
  console.error(err);
24
24
  }
@@ -40,7 +40,7 @@ exports.default = ({ strapi }) => ({
40
40
  }
41
41
  }
42
42
  catch (err) {
43
- if (err.message.includes('ECONNREFUSED')) {
43
+ if (err?.message?.includes('ECONNREFUSED')) {
44
44
  console.log('strapi-plugin-elasticsearch : Error while creating index - connection to ElasticSearch refused.');
45
45
  console.log(err);
46
46
  }
@@ -57,7 +57,7 @@ exports.default = ({ strapi }) => ({
57
57
  });
58
58
  }
59
59
  catch (err) {
60
- if (err.message.includes('ECONNREFUSED')) {
60
+ if (err?.message?.includes('ECONNREFUSED')) {
61
61
  console.log('strapi-plugin-elasticsearch : Connection to ElasticSearch refused.');
62
62
  console.log(err);
63
63
  }
@@ -83,7 +83,7 @@ exports.default = ({ strapi }) => ({
83
83
  await client.indices.putAlias({ index: indexName, name: aliasName });
84
84
  }
85
85
  catch (err) {
86
- if (err.message.includes('ECONNREFUSED')) {
86
+ if (err?.message?.includes('ECONNREFUSED')) {
87
87
  console.log('strapi-plugin-elasticsearch : Attaching alias to the index - Connection to ElasticSearch refused.');
88
88
  console.log(err);
89
89
  }
@@ -97,7 +97,7 @@ exports.default = ({ strapi }) => ({
97
97
  if (!client)
98
98
  return false;
99
99
  try {
100
- await client.ping();
100
+ await client?.ping();
101
101
  return true;
102
102
  }
103
103
  catch (error) {
@@ -135,7 +135,7 @@ exports.default = ({ strapi }) => ({
135
135
  await client.indices.refresh({ index: pluginConfig.indexAliasName });
136
136
  }
137
137
  catch (err) {
138
- if (err.meta.statusCode === 404)
138
+ if (err?.meta?.statusCode === 404)
139
139
  console.error('strapi-plugin-elasticsearch : The entry to be removed from the index already does not exist.');
140
140
  else {
141
141
  console.error('strapi-plugin-elasticsearch : Error encountered while removing indexed data from ElasticSearch.');
@@ -25,34 +25,35 @@ const getFullPopulateObject = (modelUid, maxDepth = 20, ignore) => {
25
25
  if (maxDepth <= 1) {
26
26
  return true;
27
27
  }
28
- if (modelUid === "admin::user" && skipCreatorFields) {
28
+ if (modelUid === 'admin::user' && skipCreatorFields) {
29
29
  return undefined;
30
30
  }
31
31
  const populate = {};
32
32
  const model = strapi.getModel(modelUid);
33
33
  if (ignore && !ignore.includes(model.collectionName))
34
34
  ignore.push(model.collectionName);
35
- for (const [key, value] of Object.entries(getModelPopulationAttributes(model))) {
35
+ for (const [key, valueRaw] of Object.entries(getModelPopulationAttributes(model))) {
36
36
  if (ignore?.includes(key))
37
37
  continue;
38
+ const value = valueRaw;
38
39
  if (value) {
39
- if (value.type === "component") {
40
+ if (value.type === 'component') {
40
41
  populate[key] = getFullPopulateObject(value.component, maxDepth - 1);
41
42
  }
42
- else if (value.type === "dynamiczone") {
43
- const dynamicPopulate = value.components.reduce((prev, cur) => {
43
+ else if (value.type === 'dynamiczone') {
44
+ const dynamicPopulate = value.components?.reduce((prev, cur) => {
44
45
  const curPopulate = getFullPopulateObject(cur, maxDepth - 1);
45
46
  return curPopulate === true ? prev : (0, fp_1.merge)(prev, curPopulate);
46
47
  }, {});
47
48
  populate[key] = (0, fp_1.isEmpty)(dynamicPopulate) ? true : dynamicPopulate;
48
49
  }
49
- else if (value.type === "relation") {
50
- const relationPopulate = getFullPopulateObject(value.target, (key === 'localizations') && maxDepth > 2 ? 1 : maxDepth - 1, ignore);
50
+ else if (value.type === 'relation') {
51
+ const relationPopulate = getFullPopulateObject(value.target, key === 'localizations' && maxDepth > 2 ? 1 : maxDepth - 1, ignore);
51
52
  if (relationPopulate) {
52
53
  populate[key] = relationPopulate;
53
54
  }
54
55
  }
55
- else if (value.type === "media") {
56
+ else if (value.type === 'media') {
56
57
  populate[key] = true;
57
58
  }
58
59
  }
@@ -108,52 +109,56 @@ function extractSubfieldData({ config, data }) {
108
109
  if (Object.keys(extractItem).includes('__component')) {
109
110
  if (conf.component === extractItem.__component &&
110
111
  !Object.keys(conf).includes('subfields') &&
111
- typeof extractItem[conf['field']] !== "undefined" &&
112
+ typeof extractItem[conf['field']] !== 'undefined' &&
112
113
  extractItem[conf['field']]) {
113
114
  let val = extractItem[conf['field']];
114
- if (Object.keys(conf).includes('transform')
115
- && conf['transform'] === 'markdown')
115
+ if (Object.keys(conf).includes('transform') && conf['transform'] === 'markdown')
116
116
  val = transform_content_1.default.transform({ content: val, from: 'markdown' });
117
117
  returnData = returnData + '\n' + val;
118
118
  }
119
- else if (conf.component === extractItem.__component &&
120
- Object.keys(conf).includes('subfields')) {
121
- returnData = returnData + '\n' + extractSubfieldData({
122
- config: conf['subfields'], data: extractItem[conf['field']]
123
- });
119
+ else if (conf.component === extractItem.__component && Object.keys(conf).includes('subfields')) {
120
+ returnData =
121
+ returnData +
122
+ '\n' +
123
+ extractSubfieldData({
124
+ config: conf['subfields'],
125
+ data: extractItem[conf['field']],
126
+ });
124
127
  }
125
128
  }
126
129
  else {
127
- if (!Object.keys(conf).includes('subfields') &&
128
- typeof extractItem[conf['field']] !== "undefined" &&
129
- extractItem[conf['field']]) {
130
+ if (!Object.keys(conf).includes('subfields') && typeof extractItem[conf['field']] !== 'undefined' && extractItem[conf['field']]) {
130
131
  let val = extractItem[conf['field']];
131
- if (Object.keys(conf).includes('transform')
132
- && conf['transform'] === 'markdown')
132
+ if (Object.keys(conf).includes('transform') && conf['transform'] === 'markdown')
133
133
  val = transform_content_1.default.transform({ content: val, from: 'markdown' });
134
134
  returnData = returnData + '\n' + val;
135
135
  }
136
136
  else if (Object.keys(conf).includes('subfields')) {
137
- returnData = returnData + '\n' + extractSubfieldData({
138
- config: conf['subfields'], data: extractItem[conf['field']]
139
- });
137
+ returnData =
138
+ returnData +
139
+ '\n' +
140
+ extractSubfieldData({
141
+ config: conf['subfields'],
142
+ data: extractItem[conf['field']],
143
+ });
140
144
  }
141
145
  }
142
146
  }
143
147
  }
144
- }
145
- else //for single component as a field
146
- {
148
+ } //for single component as a field
149
+ else {
147
150
  for (let s = 0; s < config.length; s++) {
148
151
  const conf = config[s];
149
- if (!Object.keys(conf).includes('subfields') &&
150
- typeof data[conf['field']] !== "undefined" &&
151
- data[conf['field']])
152
+ if (!Object.keys(conf).includes('subfields') && typeof data[conf['field']] !== 'undefined' && data[conf['field']])
152
153
  returnData = returnData + '\n' + data[conf['field']];
153
154
  else if (Object.keys(conf).includes('subfields')) {
154
- returnData = returnData + '\n' + extractSubfieldData({
155
- config: conf['subfields'], data: data[conf['field']]
156
- });
155
+ returnData =
156
+ returnData +
157
+ '\n' +
158
+ extractSubfieldData({
159
+ config: conf['subfields'],
160
+ data: data[conf['field']],
161
+ });
157
162
  }
158
163
  }
159
164
  }
@@ -164,20 +169,12 @@ exports.default = ({ strapi }) => ({
164
169
  const configureService = strapi.plugins['elasticsearch'].services.configureIndexing;
165
170
  const esInterface = strapi.plugins['elasticsearch'].services.esInterface;
166
171
  const pluginConfig = await strapi.config.get('plugin.elasticsearch');
167
- const connected = pluginConfig.searchConnector && pluginConfig.searchConnector.host
168
- ? await esInterface.checkESConnection()
169
- : false;
172
+ const connected = pluginConfig.searchConnector && pluginConfig.searchConnector.host ? await esInterface.checkESConnection() : false;
170
173
  return {
171
174
  indexingCronSchedule: pluginConfig.indexingCronSchedule || 'Not configured',
172
- elasticHost: pluginConfig.searchConnector
173
- ? pluginConfig.searchConnector.host || 'Not configured'
174
- : 'Not configured',
175
- elasticUserName: pluginConfig.searchConnector
176
- ? pluginConfig.searchConnector.username || 'Not configured'
177
- : 'Not configured',
178
- elasticCertificate: pluginConfig.searchConnector
179
- ? pluginConfig.searchConnector.certificate || 'Not configured'
180
- : 'Not configured',
175
+ elasticHost: pluginConfig.searchConnector ? pluginConfig.searchConnector.host || 'Not configured' : 'Not configured',
176
+ elasticUserName: pluginConfig.searchConnector ? pluginConfig.searchConnector.username || 'Not configured' : 'Not configured',
177
+ elasticCertificate: pluginConfig.searchConnector ? pluginConfig.searchConnector.certificate || 'Not configured' : 'Not configured',
181
178
  elasticIndexAlias: pluginConfig.indexAliasName || 'Not configured',
182
179
  connected: connected,
183
180
  initialized: configureService.isInitialized(),
@@ -197,7 +194,7 @@ exports.default = ({ strapi }) => ({
197
194
  },
198
195
  async getCurrentIndexName() {
199
196
  const pluginStore = getPluginStore();
200
- const settings = await pluginStore.get({ key: 'configsettings' });
197
+ const settings = (await pluginStore.get({ key: 'configsettings' }));
201
198
  let indexName = 'strapi-plugin-elasticsearch-index_000001';
202
199
  if (settings) {
203
200
  const objSettings = JSON.parse(settings);
@@ -215,7 +212,7 @@ exports.default = ({ strapi }) => ({
215
212
  },
216
213
  async storeCurrentIndexName(indexName) {
217
214
  const pluginStore = getPluginStore();
218
- const settings = await pluginStore.get({ key: 'configsettings' });
215
+ const settings = (await pluginStore.get({ key: 'configsettings' }));
219
216
  if (settings) {
220
217
  const objSettings = JSON.parse(settings);
221
218
  objSettings['indexConfig'] = { name: indexName };
@@ -262,8 +259,7 @@ exports.default = ({ strapi }) => ({
262
259
  }
263
260
  else {
264
261
  val = data[fti[k]];
265
- if (Object.keys(fieldConfig).includes('transform') &&
266
- fieldConfig['transform'] === 'markdown')
262
+ if (Object.keys(fieldConfig).includes('transform') && fieldConfig['transform'] === 'markdown')
267
263
  val = transform_content_1.default.transform({ content: val, from: 'markdown' });
268
264
  }
269
265
  if (Object.keys(fieldConfig).includes('searchFieldName'))
@@ -42,34 +42,11 @@ declare const _default: {
42
42
  };
43
43
  esInterface: ({ strapi }: {
44
44
  strapi: any;
45
- }) => {
46
- initializeSearchEngine({ host, uname, password, cert }: {
47
- host: any;
48
- uname: any;
49
- password: any;
50
- cert: any;
51
- }): Promise<void>;
52
- createIndex(indexName: any): Promise<void>;
53
- deleteIndex(indexName: any): Promise<void>;
54
- attachAliasToIndex(indexName: any): Promise<void>;
55
- checkESConnection(): Promise<boolean>;
56
- indexDataToSpecificIndex({ itemId, itemData }: {
57
- itemId: any;
58
- itemData: any;
59
- }, iName: any): Promise<void>;
60
- indexData({ itemId, itemData }: {
61
- itemId: any;
62
- itemData: any;
63
- }): Promise<void>;
64
- removeItemFromIndex({ itemId }: {
65
- itemId: any;
66
- }): Promise<void>;
67
- searchData(searchQuery: any): Promise<any>;
68
- };
45
+ }) => import("..").EsInterfaceService;
69
46
  indexer: ({ strapi }: {
70
47
  strapi: any;
71
48
  }) => {
72
- rebuildIndex(): Promise<boolean | undefined>;
49
+ rebuildIndex(): Promise<boolean>;
73
50
  indexCollection(collectionName: any, indexName?: null): Promise<boolean>;
74
51
  indexPendingData(): Promise<boolean>;
75
52
  };
@@ -120,5 +97,11 @@ declare const _default: {
120
97
  from: any;
121
98
  }): any;
122
99
  };
100
+ virtualCollectionsRegistry: ({ strapi }: {
101
+ strapi: any;
102
+ }) => import("..").VirtualCollectionsRegistryService;
103
+ virtualCollectionsIndexer: ({ strapi }: {
104
+ strapi: any;
105
+ }) => import("..").VirtualCollectionsIndexerService;
123
106
  };
124
107
  export default _default;
@@ -10,6 +10,8 @@ const perform_indexing_1 = __importDefault(require("./perform-indexing"));
10
10
  const log_indexing_1 = __importDefault(require("./log-indexing"));
11
11
  const helper_1 = __importDefault(require("./helper"));
12
12
  const transform_content_1 = __importDefault(require("./transform-content"));
13
+ const virtual_collections_registry_1 = __importDefault(require("./virtual-collections-registry"));
14
+ const virtual_collections_indexer_1 = __importDefault(require("./virtual-collections-indexer"));
13
15
  exports.default = {
14
16
  configureIndexing: configure_indexing_1.default,
15
17
  scheduleIndexing: schedule_indexing_1.default,
@@ -18,4 +20,6 @@ exports.default = {
18
20
  logIndexing: log_indexing_1.default,
19
21
  helper: helper_1.default,
20
22
  transformContent: transform_content_1.default,
23
+ virtualCollectionsRegistry: virtual_collections_registry_1.default,
24
+ virtualCollectionsIndexer: virtual_collections_indexer_1.default,
21
25
  };
@@ -1,7 +1,7 @@
1
1
  declare const _default: ({ strapi }: {
2
2
  strapi: any;
3
3
  }) => {
4
- rebuildIndex(): Promise<boolean | undefined>;
4
+ rebuildIndex(): Promise<boolean>;
5
5
  indexCollection(collectionName: any, indexName?: null): Promise<boolean>;
6
6
  indexPendingData(): Promise<boolean>;
7
7
  };
@@ -7,6 +7,7 @@ exports.default = ({ strapi }) => ({
7
7
  const scheduleIndexingService = strapi.plugins['elasticsearch'].services.scheduleIndexing;
8
8
  const configureIndexingService = strapi.plugins['elasticsearch'].services.configureIndexing;
9
9
  const logIndexingService = strapi.plugins['elasticsearch'].services.logIndexing;
10
+ const virtualCollectionsIndexer = strapi.plugins['elasticsearch'].services['virtualCollectionsIndexer'];
10
11
  try {
11
12
  console.log('strapi-plugin-elasticsearch : Request to rebuild the index received.');
12
13
  const oldIndexName = await helper.getCurrentIndexName();
@@ -22,6 +23,9 @@ exports.default = ({ strapi }) => ({
22
23
  const cols = await configureIndexingService.getCollectionsConfiguredForIndexing();
23
24
  for (let r = 0; r < cols.length; r++)
24
25
  await this.indexCollection(cols[r], newIndexName);
26
+ // Indexing the virtual collections
27
+ console.log('strapi-plugin-elasticsearch : Starting to index virtual collections.');
28
+ const totalIndexed = await virtualCollectionsIndexer.reindexAll(newIndexName);
25
29
  await scheduleIndexingService.markIndexingTaskComplete(item.id);
26
30
  console.log('strapi-plugin-elasticsearch : Indexing of data into the new index complete.');
27
31
  //Step 4 : Move the alias to this new index
@@ -44,6 +48,7 @@ exports.default = ({ strapi }) => ({
44
48
  console.log('strapi-plugin-elasticsearch : searchController : An error was encountered while re-indexing.');
45
49
  console.log(err);
46
50
  await logIndexingService.recordIndexingFail(err);
51
+ throw err;
47
52
  }
48
53
  },
49
54
  async indexCollection(collectionName, indexName = null) {
@@ -54,7 +59,7 @@ exports.default = ({ strapi }) => ({
54
59
  const esInterface = strapi.plugins['elasticsearch'].services.esInterface;
55
60
  if (indexName === null)
56
61
  indexName = await helper.getCurrentIndexName();
57
- let entries = [];
62
+ let entries = []; //TODO: strapi should provide a type for this
58
63
  if (isCollectionDraftPublish) {
59
64
  entries = await strapi.entityService.findMany(collectionName, {
60
65
  sort: { createdAt: 'DESC' },
@@ -0,0 +1,8 @@
1
+ import { VirtualCollectionsIndexerService } from '../types';
2
+ /**
3
+ * Service to handle indexing of virtual collections
4
+ */
5
+ declare const _default: ({ strapi }: {
6
+ strapi: any;
7
+ }) => VirtualCollectionsIndexerService;
8
+ export default _default;
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const pretty_ms_1 = __importDefault(require("pretty-ms"));
7
+ /**
8
+ * Service to handle indexing of virtual collections
9
+ */
10
+ exports.default = ({ strapi }) => {
11
+ const getElasticsearchService = () => strapi.plugin('elasticsearch').service('esInterface');
12
+ const getRegistryService = () => strapi.service('plugin::elasticsearch.virtualCollectionsRegistry');
13
+ const getHelperService = () => strapi.plugins['elasticsearch'].services.helper;
14
+ return {
15
+ /**
16
+ * Index a single item from a virtual collection
17
+ */
18
+ async indexItem(collectionName, itemId) {
19
+ const registry = getRegistryService();
20
+ const collection = registry.get(collectionName);
21
+ if (!collection) {
22
+ throw new Error(`Virtual collection not found: ${collectionName}`);
23
+ }
24
+ try {
25
+ const results = await collection.extractById([itemId]);
26
+ if (!results || !Array.isArray(results) || results.length === 0) {
27
+ strapi.log.warn(`No data extracted for ${collectionName} with ID ${itemId}`);
28
+ return null;
29
+ }
30
+ const data = results[0];
31
+ const indexData = collection.mapToIndex ? data : data;
32
+ const esService = getElasticsearchService();
33
+ const helper = getHelperService();
34
+ const indexItemId = helper.getIndexItemId(collectionName, itemId);
35
+ await esService.indexDataToSpecificIndex({ itemId: indexItemId, itemData: indexData }, collection.indexName);
36
+ strapi.log.debug(`Indexed virtual item: ${collectionName}:${itemId}`);
37
+ return indexData;
38
+ }
39
+ catch (error) {
40
+ strapi.log.error(`Error indexing ${collectionName}:${itemId}: ${error?.message}`);
41
+ throw error;
42
+ }
43
+ },
44
+ async reindexAll(indexName) {
45
+ const registry = getRegistryService();
46
+ const collections = registry.getAll();
47
+ let totalIndexed = 0;
48
+ for (const collection of collections) {
49
+ totalIndexed += await this.reindex(collection.collectionName, indexName);
50
+ }
51
+ console.log(`strapi-plugin-elasticsearch : Reindexed ${totalIndexed} items across all ${collections.length} virtual collections`);
52
+ return totalIndexed;
53
+ },
54
+ /**
55
+ * Reindex all items in a virtual collection
56
+ */
57
+ async reindex(collectionName, indexName) {
58
+ const registry = getRegistryService();
59
+ const collection = registry.get(collectionName);
60
+ if (!collection) {
61
+ throw new Error(`Virtual collection not found: ${collectionName}`);
62
+ }
63
+ const timestamp = Date.now();
64
+ try {
65
+ const esService = getElasticsearchService();
66
+ const helper = getHelperService();
67
+ // await esService.createIndex(tempIndexName);
68
+ let page = 0;
69
+ let hasMoreData = true;
70
+ let totalIndexed = 0;
71
+ while (hasMoreData) {
72
+ const pageData = await collection.extractData(page);
73
+ if (!Array.isArray(pageData) || pageData.length === 0) {
74
+ hasMoreData = false;
75
+ break;
76
+ }
77
+ const operations = [];
78
+ for (const item of pageData) {
79
+ const itemId = helper.getIndexItemId({ collectionName, itemId: item.id });
80
+ const itemData = collection.mapToIndex ? item : item;
81
+ operations.push({ itemId, itemData });
82
+ }
83
+ if (operations.length > 0) {
84
+ await Promise.all(operations.map((op) => esService.indexDataToSpecificIndex(op, indexName)));
85
+ }
86
+ totalIndexed += pageData.length;
87
+ page++;
88
+ }
89
+ strapi.log.info(`Reindexed ${totalIndexed} items for virtual collection: ${collectionName}. took ${(0, pretty_ms_1.default)(Date.now() - timestamp)}`);
90
+ return totalIndexed;
91
+ }
92
+ catch (error) {
93
+ strapi.log.error(`Error reindexing ${collectionName}: ${error?.message} after ${(0, pretty_ms_1.default)(Date.now() - timestamp)}`);
94
+ throw error;
95
+ }
96
+ },
97
+ /**
98
+ * Handle a trigger event from a collection
99
+ */
100
+ async handleTriggerEvent(event) {
101
+ const { model, result } = event;
102
+ const registry = getRegistryService();
103
+ // Find virtual collections that should be triggered by this model
104
+ const affectedCollections = registry.findTriggersByCollection(model);
105
+ for (const collection of affectedCollections) {
106
+ // Find the specific trigger for this collection
107
+ const trigger = collection.triggers.find((t) => t.collection === model);
108
+ if (trigger && trigger.getIdsToReindex) {
109
+ // Get IDs that need to be reindexed
110
+ const idsToReindex = await trigger.getIdsToReindex(result);
111
+ // Reindex each item
112
+ for (const id of idsToReindex) {
113
+ await this.indexItem(collection.collectionName, id);
114
+ }
115
+ }
116
+ }
117
+ },
118
+ /**
119
+ * Delete an item from a virtual collection index
120
+ */
121
+ async deleteItem(collectionName, itemId) {
122
+ const registry = getRegistryService();
123
+ const collection = registry.get(collectionName);
124
+ if (!collection) {
125
+ throw new Error(`Virtual collection not found: ${collectionName}`);
126
+ }
127
+ try {
128
+ const esService = getElasticsearchService();
129
+ await esService.removeItemFromIndex({ itemId });
130
+ strapi.log.debug(`Deleted indexed item: ${collectionName}:${itemId}`);
131
+ return true;
132
+ }
133
+ catch (error) {
134
+ if (error?.meta?.statusCode === 404) {
135
+ return false;
136
+ }
137
+ strapi.log.error(`Error deleting ${collectionName}:${itemId}: ${error.message}`);
138
+ throw error;
139
+ }
140
+ },
141
+ };
142
+ };
@@ -0,0 +1,8 @@
1
+ import { VirtualCollectionsRegistryService } from '../types';
2
+ /**
3
+ * Service to handle indexing of virtual collections
4
+ */
5
+ declare const _default: ({ strapi }: {
6
+ strapi: any;
7
+ }) => VirtualCollectionsRegistryService;
8
+ export default _default;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Service to handle indexing of virtual collections
5
+ */
6
+ exports.default = ({ strapi }) => {
7
+ // const getElasticsearchService = () => strapi.plugin('elasticsearch').service('esInterface');
8
+ // const getAll = () => {
9
+ // const config = strapi.plugin('elasticsearch').config('virtualCollections');
10
+ // return config || [];
11
+ // };
12
+ // const get = (collectionName) => {
13
+ // return getAll().find((collection) => collection.collectionName === collectionName);
14
+ // };
15
+ return {
16
+ getAll() {
17
+ const config = strapi.plugin('elasticsearch').config('virtualCollections');
18
+ return config || [];
19
+ },
20
+ get(collectionName) {
21
+ return this.getAll().find((collection) => collection.collectionName === collectionName) ?? null;
22
+ },
23
+ register: function (config) {
24
+ throw new Error('Function not implemented.');
25
+ },
26
+ findTriggersByCollection: function (collectionUID) {
27
+ return this.getAll().filter((collection) => {
28
+ return collection.triggers.some((trigger) => trigger.collection === collectionUID);
29
+ });
30
+ },
31
+ };
32
+ };
@@ -0,0 +1,70 @@
1
+ export interface EsInterfaceService {
2
+ /**
3
+ * Initializes the search engine connection.
4
+ * @param params - Connection parameters that include host, uname, password, and cert.
5
+ * @returns A promise that resolves when the initialization is complete.
6
+ */
7
+ initializeSearchEngine(params: {
8
+ host: string;
9
+ uname: string;
10
+ password: string;
11
+ cert: string;
12
+ }): Promise<void>;
13
+ /**
14
+ * Creates an index in the search engine.
15
+ * @param indexName - The name of the index to create.
16
+ * @returns A promise that resolves when the index is created.
17
+ */
18
+ createIndex(indexName: string): Promise<void>;
19
+ /**
20
+ * Deletes an index from the search engine.
21
+ * @param indexName - The name of the index to delete.
22
+ * @returns A promise that resolves when the index is deleted.
23
+ */
24
+ deleteIndex(indexName: string): Promise<void>;
25
+ /**
26
+ * Attaches an alias to a specific index.
27
+ * @param indexName - The index to which the alias should be attached.
28
+ * @returns A promise that resolves when the alias is set.
29
+ */
30
+ attachAliasToIndex(indexName: string): Promise<void>;
31
+ /**
32
+ * Checks the connection status of the search engine.
33
+ * @returns A promise that resolves with the connection status.
34
+ */
35
+ checkESConnection(): Promise<any>;
36
+ /**
37
+ * Indexes data to a specific index.
38
+ * @param data - An object containing the itemId and itemData to index.
39
+ * @param data.itemId - The full ID of the item to index, in format `collectionName + '::' + itemId`.
40
+ * @param indexName - The target index name.
41
+ * @returns A promise that resolves when the data is indexed.
42
+ */
43
+ indexDataToSpecificIndex(data: {
44
+ itemId: string;
45
+ itemData: any;
46
+ }, indexName: string): Promise<any>;
47
+ /**
48
+ * Indexes data.
49
+ * @param data - An object containing the itemId and itemData to index.
50
+ * @returns A promise that resolves when the data is indexed.
51
+ */
52
+ indexData(data: {
53
+ itemId: string;
54
+ itemData: any;
55
+ }): Promise<any>;
56
+ /**
57
+ * Removes an item from the index.
58
+ * @param data - An object containing the itemId.
59
+ * @returns A promise that resolves when the item is removed.
60
+ */
61
+ removeItemFromIndex(data: {
62
+ itemId: string;
63
+ }): Promise<any>;
64
+ /**
65
+ * Searches data in the search engine.
66
+ * @param searchQuery - The search query.
67
+ * @returns A promise that resolves with the search results.
68
+ */
69
+ searchData(searchQuery: any): Promise<any>;
70
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export * from './virtual-collections.type';
2
+ export * from './esInterface.type';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./virtual-collections.type"), exports);
18
+ __exportStar(require("./esInterface.type"), exports);
@@ -0,0 +1,30 @@
1
+ export type VirtualCollectionConfig<T extends {}> = {
2
+ indexName: string;
3
+ collectionName: string;
4
+ extractData: (page: number, pageSize?: number) => Promise<T[]>;
5
+ extractById: (ids: number[]) => Promise<T[]>;
6
+ triggers: Array<{
7
+ collection: string;
8
+ getIdsToReindex: (result: any) => Promise<number[]>;
9
+ }>;
10
+ mapToIndex: (item: T) => Promise<object>;
11
+ };
12
+ export interface VirtualCollectionsRegistryService {
13
+ /**
14
+ * Initialize indexes for all registered virtual collections
15
+ */
16
+ initializeIndexes(): Promise<void>;
17
+ /**
18
+ * Register a virtual collection
19
+ * @param config - The configuration for the virtual collection
20
+ * @returns The current instance of the registry
21
+ */
22
+ register<T extends {}>(config: VirtualCollectionConfig<T>): this;
23
+ /**
24
+ * get all registered virtual collections
25
+ * @returns An array of all registered virtual collections
26
+ */
27
+ getAll(): Array<VirtualCollectionConfig<any>>;
28
+ get(collectionName: string): VirtualCollectionConfig<any> | null;
29
+ findTriggersByCollection(collectionUID: string): Array<VirtualCollectionConfig<any>>;
30
+ }
@@ -0,0 +1,2 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,57 @@
1
+ export type VirtualCollectionConfig<T extends {}> = {
2
+ indexName: string;
3
+ collectionName: string;
4
+ extractData: (page: number, pageSize?: number) => Promise<T[]>;
5
+ extractById: (ids: number[]) => Promise<T[]>;
6
+ triggers: Array<{
7
+ collection: string;
8
+ getIdsToReindex: (result: any) => Promise<number[]>;
9
+ }>;
10
+ mapToIndex?: (item: T) => Promise<object>;
11
+ };
12
+ export interface VirtualCollectionsRegistryService {
13
+ /**
14
+ * Register a virtual collection
15
+ * @param config - The configuration for the virtual collection
16
+ * @returns The current instance of the registry
17
+ */
18
+ register<T extends {}>(config: VirtualCollectionConfig<T>): this;
19
+ /**
20
+ * get all registered virtual collections
21
+ * @returns An array of all registered virtual collections
22
+ */
23
+ getAll(): Array<VirtualCollectionConfig<any>>;
24
+ get(collectionName: string): VirtualCollectionConfig<any> | null;
25
+ findTriggersByCollection(collectionUID: string): Array<VirtualCollectionConfig<any>>;
26
+ }
27
+ export interface VirtualCollectionsIndexerService {
28
+ /**
29
+ * Index a single item from a virtual collection.
30
+ * @param collectionName - The name of the virtual collection.
31
+ * @param itemId - The id of the item to be indexed.
32
+ */
33
+ indexItem(collectionName: string, itemId: number): Promise<any>;
34
+ /**
35
+ * Reindex all items in a virtual collection index.
36
+ * @param indexName - The target index name.
37
+ */
38
+ reindexAll(indexName: string): Promise<any>;
39
+ /**
40
+ * Reindex all items in a virtual collection.
41
+ * @param collectionName - The name of the virtual collection.
42
+ * @param indexName - The target index name.
43
+ */
44
+ reindex(collectionName: string, indexName: string): Promise<any>;
45
+ /**
46
+ * Handle a trigger event from a collection.
47
+ * @param event - The trigger event.
48
+ */
49
+ handleTriggerEvent(event: any): Promise<any>;
50
+ /**
51
+ * Delete an item from a virtual collection index.
52
+ * @param collectionName - The name of the virtual collection.
53
+ * @param itemId - The id of the item to be deleted.
54
+ * @returns A promise that resolves to a boolean indicating success.
55
+ */
56
+ deleteItem(collectionName: string, itemId: string): Promise<boolean>;
57
+ }
@@ -0,0 +1,2 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegan-friendly/strapi-plugin-elasticsearch",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A Strapi plugin to enable using Elasticsearch with Strapi CMS.",
5
5
  "homepage": "https://github.com/vegan-friendly/strapi-plugin-elasticsearch",
6
6
  "strapi": {
@@ -37,7 +37,8 @@
37
37
  "dependencies": {
38
38
  "@elastic/elasticsearch": "^8.9.0",
39
39
  "@strapi/design-system": "^1.19.0",
40
- "markdown-to-txt": "^2.0.1"
40
+ "markdown-to-txt": "^2.0.1",
41
+ "pretty-ms": "^9.2.0"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@strapi/strapi": "^4.0.0"