@localazy/strapi-plugin 0.1.17 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,386 @@
1
+ "use strict";
2
+
3
+ const {
4
+ findSetupModelByCollectionUid,
5
+ } = require("../utils/transfer-setup-utils");
6
+ const config = require("../config").default;
7
+ const {
8
+ isoLocalazyToStrapi,
9
+ } = require("../utils/iso-locales-utils");
10
+ const shouldSetDownloadedProperty = require("../functions/should-set-downloaded-property");
11
+ const set = require("lodash/set");
12
+ const isEmpty = require("lodash/isEmpty");
13
+ const RequestInitiatorHelper = require('../utils/request-initiator-helper');
14
+ const PluginSettingsServiceHelper = require('../services/helpers/plugin-settings-service-helper');
15
+ const { DOWNLOAD_EVENT, DOWNLOAD_FINISHED_EVENT } = require('../constants/events');
16
+
17
+ const getFilteredLanguagesCodesForDownload = async (languagesCodes) => {
18
+ const pluginSettingsServiceHelper = new PluginSettingsServiceHelper(strapi);
19
+ const requestInitiatorHelper = new RequestInitiatorHelper(strapi);
20
+ await pluginSettingsServiceHelper.setup();
21
+ let localLanguagesCodes;
22
+ if (requestInitiatorHelper.isInitiatedByLocalazyWebhook()) {
23
+ // called by a webhook
24
+ localLanguagesCodes = pluginSettingsServiceHelper.getWebhookLanguagesCodes();
25
+ } else if (requestInitiatorHelper.isInitiatedByLocalazyPluginUI()) {
26
+ // called by a user from the UI
27
+ localLanguagesCodes = pluginSettingsServiceHelper.getUiLanguagesCodes();
28
+ } else {
29
+ strapi.log.warn("Called by an unknown initiator.");
30
+ return languagesCodes;
31
+ }
32
+
33
+ if (isEmpty(localLanguagesCodes)) {
34
+ return languagesCodes;
35
+ }
36
+
37
+ return languagesCodes.filter((code) => localLanguagesCodes.includes(code));
38
+ };
39
+
40
+ module.exports = ({ strapi }) => ({
41
+ async download(JobNotificationService, ctx) {
42
+ console.time("download");
43
+ strapi.log.info("Download started");
44
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
45
+ message: 'Download started',
46
+ });
47
+
48
+ let success = true;
49
+
50
+ // Strapi service
51
+ const StrapiService = strapi
52
+ .plugin("localazy")
53
+ .service("strapiService");
54
+
55
+ // Strapi i18n Service
56
+ const StrapiI18nService = strapi
57
+ .plugin("localazy")
58
+ .service("strapiI18nService");
59
+
60
+ const LocalazyUserService = strapi
61
+ .plugin("localazy")
62
+ .service("localazyUserService");
63
+ const LocalazyPubApiService = strapi
64
+ .plugin("localazy")
65
+ .service("localazyPubApiService");
66
+ const LocalazyDownloadService = strapi
67
+ .plugin("localazy")
68
+ .service("localazyDownloadService");
69
+ const strapiLocalazyI18nService = strapi
70
+ .plugin("localazy")
71
+ .service("strapiLocalazyI18nService");
72
+
73
+ const contentTransferSetup = await strapi
74
+ .plugin("localazy")
75
+ .service("pluginSettingsService")
76
+ .getContentTransferSetup();
77
+
78
+ const user = await LocalazyUserService.getUser();
79
+
80
+ /**
81
+ * Get Strapi File
82
+ */
83
+ const strapiFile = await LocalazyPubApiService.getStrapiFile(
84
+ user.project.id
85
+ );
86
+ if (!strapiFile) {
87
+ success = false;
88
+ const message = `File ${config.LOCALAZY_DEFAULT_FILE_NAME} not found`;
89
+ strapi.log.error(message);
90
+ await JobNotificationService.emit(DOWNLOAD_FINISHED_EVENT, {
91
+ success,
92
+ message,
93
+ });
94
+ return;
95
+ }
96
+
97
+ /**
98
+ * Get Localazy Project (incl. languages)
99
+ */
100
+ const project = await LocalazyPubApiService.getProject(
101
+ user.project.id,
102
+ false,
103
+ true
104
+ );
105
+ if (!project) {
106
+ success = false;
107
+ const message = `Project ${user.project.id} not found`;
108
+ strapi.log.error(message);
109
+ await JobNotificationService.emit(DOWNLOAD_FINISHED_EVENT, {
110
+ success,
111
+ message,
112
+ });
113
+ return;
114
+ }
115
+
116
+ /**
117
+ * Get Localazy project languages
118
+ */
119
+ const projectLanguages = project.languages;
120
+ const projectSourceLanguageId = project.sourceLanguage;
121
+ const sourceLanguage = project.languages.find(
122
+ (language) => language.id === projectSourceLanguageId
123
+ );
124
+
125
+ if (!sourceLanguage) {
126
+ success = false;
127
+ const message = "Source language not found";
128
+ strapi.log.error(message);
129
+ await JobNotificationService.emit(DOWNLOAD_FINISHED_EVENT, {
130
+ success,
131
+ message,
132
+ });
133
+ return;
134
+ }
135
+
136
+ const projectSourceLanguageCode = sourceLanguage.code;
137
+ const projectLanguagesWithoutSourceLanguage = projectLanguages.filter(
138
+ (language) => language.code !== projectSourceLanguageCode
139
+ );
140
+
141
+ if (!projectLanguagesWithoutSourceLanguage.length) {
142
+ const message = "Your Localazy project is not translated to other languages.";
143
+ strapi.log.info(message);
144
+ await JobNotificationService.emit(DOWNLOAD_FINISHED_EVENT, {
145
+ success,
146
+ message,
147
+ });
148
+ return;
149
+ }
150
+
151
+ /**
152
+ * Get project languages codes
153
+ */
154
+ let languagesCodes = projectLanguagesWithoutSourceLanguage.map(
155
+ (language) => language.code
156
+ );
157
+ // process filtered languages only / keep all if empty!
158
+ languagesCodes = await getFilteredLanguagesCodesForDownload(languagesCodes);
159
+
160
+ /**
161
+ * Iterate over languages and create the ones that are not present in Strapi
162
+ */
163
+ const strapiUnsupportedLanguages = []; // do not iterate over these languages later
164
+ const strapiLocales = await StrapiI18nService.getLocales(ctx);
165
+ for (const isoLocalazy of languagesCodes) {
166
+ try {
167
+ const isoStrapi = isoLocalazyToStrapi(isoLocalazy);
168
+ const strapiLocale = strapiLocales.find(
169
+ (locale) => locale.code === isoStrapi
170
+ );
171
+ // skip creating locale if it already exists
172
+ if (strapiLocale) {
173
+ strapi.log.info(`The Localazy locale ${isoLocalazy} already exists.`);
174
+ continue;
175
+ }
176
+
177
+ const newLocaleCode = await StrapiI18nService.createStrapiLocale(
178
+ ctx,
179
+ isoLocalazy
180
+ );
181
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
182
+ message: `Created locale ${newLocaleCode}`,
183
+ });
184
+ } catch (e) {
185
+ strapi.log.error(e.message);
186
+ if (e.name === "ValidationError") {
187
+ // store unsupported language code
188
+ strapiUnsupportedLanguages.push(isoLocalazy);
189
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
190
+ message: `Language ${isoLocalazy} is not supported by Strapi`,
191
+ });
192
+ } else {
193
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
194
+ message: e.message,
195
+ });
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Download all keys for Strapi-supported languages
202
+ */
203
+ const supportedLanguages = languagesCodes.filter(
204
+ (languageCode) => !strapiUnsupportedLanguages.includes(languageCode)
205
+ );
206
+ const localazyContent = {};
207
+ for (const isoLocalazy of supportedLanguages) {
208
+ const isoStrapi = isoLocalazyToStrapi(isoLocalazy);
209
+ const result = await LocalazyDownloadService.download({
210
+ projectId: user.project.id,
211
+ fileId: strapiFile.id,
212
+ lang: isoStrapi,
213
+ });
214
+ if (!result.success) {
215
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
216
+ message: result.message,
217
+ });
218
+ }
219
+ const langKeys = result.data;
220
+ localazyContent[isoLocalazy] = langKeys;
221
+ }
222
+
223
+ /**
224
+ * Parse Localazy content
225
+ */
226
+ const parsedLocalazyContent = {};
227
+ const strapiContentTypesModels = await StrapiService.getModels();
228
+ for (const [isoLocalazy, keys] of Object.entries(localazyContent)) {
229
+ const isoStrapi = isoLocalazyToStrapi(isoLocalazy);
230
+ if (!isoStrapi) {
231
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
232
+ message: `Language ${isoLocalazy} is not supported by Strapi`,
233
+ });
234
+ continue;
235
+ }
236
+
237
+ for (const localazyEntry of keys) {
238
+ const key = localazyEntry.key[0];
239
+ const value = localazyEntry.value;
240
+
241
+ const parsedKey = StrapiI18nService.parseLocalazyKey(key);
242
+
243
+ const modelContentTransferSetup = findSetupModelByCollectionUid(
244
+ contentTransferSetup,
245
+ strapiContentTypesModels,
246
+ parsedKey.uid
247
+ );
248
+
249
+ if (typeof modelContentTransferSetup !== "undefined" && shouldSetDownloadedProperty(modelContentTransferSetup, parsedKey.rest)) {
250
+ const parsedKeyRestWithoutComponents = parsedKey.rest;
251
+ const setKey = [
252
+ isoStrapi,
253
+ parsedKey.uid,
254
+ parsedKey.id,
255
+ ...parsedKeyRestWithoutComponents,
256
+ ];
257
+
258
+ set(parsedLocalazyContent, setKey, value);
259
+ }
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Iterate over parsed Localazy content and insert/update content in Strapi
265
+ */
266
+ for (const [isoStrapi, contentTypes] of Object.entries(
267
+ parsedLocalazyContent
268
+ )) {
269
+ for (const [uid, models] of Object.entries(contentTypes)) {
270
+ /**
271
+ * Model could not exist anymore in Strapi
272
+ */
273
+ const strapiContentTypeModel = strapiContentTypesModels.find(
274
+ (model) => model.uid === uid
275
+ );
276
+ if (!strapiContentTypeModel) {
277
+ strapi.log.info(
278
+ `Model ${uid} not found in Strapi, it has been deleted, skipping...`
279
+ );
280
+ continue;
281
+ }
282
+
283
+ for (const [id, translatedModel] of Object.entries(models)) {
284
+ try {
285
+ /**
286
+ * Get original source language entry
287
+ */
288
+ const baseEntry = await strapi.entityService.findOne(uid, id, {
289
+ populate: "deep",
290
+ });
291
+
292
+ if (isEmpty(baseEntry)) {
293
+ const message = `Source language entry ${uid}[${id}] does not exist anymore, skipping...`;
294
+ throw new Error(message);
295
+ }
296
+
297
+ /**
298
+ * Get current language entry
299
+ */
300
+ const baseEntryLocalizations = baseEntry.localizations;
301
+
302
+ if (!Array.isArray(baseEntryLocalizations)) {
303
+ throw new Error(
304
+ `Content type ${uid} does not support localizations, skipping...`
305
+ );
306
+ }
307
+
308
+ const baseEntryCurrentLanguageLocalizationInfo =
309
+ baseEntryLocalizations.find(
310
+ (localization) => localization.locale === isoStrapi
311
+ );
312
+
313
+ if (!baseEntryCurrentLanguageLocalizationInfo) {
314
+ // create new entry
315
+ try {
316
+ const createdEntry = await strapiLocalazyI18nService.createEntry(
317
+ ctx,
318
+ uid,
319
+ strapiContentTypesModels,
320
+ translatedModel,
321
+ baseEntry,
322
+ isoStrapi,
323
+ );
324
+
325
+ const message = `Created new entry ${uid}[${createdEntry.id}] in language ${isoStrapi}`;
326
+ strapi.log.info(message);
327
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
328
+ message,
329
+ });
330
+ } catch (e) {
331
+ success = false;
332
+ strapi.log.error(e.message);
333
+ strapi.log.error(JSON.stringify(e.details?.errors || {}));
334
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
335
+ message: `Cannot create an entry in ${isoStrapi} for ${uid}[${baseEntry.id}]: ${e.message}`,
336
+ });
337
+ }
338
+ } else {
339
+ // update existing entry
340
+ try {
341
+ const localizedEntryId =
342
+ baseEntryCurrentLanguageLocalizationInfo.id;
343
+
344
+ const updatedEntry = await strapiLocalazyI18nService.updateEntry(
345
+ uid,
346
+ localizedEntryId,
347
+ strapiContentTypesModels,
348
+ translatedModel,
349
+ baseEntry,
350
+ isoStrapi,
351
+ );
352
+
353
+ const message = `Updated ${uid}[${updatedEntry.id}] (${isoStrapi})`;
354
+ strapi.log.info(message);
355
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
356
+ message,
357
+ });
358
+ } catch (e) {
359
+ success = false;
360
+ strapi.log.error(e.message);
361
+ strapi.log.error(JSON.stringify(e.details?.errors || {}));
362
+ await JobNotificationService.emit(DOWNLOAD_EVENT, {
363
+ message: `Cannot update an ${uid}[${baseEntryCurrentLanguageLocalizationInfo.id}] (${isoStrapi}): ${e.message}`,
364
+ });
365
+ }
366
+ }
367
+ } catch (e) {
368
+ success = false;
369
+ strapi.log.error(e.message);
370
+ await JobNotificationService.emit(DOWNLOAD_FINISHED_EVENT, {
371
+ success,
372
+ message: `An error occured while processing download: ${e.message}`,
373
+ });
374
+ }
375
+ }
376
+ }
377
+ }
378
+
379
+ strapi.log.info("Download finished in");
380
+ await JobNotificationService.emit(DOWNLOAD_FINISHED_EVENT, {
381
+ success,
382
+ message: 'Download finished',
383
+ });
384
+ console.timeEnd("download");
385
+ },
386
+ });
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+
3
+ const flattenObject = require("../utils/flatten-object");
4
+ const {
5
+ getCollectionsNames,
6
+ findSetupModelByCollectionName,
7
+ isCollectionTransferEnabled,
8
+ getPickPathsWithComponents,
9
+ } = require("../utils/transfer-setup-utils");
10
+ const pickEntries = require("../utils/pick-entries");
11
+ const config = require("../config").default;
12
+ const {
13
+ isoStrapiToLocalazy,
14
+ } = require("../utils/iso-locales-utils");
15
+ const omitDeep = require("../utils/omit-deep");
16
+ const { UPLOAD_EVENT, UPLOAD_FINISHED_EVENT } = require('../constants/events');
17
+
18
+ module.exports = ({ strapi }) => ({
19
+ async upload(JobNotificationService, ctx) {
20
+ console.time("upload");
21
+ await JobNotificationService.emit(UPLOAD_EVENT, {
22
+ message: 'Upload started',
23
+ });
24
+
25
+ let success = true;
26
+
27
+ // Strapi Service
28
+ const StrapiService = strapi
29
+ .plugin("localazy")
30
+ .service("strapiService");
31
+
32
+ // Strapi i18n Service
33
+ const StrapiI18nService = strapi
34
+ .plugin("localazy")
35
+ .service("strapiI18nService");
36
+
37
+ // Localazy Upload Service
38
+ const LocalazyUploadService = strapi
39
+ .plugin("localazy")
40
+ .service("localazyUploadService");
41
+
42
+ // get content transfer setup
43
+ const contentTransferSetup = await strapi
44
+ .plugin("localazy")
45
+ .service("pluginSettingsService")
46
+ .getContentTransferSetup();
47
+
48
+ if (!contentTransferSetup.has_setup) {
49
+ const message = "Content transfer setup is not set up.";
50
+ success = false;
51
+ await JobNotificationService.emit(UPLOAD_FINISHED_EVENT, {
52
+ success,
53
+ message,
54
+ });
55
+ strapi.log.info(message);
56
+ return;
57
+ }
58
+
59
+ const { setup } = contentTransferSetup;
60
+ const collectionsNames = getCollectionsNames(setup);
61
+ const models = await StrapiService.getModels();
62
+ // flatten Strapi content
63
+ let flattenContent = {};
64
+
65
+ for (const collectionName of collectionsNames) {
66
+ const currentModel = models.find(
67
+ (model) => model.collectionName === collectionName
68
+ );
69
+ const modelUid = currentModel.uid;
70
+ if (!currentModel) {
71
+ strapi.log.warn(`Model with uid ${modelUid} is not found.`);
72
+ continue;
73
+ }
74
+
75
+ const transferSetupModel = findSetupModelByCollectionName(
76
+ setup,
77
+ collectionName
78
+ );
79
+
80
+ if (!isCollectionTransferEnabled(setup, collectionName)) {
81
+ const message = `Collection ${collectionName} transfer is disabled.`;
82
+ await JobNotificationService.emit(UPLOAD_EVENT, {
83
+ message,
84
+ });
85
+ strapi.log.info(message);
86
+ continue;
87
+ }
88
+
89
+ // get only enabled fields paths
90
+ const currentTransferSetupModel = transferSetupModel[collectionName];
91
+ const pickPaths = getPickPathsWithComponents(currentTransferSetupModel);
92
+ if (!pickPaths.length) {
93
+ const message = `No fields for collection ${collectionName} transfer are enabled.`;
94
+ await JobNotificationService.emit(UPLOAD_EVENT, {
95
+ message,
96
+ });
97
+ strapi.log.warn(message);
98
+ continue;
99
+ }
100
+ const pickPathsWithUid = pickPaths.map(
101
+ (pickPath) => `${modelUid}.${pickPath}`
102
+ );
103
+
104
+ let entries = await strapi.entityService.findMany(modelUid, {
105
+ populate: "deep",
106
+ });
107
+ entries = omitDeep(entries, [
108
+ // "__component",
109
+ "locale",
110
+ "localizations",
111
+ "createdAt",
112
+ "createdBy",
113
+ "updatedAt",
114
+ "updatedBy",
115
+ "publishedAt",
116
+ ]);
117
+
118
+ if (!entries) {
119
+ strapi.log.info(`No entries found for model ${modelUid}`);
120
+ continue;
121
+ }
122
+
123
+ if (!Array.isArray(entries)) {
124
+ entries = [entries];
125
+ }
126
+
127
+ entries.forEach((entry) => {
128
+ const flatten = flattenObject({
129
+ [modelUid]: entry,
130
+ });
131
+ // get only enabled fields; "__component" will be filtered out inside of the function
132
+ const pickedFlatten = pickEntries(flatten, pickPathsWithUid);
133
+
134
+ flattenContent = {
135
+ ...flattenContent,
136
+ ...pickedFlatten,
137
+ };
138
+ });
139
+ }
140
+
141
+ // get Strapi default language and convert it to Localazy language code
142
+ const strapiLocales = await StrapiI18nService.getLocales(ctx);
143
+ const defaultLocale = strapiLocales.find((locale) => locale.isDefault);
144
+
145
+ const locale = defaultLocale
146
+ ? isoStrapiToLocalazy(defaultLocale.code)
147
+ : config.LOCALAZY_DEFAULT_LOCALE;
148
+
149
+ const chunks = LocalazyUploadService.splitToChunks(flattenContent);
150
+ const importFile = LocalazyUploadService.createImportFileRepresentation(
151
+ config.LOCALAZY_DEFAULT_FILE_NAME,
152
+ config.LOCALAZY_DEFAULT_FILE_PATH,
153
+ config.LOCALAZY_DEFAULT_FILE_EXTENSION,
154
+ locale,
155
+ chunks
156
+ );
157
+ // Use `deprecate: "file"` if there is one chunk of transferred data only!
158
+ const hasMoreTransferFilesChunks = importFile.length > 1;
159
+ const uploadConfig = !hasMoreTransferFilesChunks ? { deprecate: "file" } : {};
160
+ await JobNotificationService.emit(UPLOAD_EVENT, {
161
+ message: "Uploading collections to Localazy...",
162
+ });
163
+ await LocalazyUploadService.upload(
164
+ importFile,
165
+ uploadConfig
166
+ );
167
+ strapi.log.info("Upload finished in");
168
+ await JobNotificationService.emit(UPLOAD_FINISHED_EVENT, {
169
+ success,
170
+ message: "Upload finished",
171
+ });
172
+ console.timeEnd("upload");
173
+ },
174
+ });
@@ -0,0 +1,3 @@
1
+ const generateRandomId = (length = 16) => `_${Math.random().toString(36).substr(2, length)}`;
2
+
3
+ module.exports = generateRandomId;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const isDynamicZoneKey = (key) => {
4
- const matches = key.match(/\[\d+;\w+\./g);
4
+ const matches = key.match(/\[\d+;[\w-]+\./g);
5
5
  return matches !== null;
6
6
  };
7
7
 
@@ -20,7 +20,7 @@ const pickEntries = (flatten, pickPaths) => {
20
20
  let filteredKey = key;
21
21
  filteredKey = filteredKey.replace(/\[\d+\]/g, "");
22
22
  if (isDynamicZoneKey(key)) {
23
- filteredKey = filteredKey.replace(/\[\d+;\w+\./g, `[`);
23
+ filteredKey = filteredKey.replace(/\[\d+;[\w-]+\./g, `[`);
24
24
  }
25
25
  if (mappedPickPaths.includes(filteredKey)) {
26
26
  pickedEntries[key] = flatten[key];