strapi-plugin-meilisearch-plus 0.0.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/README.md +266 -0
- package/dist/_chunks/CollectionsTable-DhFoYbHi.mjs +376 -0
- package/dist/_chunks/CollectionsTable-QsBP3lXJ.js +376 -0
- package/dist/_chunks/CredentialsTab-CNnDxmdX.js +223 -0
- package/dist/_chunks/CredentialsTab-UuDtCixJ.mjs +223 -0
- package/dist/_chunks/HomePage-CwtBGxKu.js +36 -0
- package/dist/_chunks/HomePage-VgGeNynA.mjs +36 -0
- package/dist/_chunks/IndexSettingsTab-EvPHd16e.js +270 -0
- package/dist/_chunks/IndexSettingsTab-P4Oz-QWd.mjs +270 -0
- package/dist/_chunks/en-BijiR88Y.js +42 -0
- package/dist/_chunks/en-KhozGU-M.mjs +42 -0
- package/dist/_chunks/es-B2Fg_gLt.js +42 -0
- package/dist/_chunks/es-DAneCWM5.mjs +42 -0
- package/dist/_chunks/index-DgpT8G3z.mjs +134 -0
- package/dist/_chunks/index-eoiIjp_f.js +133 -0
- package/dist/_chunks/useI18n-B9yJCqkO.js +18 -0
- package/dist/_chunks/useI18n-RYsv52c9.mjs +19 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/admin/src/components/Icons/DangerIcon.d.ts +7 -0
- package/dist/admin/src/components/Icons/RefreshIcon.d.ts +7 -0
- package/dist/admin/src/components/Icons/XIcon.d.ts +7 -0
- package/dist/admin/src/components/Icons/index.d.ts +3 -0
- package/dist/admin/src/components/Initializer.d.ts +10 -0
- package/dist/admin/src/components/PluginIcon.d.ts +10 -0
- package/dist/admin/src/containers/CollectionsTab.d.ts +6 -0
- package/dist/admin/src/containers/CollectionsTable.d.ts +6 -0
- package/dist/admin/src/containers/ContentTypesToggle.d.ts +6 -0
- package/dist/admin/src/containers/CredentialsTab.d.ts +6 -0
- package/dist/admin/src/containers/IndexSettingsTab.d.ts +6 -0
- package/dist/admin/src/containers/PluginTabs.d.ts +6 -0
- package/dist/admin/src/hooks/index.d.ts +3 -0
- package/dist/admin/src/hooks/useCredentials.d.ts +18 -0
- package/dist/admin/src/hooks/useI18n.d.ts +7 -0
- package/dist/admin/src/hooks/useIndexedContentTypes.d.ts +15 -0
- package/dist/admin/src/index.d.ts +14 -0
- package/dist/admin/src/pages/App.d.ts +2 -0
- package/dist/admin/src/pages/HomePage.d.ts +5 -0
- package/dist/admin/src/pages/SettingsPage.d.ts +5 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/utils/getTranslation.d.ts +2 -0
- package/dist/admin/src/utils/pluginId.d.ts +1 -0
- package/dist/server/index.js +1346 -0
- package/dist/server/index.mjs +1347 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/config/index.d.ts +5 -0
- package/dist/server/src/content-types/index.d.ts +2 -0
- package/dist/server/src/controllers/content-types.d.ts +14 -0
- package/dist/server/src/controllers/controller.d.ts +7 -0
- package/dist/server/src/controllers/credentials.d.ts +12 -0
- package/dist/server/src/controllers/index-settings.d.ts +17 -0
- package/dist/server/src/controllers/index.d.ts +40 -0
- package/dist/server/src/controllers/indexing.d.ts +10 -0
- package/dist/server/src/controllers/search.d.ts +10 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/index.d.ts +216 -0
- package/dist/server/src/middlewares/index.d.ts +2 -0
- package/dist/server/src/policies/index.d.ts +2 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/routes/admin/index.d.ts +21 -0
- package/dist/server/src/routes/content-api/index.d.ts +12 -0
- package/dist/server/src/routes/index.d.ts +34 -0
- package/dist/server/src/services/content-types.d.ts +59 -0
- package/dist/server/src/services/index.d.ts +123 -0
- package/dist/server/src/services/lifecycle.d.ts +15 -0
- package/dist/server/src/services/meilisearch-client.d.ts +18 -0
- package/dist/server/src/services/meilisearch.d.ts +42 -0
- package/dist/server/src/services/service.d.ts +7 -0
- package/dist/server/src/services/store.d.ts +42 -0
- package/package.json +78 -0
|
@@ -0,0 +1,1347 @@
|
|
|
1
|
+
import { MeiliSearch } from "meilisearch";
|
|
2
|
+
const bootstrap = ({ strapi }) => {
|
|
3
|
+
try {
|
|
4
|
+
strapi.log.info("[meilisearch-plus] Bootstrapping plugin");
|
|
5
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
6
|
+
const lifecycleService = strapi.plugin("meilisearch-plus").service("lifecycle");
|
|
7
|
+
const meilisearchService = strapi.plugin("meilisearch-plus").service("meilisearch");
|
|
8
|
+
(async () => {
|
|
9
|
+
try {
|
|
10
|
+
await storeService.syncCredentials();
|
|
11
|
+
strapi.log.info("[meilisearch-plus] Credentials synced from plugin config");
|
|
12
|
+
await meilisearchService.syncIndexedCollections();
|
|
13
|
+
const indexedContentTypes = await storeService.getIndexedContentTypes();
|
|
14
|
+
if (indexedContentTypes && indexedContentTypes.length > 0) {
|
|
15
|
+
strapi.log.info(
|
|
16
|
+
`[meilisearch-plus] Subscribing to ${indexedContentTypes.length} indexed content types:`,
|
|
17
|
+
indexedContentTypes
|
|
18
|
+
);
|
|
19
|
+
for (const contentType of indexedContentTypes) {
|
|
20
|
+
try {
|
|
21
|
+
lifecycleService.subscribeContentType(contentType);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
strapi.log.warn(`[meilisearch-plus] Failed to subscribe to ${contentType}:`, error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
strapi.log.info("[meilisearch-plus] No indexed content types found");
|
|
28
|
+
}
|
|
29
|
+
strapi.log.info("[meilisearch-plus] Bootstrap complete");
|
|
30
|
+
} catch (error) {
|
|
31
|
+
strapi.log.error("[meilisearch-plus] Bootstrap error:", error);
|
|
32
|
+
}
|
|
33
|
+
})();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
strapi.log.error("[meilisearch-plus] Bootstrap initialization error:", error);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const destroy = ({ strapi }) => {
|
|
39
|
+
try {
|
|
40
|
+
strapi.log.info("[meilisearch-plus] Destroying plugin");
|
|
41
|
+
} catch (error) {
|
|
42
|
+
strapi.log.error("[meilisearch-plus] Destroy error:", error);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const register = ({ strapi }) => {
|
|
46
|
+
strapi.log.info("[meilisearch-plus] Registering plugin");
|
|
47
|
+
};
|
|
48
|
+
const config = {
|
|
49
|
+
default: {},
|
|
50
|
+
validator() {
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const contentTypes$2 = {};
|
|
54
|
+
const credentials = ({ strapi }) => ({
|
|
55
|
+
async index(ctx) {
|
|
56
|
+
ctx.body = { message: "MeiliSearch Plus API" };
|
|
57
|
+
},
|
|
58
|
+
async getCredentials(ctx) {
|
|
59
|
+
try {
|
|
60
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
61
|
+
const credentials2 = await storeService.getCredentials();
|
|
62
|
+
ctx.body = { data: credentials2 };
|
|
63
|
+
} catch (error) {
|
|
64
|
+
strapi.log.error("[meilisearch-plus] Failed to get credentials:", error);
|
|
65
|
+
ctx.body = { error: error.message };
|
|
66
|
+
ctx.status = 500;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
async setCredentials(ctx) {
|
|
70
|
+
try {
|
|
71
|
+
const { host, apiKey, indexName } = ctx.request.body;
|
|
72
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
73
|
+
await storeService.setCredentials({ host, apiKey, indexName });
|
|
74
|
+
const credentials2 = await storeService.getCredentials();
|
|
75
|
+
ctx.body = { data: credentials2 };
|
|
76
|
+
} catch (error) {
|
|
77
|
+
strapi.log.error("[meilisearch-plus] Failed to set credentials:", error);
|
|
78
|
+
ctx.body = { error: error.message };
|
|
79
|
+
ctx.status = 500;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
async testConnection(ctx) {
|
|
83
|
+
try {
|
|
84
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
85
|
+
const clientService = strapi.plugin("meilisearch-plus").service("meilisearch-client");
|
|
86
|
+
const credentials2 = await storeService.getCredentials();
|
|
87
|
+
const client = clientService.createClient(credentials2);
|
|
88
|
+
const isHealthy = await clientService.testConnection(client);
|
|
89
|
+
ctx.body = { data: { healthy: isHealthy } };
|
|
90
|
+
} catch (error) {
|
|
91
|
+
strapi.log.error("[meilisearch-plus] Connection test failed:", error);
|
|
92
|
+
ctx.body = { error: error.message, data: { healthy: false } };
|
|
93
|
+
ctx.status = 500;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const search = ({ strapi }) => ({
|
|
98
|
+
async search(ctx) {
|
|
99
|
+
try {
|
|
100
|
+
const { query, filters, limit = 20, offset = 0 } = ctx.request.body;
|
|
101
|
+
if (!query) {
|
|
102
|
+
ctx.status = 400;
|
|
103
|
+
ctx.body = { error: "Query parameter is required" };
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const meilisearchService = strapi.plugin("meilisearch-plus").service("meilisearch");
|
|
107
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
108
|
+
const indexName = await storeService.getIndexName();
|
|
109
|
+
if (!indexName) {
|
|
110
|
+
ctx.status = 400;
|
|
111
|
+
ctx.body = { error: "Index name not configured" };
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const results = await meilisearchService.search({
|
|
115
|
+
query,
|
|
116
|
+
filters,
|
|
117
|
+
limit,
|
|
118
|
+
offset,
|
|
119
|
+
indexName
|
|
120
|
+
});
|
|
121
|
+
ctx.body = { data: results };
|
|
122
|
+
} catch (error) {
|
|
123
|
+
strapi.log.error("[meilisearch-plus] Search failed:", error);
|
|
124
|
+
ctx.body = { error: error.message };
|
|
125
|
+
ctx.status = 500;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
async getIndexStatus(ctx) {
|
|
129
|
+
try {
|
|
130
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
131
|
+
const clientService = strapi.plugin("meilisearch-plus").service("meilisearch-client");
|
|
132
|
+
const credentials2 = await storeService.getCredentials();
|
|
133
|
+
const indexName = await storeService.getIndexName();
|
|
134
|
+
if (!indexName) {
|
|
135
|
+
ctx.body = { data: { status: "not-configured" } };
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const client = clientService.createClient(credentials2);
|
|
139
|
+
if (!client) {
|
|
140
|
+
ctx.body = { data: { status: "disconnected" } };
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const isHealthy = await clientService.testConnection(client);
|
|
144
|
+
if (!isHealthy) {
|
|
145
|
+
ctx.body = { data: { status: "unhealthy" } };
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const indexUids = await clientService.getIndexUids(client);
|
|
149
|
+
const indexExists = indexUids.includes(indexName);
|
|
150
|
+
if (!indexExists) {
|
|
151
|
+
ctx.body = { data: { status: "index-missing" } };
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const indexedContentTypes = await storeService.getIndexedContentTypes();
|
|
155
|
+
const stats = {
|
|
156
|
+
status: "healthy",
|
|
157
|
+
indexName,
|
|
158
|
+
indexedContentTypes: indexedContentTypes || [],
|
|
159
|
+
totalIndexes: indexUids.length
|
|
160
|
+
};
|
|
161
|
+
ctx.body = { data: stats };
|
|
162
|
+
} catch (error) {
|
|
163
|
+
strapi.log.error("[meilisearch-plus] Failed to get index status:", error);
|
|
164
|
+
ctx.body = { error: error.message };
|
|
165
|
+
ctx.status = 500;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
const contentTypes$1 = ({ strapi }) => ({
|
|
170
|
+
async getConfiguredContentTypes(ctx) {
|
|
171
|
+
try {
|
|
172
|
+
let pluginConfig = strapi.config.get("plugin::meilisearch-plus");
|
|
173
|
+
strapi.log.info("[meilisearch-plus] Accessed via plugin::meilisearch-plus:", !!pluginConfig);
|
|
174
|
+
if (!pluginConfig) {
|
|
175
|
+
pluginConfig = strapi.config.get("plugins")?.["meilisearch-plus"];
|
|
176
|
+
strapi.log.info(
|
|
177
|
+
"[meilisearch-plus] Accessed via plugins.meilisearch-plus:",
|
|
178
|
+
!!pluginConfig
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
pluginConfig = pluginConfig || {};
|
|
182
|
+
strapi.log.info(
|
|
183
|
+
"[meilisearch-plus] Final plugin config:",
|
|
184
|
+
JSON.stringify(pluginConfig, null, 2)
|
|
185
|
+
);
|
|
186
|
+
const configured = Object.keys(pluginConfig).filter(
|
|
187
|
+
(key) => key !== "host" && key !== "apiKey" && key !== "enabled" && key !== "resolve" && typeof pluginConfig[key] === "object"
|
|
188
|
+
);
|
|
189
|
+
strapi.log.info("[meilisearch-plus] Configured content types:", configured);
|
|
190
|
+
ctx.body = { data: configured };
|
|
191
|
+
} catch (error) {
|
|
192
|
+
strapi.log.error("[meilisearch-plus] Failed to get configured content types:", error);
|
|
193
|
+
ctx.body = { error: error.message };
|
|
194
|
+
ctx.status = 500;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
async getSyncStatus(ctx) {
|
|
198
|
+
try {
|
|
199
|
+
const contentTypeService = strapi.plugin("meilisearch-plus").service("content-types");
|
|
200
|
+
const { contentType } = ctx.query;
|
|
201
|
+
if (!contentType) {
|
|
202
|
+
ctx.status = 400;
|
|
203
|
+
ctx.body = { error: "Content type is required" };
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const syncStatus = await contentTypeService.getSyncStatus({ contentType });
|
|
207
|
+
ctx.body = { data: syncStatus };
|
|
208
|
+
} catch (error) {
|
|
209
|
+
strapi.log.error("[meilisearch-plus] Failed to get sync status:", error);
|
|
210
|
+
ctx.body = { error: error.message };
|
|
211
|
+
ctx.status = 500;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
async getAllSyncStatus(ctx) {
|
|
215
|
+
try {
|
|
216
|
+
const contentTypeService = strapi.plugin("meilisearch-plus").service("content-types");
|
|
217
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
218
|
+
let pluginConfig = strapi.config.get("plugin::meilisearch-plus");
|
|
219
|
+
if (!pluginConfig) {
|
|
220
|
+
pluginConfig = strapi.config.get("plugins")?.["meilisearch-plus"] || {};
|
|
221
|
+
}
|
|
222
|
+
const configuredShortNames = Object.keys(pluginConfig).filter(
|
|
223
|
+
(key) => key !== "host" && key !== "apiKey" && key !== "enabled" && key !== "resolve" && typeof pluginConfig[key] === "object"
|
|
224
|
+
);
|
|
225
|
+
const allContentTypes = strapi.contentTypes;
|
|
226
|
+
const shortNameToUid = {};
|
|
227
|
+
Object.keys(allContentTypes).forEach((uid) => {
|
|
228
|
+
const lastPart = uid.split(".").pop();
|
|
229
|
+
if (lastPart && configuredShortNames.includes(lastPart)) {
|
|
230
|
+
shortNameToUid[lastPart] = uid;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
const configuredUids = configuredShortNames.map((short) => shortNameToUid[short]).filter(Boolean);
|
|
234
|
+
const indexed = await storeService.getIndexedContentTypes();
|
|
235
|
+
const syncStatuses = await contentTypeService.getSyncStatusForMultiple({
|
|
236
|
+
contentTypes: configuredUids
|
|
237
|
+
});
|
|
238
|
+
const result = configuredUids.map((uid) => ({
|
|
239
|
+
contentType: uid,
|
|
240
|
+
displayName: allContentTypes[uid]?.info?.displayName || uid,
|
|
241
|
+
...syncStatuses[uid],
|
|
242
|
+
isIndexed: indexed.includes(uid)
|
|
243
|
+
}));
|
|
244
|
+
ctx.body = { data: result };
|
|
245
|
+
} catch (error) {
|
|
246
|
+
strapi.log.error("[meilisearch-plus] Failed to get all sync statuses:", error);
|
|
247
|
+
ctx.body = { error: error.message };
|
|
248
|
+
ctx.status = 500;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
async getIndexedContentTypes(ctx) {
|
|
252
|
+
try {
|
|
253
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
254
|
+
const indexedContentTypes = await storeService.getIndexedContentTypes();
|
|
255
|
+
ctx.body = { data: indexedContentTypes || [] };
|
|
256
|
+
} catch (error) {
|
|
257
|
+
strapi.log.error("[meilisearch-plus] Failed to get indexed content types:", error);
|
|
258
|
+
ctx.body = { error: error.message };
|
|
259
|
+
ctx.status = 500;
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
async addIndexedContentType(ctx) {
|
|
263
|
+
try {
|
|
264
|
+
const { contentType } = ctx.request.body;
|
|
265
|
+
if (!contentType) {
|
|
266
|
+
ctx.status = 400;
|
|
267
|
+
ctx.body = { error: "Content type is required" };
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
271
|
+
const lifecycleService = strapi.plugin("meilisearch-plus").service("lifecycle");
|
|
272
|
+
await storeService.addIndexedContentType(contentType);
|
|
273
|
+
lifecycleService.subscribeContentType(contentType);
|
|
274
|
+
const indexedContentTypes = await storeService.getIndexedContentTypes();
|
|
275
|
+
ctx.body = { data: indexedContentTypes };
|
|
276
|
+
} catch (error) {
|
|
277
|
+
strapi.log.error("[meilisearch-plus] Failed to add indexed content type:", error);
|
|
278
|
+
ctx.body = { error: error.message };
|
|
279
|
+
ctx.status = 500;
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
async removeIndexedContentType(ctx) {
|
|
283
|
+
try {
|
|
284
|
+
const { contentType } = ctx.query;
|
|
285
|
+
if (!contentType) {
|
|
286
|
+
ctx.status = 400;
|
|
287
|
+
ctx.body = { error: "Content type is required" };
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
291
|
+
const lifecycleService = strapi.plugin("meilisearch-plus").service("lifecycle");
|
|
292
|
+
const meilisearchService = strapi.plugin("meilisearch-plus").service("meilisearch");
|
|
293
|
+
await meilisearchService.emptyOrDeleteIndex({ contentType });
|
|
294
|
+
await storeService.removeIndexedContentType(contentType);
|
|
295
|
+
lifecycleService.unsubscribeContentType(contentType);
|
|
296
|
+
const indexedContentTypes = await storeService.getIndexedContentTypes();
|
|
297
|
+
ctx.body = { data: indexedContentTypes };
|
|
298
|
+
} catch (error) {
|
|
299
|
+
strapi.log.error("[meilisearch-plus] Failed to remove indexed content type:", error);
|
|
300
|
+
ctx.body = { error: error.message };
|
|
301
|
+
ctx.status = 500;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const indexing = ({ strapi }) => ({
|
|
306
|
+
async reindexContentType(ctx) {
|
|
307
|
+
try {
|
|
308
|
+
const { contentType } = ctx.request.body || ctx.params;
|
|
309
|
+
if (!contentType) {
|
|
310
|
+
ctx.status = 400;
|
|
311
|
+
ctx.body = { error: "Content type is required" };
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const meilisearchService = strapi.plugin("meilisearch-plus").service("meilisearch");
|
|
315
|
+
const taskUids = await meilisearchService.updateContentTypeInMeiliSearch({ contentType });
|
|
316
|
+
ctx.body = { data: { reindexed: taskUids.length } };
|
|
317
|
+
} catch (error) {
|
|
318
|
+
strapi.log.error("[meilisearch-plus] Reindexing failed:", error);
|
|
319
|
+
ctx.body = { error: error.message };
|
|
320
|
+
ctx.status = 500;
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
async clearIndex(ctx) {
|
|
324
|
+
try {
|
|
325
|
+
const meilisearchService = strapi.plugin("meilisearch-plus").service("meilisearch");
|
|
326
|
+
const { contentType } = ctx.request.body || {};
|
|
327
|
+
await meilisearchService.deleteAllDocuments({ contentType });
|
|
328
|
+
ctx.body = { data: { cleared: true } };
|
|
329
|
+
} catch (error) {
|
|
330
|
+
strapi.log.error("[meilisearch-plus] Clear index failed:", error);
|
|
331
|
+
ctx.body = { error: error.message };
|
|
332
|
+
ctx.status = 500;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
const indexSettings = ({ strapi }) => ({
|
|
337
|
+
/**
|
|
338
|
+
* Get all indexed content types and their fields for attribute selection
|
|
339
|
+
*/
|
|
340
|
+
async getContentTypesAndFields(ctx) {
|
|
341
|
+
try {
|
|
342
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
343
|
+
const indexedContentTypes = await storeService.getIndexedContentTypes();
|
|
344
|
+
const fields = {};
|
|
345
|
+
const filterableAttributes = {};
|
|
346
|
+
const sortableAttributes = {};
|
|
347
|
+
for (const contentType of indexedContentTypes) {
|
|
348
|
+
try {
|
|
349
|
+
const model = strapi.getModel(contentType);
|
|
350
|
+
if (!model || !model.attributes) {
|
|
351
|
+
strapi.log.warn(
|
|
352
|
+
`[meilisearch-plus] Could not find model for content type: ${contentType}`
|
|
353
|
+
);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
fields[contentType] = [
|
|
357
|
+
...Object.entries(model.attributes).filter(([_, attr]) => {
|
|
358
|
+
const type = attr.type;
|
|
359
|
+
return type === "string" || type === "text" || type === "richtext" || type === "email" || type === "integer" || type === "decimal" || type === "biginteger" || type === "float" || type === "boolean" || type === "date" || type === "datetime" || type === "time" || type === "enumeration";
|
|
360
|
+
}).map(([name]) => name)
|
|
361
|
+
];
|
|
362
|
+
filterableAttributes[contentType] = {};
|
|
363
|
+
sortableAttributes[contentType] = {};
|
|
364
|
+
} catch (err) {
|
|
365
|
+
strapi.log.error(`[meilisearch-plus] Error processing content type ${contentType}:`, err);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const storedSettings = await storeService.getStoreKey({
|
|
369
|
+
key: "meilisearch-index-settings"
|
|
370
|
+
});
|
|
371
|
+
if (storedSettings) {
|
|
372
|
+
const storedFilterable = storedSettings.filterableAttributes || [];
|
|
373
|
+
const storedSortable = storedSettings.sortableAttributes || [];
|
|
374
|
+
indexedContentTypes.forEach((contentType) => {
|
|
375
|
+
storedFilterable.forEach((field) => {
|
|
376
|
+
if (fields[contentType]?.includes(field)) {
|
|
377
|
+
filterableAttributes[contentType][field] = true;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
storedSortable.forEach((field) => {
|
|
381
|
+
if (fields[contentType]?.includes(field)) {
|
|
382
|
+
sortableAttributes[contentType][field] = true;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
strapi.log.info(
|
|
387
|
+
`[meilisearch-plus] Loaded stored settings: filterable=[${storedFilterable.join(", ")}], sortable=[${storedSortable.join(", ")}]`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const storedMaxHits = await storeService.getStoreKey({
|
|
391
|
+
key: "meilisearch-max-total-hits"
|
|
392
|
+
});
|
|
393
|
+
ctx.body = {
|
|
394
|
+
contentTypes: indexedContentTypes.map((uid) => ({
|
|
395
|
+
uid,
|
|
396
|
+
name: strapi.getModel(uid)?.info?.displayName || uid
|
|
397
|
+
})),
|
|
398
|
+
fields,
|
|
399
|
+
filterableAttributes,
|
|
400
|
+
sortableAttributes,
|
|
401
|
+
maxTotalHits: storedMaxHits || 1e3
|
|
402
|
+
};
|
|
403
|
+
} catch (error) {
|
|
404
|
+
strapi.log.error("[meilisearch-plus] Error in getContentTypesAndFields:", error);
|
|
405
|
+
ctx.throw(500, error.message);
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
/**
|
|
409
|
+
* Save index settings (filterable/sortable attributes and maxTotalHits)
|
|
410
|
+
*/
|
|
411
|
+
async saveIndexSettings(ctx) {
|
|
412
|
+
try {
|
|
413
|
+
const { filterableAttributes, sortableAttributes, maxTotalHits } = ctx.request.body;
|
|
414
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
415
|
+
strapi.log.info("[meilisearch-plus] saveIndexSettings received maxTotalHits:", maxTotalHits);
|
|
416
|
+
const mergedFilterableAttributes = [];
|
|
417
|
+
const mergedSortableAttributes = [];
|
|
418
|
+
for (const [contentType, attrs] of Object.entries(filterableAttributes)) {
|
|
419
|
+
if (Array.isArray(attrs)) {
|
|
420
|
+
attrs.forEach((field) => {
|
|
421
|
+
if (!mergedFilterableAttributes.includes(field)) {
|
|
422
|
+
mergedFilterableAttributes.push(field);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
for (const [contentType, attrs] of Object.entries(sortableAttributes)) {
|
|
428
|
+
if (Array.isArray(attrs)) {
|
|
429
|
+
attrs.forEach((field) => {
|
|
430
|
+
if (!mergedSortableAttributes.includes(field)) {
|
|
431
|
+
mergedSortableAttributes.push(field);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (!mergedFilterableAttributes.includes("_contentType")) {
|
|
437
|
+
mergedFilterableAttributes.unshift("_contentType");
|
|
438
|
+
}
|
|
439
|
+
if (!mergedSortableAttributes.includes("_contentType")) {
|
|
440
|
+
mergedSortableAttributes.unshift("_contentType");
|
|
441
|
+
}
|
|
442
|
+
await storeService.setStoreKey({
|
|
443
|
+
key: "meilisearch-index-settings",
|
|
444
|
+
value: {
|
|
445
|
+
filterableAttributes: mergedFilterableAttributes,
|
|
446
|
+
sortableAttributes: mergedSortableAttributes
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
strapi.log.info(
|
|
450
|
+
`[meilisearch-plus] Saved merged index settings: filterable=[${mergedFilterableAttributes.join(", ")}], sortable=[${mergedSortableAttributes.join(", ")}]`
|
|
451
|
+
);
|
|
452
|
+
if (maxTotalHits) {
|
|
453
|
+
await storeService.setStoreKey({
|
|
454
|
+
key: "meilisearch-max-total-hits",
|
|
455
|
+
value: maxTotalHits
|
|
456
|
+
});
|
|
457
|
+
strapi.log.info(`[meilisearch-plus] Saved maxTotalHits: ${maxTotalHits}`);
|
|
458
|
+
}
|
|
459
|
+
ctx.body = {
|
|
460
|
+
data: {
|
|
461
|
+
message: "Index settings saved successfully"
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
} catch (error) {
|
|
465
|
+
strapi.log.error("[meilisearch-plus] Error in saveIndexSettings:", error);
|
|
466
|
+
ctx.throw(500, error.message);
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
/**
|
|
470
|
+
* Apply current settings to Meilisearch index
|
|
471
|
+
*/
|
|
472
|
+
async applyIndexSettings(ctx) {
|
|
473
|
+
try {
|
|
474
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
475
|
+
const meilisearchService = strapi.plugin("meilisearch-plus").service("meilisearch");
|
|
476
|
+
const settings = await storeService.getStoreKey({
|
|
477
|
+
key: "meilisearch-index-settings"
|
|
478
|
+
});
|
|
479
|
+
const filterableAttributes = settings?.filterableAttributes || [];
|
|
480
|
+
const sortableAttributes = settings?.sortableAttributes || [];
|
|
481
|
+
if (!filterableAttributes.includes("_contentType")) {
|
|
482
|
+
filterableAttributes.unshift("_contentType");
|
|
483
|
+
}
|
|
484
|
+
if (!sortableAttributes.includes("_contentType")) {
|
|
485
|
+
sortableAttributes.unshift("_contentType");
|
|
486
|
+
}
|
|
487
|
+
strapi.log.info(
|
|
488
|
+
`[meilisearch-plus] Applying settings: filterable=[${filterableAttributes.join(", ")}], sortable=[${sortableAttributes.join(", ")}]`
|
|
489
|
+
);
|
|
490
|
+
await meilisearchService.updateIndexSettings({
|
|
491
|
+
settings: {
|
|
492
|
+
filterableAttributes,
|
|
493
|
+
sortableAttributes
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
ctx.body = {
|
|
497
|
+
data: {
|
|
498
|
+
message: "Settings applied to Meilisearch index successfully"
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
} catch (error) {
|
|
502
|
+
strapi.log.error("[meilisearch-plus] Error in applyIndexSettings:", error);
|
|
503
|
+
ctx.throw(500, error.message);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
const controllers = {
|
|
508
|
+
credentials,
|
|
509
|
+
search,
|
|
510
|
+
contentTypes: contentTypes$1,
|
|
511
|
+
indexing,
|
|
512
|
+
indexSettings
|
|
513
|
+
};
|
|
514
|
+
const middlewares = {};
|
|
515
|
+
const policies = {};
|
|
516
|
+
const contentAPIRoutes = {
|
|
517
|
+
type: "content-api",
|
|
518
|
+
routes: [
|
|
519
|
+
{
|
|
520
|
+
method: "POST",
|
|
521
|
+
path: "/search",
|
|
522
|
+
handler: "search.search",
|
|
523
|
+
config: {
|
|
524
|
+
policies: []
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
]
|
|
528
|
+
};
|
|
529
|
+
const adminAPIRoutes = {
|
|
530
|
+
type: "admin",
|
|
531
|
+
routes: [
|
|
532
|
+
{
|
|
533
|
+
method: "GET",
|
|
534
|
+
path: "/",
|
|
535
|
+
handler: "credentials.index",
|
|
536
|
+
config: {
|
|
537
|
+
auth: false
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
method: "GET",
|
|
542
|
+
path: "/credentials",
|
|
543
|
+
handler: "credentials.getCredentials",
|
|
544
|
+
config: {
|
|
545
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
method: "POST",
|
|
550
|
+
path: "/credentials",
|
|
551
|
+
handler: "credentials.setCredentials",
|
|
552
|
+
config: {
|
|
553
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
method: "GET",
|
|
558
|
+
path: "/test-connection",
|
|
559
|
+
handler: "credentials.testConnection",
|
|
560
|
+
config: {
|
|
561
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
method: "POST",
|
|
566
|
+
path: "/search",
|
|
567
|
+
handler: "search.search",
|
|
568
|
+
config: {
|
|
569
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
method: "GET",
|
|
574
|
+
path: "/index-status",
|
|
575
|
+
handler: "search.getIndexStatus",
|
|
576
|
+
config: {
|
|
577
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
method: "GET",
|
|
582
|
+
path: "/configured-content-types",
|
|
583
|
+
handler: "contentTypes.getConfiguredContentTypes",
|
|
584
|
+
config: {
|
|
585
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
method: "GET",
|
|
590
|
+
path: "/sync-status",
|
|
591
|
+
handler: "contentTypes.getSyncStatus",
|
|
592
|
+
config: {
|
|
593
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
method: "GET",
|
|
598
|
+
path: "/sync-status/all",
|
|
599
|
+
handler: "contentTypes.getAllSyncStatus",
|
|
600
|
+
config: {
|
|
601
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
method: "GET",
|
|
606
|
+
path: "/indexed-content-types",
|
|
607
|
+
handler: "contentTypes.getIndexedContentTypes",
|
|
608
|
+
config: {
|
|
609
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
method: "POST",
|
|
614
|
+
path: "/indexed-content-types",
|
|
615
|
+
handler: "contentTypes.addIndexedContentType",
|
|
616
|
+
config: {
|
|
617
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
method: "DELETE",
|
|
622
|
+
path: "/indexed-content-types",
|
|
623
|
+
handler: "contentTypes.removeIndexedContentType",
|
|
624
|
+
config: {
|
|
625
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
method: "POST",
|
|
630
|
+
path: "/reindex",
|
|
631
|
+
handler: "indexing.reindexContentType",
|
|
632
|
+
config: {
|
|
633
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
method: "POST",
|
|
638
|
+
path: "/clear-index",
|
|
639
|
+
handler: "indexing.clearIndex",
|
|
640
|
+
config: {
|
|
641
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
method: "GET",
|
|
646
|
+
path: "/index-settings/content-types",
|
|
647
|
+
handler: "indexSettings.getContentTypesAndFields",
|
|
648
|
+
config: {
|
|
649
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
method: "POST",
|
|
654
|
+
path: "/index-settings/save",
|
|
655
|
+
handler: "indexSettings.saveIndexSettings",
|
|
656
|
+
config: {
|
|
657
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
method: "POST",
|
|
662
|
+
path: "/index-settings/apply",
|
|
663
|
+
handler: "indexSettings.applyIndexSettings",
|
|
664
|
+
config: {
|
|
665
|
+
policies: ["admin::isAuthenticatedAdmin"]
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
]
|
|
669
|
+
};
|
|
670
|
+
const routes = {
|
|
671
|
+
"content-api": contentAPIRoutes,
|
|
672
|
+
admin: adminAPIRoutes
|
|
673
|
+
};
|
|
674
|
+
const meilisearchClient = ({ strapi }) => ({
|
|
675
|
+
createClient(config2) {
|
|
676
|
+
if (!config2.host || !config2.apiKey) {
|
|
677
|
+
strapi.log.warn("[meilisearch-plus] MeiliSearch credentials not configured");
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
const client = new MeiliSearch({
|
|
682
|
+
host: config2.host,
|
|
683
|
+
apiKey: config2.apiKey
|
|
684
|
+
});
|
|
685
|
+
strapi.log.info("[meilisearch-plus] MeiliSearch client initialized");
|
|
686
|
+
return client;
|
|
687
|
+
} catch (error) {
|
|
688
|
+
strapi.log.error("[meilisearch-plus] Failed to initialize MeiliSearch client:", error);
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
async testConnection(client) {
|
|
693
|
+
if (!client) return false;
|
|
694
|
+
try {
|
|
695
|
+
await client.health();
|
|
696
|
+
strapi.log.info("[meilisearch-plus] MeiliSearch connection successful");
|
|
697
|
+
return true;
|
|
698
|
+
} catch (error) {
|
|
699
|
+
strapi.log.error("[meilisearch-plus] MeiliSearch connection failed:", error);
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
async getIndexUids(client) {
|
|
704
|
+
if (!client) return [];
|
|
705
|
+
try {
|
|
706
|
+
const response = await client.getIndexes();
|
|
707
|
+
return response.results.map((index2) => index2.uid);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
strapi.log.error("[meilisearch-plus] Failed to get index UIDs:", error);
|
|
710
|
+
return [];
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
async createIndex(client, indexName, primaryKey = "id") {
|
|
714
|
+
if (!client) return null;
|
|
715
|
+
try {
|
|
716
|
+
const index2 = await client.createIndex(indexName, { primaryKey });
|
|
717
|
+
strapi.log.info(`[meilisearch-plus] Created index: ${indexName}`);
|
|
718
|
+
return index2;
|
|
719
|
+
} catch (error) {
|
|
720
|
+
strapi.log.error(`[meilisearch-plus] Failed to create index ${indexName}:`, error);
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
async deleteIndex(client, indexName) {
|
|
725
|
+
if (!client) return false;
|
|
726
|
+
try {
|
|
727
|
+
await client.deleteIndex(indexName);
|
|
728
|
+
strapi.log.info(`[meilisearch-plus] Deleted index: ${indexName}`);
|
|
729
|
+
return true;
|
|
730
|
+
} catch (error) {
|
|
731
|
+
strapi.log.error(`[meilisearch-plus] Failed to delete index ${indexName}:`, error);
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
const meilisearch = ({ strapi }) => ({
|
|
737
|
+
async initializeClient() {
|
|
738
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
739
|
+
const clientService = strapi.plugin("meilisearch-plus").service("meilisearch-client");
|
|
740
|
+
const credentials2 = await storeService.getCredentials();
|
|
741
|
+
const client = clientService.createClient(credentials2);
|
|
742
|
+
if (!client) {
|
|
743
|
+
strapi.log.warn("[meilisearch-plus] No MeiliSearch client available");
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
const isHealthy = await clientService.testConnection(client);
|
|
747
|
+
if (!isHealthy) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
return client;
|
|
751
|
+
},
|
|
752
|
+
// Batch size for MeiliSearch operations
|
|
753
|
+
BATCH_SIZE: 1e3,
|
|
754
|
+
/**
|
|
755
|
+
* Sanitize and transform entries for MeiliSearch
|
|
756
|
+
*/
|
|
757
|
+
async sanitizeEntries({
|
|
758
|
+
contentType,
|
|
759
|
+
entries
|
|
760
|
+
}) {
|
|
761
|
+
return entries.map((entry) => ({
|
|
762
|
+
_contentType: contentType,
|
|
763
|
+
id: entry.id || entry.documentId,
|
|
764
|
+
...entry
|
|
765
|
+
}));
|
|
766
|
+
},
|
|
767
|
+
/**
|
|
768
|
+
* Add all entries for a content type to MeiliSearch (full reindex)
|
|
769
|
+
*/
|
|
770
|
+
async addContentTypeInMeiliSearch({ contentType }) {
|
|
771
|
+
const client = await this.initializeClient();
|
|
772
|
+
if (!client) return [];
|
|
773
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
774
|
+
const contentTypeService = strapi.plugin("meilisearch-plus").service("content-types");
|
|
775
|
+
const finalIndexName = await storeService.getIndexName();
|
|
776
|
+
if (!finalIndexName) {
|
|
777
|
+
strapi.log.warn("[meilisearch-plus] No index name configured");
|
|
778
|
+
return [];
|
|
779
|
+
}
|
|
780
|
+
const index2 = client.index(finalIndexName);
|
|
781
|
+
const contentTypeUid = contentTypeService.getContentTypeUid({ contentType });
|
|
782
|
+
if (!contentTypeUid) {
|
|
783
|
+
const availableUids = Object.keys(strapi.contentTypes);
|
|
784
|
+
strapi.log.error(
|
|
785
|
+
`[meilisearch-plus] Invalid content type: ${contentType}. Available UIDs: ${availableUids.join(", ")}`
|
|
786
|
+
);
|
|
787
|
+
throw new Error(`[meilisearch-plus] Invalid content type: ${contentType}`);
|
|
788
|
+
}
|
|
789
|
+
const documents = await strapi.documents(contentTypeUid).findMany({ limit: -1, status: "published" });
|
|
790
|
+
if (!documents || documents.length === 0) {
|
|
791
|
+
strapi.log.info(`[meilisearch-plus] No documents found for ${contentType}`);
|
|
792
|
+
return [];
|
|
793
|
+
}
|
|
794
|
+
const sanitized = await this.sanitizeEntries({ contentType, entries: documents });
|
|
795
|
+
let taskUids = [];
|
|
796
|
+
for (let i = 0; i < sanitized.length; i += this.BATCH_SIZE) {
|
|
797
|
+
const batch = sanitized.slice(i, i + this.BATCH_SIZE);
|
|
798
|
+
const response = await index2.addDocuments(batch, { primaryKey: "id" });
|
|
799
|
+
strapi.log.info(
|
|
800
|
+
`[meilisearch-plus] Added batch of ${batch.length} documents for ${contentType} (Task uid: ${response.taskUid})`
|
|
801
|
+
);
|
|
802
|
+
taskUids.push(response.taskUid);
|
|
803
|
+
}
|
|
804
|
+
await storeService.addIndexedContentType(contentType);
|
|
805
|
+
return taskUids;
|
|
806
|
+
},
|
|
807
|
+
/**
|
|
808
|
+
* Remove all documents for a content type from MeiliSearch
|
|
809
|
+
*/
|
|
810
|
+
async emptyOrDeleteIndex({ contentType }) {
|
|
811
|
+
const client = await this.initializeClient();
|
|
812
|
+
if (!client) return;
|
|
813
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
814
|
+
const finalIndexName = await storeService.getIndexName();
|
|
815
|
+
if (!finalIndexName) {
|
|
816
|
+
strapi.log.warn("[meilisearch-plus] No index name configured");
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const index2 = client.index(finalIndexName);
|
|
820
|
+
const searchResult = await index2.search("", {
|
|
821
|
+
filter: [`_contentType = "${contentType}"`],
|
|
822
|
+
limit: 1e4
|
|
823
|
+
});
|
|
824
|
+
const ids = searchResult.hits.map((hit) => hit.id);
|
|
825
|
+
if (ids.length > 0) {
|
|
826
|
+
await index2.deleteDocuments(ids);
|
|
827
|
+
strapi.log.info(`[meilisearch-plus] Deleted ${ids.length} documents for ${contentType}`);
|
|
828
|
+
}
|
|
829
|
+
await storeService.removeIndexedContentType(contentType);
|
|
830
|
+
},
|
|
831
|
+
/**
|
|
832
|
+
* Update (reindex) all entries for a content type
|
|
833
|
+
*/
|
|
834
|
+
async updateContentTypeInMeiliSearch({ contentType }) {
|
|
835
|
+
await this.emptyOrDeleteIndex({ contentType });
|
|
836
|
+
return this.addContentTypeInMeiliSearch({ contentType });
|
|
837
|
+
},
|
|
838
|
+
async deleteDocument({ contentType, documentId, indexName = null }) {
|
|
839
|
+
const client = await this.initializeClient();
|
|
840
|
+
if (!client) return;
|
|
841
|
+
try {
|
|
842
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
843
|
+
const finalIndexName = indexName || await storeService.getIndexName();
|
|
844
|
+
if (!finalIndexName) {
|
|
845
|
+
strapi.log.warn("[meilisearch-plus] No index name configured");
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const index2 = client.index(finalIndexName);
|
|
849
|
+
await index2.deleteDocument(documentId);
|
|
850
|
+
strapi.log.debug(`[meilisearch-plus] Deleted document ${documentId} from ${finalIndexName}`);
|
|
851
|
+
} catch (error) {
|
|
852
|
+
strapi.log.error("[meilisearch-plus] Failed to delete document:", error);
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
async deleteAllDocuments({ contentType, indexName = null }) {
|
|
856
|
+
const client = await this.initializeClient();
|
|
857
|
+
if (!client) return;
|
|
858
|
+
try {
|
|
859
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
860
|
+
const finalIndexName = indexName || await storeService.getIndexName();
|
|
861
|
+
if (!finalIndexName) {
|
|
862
|
+
strapi.log.warn("[meilisearch-plus] No index name configured");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const index2 = client.index(finalIndexName);
|
|
866
|
+
if (contentType) {
|
|
867
|
+
const searchResult = await index2.search("", {
|
|
868
|
+
filter: [`_contentType = "${contentType}"`],
|
|
869
|
+
limit: 1e4
|
|
870
|
+
});
|
|
871
|
+
const ids = searchResult.hits.map((hit) => hit.id);
|
|
872
|
+
if (ids.length > 0) {
|
|
873
|
+
await index2.deleteDocuments(ids);
|
|
874
|
+
strapi.log.info(`[meilisearch-plus] Deleted ${ids.length} documents for ${contentType}`);
|
|
875
|
+
} else {
|
|
876
|
+
strapi.log.info(`[meilisearch-plus] No documents found for ${contentType} to delete.`);
|
|
877
|
+
}
|
|
878
|
+
} else {
|
|
879
|
+
await index2.deleteAllDocuments();
|
|
880
|
+
strapi.log.info(`[meilisearch-plus] Deleted all documents in index`);
|
|
881
|
+
}
|
|
882
|
+
} catch (error) {
|
|
883
|
+
strapi.log.error("[meilisearch-plus] Failed to delete all documents:", error);
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
async search({ query, indexName = null, filters = null, limit = 10, offset = 0 }) {
|
|
887
|
+
const client = await this.initializeClient();
|
|
888
|
+
if (!client) return null;
|
|
889
|
+
try {
|
|
890
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
891
|
+
const finalIndexName = indexName || await storeService.getIndexName();
|
|
892
|
+
if (!finalIndexName) {
|
|
893
|
+
strapi.log.warn("[meilisearch-plus] No index name configured");
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
const index2 = client.index(finalIndexName);
|
|
897
|
+
const results = await index2.search(query, {
|
|
898
|
+
filter: filters,
|
|
899
|
+
limit,
|
|
900
|
+
offset
|
|
901
|
+
});
|
|
902
|
+
return results;
|
|
903
|
+
} catch (error) {
|
|
904
|
+
strapi.log.error("[meilisearch-plus] Search failed:", error);
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
async getIndexSettings({ indexName = null }) {
|
|
909
|
+
const client = await this.initializeClient();
|
|
910
|
+
if (!client) return null;
|
|
911
|
+
try {
|
|
912
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
913
|
+
const finalIndexName = indexName || await storeService.getIndexName();
|
|
914
|
+
if (!finalIndexName) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
const index2 = client.index(finalIndexName);
|
|
918
|
+
const settings = await index2.getSettings();
|
|
919
|
+
return settings;
|
|
920
|
+
} catch (error) {
|
|
921
|
+
strapi.log.error("[meilisearch-plus] Failed to get index settings:", error);
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
async updateIndexSettings({ indexName = null, settings }) {
|
|
926
|
+
const client = await this.initializeClient();
|
|
927
|
+
if (!client) return false;
|
|
928
|
+
try {
|
|
929
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
930
|
+
const finalIndexName = indexName || await storeService.getIndexName();
|
|
931
|
+
if (!finalIndexName) {
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
const index2 = client.index(finalIndexName);
|
|
935
|
+
await index2.updateSettings(settings);
|
|
936
|
+
strapi.log.info(`[meilisearch-plus] Updated settings for index ${finalIndexName}`);
|
|
937
|
+
return true;
|
|
938
|
+
} catch (error) {
|
|
939
|
+
strapi.log.error("[meilisearch-plus] Failed to update index settings:", error);
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
},
|
|
943
|
+
async ensureContentTypeFilterable({ indexName = null }) {
|
|
944
|
+
const client = await this.initializeClient();
|
|
945
|
+
if (!client) return false;
|
|
946
|
+
try {
|
|
947
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
948
|
+
const finalIndexName = indexName || await storeService.getIndexName();
|
|
949
|
+
if (!finalIndexName) return false;
|
|
950
|
+
const index2 = client.index(finalIndexName);
|
|
951
|
+
const settings = await index2.getSettings();
|
|
952
|
+
const filterable = settings.filterableAttributes || [];
|
|
953
|
+
if (!filterable.includes("_contentType")) {
|
|
954
|
+
filterable.push("_contentType");
|
|
955
|
+
await index2.updateSettings({ filterableAttributes: filterable });
|
|
956
|
+
strapi.log.info(
|
|
957
|
+
`[meilisearch-plus] Added _contentType to filterableAttributes for index ${finalIndexName}`
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
return true;
|
|
961
|
+
} catch (error) {
|
|
962
|
+
strapi.log.error("[meilisearch-plus] Failed to ensure _contentType is filterable:", error);
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
async syncIndexedCollections() {
|
|
967
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
968
|
+
const client = await this.initializeClient();
|
|
969
|
+
if (!client) {
|
|
970
|
+
strapi.log.debug("[meilisearch-plus] No MeiliSearch client available for sync");
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
const indexName = await storeService.getIndexName();
|
|
975
|
+
if (!indexName) {
|
|
976
|
+
strapi.log.debug("[meilisearch-plus] No index name configured for sync");
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const indexedContentTypes = await storeService.getIndexedContentTypes();
|
|
980
|
+
if (!indexedContentTypes || indexedContentTypes.length === 0) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
const index2 = client.index(indexName);
|
|
984
|
+
for (const contentType of indexedContentTypes) {
|
|
985
|
+
try {
|
|
986
|
+
const searchResult = await index2.search("", {
|
|
987
|
+
filter: [`_contentType = "${contentType}"`],
|
|
988
|
+
limit: 0
|
|
989
|
+
});
|
|
990
|
+
if (searchResult.estimatedTotalHits === 0) {
|
|
991
|
+
strapi.log.debug(
|
|
992
|
+
`[meilisearch-plus] No documents found for ${contentType}, removing from indexed list`
|
|
993
|
+
);
|
|
994
|
+
await storeService.removeIndexedContentType(contentType);
|
|
995
|
+
}
|
|
996
|
+
} catch (e) {
|
|
997
|
+
strapi.log.debug(
|
|
998
|
+
`[meilisearch-plus] Could not search for ${contentType} during sync: ${e.message}`
|
|
999
|
+
);
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
strapi.log.info("[meilisearch-plus] Synced indexed collections");
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
strapi.log.error("[meilisearch-plus] Failed to sync indexed collections:", error);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
const store = ({ strapi }) => {
|
|
1010
|
+
const strapiStore = strapi.store({
|
|
1011
|
+
type: "plugin",
|
|
1012
|
+
name: "meilisearch-plus"
|
|
1013
|
+
});
|
|
1014
|
+
return {
|
|
1015
|
+
async getApiKey() {
|
|
1016
|
+
const credentials2 = await strapiStore.get({ key: "credentials" });
|
|
1017
|
+
return credentials2?.apiKey || "";
|
|
1018
|
+
},
|
|
1019
|
+
async setApiKey(apiKey) {
|
|
1020
|
+
const credentials2 = await strapiStore.get({ key: "credentials" });
|
|
1021
|
+
await strapiStore.set({ key: "credentials", value: { ...credentials2, apiKey } });
|
|
1022
|
+
},
|
|
1023
|
+
async getHost() {
|
|
1024
|
+
const credentials2 = await strapiStore.get({ key: "credentials" });
|
|
1025
|
+
return credentials2?.host || "";
|
|
1026
|
+
},
|
|
1027
|
+
async setHost(host) {
|
|
1028
|
+
const credentials2 = await strapiStore.get({ key: "credentials" });
|
|
1029
|
+
await strapiStore.set({ key: "credentials", value: { ...credentials2, host } });
|
|
1030
|
+
},
|
|
1031
|
+
async getIndexName() {
|
|
1032
|
+
const credentials2 = await strapiStore.get({ key: "credentials" });
|
|
1033
|
+
return credentials2?.indexName || "";
|
|
1034
|
+
},
|
|
1035
|
+
async setIndexName(indexName) {
|
|
1036
|
+
const credentials2 = await strapiStore.get({ key: "credentials" });
|
|
1037
|
+
await strapiStore.set({ key: "credentials", value: { ...credentials2, indexName } });
|
|
1038
|
+
},
|
|
1039
|
+
async getCredentials() {
|
|
1040
|
+
const data = await strapiStore.get({ key: "credentials" });
|
|
1041
|
+
let host = data?.host || "";
|
|
1042
|
+
let apiKey = data?.apiKey || "";
|
|
1043
|
+
let indexName = data?.indexName || "";
|
|
1044
|
+
if (!host || !apiKey) {
|
|
1045
|
+
try {
|
|
1046
|
+
const pluginConfig = strapi.config.get("plugin::meilisearch-plus");
|
|
1047
|
+
if (pluginConfig) {
|
|
1048
|
+
if (!host && pluginConfig.host) {
|
|
1049
|
+
host = pluginConfig.host;
|
|
1050
|
+
}
|
|
1051
|
+
if (!apiKey && pluginConfig.apiKey) {
|
|
1052
|
+
apiKey = pluginConfig.apiKey;
|
|
1053
|
+
}
|
|
1054
|
+
if (!indexName && pluginConfig.post?.indexName) {
|
|
1055
|
+
indexName = pluginConfig.post.indexName;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
strapi.log.debug("[meilisearch-plus] Could not retrieve plugin config");
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return { host, apiKey, indexName };
|
|
1063
|
+
},
|
|
1064
|
+
async syncCredentials() {
|
|
1065
|
+
const pluginConfig = strapi.config.get("plugin::meilisearch-plus");
|
|
1066
|
+
let apiKey = "";
|
|
1067
|
+
let host = "";
|
|
1068
|
+
if (pluginConfig) {
|
|
1069
|
+
apiKey = pluginConfig.apiKey || "";
|
|
1070
|
+
host = pluginConfig.host || "";
|
|
1071
|
+
}
|
|
1072
|
+
const existingApiKey = await this.getApiKey();
|
|
1073
|
+
const existingHost = await this.getHost();
|
|
1074
|
+
if (!existingApiKey && apiKey) {
|
|
1075
|
+
await this.setApiKey(apiKey);
|
|
1076
|
+
}
|
|
1077
|
+
if (!existingHost && host) {
|
|
1078
|
+
await this.setHost(host);
|
|
1079
|
+
}
|
|
1080
|
+
return { apiKey, host };
|
|
1081
|
+
},
|
|
1082
|
+
async setCredentials(credentials2) {
|
|
1083
|
+
await strapiStore.set({ key: "credentials", value: credentials2 });
|
|
1084
|
+
strapi.log.info("[meilisearch-plus] Credentials updated");
|
|
1085
|
+
},
|
|
1086
|
+
async getIndexedContentTypes() {
|
|
1087
|
+
const data = await strapiStore.get({ key: "indexed-content-types" });
|
|
1088
|
+
return data?.contentTypes || [];
|
|
1089
|
+
},
|
|
1090
|
+
async setIndexedContentTypes(contentTypes2) {
|
|
1091
|
+
await strapiStore.set({ key: "indexed-content-types", value: { contentTypes: contentTypes2 } });
|
|
1092
|
+
},
|
|
1093
|
+
async addIndexedContentType(contentType) {
|
|
1094
|
+
const contentTypes2 = await this.getIndexedContentTypes();
|
|
1095
|
+
console.log("Current indexed content types:", contentTypes2);
|
|
1096
|
+
console.log("Adding content type:", contentType);
|
|
1097
|
+
if (!contentTypes2.includes(contentType)) {
|
|
1098
|
+
contentTypes2.push(contentType);
|
|
1099
|
+
await this.setIndexedContentTypes(contentTypes2);
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1102
|
+
async removeIndexedContentType(contentType) {
|
|
1103
|
+
const contentTypes2 = await this.getIndexedContentTypes();
|
|
1104
|
+
const filtered = contentTypes2.filter((ct) => ct !== contentType);
|
|
1105
|
+
await this.setIndexedContentTypes(filtered);
|
|
1106
|
+
},
|
|
1107
|
+
async getListenedContentTypes() {
|
|
1108
|
+
const data = await strapiStore.get({ key: "listened-content-types" });
|
|
1109
|
+
return data?.contentTypes || [];
|
|
1110
|
+
},
|
|
1111
|
+
async setListenedContentTypes(contentTypes2) {
|
|
1112
|
+
await strapiStore.set({ key: "listened-content-types", value: { contentTypes: contentTypes2 } });
|
|
1113
|
+
},
|
|
1114
|
+
async getStoreKey(options) {
|
|
1115
|
+
const data = await strapiStore.get({ key: options.key });
|
|
1116
|
+
return data;
|
|
1117
|
+
},
|
|
1118
|
+
async setStoreKey(options) {
|
|
1119
|
+
await strapiStore.set({ key: options.key, value: options.value });
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
};
|
|
1123
|
+
const lifecycle = ({ strapi }) => ({
|
|
1124
|
+
async subscribeContentType({ contentType }) {
|
|
1125
|
+
const meilisearchService = strapi.plugin("meilisearch-plus").service("meilisearch");
|
|
1126
|
+
strapi.plugin("meilisearch-plus").service("store");
|
|
1127
|
+
strapi.db.lifecycles.subscribe({
|
|
1128
|
+
models: [contentType],
|
|
1129
|
+
async afterCreate(event) {
|
|
1130
|
+
const { result } = event;
|
|
1131
|
+
try {
|
|
1132
|
+
await meilisearchService.indexDocument({ contentType, document: result });
|
|
1133
|
+
strapi.log.debug(`[meilisearch-plus] Indexed document for ${contentType}`);
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
strapi.log.error(
|
|
1136
|
+
`[meilisearch-plus] Failed to index document for ${contentType}:`,
|
|
1137
|
+
error
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
strapi.db.lifecycles.subscribe({
|
|
1143
|
+
models: [contentType],
|
|
1144
|
+
async afterUpdate(event) {
|
|
1145
|
+
const { result } = event;
|
|
1146
|
+
try {
|
|
1147
|
+
await meilisearchService.indexDocument({ contentType, document: result });
|
|
1148
|
+
strapi.log.debug(`[meilisearch-plus] Updated indexed document for ${contentType}`);
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
strapi.log.error(
|
|
1151
|
+
`[meilisearch-plus] Failed to update indexed document for ${contentType}:`,
|
|
1152
|
+
error
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
strapi.db.lifecycles.subscribe({
|
|
1158
|
+
models: [contentType],
|
|
1159
|
+
async afterDelete(event) {
|
|
1160
|
+
const { result } = event;
|
|
1161
|
+
try {
|
|
1162
|
+
await meilisearchService.deleteDocument({ contentType, documentId: result.id });
|
|
1163
|
+
strapi.log.debug(`[meilisearch-plus] Deleted indexed document for ${contentType}`);
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
strapi.log.error(
|
|
1166
|
+
`[meilisearch-plus] Failed to delete indexed document for ${contentType}:`,
|
|
1167
|
+
error
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
strapi.log.info(`[meilisearch-plus] Subscribed to lifecycle events for ${contentType}`);
|
|
1173
|
+
},
|
|
1174
|
+
async unsubscribeContentType({ contentType }) {
|
|
1175
|
+
strapi.log.info(`[meilisearch-plus] Unsubscribed from lifecycle events for ${contentType}`);
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
const contentTypes = ({ strapi }) => ({
|
|
1179
|
+
/**
|
|
1180
|
+
* Get the content type UID from either a UID or a model name
|
|
1181
|
+
*/
|
|
1182
|
+
getContentTypeUid({ contentType }) {
|
|
1183
|
+
const contentTypes2 = strapi.contentTypes;
|
|
1184
|
+
const contentTypeUids = Object.keys(contentTypes2);
|
|
1185
|
+
strapi.log.debug(`[meilisearch-plus] getContentTypeUid: Looking for "${contentType}"`);
|
|
1186
|
+
strapi.log.debug(`[meilisearch-plus] Available UIDs:`, contentTypeUids);
|
|
1187
|
+
if (contentTypeUids.includes(contentType)) {
|
|
1188
|
+
strapi.log.debug(`[meilisearch-plus] Found as UID: ${contentType}`);
|
|
1189
|
+
return contentType;
|
|
1190
|
+
}
|
|
1191
|
+
const withPrefix = `api::${contentType}.${contentType}`;
|
|
1192
|
+
if (contentTypeUids.includes(withPrefix)) {
|
|
1193
|
+
strapi.log.debug(`[meilisearch-plus] Found with prefix: ${withPrefix}`);
|
|
1194
|
+
return withPrefix;
|
|
1195
|
+
}
|
|
1196
|
+
const found = contentTypeUids.find((uid) => {
|
|
1197
|
+
const ctConfig = contentTypes2[uid];
|
|
1198
|
+
const matches = ctConfig.modelName === contentType;
|
|
1199
|
+
if (matches) {
|
|
1200
|
+
strapi.log.debug(`[meilisearch-plus] Found by modelName match: ${uid}`);
|
|
1201
|
+
}
|
|
1202
|
+
return matches;
|
|
1203
|
+
});
|
|
1204
|
+
if (!found) {
|
|
1205
|
+
const fallback = contentTypeUids.find((uid) => uid.split(".").pop() === contentType);
|
|
1206
|
+
if (fallback) {
|
|
1207
|
+
strapi.log.debug(`[meilisearch-plus] Fallback match by last segment: ${fallback}`);
|
|
1208
|
+
return fallback;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (!found) {
|
|
1212
|
+
strapi.log.warn(
|
|
1213
|
+
`[meilisearch-plus] Could not find UID for "${contentType}". Available:`,
|
|
1214
|
+
contentTypeUids
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
return found;
|
|
1218
|
+
},
|
|
1219
|
+
/**
|
|
1220
|
+
* Get the collection/model name from a content type UID or name
|
|
1221
|
+
*/
|
|
1222
|
+
getCollectionName({ contentType }) {
|
|
1223
|
+
const contentTypes2 = strapi.contentTypes;
|
|
1224
|
+
const contentTypeUids = Object.keys(contentTypes2);
|
|
1225
|
+
if (contentTypeUids.includes(contentType)) {
|
|
1226
|
+
return contentTypes2[contentType].modelName;
|
|
1227
|
+
}
|
|
1228
|
+
return contentType;
|
|
1229
|
+
},
|
|
1230
|
+
/**
|
|
1231
|
+
* Count published documents in a content type
|
|
1232
|
+
*/
|
|
1233
|
+
async getDocumentCount({
|
|
1234
|
+
contentType,
|
|
1235
|
+
filters = {},
|
|
1236
|
+
status = "published"
|
|
1237
|
+
}) {
|
|
1238
|
+
const contentTypeUid = this.getContentTypeUid({ contentType });
|
|
1239
|
+
if (!contentTypeUid) {
|
|
1240
|
+
const contentTypes2 = strapi.contentTypes;
|
|
1241
|
+
const availableUids = Object.keys(contentTypes2);
|
|
1242
|
+
const apiContentTypes = availableUids.filter((uid) => uid.startsWith("api::"));
|
|
1243
|
+
strapi.log.warn(
|
|
1244
|
+
`[meilisearch-plus] Could not find UID for "${contentType}". Available API types:`,
|
|
1245
|
+
apiContentTypes
|
|
1246
|
+
);
|
|
1247
|
+
return 0;
|
|
1248
|
+
}
|
|
1249
|
+
try {
|
|
1250
|
+
const count = await strapi.documents(contentTypeUid).count({
|
|
1251
|
+
filters,
|
|
1252
|
+
status
|
|
1253
|
+
});
|
|
1254
|
+
return count || 0;
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
strapi.log.warn(`[meilisearch-plus] getDocumentCount error for ${contentType}:`, error);
|
|
1257
|
+
return 0;
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
/**
|
|
1261
|
+
* Count indexed documents in MeiliSearch for a content type
|
|
1262
|
+
*/
|
|
1263
|
+
async getIndexedDocumentCount({
|
|
1264
|
+
contentType,
|
|
1265
|
+
indexName = null
|
|
1266
|
+
}) {
|
|
1267
|
+
try {
|
|
1268
|
+
const meilisearchService = strapi.plugin("meilisearch-plus").service("meilisearch");
|
|
1269
|
+
const storeService = strapi.plugin("meilisearch-plus").service("store");
|
|
1270
|
+
const finalIndexName = indexName || await storeService.getIndexName();
|
|
1271
|
+
if (!finalIndexName) {
|
|
1272
|
+
strapi.log.debug("[meilisearch-plus] No index name configured");
|
|
1273
|
+
return 0;
|
|
1274
|
+
}
|
|
1275
|
+
const client = await meilisearchService.initializeClient();
|
|
1276
|
+
if (!client) {
|
|
1277
|
+
return 0;
|
|
1278
|
+
}
|
|
1279
|
+
const index2 = client.index(finalIndexName);
|
|
1280
|
+
const result = await index2.search("", {
|
|
1281
|
+
filter: [`_contentType = "${contentType}"`],
|
|
1282
|
+
limit: 0
|
|
1283
|
+
});
|
|
1284
|
+
return result.estimatedTotalHits || 0;
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
if (error?.message?.includes("not found")) {
|
|
1287
|
+
strapi.log.debug(
|
|
1288
|
+
`[meilisearch-plus] Index not found for ${contentType} (this is normal on first setup)`
|
|
1289
|
+
);
|
|
1290
|
+
return 0;
|
|
1291
|
+
}
|
|
1292
|
+
strapi.log.warn(
|
|
1293
|
+
`[meilisearch-plus] getIndexedDocumentCount error for ${contentType}:`,
|
|
1294
|
+
error
|
|
1295
|
+
);
|
|
1296
|
+
return 0;
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
/**
|
|
1300
|
+
* Get sync status for a content type
|
|
1301
|
+
* Returns: total, indexed, syncPercentage
|
|
1302
|
+
*/
|
|
1303
|
+
async getSyncStatus({ contentType }) {
|
|
1304
|
+
const total = await this.getDocumentCount({ contentType });
|
|
1305
|
+
const indexed = await this.getIndexedDocumentCount({ contentType });
|
|
1306
|
+
const syncPercentage = total === 0 ? 0 : Math.round(indexed / total * 100);
|
|
1307
|
+
const isSynced = total === indexed && total > 0;
|
|
1308
|
+
return {
|
|
1309
|
+
total,
|
|
1310
|
+
indexed,
|
|
1311
|
+
syncPercentage,
|
|
1312
|
+
isSynced
|
|
1313
|
+
};
|
|
1314
|
+
},
|
|
1315
|
+
/**
|
|
1316
|
+
* Get sync status for multiple content types
|
|
1317
|
+
*/
|
|
1318
|
+
async getSyncStatusForMultiple({ contentTypes: contentTypes2 }) {
|
|
1319
|
+
const results = {};
|
|
1320
|
+
for (const contentType of contentTypes2) {
|
|
1321
|
+
results[contentType] = await this.getSyncStatus({ contentType });
|
|
1322
|
+
}
|
|
1323
|
+
return results;
|
|
1324
|
+
}
|
|
1325
|
+
});
|
|
1326
|
+
const services = {
|
|
1327
|
+
"meilisearch-client": meilisearchClient,
|
|
1328
|
+
meilisearch,
|
|
1329
|
+
store,
|
|
1330
|
+
lifecycle,
|
|
1331
|
+
"content-types": contentTypes
|
|
1332
|
+
};
|
|
1333
|
+
const index = {
|
|
1334
|
+
register,
|
|
1335
|
+
bootstrap,
|
|
1336
|
+
destroy,
|
|
1337
|
+
config,
|
|
1338
|
+
controllers,
|
|
1339
|
+
routes,
|
|
1340
|
+
services,
|
|
1341
|
+
contentTypes: contentTypes$2,
|
|
1342
|
+
policies,
|
|
1343
|
+
middlewares
|
|
1344
|
+
};
|
|
1345
|
+
export {
|
|
1346
|
+
index as default
|
|
1347
|
+
};
|