@vegan-friendly/strapi-plugin-elasticsearch 0.0.11-alpha.7 → 0.1.0-alpha.1

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.
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "endOfLine": "lf",
3
+ "tabWidth": 2,
4
+ "printWidth": 100,
5
+ "singleQuote": true,
6
+ "trailingComma": "es5"
7
+ }
@@ -1,3 +1,24 @@
1
1
  {
2
2
  "editor.defaultFormatter": "esbenp.prettier-vscode",
3
+ "[javascript]": {
4
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
5
+ },
6
+ "[javascriptreact]": {
7
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
8
+ },
9
+ "[typescript]": {
10
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
11
+ },
12
+ "[typescriptreact]": {
13
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
14
+ },
15
+ "[jsonc]": {
16
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
17
+ },
18
+ "[json]": {
19
+ //redundant, but just in case
20
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
21
+ },
22
+ "editor.formatOnSaveMode": "modifications",
23
+ "editor.formatOnSave": true
3
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegan-friendly/strapi-plugin-elasticsearch",
3
- "version": "0.0.11-alpha.7",
3
+ "version": "0.1.0-alpha.1",
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": {
@@ -7,6 +7,7 @@ const indexer = require('./perform-indexing');
7
7
  const logIndexing = require('./log-indexing');
8
8
  const helper = require('./helper');
9
9
  const transformContent = require('./transform-content');
10
+ const virtualCollectionsRegistry = require('./virtual-collections-registry');
10
11
 
11
12
  module.exports = {
12
13
  configureIndexing,
@@ -15,5 +16,6 @@ module.exports = {
15
16
  indexer,
16
17
  logIndexing,
17
18
  helper,
18
- transformContent
19
+ transformContent,
20
+ virtualCollectionsRegistry,
19
21
  };
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Service to handle indexing of virtual collections
3
+ */
4
+ module.exports = ({ strapi }) => {
5
+ const getElasticsearchService = () => strapi.plugin('elasticsearch').service('elasticsearch');
6
+ const getRegistryService = () =>
7
+ strapi.service('plugin::elasticsearch.virtual-collections-registry');
8
+
9
+ return {
10
+ /**
11
+ * Initialize indexes for all registered virtual collections
12
+ */
13
+ async initializeIndexes() {
14
+ const registry = getRegistryService();
15
+ const collections = registry.getAll();
16
+
17
+ for (const collection of collections) {
18
+ await this.createIndexIfNotExists(collection.indexName);
19
+ }
20
+ },
21
+
22
+ /**
23
+ * Create an Elasticsearch index if it doesn't exist
24
+ */
25
+ async createIndexIfNotExists(indexName) {
26
+ const esService = getElasticsearchService();
27
+ const indexExists = await esService.indices.exists({ index: indexName });
28
+
29
+ if (!indexExists) {
30
+ await esService.indices.create({
31
+ index: indexName,
32
+ body: {
33
+ settings: {
34
+ analysis: {
35
+ analyzer: {
36
+ default: {
37
+ type: 'standard',
38
+ },
39
+ },
40
+ },
41
+ },
42
+ },
43
+ });
44
+
45
+ strapi.log.info(`Created Elasticsearch index: ${indexName}`);
46
+ }
47
+ },
48
+
49
+ /**
50
+ * Index a single item from a virtual collection
51
+ */
52
+ async indexItem(collectionName, itemId) {
53
+ const registry = getRegistryService();
54
+ const collection = registry.get(collectionName);
55
+
56
+ if (!collection) {
57
+ throw new Error(`Virtual collection not found: ${collectionName}`);
58
+ }
59
+
60
+ try {
61
+ // Extract data using the collection's extractData
62
+ // For a single item, we'll pass the ID as a filter parameter
63
+ const results = await collection.extractData(0, { id: itemId });
64
+
65
+ if (!results || !Array.isArray(results) || results.length === 0) {
66
+ strapi.log.warn(`No data extracted for ${collectionName} with ID ${itemId}`);
67
+ return null;
68
+ }
69
+
70
+ const data = results[0];
71
+
72
+ // Map the data to the index format
73
+ const indexData = collection.mapToIndex(data);
74
+
75
+ // Index the data
76
+ const esService = getElasticsearchService();
77
+ await esService.index({
78
+ index: collection.indexName,
79
+ id: itemId,
80
+ body: indexData,
81
+ refresh: true,
82
+ });
83
+
84
+ strapi.log.debug(`Indexed virtual item: ${collectionName}:${itemId}`);
85
+ return indexData;
86
+ } catch (error) {
87
+ strapi.log.error(`Error indexing ${collectionName}:${itemId}: ${error.message}`);
88
+ throw error;
89
+ }
90
+ },
91
+
92
+ /**
93
+ * Reindex all items in a virtual collection
94
+ */
95
+ async reindexAll(collectionName) {
96
+ const registry = getRegistryService();
97
+ const collection = registry.get(collectionName);
98
+
99
+ if (!collection) {
100
+ throw new Error(`Virtual collection not found: ${collectionName}`);
101
+ }
102
+
103
+ try {
104
+ // Create a new index with a timestamp
105
+ const timestamp = Date.now();
106
+ const tempIndexName = `${collection.indexName}_${timestamp}`;
107
+
108
+ // Create the temporary index
109
+ const esService = getElasticsearchService();
110
+ await esService.indices.create({
111
+ index: tempIndexName,
112
+ body: {
113
+ settings: {
114
+ analysis: {
115
+ analyzer: {
116
+ default: {
117
+ type: 'standard',
118
+ },
119
+ },
120
+ },
121
+ },
122
+ },
123
+ });
124
+
125
+ // Pagination variables
126
+ let page = 0;
127
+ let hasMoreData = true;
128
+ let totalIndexed = 0;
129
+
130
+ // Process data in batches
131
+ while (hasMoreData) {
132
+ // Extract data for current page
133
+ const pageData = await collection.extractData(page);
134
+
135
+ if (!Array.isArray(pageData) || pageData.length === 0) {
136
+ hasMoreData = false;
137
+ break;
138
+ }
139
+
140
+ // Bulk index items in this page
141
+ const operations = [];
142
+
143
+ for (const item of pageData) {
144
+ const indexData = collection.mapToIndex(item);
145
+ operations.push({
146
+ index: {
147
+ _index: tempIndexName,
148
+ _id: item.id,
149
+ },
150
+ });
151
+ operations.push(indexData);
152
+ }
153
+
154
+ if (operations.length > 0) {
155
+ await esService.bulk({ body: operations, refresh: true });
156
+ }
157
+
158
+ totalIndexed += pageData.length;
159
+ page++;
160
+ }
161
+
162
+ // Swap the indices
163
+ const oldIndex = collection.indexName;
164
+ const indexExists = await esService.indices.exists({ index: oldIndex });
165
+
166
+ if (indexExists) {
167
+ // Create or update alias
168
+ const aliasExists = await esService.indices.existsAlias({
169
+ name: collection.indexName,
170
+ });
171
+
172
+ if (aliasExists) {
173
+ // Update existing alias
174
+ await esService.indices.updateAliases({
175
+ body: {
176
+ actions: [
177
+ { remove: { index: '_all', alias: collection.indexName } },
178
+ { add: { index: tempIndexName, alias: collection.indexName } },
179
+ ],
180
+ },
181
+ });
182
+ } else {
183
+ // Create new alias
184
+ await esService.indices.putAlias({
185
+ index: tempIndexName,
186
+ name: collection.indexName,
187
+ });
188
+ }
189
+
190
+ // Delete old indices with this prefix
191
+ const { body: indices } = await esService.indices.get({
192
+ index: `${collection.indexName}_*`,
193
+ });
194
+
195
+ for (const indexName in indices) {
196
+ if (indexName !== tempIndexName) {
197
+ await esService.indices.delete({ index: indexName });
198
+ }
199
+ }
200
+ } else {
201
+ // Just add the alias if the original index doesn't exist
202
+ await esService.indices.putAlias({
203
+ index: tempIndexName,
204
+ name: collection.indexName,
205
+ });
206
+ }
207
+
208
+ strapi.log.info(
209
+ `Reindexed ${totalIndexed} items for virtual collection: ${collectionName}`
210
+ );
211
+ return totalIndexed;
212
+ } catch (error) {
213
+ strapi.log.error(`Error reindexing ${collectionName}: ${error.message}`);
214
+ throw error;
215
+ }
216
+ },
217
+
218
+ /**
219
+ * Handle a trigger event from a collection
220
+ */
221
+ async handleTriggerEvent(event) {
222
+ const { model, result } = event;
223
+ const registry = getRegistryService();
224
+
225
+ // Find virtual collections that should be triggered by this model
226
+ const affectedCollections = registry.findTriggersByCollection(model);
227
+
228
+ for (const collection of affectedCollections) {
229
+ // Find the specific trigger for this collection
230
+ const trigger = collection.triggers.find((t) => t.collection === model);
231
+
232
+ if (trigger && trigger.getIdsToReindex) {
233
+ // Get IDs that need to be reindexed
234
+ const idsToReindex = await trigger.getIdsToReindex(result);
235
+
236
+ // Reindex each item
237
+ for (const id of idsToReindex) {
238
+ await this.indexItem(collection.collectionName, id);
239
+ }
240
+ }
241
+ }
242
+ },
243
+
244
+ /**
245
+ * Delete an item from a virtual collection index
246
+ */
247
+ async deleteItem(collectionName, itemId) {
248
+ const registry = getRegistryService();
249
+ const collection = registry.get(collectionName);
250
+
251
+ if (!collection) {
252
+ throw new Error(`Virtual collection not found: ${collectionName}`);
253
+ }
254
+
255
+ try {
256
+ const esService = getElasticsearchService();
257
+ await esService.delete({
258
+ index: collection.indexName,
259
+ id: itemId,
260
+ refresh: true,
261
+ });
262
+
263
+ strapi.log.debug(`Deleted indexed item: ${collectionName}:${itemId}`);
264
+ return true;
265
+ } catch (error) {
266
+ if (error.meta && error.meta.statusCode === 404) {
267
+ // Item not found - that's fine
268
+ return false;
269
+ }
270
+ strapi.log.error(`Error deleting ${collectionName}:${itemId}: ${error.message}`);
271
+ throw error;
272
+ }
273
+ },
274
+ };
275
+ };
276
+
277
+ // path: ./src/extensions/strapi-plugin-elasticsearch/strapi-server.js
278
+
279
+ module.exports = (plugin) => {
280
+ // Preserve original services, controllers, etc.
281
+ const originalServices = plugin.services || {};
282
+
283
+ // Add our new services
284
+ plugin.services = {
285
+ ...originalServices,
286
+ 'virtual-collections-registry': require('./services/virtual-collections-registry'),
287
+ 'virtual-collections-indexer': require('./services/virtual-collections-indexer'),
288
+ };
289
+
290
+ // Extend the bootstrap function
291
+ const originalBootstrap = plugin.bootstrap;
292
+ plugin.bootstrap = async ({ strapi }) => {
293
+ // Call the original bootstrap if it exists
294
+ if (originalBootstrap) {
295
+ await originalBootstrap({ strapi });
296
+ }
297
+
298
+ // Initialize the registry from config
299
+ strapi.service('plugin::elasticsearch.virtual-collections-registry').initialize();
300
+
301
+ // Setup lifecycle hooks
302
+ const registry = strapi.service('plugin::elasticsearch.virtual-collections-registry');
303
+ const virtualCollections = registry.getAll();
304
+
305
+ // Create a set of all collections that need hooks
306
+ const collectionsToHook = new Set();
307
+
308
+ virtualCollections.forEach((collection) => {
309
+ collection.triggers.forEach((trigger) => {
310
+ collectionsToHook.add(trigger.collection);
311
+ });
312
+ });
313
+
314
+ // Setup hooks for each collection
315
+ collectionsToHook.forEach((collectionUID) => {
316
+ strapi.log.info(`Setting up Elasticsearch lifecycle hooks for collection: ${collectionUID}`);
317
+
318
+ strapi.db.lifecycles.subscribe({
319
+ models: [collectionUID],
320
+
321
+ afterCreate: async (event) => {
322
+ await strapi
323
+ .service('plugin::elasticsearch.virtual-collections-indexer')
324
+ .handleTriggerEvent(event);
325
+ },
326
+
327
+ afterUpdate: async (event) => {
328
+ await strapi
329
+ .service('plugin::elasticsearch.virtual-collections-indexer')
330
+ .handleTriggerEvent(event);
331
+ },
332
+
333
+ afterDelete: async (event) => {
334
+ await strapi
335
+ .service('plugin::elasticsearch.virtual-collections-indexer')
336
+ .handleTriggerEvent(event);
337
+ },
338
+ });
339
+ });
340
+
341
+ // Initialize indexes
342
+ await strapi.service('plugin::elasticsearch.virtual-collections-indexer').initializeIndexes();
343
+ };
344
+
345
+ return plugin;
346
+ };