strapi-plugin-meilisearch 0.16.0 → 0.16.2
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/dist/_chunks/{App-CZQma2Z0.js → App-B3uJ14YG.js} +1 -1
- package/dist/_chunks/{App-DZ9riQGK.mjs → App-BmxrI4i3.mjs} +1 -1
- package/dist/_chunks/{index-BwElNDhM.js → index-B13pYJxT.js} +3 -2
- package/dist/_chunks/{index-sVrQdAxF.mjs → index-D0WRGIBD.mjs} +3 -2
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +411 -253
- package/dist/server/index.mjs +411 -253
- package/package.json +2 -1
package/dist/server/index.mjs
CHANGED
|
@@ -89,10 +89,320 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
89
89
|
};
|
|
90
90
|
const destroy = () => {
|
|
91
91
|
};
|
|
92
|
+
function isObject(elem) {
|
|
93
|
+
return typeof elem === "object" && !Array.isArray(elem) && elem !== null;
|
|
94
|
+
}
|
|
95
|
+
const normalizeEntryLocale = (locale) => {
|
|
96
|
+
if (locale === "all") return "*";
|
|
97
|
+
return locale;
|
|
98
|
+
};
|
|
99
|
+
const normalizeEntryScope = (options = {}) => {
|
|
100
|
+
const { filters = {}, status = "published", locale } = options;
|
|
101
|
+
return {
|
|
102
|
+
filters,
|
|
103
|
+
status,
|
|
104
|
+
locale: normalizeEntryLocale(locale)
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
const isWildcardLocale = (locale) => normalizeEntryLocale(locale) === "*";
|
|
108
|
+
const aborted = ({ contentType: contentType2, action }) => {
|
|
109
|
+
strapi.log.error(
|
|
110
|
+
`Indexing of ${contentType2} aborted as the data could not be ${action}`
|
|
111
|
+
);
|
|
112
|
+
return [];
|
|
113
|
+
};
|
|
114
|
+
const configurationService = ({ strapi: strapi2 }) => {
|
|
115
|
+
const meilisearchConfig = strapi2.config.get("plugin::meilisearch") || {};
|
|
116
|
+
const contentTypeService2 = strapi2.plugin("meilisearch").service("contentType");
|
|
117
|
+
return {
|
|
118
|
+
/**
|
|
119
|
+
* Get the names of the indexes from Meilisearch in which the contentType content is added.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} options
|
|
122
|
+
* @param {string} options.contentType - ContentType name.
|
|
123
|
+
*
|
|
124
|
+
* @return {String[]} - Index names
|
|
125
|
+
*/
|
|
126
|
+
getIndexNamesOfContentType: function({ contentType: contentType2 }) {
|
|
127
|
+
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
128
|
+
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
129
|
+
let indexNames = [contentTypeConfig.indexName].flat(2).filter((index2) => index2);
|
|
130
|
+
return indexNames.length > 0 ? indexNames : [collection];
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* Get the entries query rule of a content-type that are applied when fetching entries in the Strapi database.
|
|
134
|
+
*
|
|
135
|
+
* @param {object} options
|
|
136
|
+
* @param {string} options.contentType - ContentType name.
|
|
137
|
+
*
|
|
138
|
+
* @return {String} - EntriesQuery rules.
|
|
139
|
+
*/
|
|
140
|
+
entriesQuery: function({ contentType: contentType2 }) {
|
|
141
|
+
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
142
|
+
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
143
|
+
return contentTypeConfig.entriesQuery || {};
|
|
144
|
+
},
|
|
145
|
+
/**
|
|
146
|
+
* Transform contentTypes entries before indexation in Meilisearch.
|
|
147
|
+
*
|
|
148
|
+
* @param {object} options
|
|
149
|
+
* @param {string} options.contentType - ContentType name.
|
|
150
|
+
* @param {Array<Object>} options.entries - The data to convert. Conversion will use
|
|
151
|
+
* the static method `toSearchIndex` defined in the model definition
|
|
152
|
+
*
|
|
153
|
+
* @return {Promise<Array<Object>>} - Converted or mapped data
|
|
154
|
+
*/
|
|
155
|
+
transformEntries: async function({ contentType: contentType2, entries = [] }) {
|
|
156
|
+
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
157
|
+
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
158
|
+
try {
|
|
159
|
+
if (Array.isArray(entries) && typeof contentTypeConfig?.transformEntry === "function") {
|
|
160
|
+
const transformed = await Promise.all(
|
|
161
|
+
entries.map(
|
|
162
|
+
async (entry) => await contentTypeConfig.transformEntry({
|
|
163
|
+
entry,
|
|
164
|
+
contentType: contentType2
|
|
165
|
+
})
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
if (transformed.length > 0 && !isObject(transformed[0])) {
|
|
169
|
+
return aborted({ contentType: contentType2, action: "transformed" });
|
|
170
|
+
}
|
|
171
|
+
return transformed;
|
|
172
|
+
}
|
|
173
|
+
} catch (e) {
|
|
174
|
+
strapi2.log.error(e);
|
|
175
|
+
return aborted({ contentType: contentType2, action: "transformed" });
|
|
176
|
+
}
|
|
177
|
+
return entries;
|
|
178
|
+
},
|
|
179
|
+
/**
|
|
180
|
+
* Filter contentTypes entries before indexation in Meilisearch.
|
|
181
|
+
*
|
|
182
|
+
* @param {object} options
|
|
183
|
+
* @param {string} options.contentType - ContentType name.
|
|
184
|
+
* @param {Array<Object>} options.entries - The data to convert. Conversion will use
|
|
185
|
+
* the static method `toSearchIndex` defined in the model definition
|
|
186
|
+
*
|
|
187
|
+
* @return {Promise<Array<Object>>} - Converted or mapped data
|
|
188
|
+
*/
|
|
189
|
+
filterEntries: async function({ contentType: contentType2, entries = [] }) {
|
|
190
|
+
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
191
|
+
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
192
|
+
try {
|
|
193
|
+
if (Array.isArray(entries) && typeof contentTypeConfig?.filterEntry === "function") {
|
|
194
|
+
const filtered = await entries.reduce(
|
|
195
|
+
async (filteredEntries, entry) => {
|
|
196
|
+
const isValid = await contentTypeConfig.filterEntry({
|
|
197
|
+
entry,
|
|
198
|
+
contentType: contentType2
|
|
199
|
+
});
|
|
200
|
+
if (!isValid) return filteredEntries;
|
|
201
|
+
const syncFilteredEntries = await filteredEntries;
|
|
202
|
+
return [...syncFilteredEntries, entry];
|
|
203
|
+
},
|
|
204
|
+
[]
|
|
205
|
+
);
|
|
206
|
+
return filtered;
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
strapi2.log.error(e);
|
|
210
|
+
return aborted({ contentType: contentType2, action: "filtered" });
|
|
211
|
+
}
|
|
212
|
+
return entries;
|
|
213
|
+
},
|
|
214
|
+
/**
|
|
215
|
+
* Returns Meilisearch index settings from model definition.
|
|
216
|
+
*
|
|
217
|
+
* @param {object} options
|
|
218
|
+
* @param {string} options.contentType - ContentType name.
|
|
219
|
+
* @param {Array<Object>} [options.entries] - The data to convert. Conversion will use
|
|
220
|
+
|
|
221
|
+
* @typedef Settings
|
|
222
|
+
* @type {import('meilisearch').Settings}
|
|
223
|
+
* @return {Settings} - Meilisearch index settings
|
|
224
|
+
*/
|
|
225
|
+
getSettings: function({ contentType: contentType2 }) {
|
|
226
|
+
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
227
|
+
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
228
|
+
const settings = contentTypeConfig.settings || {};
|
|
229
|
+
return settings;
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* Return all contentTypes having the provided indexName setting.
|
|
233
|
+
*
|
|
234
|
+
* @param {object} options
|
|
235
|
+
* @param {string} options.indexName - Index in Meilisearch.
|
|
236
|
+
*
|
|
237
|
+
* @returns {string[]} List of contentTypes storing its data in the provided indexName
|
|
238
|
+
*/
|
|
239
|
+
listContentTypesWithCustomIndexName: function({ indexName }) {
|
|
240
|
+
const contentTypes = strapi2.plugin("meilisearch").service("contentType").getContentTypesUid() || [];
|
|
241
|
+
const collectionNames = contentTypes.map(
|
|
242
|
+
(contentType2) => contentTypeService2.getCollectionName({ contentType: contentType2 })
|
|
243
|
+
);
|
|
244
|
+
const contentTypeWithIndexName = collectionNames.filter((contentType2) => {
|
|
245
|
+
const names = this.getIndexNamesOfContentType({
|
|
246
|
+
contentType: contentType2
|
|
247
|
+
});
|
|
248
|
+
return names.includes(indexName);
|
|
249
|
+
});
|
|
250
|
+
return contentTypeWithIndexName;
|
|
251
|
+
},
|
|
252
|
+
/**
|
|
253
|
+
* Remove sensitive fields (password, author, etc, ..) from entry.
|
|
254
|
+
*
|
|
255
|
+
* @param {object} options
|
|
256
|
+
* @param {Array<Object>} options.entries - The entries to sanitize
|
|
257
|
+
*
|
|
258
|
+
*
|
|
259
|
+
* @return {Array<Object>} - Entries
|
|
260
|
+
*/
|
|
261
|
+
removeSensitiveFields: function({ contentType: contentType2, entries }) {
|
|
262
|
+
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
263
|
+
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
264
|
+
const noSanitizePrivateFields = contentTypeConfig.noSanitizePrivateFields || [];
|
|
265
|
+
if (noSanitizePrivateFields.includes("*")) {
|
|
266
|
+
return entries;
|
|
267
|
+
}
|
|
268
|
+
const attrs = strapi2.contentTypes[contentType2].attributes;
|
|
269
|
+
const privateFields = Object.entries(attrs).map(
|
|
270
|
+
([field, schema]) => schema.private && !noSanitizePrivateFields.includes(field) ? field : false
|
|
271
|
+
);
|
|
272
|
+
return entries.map((entry) => {
|
|
273
|
+
privateFields.forEach((attr) => delete entry[attr]);
|
|
274
|
+
return entry;
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
/**
|
|
278
|
+
* Remove unpublished entries from array of entries
|
|
279
|
+
* unless `status` is set to 'draft'.
|
|
280
|
+
*
|
|
281
|
+
* @param {object} options
|
|
282
|
+
* @param {Array<Object>} options.entries - The entries to filter.
|
|
283
|
+
* @param {string} options.contentType - ContentType name.
|
|
284
|
+
*
|
|
285
|
+
* @return {Array<Object>} - Published entries.
|
|
286
|
+
*/
|
|
287
|
+
removeUnpublishedArticles: function({ entries, contentType: contentType2 }) {
|
|
288
|
+
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
289
|
+
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
290
|
+
const entriesQuery = contentTypeConfig.entriesQuery || {};
|
|
291
|
+
if (entriesQuery.status === "draft") {
|
|
292
|
+
return entries;
|
|
293
|
+
} else {
|
|
294
|
+
return entries.filter(
|
|
295
|
+
(entry) => !(entry?.publishedAt === void 0 || entry?.publishedAt === null)
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
/**
|
|
300
|
+
* Remove language entries.
|
|
301
|
+
* In the plugin entriesQuery, if `locale` is set and not equal to
|
|
302
|
+
* `all` (used with the i18n plugin for Strapi) or `*` (used with Strapi 5
|
|
303
|
+
* native localization), all entries that do not have the specified
|
|
304
|
+
* language are removed.
|
|
305
|
+
*
|
|
306
|
+
* @param {object} options
|
|
307
|
+
* @param {Array<Object>} options.entries - The entries to filter.
|
|
308
|
+
* @param {string} options.contentType - ContentType name.
|
|
309
|
+
*
|
|
310
|
+
* @return {Array<Object>} - Published entries.
|
|
311
|
+
*/
|
|
312
|
+
removeLocaleEntries: function({ entries, contentType: contentType2 }) {
|
|
313
|
+
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
314
|
+
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
315
|
+
const entriesQuery = contentTypeConfig.entriesQuery || {};
|
|
316
|
+
if (!entriesQuery.locale || isWildcardLocale(entriesQuery.locale)) {
|
|
317
|
+
return entries;
|
|
318
|
+
} else {
|
|
319
|
+
return entries.filter((entry) => entry.locale === entriesQuery.locale);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
};
|
|
92
324
|
async function registerDocumentMiddleware({ strapi: strapi2 }) {
|
|
93
325
|
if (!strapi2?.documents || typeof strapi2.documents.use !== "function") {
|
|
94
326
|
return;
|
|
95
327
|
}
|
|
328
|
+
const extractEntryCandidates = (result) => {
|
|
329
|
+
if (result == null) return [];
|
|
330
|
+
const candidates = [];
|
|
331
|
+
const appendCandidate = (data, source) => {
|
|
332
|
+
if (data != null && typeof data === "object") {
|
|
333
|
+
candidates.push({ data, source });
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
if (Array.isArray(result)) {
|
|
337
|
+
result.forEach((data) => appendCandidate(data, "root"));
|
|
338
|
+
return candidates;
|
|
339
|
+
}
|
|
340
|
+
appendCandidate(result, "root");
|
|
341
|
+
if (Array.isArray(result.versions)) {
|
|
342
|
+
result.versions.forEach((data) => appendCandidate(data, "versions"));
|
|
343
|
+
}
|
|
344
|
+
if (Array.isArray(result.entries)) {
|
|
345
|
+
result.entries.forEach((data) => appendCandidate(data, "entries"));
|
|
346
|
+
}
|
|
347
|
+
if (result.entry != null) {
|
|
348
|
+
appendCandidate(result.entry, "entry");
|
|
349
|
+
}
|
|
350
|
+
return candidates;
|
|
351
|
+
};
|
|
352
|
+
const isPublishedEntry = (entry) => !(entry?.publishedAt === void 0 || entry?.publishedAt === null);
|
|
353
|
+
const rankEntryCandidates = (candidates) => {
|
|
354
|
+
return [...candidates].sort((a, b) => {
|
|
355
|
+
const aHasPrimaryKey = a.data?.id != null;
|
|
356
|
+
const bHasPrimaryKey = b.data?.id != null;
|
|
357
|
+
if (aHasPrimaryKey && !bHasPrimaryKey) return -1;
|
|
358
|
+
if (!aHasPrimaryKey && bHasPrimaryKey) return 1;
|
|
359
|
+
const aIsRoot = a.source === "root";
|
|
360
|
+
const bIsRoot = b.source === "root";
|
|
361
|
+
if (!aIsRoot && bIsRoot) return -1;
|
|
362
|
+
if (aIsRoot && !bIsRoot) return 1;
|
|
363
|
+
return 0;
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
const getEntryFromResult = ({
|
|
367
|
+
resultCandidates,
|
|
368
|
+
documentId,
|
|
369
|
+
entriesQuery
|
|
370
|
+
}) => {
|
|
371
|
+
const documentCandidates = (resultCandidates || []).filter(
|
|
372
|
+
(candidate) => candidate?.data?.documentId === documentId
|
|
373
|
+
);
|
|
374
|
+
if (documentCandidates.length === 0) return null;
|
|
375
|
+
const rankedCandidates = rankEntryCandidates(documentCandidates);
|
|
376
|
+
if (entriesQuery?.status === "draft") {
|
|
377
|
+
const draftCandidate = rankedCandidates.find(
|
|
378
|
+
(candidate) => !isPublishedEntry(candidate.data)
|
|
379
|
+
);
|
|
380
|
+
return draftCandidate?.data || rankedCandidates[0]?.data || null;
|
|
381
|
+
}
|
|
382
|
+
const publishedCandidate = rankedCandidates.find(
|
|
383
|
+
(candidate) => isPublishedEntry(candidate.data)
|
|
384
|
+
);
|
|
385
|
+
return publishedCandidate?.data || null;
|
|
386
|
+
};
|
|
387
|
+
const getEntryOutsideTransaction = ({
|
|
388
|
+
contentTypeService: contentTypeService2,
|
|
389
|
+
contentType: contentType2,
|
|
390
|
+
documentId,
|
|
391
|
+
entriesQuery
|
|
392
|
+
}) => new Promise((resolve, reject) => {
|
|
393
|
+
setImmediate(async () => {
|
|
394
|
+
try {
|
|
395
|
+
const entry = await contentTypeService2.getEntry({
|
|
396
|
+
contentType: contentType2,
|
|
397
|
+
documentId,
|
|
398
|
+
entriesQuery: { ...entriesQuery }
|
|
399
|
+
});
|
|
400
|
+
resolve(entry);
|
|
401
|
+
} catch (error2) {
|
|
402
|
+
reject(error2);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
});
|
|
96
406
|
strapi2.documents.use(async (ctx, next) => {
|
|
97
407
|
let result;
|
|
98
408
|
try {
|
|
@@ -115,7 +425,7 @@ async function registerDocumentMiddleware({ strapi: strapi2 }) {
|
|
|
115
425
|
"discardDraft"
|
|
116
426
|
];
|
|
117
427
|
const entriesQuery = meilisearch2.entriesQuery({ contentType: contentType2 });
|
|
118
|
-
const shouldDeleteByLocale = entriesQuery.locale
|
|
428
|
+
const shouldDeleteByLocale = isWildcardLocale(entriesQuery.locale);
|
|
119
429
|
const { status } = entriesQuery || {};
|
|
120
430
|
const statusFilter = typeof status === "string" && status.length > 0 ? { status } : {};
|
|
121
431
|
const preDeleteDocumentId = deleteActions.includes(ctx.action) && ctx?.params?.documentId ? ctx.params.documentId : null;
|
|
@@ -147,24 +457,39 @@ async function registerDocumentMiddleware({ strapi: strapi2 }) {
|
|
|
147
457
|
}
|
|
148
458
|
}
|
|
149
459
|
result = await next();
|
|
150
|
-
const
|
|
460
|
+
const contextDocumentId = typeof ctx?.params?.documentId === "string" && ctx.params.documentId.length > 0 ? ctx.params.documentId : null;
|
|
461
|
+
const documentId = contextDocumentId ?? result?.documentId ?? preDeleteEntry?.documentId ?? preDeleteDocumentId ?? null;
|
|
151
462
|
if (updateActions.includes(ctx.action) && documentId != null) {
|
|
152
|
-
const
|
|
153
|
-
|
|
463
|
+
const resultCandidates = extractEntryCandidates(result);
|
|
464
|
+
let entry = getEntryFromResult({
|
|
465
|
+
resultCandidates,
|
|
154
466
|
documentId,
|
|
155
|
-
entriesQuery
|
|
467
|
+
entriesQuery
|
|
156
468
|
});
|
|
469
|
+
if (!entry) {
|
|
470
|
+
entry = await getEntryOutsideTransaction({
|
|
471
|
+
contentTypeService: contentTypeService2,
|
|
472
|
+
contentType: contentType2,
|
|
473
|
+
documentId,
|
|
474
|
+
entriesQuery
|
|
475
|
+
});
|
|
476
|
+
}
|
|
157
477
|
if (entry) {
|
|
478
|
+
const normalizedEntry = entry.documentId === documentId ? entry : { ...entry, documentId };
|
|
158
479
|
await meilisearch2.updateEntriesInMeilisearch({
|
|
159
480
|
contentType: contentType2,
|
|
160
|
-
entries: [
|
|
481
|
+
entries: [normalizedEntry]
|
|
161
482
|
});
|
|
162
|
-
} else {
|
|
483
|
+
} else if (ctx.action === "create" || ctx.action === "publish") {
|
|
163
484
|
await meilisearch2.deleteEntriesFromMeiliSearch({
|
|
164
485
|
contentType: contentType2,
|
|
165
486
|
documentIds: [documentId],
|
|
166
487
|
entriesQuery
|
|
167
488
|
});
|
|
489
|
+
} else {
|
|
490
|
+
strapi2.log.info(
|
|
491
|
+
`Meilisearch document middleware skipped indexing ${contentType2} documentId=${documentId} for action ${ctx.action}: no indexable entry in result payload`
|
|
492
|
+
);
|
|
168
493
|
}
|
|
169
494
|
} else if (deleteActions.includes(ctx.action)) {
|
|
170
495
|
if (documentId != null) {
|
|
@@ -195,9 +520,6 @@ async function registerDocumentMiddleware({ strapi: strapi2 }) {
|
|
|
195
520
|
const register = async ({ strapi: strapi2 }) => {
|
|
196
521
|
await registerDocumentMiddleware({ strapi: strapi2 });
|
|
197
522
|
};
|
|
198
|
-
function isObject(elem) {
|
|
199
|
-
return typeof elem === "object" && !Array.isArray(elem) && elem !== null;
|
|
200
|
-
}
|
|
201
523
|
function EntriesQuery({ configuration, collectionName }) {
|
|
202
524
|
const log = strapi.log;
|
|
203
525
|
const {
|
|
@@ -774,22 +1096,39 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
|
|
|
774
1096
|
*
|
|
775
1097
|
* @param {object} options
|
|
776
1098
|
* @param {string} options.contentType - Name of the contentType.
|
|
777
|
-
* @param {object} [options.
|
|
1099
|
+
* @param {object} [options.filters] - Filter condition.
|
|
1100
|
+
* @param {string} [options.status='published'] - Publication state.
|
|
1101
|
+
* @param {string} [options.locale] - Locale to query.
|
|
778
1102
|
*
|
|
779
1103
|
* @returns {Promise<number>} number of entries in the content type.
|
|
780
1104
|
*/
|
|
781
1105
|
numberOfEntries: async function({
|
|
782
1106
|
contentType: contentType2,
|
|
783
1107
|
filters = {},
|
|
784
|
-
status = "published"
|
|
1108
|
+
status = "published",
|
|
1109
|
+
locale
|
|
785
1110
|
}) {
|
|
786
1111
|
const contentTypeUid = this.getContentTypeUid({ contentType: contentType2 });
|
|
787
1112
|
if (contentTypeUid === void 0) return 0;
|
|
1113
|
+
const queryOptions = normalizeEntryScope({
|
|
1114
|
+
filters,
|
|
1115
|
+
status,
|
|
1116
|
+
locale
|
|
1117
|
+
});
|
|
788
1118
|
try {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1119
|
+
if (queryOptions.locale === "*") {
|
|
1120
|
+
const batchCounts = await this.actionInBatches({
|
|
1121
|
+
contentType: contentType2,
|
|
1122
|
+
entriesQuery: {
|
|
1123
|
+
...queryOptions,
|
|
1124
|
+
fields: ["documentId"],
|
|
1125
|
+
populate: {}
|
|
1126
|
+
},
|
|
1127
|
+
callback: ({ entries }) => entries.length
|
|
1128
|
+
});
|
|
1129
|
+
return batchCounts.reduce((total, count2) => total + count2, 0);
|
|
1130
|
+
}
|
|
1131
|
+
const count = await strapi2.documents(contentTypeUid).count(queryOptions);
|
|
793
1132
|
return count;
|
|
794
1133
|
} catch (e) {
|
|
795
1134
|
strapi2.log.warn(e);
|
|
@@ -801,18 +1140,29 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
|
|
|
801
1140
|
*
|
|
802
1141
|
* @param {object} options
|
|
803
1142
|
* @param {string[]} options.contentTypes - Names of the contentType.
|
|
804
|
-
* @param {object} [options.
|
|
1143
|
+
* @param {object} [options.filters] - Filter condition.
|
|
1144
|
+
* @param {string} [options.status='published'] - Publication state.
|
|
1145
|
+
* @param {string} [options.locale] - Locale to query.
|
|
805
1146
|
*
|
|
806
1147
|
* @returns {Promise<number>} Total entries number of the content types.
|
|
807
1148
|
*/
|
|
808
1149
|
totalNumberOfEntries: async function({
|
|
809
1150
|
contentTypes,
|
|
810
1151
|
filters = {},
|
|
811
|
-
status = "published"
|
|
1152
|
+
status = "published",
|
|
1153
|
+
locale
|
|
812
1154
|
}) {
|
|
1155
|
+
const normalizedEntryScope = normalizeEntryScope({
|
|
1156
|
+
filters,
|
|
1157
|
+
status,
|
|
1158
|
+
locale
|
|
1159
|
+
});
|
|
813
1160
|
let numberOfEntries = await Promise.all(
|
|
814
1161
|
contentTypes.map(
|
|
815
|
-
async (contentType2) => this.numberOfEntries({
|
|
1162
|
+
async (contentType2) => this.numberOfEntries({
|
|
1163
|
+
contentType: contentType2,
|
|
1164
|
+
...normalizedEntryScope
|
|
1165
|
+
})
|
|
816
1166
|
)
|
|
817
1167
|
);
|
|
818
1168
|
const entriesSum = numberOfEntries.reduce((acc, curr) => acc + curr, 0);
|
|
@@ -840,7 +1190,13 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
|
|
|
840
1190
|
status = "published",
|
|
841
1191
|
locale
|
|
842
1192
|
} = entriesQuery;
|
|
843
|
-
const queryOptions = {
|
|
1193
|
+
const queryOptions = {
|
|
1194
|
+
documentId,
|
|
1195
|
+
fields,
|
|
1196
|
+
populate,
|
|
1197
|
+
status,
|
|
1198
|
+
locale: normalizeEntryLocale(locale)
|
|
1199
|
+
};
|
|
844
1200
|
const contentTypeUid = this.getContentTypeUid({ contentType: contentType2 });
|
|
845
1201
|
if (contentTypeUid === void 0) return null;
|
|
846
1202
|
const entry = await strapi2.documents(contentTypeUid).findOne(queryOptions);
|
|
@@ -882,15 +1238,20 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
|
|
|
882
1238
|
}) {
|
|
883
1239
|
const contentTypeUid = this.getContentTypeUid({ contentType: contentType2 });
|
|
884
1240
|
if (contentTypeUid === void 0) return [];
|
|
1241
|
+
const normalizedEntryScope = normalizeEntryScope({
|
|
1242
|
+
filters,
|
|
1243
|
+
status,
|
|
1244
|
+
locale
|
|
1245
|
+
});
|
|
885
1246
|
const queryOptions = {
|
|
886
1247
|
fields: fields || "*",
|
|
887
1248
|
start,
|
|
888
1249
|
limit,
|
|
889
|
-
filters,
|
|
1250
|
+
filters: normalizedEntryScope.filters,
|
|
890
1251
|
sort,
|
|
891
1252
|
populate,
|
|
892
|
-
status,
|
|
893
|
-
locale
|
|
1253
|
+
status: normalizedEntryScope.status,
|
|
1254
|
+
locale: normalizedEntryScope.locale
|
|
894
1255
|
};
|
|
895
1256
|
const entries = await strapi2.documents(contentTypeUid).findMany(queryOptions);
|
|
896
1257
|
if (entries && !Array.isArray(entries)) return [entries];
|
|
@@ -912,23 +1273,37 @@ const contentTypeService = ({ strapi: strapi2 }) => ({
|
|
|
912
1273
|
},
|
|
913
1274
|
entriesQuery = {}
|
|
914
1275
|
}) {
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1276
|
+
const normalizedEntryScope = normalizeEntryScope({
|
|
1277
|
+
filters: entriesQuery.filters,
|
|
1278
|
+
status: entriesQuery.status,
|
|
1279
|
+
locale: entriesQuery.locale
|
|
919
1280
|
});
|
|
1281
|
+
const normalizedEntriesQuery = {
|
|
1282
|
+
...entriesQuery,
|
|
1283
|
+
...normalizedEntryScope
|
|
1284
|
+
};
|
|
1285
|
+
const {
|
|
1286
|
+
start: initialStart,
|
|
1287
|
+
limit: initialLimit,
|
|
1288
|
+
...baseQuery
|
|
1289
|
+
} = normalizedEntriesQuery;
|
|
1290
|
+
const batchSize = initialLimit ?? 500;
|
|
1291
|
+
const shouldIterateUntilEmpty = baseQuery.locale === "*";
|
|
1292
|
+
let start = initialStart ?? 0;
|
|
920
1293
|
const cbResponse = [];
|
|
921
|
-
|
|
1294
|
+
while (true) {
|
|
922
1295
|
const entries = await this.getEntries({
|
|
923
|
-
start
|
|
1296
|
+
start,
|
|
1297
|
+
limit: batchSize,
|
|
924
1298
|
contentType: contentType2,
|
|
925
|
-
...
|
|
1299
|
+
...baseQuery
|
|
926
1300
|
}) || [];
|
|
927
|
-
if (entries.length
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1301
|
+
if (entries.length === 0) break;
|
|
1302
|
+
const info = await callback({ entries, contentType: contentType2 });
|
|
1303
|
+
if (Array.isArray(info)) cbResponse.push(...info);
|
|
1304
|
+
else if (info) cbResponse.push(info);
|
|
1305
|
+
if (!shouldIterateUntilEmpty && entries.length < batchSize) break;
|
|
1306
|
+
start += batchSize;
|
|
932
1307
|
}
|
|
933
1308
|
return cbResponse;
|
|
934
1309
|
}
|
|
@@ -1260,224 +1635,7 @@ const store = ({ strapi: strapi2 }) => {
|
|
|
1260
1635
|
...createStoreConnector({ strapi: strapi2 })
|
|
1261
1636
|
};
|
|
1262
1637
|
};
|
|
1263
|
-
const
|
|
1264
|
-
const aborted = ({ contentType: contentType2, action }) => {
|
|
1265
|
-
strapi.log.error(
|
|
1266
|
-
`Indexing of ${contentType2} aborted as the data could not be ${action}`
|
|
1267
|
-
);
|
|
1268
|
-
return [];
|
|
1269
|
-
};
|
|
1270
|
-
const configurationService = ({ strapi: strapi2 }) => {
|
|
1271
|
-
const meilisearchConfig = strapi2.config.get("plugin::meilisearch") || {};
|
|
1272
|
-
const contentTypeService2 = strapi2.plugin("meilisearch").service("contentType");
|
|
1273
|
-
return {
|
|
1274
|
-
/**
|
|
1275
|
-
* Get the names of the indexes from Meilisearch in which the contentType content is added.
|
|
1276
|
-
*
|
|
1277
|
-
* @param {object} options
|
|
1278
|
-
* @param {string} options.contentType - ContentType name.
|
|
1279
|
-
*
|
|
1280
|
-
* @return {String[]} - Index names
|
|
1281
|
-
*/
|
|
1282
|
-
getIndexNamesOfContentType: function({ contentType: contentType2 }) {
|
|
1283
|
-
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
1284
|
-
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
1285
|
-
let indexNames = [contentTypeConfig.indexName].flat(2).filter((index2) => index2);
|
|
1286
|
-
return indexNames.length > 0 ? indexNames : [collection];
|
|
1287
|
-
},
|
|
1288
|
-
/**
|
|
1289
|
-
* Get the entries query rule of a content-type that are applied when fetching entries in the Strapi database.
|
|
1290
|
-
*
|
|
1291
|
-
* @param {object} options
|
|
1292
|
-
* @param {string} options.contentType - ContentType name.
|
|
1293
|
-
*
|
|
1294
|
-
* @return {String} - EntriesQuery rules.
|
|
1295
|
-
*/
|
|
1296
|
-
entriesQuery: function({ contentType: contentType2 }) {
|
|
1297
|
-
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
1298
|
-
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
1299
|
-
return contentTypeConfig.entriesQuery || {};
|
|
1300
|
-
},
|
|
1301
|
-
/**
|
|
1302
|
-
* Transform contentTypes entries before indexation in Meilisearch.
|
|
1303
|
-
*
|
|
1304
|
-
* @param {object} options
|
|
1305
|
-
* @param {string} options.contentType - ContentType name.
|
|
1306
|
-
* @param {Array<Object>} options.entries - The data to convert. Conversion will use
|
|
1307
|
-
* the static method `toSearchIndex` defined in the model definition
|
|
1308
|
-
*
|
|
1309
|
-
* @return {Promise<Array<Object>>} - Converted or mapped data
|
|
1310
|
-
*/
|
|
1311
|
-
transformEntries: async function({ contentType: contentType2, entries = [] }) {
|
|
1312
|
-
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
1313
|
-
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
1314
|
-
try {
|
|
1315
|
-
if (Array.isArray(entries) && typeof contentTypeConfig?.transformEntry === "function") {
|
|
1316
|
-
const transformed = await Promise.all(
|
|
1317
|
-
entries.map(
|
|
1318
|
-
async (entry) => await contentTypeConfig.transformEntry({
|
|
1319
|
-
entry,
|
|
1320
|
-
contentType: contentType2
|
|
1321
|
-
})
|
|
1322
|
-
)
|
|
1323
|
-
);
|
|
1324
|
-
if (transformed.length > 0 && !isObject(transformed[0])) {
|
|
1325
|
-
return aborted({ contentType: contentType2, action: "transformed" });
|
|
1326
|
-
}
|
|
1327
|
-
return transformed;
|
|
1328
|
-
}
|
|
1329
|
-
} catch (e) {
|
|
1330
|
-
strapi2.log.error(e);
|
|
1331
|
-
return aborted({ contentType: contentType2, action: "transformed" });
|
|
1332
|
-
}
|
|
1333
|
-
return entries;
|
|
1334
|
-
},
|
|
1335
|
-
/**
|
|
1336
|
-
* Filter contentTypes entries before indexation in Meilisearch.
|
|
1337
|
-
*
|
|
1338
|
-
* @param {object} options
|
|
1339
|
-
* @param {string} options.contentType - ContentType name.
|
|
1340
|
-
* @param {Array<Object>} options.entries - The data to convert. Conversion will use
|
|
1341
|
-
* the static method `toSearchIndex` defined in the model definition
|
|
1342
|
-
*
|
|
1343
|
-
* @return {Promise<Array<Object>>} - Converted or mapped data
|
|
1344
|
-
*/
|
|
1345
|
-
filterEntries: async function({ contentType: contentType2, entries = [] }) {
|
|
1346
|
-
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
1347
|
-
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
1348
|
-
try {
|
|
1349
|
-
if (Array.isArray(entries) && typeof contentTypeConfig?.filterEntry === "function") {
|
|
1350
|
-
const filtered = await entries.reduce(
|
|
1351
|
-
async (filteredEntries, entry) => {
|
|
1352
|
-
const isValid = await contentTypeConfig.filterEntry({
|
|
1353
|
-
entry,
|
|
1354
|
-
contentType: contentType2
|
|
1355
|
-
});
|
|
1356
|
-
if (!isValid) return filteredEntries;
|
|
1357
|
-
const syncFilteredEntries = await filteredEntries;
|
|
1358
|
-
return [...syncFilteredEntries, entry];
|
|
1359
|
-
},
|
|
1360
|
-
[]
|
|
1361
|
-
);
|
|
1362
|
-
return filtered;
|
|
1363
|
-
}
|
|
1364
|
-
} catch (e) {
|
|
1365
|
-
strapi2.log.error(e);
|
|
1366
|
-
return aborted({ contentType: contentType2, action: "filtered" });
|
|
1367
|
-
}
|
|
1368
|
-
return entries;
|
|
1369
|
-
},
|
|
1370
|
-
/**
|
|
1371
|
-
* Returns Meilisearch index settings from model definition.
|
|
1372
|
-
*
|
|
1373
|
-
* @param {object} options
|
|
1374
|
-
* @param {string} options.contentType - ContentType name.
|
|
1375
|
-
* @param {Array<Object>} [options.entries] - The data to convert. Conversion will use
|
|
1376
|
-
|
|
1377
|
-
* @typedef Settings
|
|
1378
|
-
* @type {import('meilisearch').Settings}
|
|
1379
|
-
* @return {Settings} - Meilisearch index settings
|
|
1380
|
-
*/
|
|
1381
|
-
getSettings: function({ contentType: contentType2 }) {
|
|
1382
|
-
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
1383
|
-
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
1384
|
-
const settings = contentTypeConfig.settings || {};
|
|
1385
|
-
return settings;
|
|
1386
|
-
},
|
|
1387
|
-
/**
|
|
1388
|
-
* Return all contentTypes having the provided indexName setting.
|
|
1389
|
-
*
|
|
1390
|
-
* @param {object} options
|
|
1391
|
-
* @param {string} options.indexName - Index in Meilisearch.
|
|
1392
|
-
*
|
|
1393
|
-
* @returns {string[]} List of contentTypes storing its data in the provided indexName
|
|
1394
|
-
*/
|
|
1395
|
-
listContentTypesWithCustomIndexName: function({ indexName }) {
|
|
1396
|
-
const contentTypes = strapi2.plugin("meilisearch").service("contentType").getContentTypesUid() || [];
|
|
1397
|
-
const collectionNames = contentTypes.map(
|
|
1398
|
-
(contentType2) => contentTypeService2.getCollectionName({ contentType: contentType2 })
|
|
1399
|
-
);
|
|
1400
|
-
const contentTypeWithIndexName = collectionNames.filter((contentType2) => {
|
|
1401
|
-
const names = this.getIndexNamesOfContentType({
|
|
1402
|
-
contentType: contentType2
|
|
1403
|
-
});
|
|
1404
|
-
return names.includes(indexName);
|
|
1405
|
-
});
|
|
1406
|
-
return contentTypeWithIndexName;
|
|
1407
|
-
},
|
|
1408
|
-
/**
|
|
1409
|
-
* Remove sensitive fields (password, author, etc, ..) from entry.
|
|
1410
|
-
*
|
|
1411
|
-
* @param {object} options
|
|
1412
|
-
* @param {Array<Object>} options.entries - The entries to sanitize
|
|
1413
|
-
*
|
|
1414
|
-
*
|
|
1415
|
-
* @return {Array<Object>} - Entries
|
|
1416
|
-
*/
|
|
1417
|
-
removeSensitiveFields: function({ contentType: contentType2, entries }) {
|
|
1418
|
-
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
1419
|
-
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
1420
|
-
const noSanitizePrivateFields = contentTypeConfig.noSanitizePrivateFields || [];
|
|
1421
|
-
if (noSanitizePrivateFields.includes("*")) {
|
|
1422
|
-
return entries;
|
|
1423
|
-
}
|
|
1424
|
-
const attrs = strapi2.contentTypes[contentType2].attributes;
|
|
1425
|
-
const privateFields = Object.entries(attrs).map(
|
|
1426
|
-
([field, schema]) => schema.private && !noSanitizePrivateFields.includes(field) ? field : false
|
|
1427
|
-
);
|
|
1428
|
-
return entries.map((entry) => {
|
|
1429
|
-
privateFields.forEach((attr) => delete entry[attr]);
|
|
1430
|
-
return entry;
|
|
1431
|
-
});
|
|
1432
|
-
},
|
|
1433
|
-
/**
|
|
1434
|
-
* Remove unpublished entries from array of entries
|
|
1435
|
-
* unless `status` is set to 'draft'.
|
|
1436
|
-
*
|
|
1437
|
-
* @param {object} options
|
|
1438
|
-
* @param {Array<Object>} options.entries - The entries to filter.
|
|
1439
|
-
* @param {string} options.contentType - ContentType name.
|
|
1440
|
-
*
|
|
1441
|
-
* @return {Array<Object>} - Published entries.
|
|
1442
|
-
*/
|
|
1443
|
-
removeUnpublishedArticles: function({ entries, contentType: contentType2 }) {
|
|
1444
|
-
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
1445
|
-
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
1446
|
-
const entriesQuery = contentTypeConfig.entriesQuery || {};
|
|
1447
|
-
if (entriesQuery.status === "draft") {
|
|
1448
|
-
return entries;
|
|
1449
|
-
} else {
|
|
1450
|
-
return entries.filter(
|
|
1451
|
-
(entry) => !(entry?.publishedAt === void 0 || entry?.publishedAt === null)
|
|
1452
|
-
);
|
|
1453
|
-
}
|
|
1454
|
-
},
|
|
1455
|
-
/**
|
|
1456
|
-
* Remove language entries.
|
|
1457
|
-
* In the plugin entriesQuery, if `locale` is set and not equal to
|
|
1458
|
-
* `all` (used with the i18n plugin for Strapi) or `*` (used with Strapi 5
|
|
1459
|
-
* native localization), all entries that do not have the specified
|
|
1460
|
-
* language are removed.
|
|
1461
|
-
*
|
|
1462
|
-
* @param {object} options
|
|
1463
|
-
* @param {Array<Object>} options.entries - The entries to filter.
|
|
1464
|
-
* @param {string} options.contentType - ContentType name.
|
|
1465
|
-
*
|
|
1466
|
-
* @return {Array<Object>} - Published entries.
|
|
1467
|
-
*/
|
|
1468
|
-
removeLocaleEntries: function({ entries, contentType: contentType2 }) {
|
|
1469
|
-
const collection = contentTypeService2.getCollectionName({ contentType: contentType2 });
|
|
1470
|
-
const contentTypeConfig = meilisearchConfig[collection] || {};
|
|
1471
|
-
const entriesQuery = contentTypeConfig.entriesQuery || {};
|
|
1472
|
-
if (!entriesQuery.locale || isWildcardLocale(entriesQuery.locale)) {
|
|
1473
|
-
return entries;
|
|
1474
|
-
} else {
|
|
1475
|
-
return entries.filter((entry) => entry.locale === entriesQuery.locale);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
};
|
|
1479
|
-
};
|
|
1480
|
-
const version = "0.16.0";
|
|
1638
|
+
const version = "0.16.2";
|
|
1481
1639
|
const Meilisearch = (config2) => {
|
|
1482
1640
|
return new MeiliSearch({
|
|
1483
1641
|
...config2,
|