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