@localazy/strapi-plugin 0.1.10 → 0.1.11-rc.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.
Files changed (59) hide show
  1. package/.eslintrc +6 -2
  2. package/admin/src/modules/@common/models/supported-content-type-fields.js +8 -0
  3. package/admin/src/modules/plugin-settings/components/ContentTransferSetup/index.js +5 -0
  4. package/admin/src/modules/plugin-settings/components/Tree/index.js +22 -5
  5. package/admin/src/modules/plugin-settings/functions/build-content-transfer-setup-schema.js +1 -1
  6. package/admin/src/modules/plugin-settings/functions/has-model-changed.js +1 -1
  7. package/admin/src/modules/plugin-settings/locale/en.js +1 -1
  8. package/admin/src/modules/plugin-settings/utils/get-filtered-models-schemas.js +1 -7
  9. package/admin/src/modules/plugin-settings/utils/get-models-tree.js +21 -3
  10. package/package.json +4 -4
  11. package/server/bootstrap.js +15 -1
  12. package/server/controllers/localazy-transfer-controller.js +67 -52
  13. package/server/functions/should-set-downloaded-property.js +61 -0
  14. package/server/models/supported-content-type-fields.js +8 -0
  15. package/server/services/index.js +2 -0
  16. package/server/services/strapi-i18n-service.js +5 -4
  17. package/server/services/strapi-localazy-i18n-service.js +104 -0
  18. package/server/tests/fixtures/parsed-localazy-entry-to-create-entry-cookbook.js +5761 -0
  19. package/server/tests/fixtures/parsed-localazy-entry-to-create-entry-lesson-dz.js +392 -0
  20. package/server/tests/fixtures/parsed-localazy-entry-to-create-entry-restaurant-simple-menu.js +536 -0
  21. package/server/tests/fixtures/parsed-localazy-entry-to-create-entry-sample-json-dz.js +4675 -0
  22. package/server/tests/fixtures/parsed-localazy-entry-to-update-entry.js +35157 -0
  23. package/server/tests/fixtures/populate-create-update-entry-with-base-entry-cookbook-empty-recipes.js +202 -0
  24. package/server/tests/fixtures/populate-create-update-entry-with-base-entry-cookbook.js +2700 -0
  25. package/server/tests/fixtures/populate-create-update-entry-with-base-entry-lesson-dz.js +309 -0
  26. package/server/tests/fixtures/populate-create-update-entry-with-base-entry-restaurant-simple-flat-simple.js +47 -0
  27. package/server/tests/fixtures/populate-create-update-entry-with-base-entry-restaurant-simple-flat-with-media.js +169 -0
  28. package/server/tests/fixtures/populate-create-update-entry-with-base-entry-restaurant-simple-menu-empty.js +3003 -0
  29. package/server/tests/fixtures/populate-create-update-entry-with-base-entry-restaurant-simple-menu-with-media.js +2057 -0
  30. package/server/tests/fixtures/populate-create-update-entry-with-base-entry-sample-json-dz.js +884 -0
  31. package/server/tests/fixtures/populate-create-update-entry-with-base-entry-sample-json.js +46 -0
  32. package/server/tests/fixtures/strapi-plugin-all-models.js +2048 -0
  33. package/server/utils/__tests__/get-models-tree.test.js +1 -1
  34. package/server/utils/__tests__/parsed-localazy-entry-to-create-entry.test.js +237 -91
  35. package/server/utils/__tests__/parsed-localazy-entry-to-update-entry.test.js +417 -101
  36. package/server/utils/__tests__/pick-entries.test.js +936 -56
  37. package/server/utils/__tests__/populate-create-update-entry-with-base-entry.test.js +1139 -0
  38. package/server/utils/build-populate.js +5 -0
  39. package/server/utils/get-full-populate-object.js +56 -0
  40. package/server/utils/get-models-tree.js +3 -1
  41. package/server/utils/model-utils.js +43 -0
  42. package/server/utils/omit-deep.js +7 -0
  43. package/server/utils/parsed-localazy-entry-to-create-entry.js +58 -21
  44. package/server/utils/parsed-localazy-entry-to-update-entry.js +44 -4
  45. package/server/utils/pick-deep.js +22 -0
  46. package/server/utils/pick-entries.js +90 -5
  47. package/server/utils/populate-create-update-entry-with-base-entry.js +270 -0
  48. package/server/utils/transfer-setup-utils.js +14 -0
  49. package/server/tests/fixtures/all-models-with-multimedia.js +0 -1064
  50. package/server/tests/fixtures/collection-flattened.js +0 -76
  51. package/server/tests/fixtures/restaurant-flattened.js +0 -46
  52. package/server/tests/fixtures/restaurant-model.js +0 -230
  53. package/server/tests/fixtures/restaurants-entries.js +0 -152
  54. package/server/tests/fixtures/restaurants-pick-paths.js +0 -15
  55. package/server/tests/fixtures/strapi-book-with-multimedia-components-cs.js +0 -31
  56. package/server/tests/fixtures/stringified-book-cs.js +0 -3
  57. package/server/tests/fixtures/stringified-book-with-multimedia-components-cs.js +0 -3
  58. package/server/tests/fixtures/stringified-restaurant-cs.js +0 -3
  59. package/server/tests/fixtures/stringified-restaurant-de.js +0 -5
@@ -30,6 +30,11 @@ const buildPopulate = (models, modelUid) => {
30
30
  : `${attributeName}`;
31
31
  const innerModelUid = attribute.component;
32
32
  buildPopulateStep(models, innerModelUid, newPrefix);
33
+ } else if (attribute.type === "dynamiczone") {
34
+ const key = prefix
35
+ ? `${prefix}.${attributeName}.populate`
36
+ : `${attributeName}.populate`;
37
+ keys.push(key);
33
38
  }
34
39
  }
35
40
 
@@ -0,0 +1,56 @@
1
+ // is taken from
2
+ // https://github.com/Barelydead/strapi-plugin-populate-deep
3
+ // based on the issue
4
+ // https://github.com/strapi/strapi/issues/11836
5
+
6
+ const isEmpty = require("lodash/isEmpty");
7
+ const merge = require("lodash/merge");
8
+
9
+ const getModelPopulationAttributes = (model) => {
10
+ if (model.uid === "plugin::upload.file") {
11
+ const { related, ...attributes } = model.attributes;
12
+ return attributes;
13
+ }
14
+
15
+ return model.attributes;
16
+ };
17
+
18
+ const getFullPopulateObject = (modelUid, maxDepth = 20) => {
19
+ if (maxDepth <= 1) {
20
+ return true;
21
+ }
22
+ if (modelUid === "admin::user") {
23
+ return undefined;
24
+ }
25
+
26
+ const populate = {};
27
+ const model = strapi.getModel(modelUid);
28
+ for (const [key, value] of Object.entries(
29
+ getModelPopulationAttributes(model)
30
+ )) {
31
+ if (value) {
32
+ if (value.type === "component") {
33
+ populate[key] = getFullPopulateObject(value.component, maxDepth - 1);
34
+ } else if (value.type === "dynamiczone") {
35
+ const dynamicPopulate = value.components.reduce((prev, cur) => {
36
+ const curPopulate = getFullPopulateObject(cur, maxDepth - 1);
37
+ return curPopulate === true ? prev : merge(prev, curPopulate);
38
+ }, {});
39
+ populate[key] = isEmpty(dynamicPopulate) ? true : dynamicPopulate;
40
+ } else if (value.type === "relation") {
41
+ const relationPopulate = getFullPopulateObject(
42
+ value.target,
43
+ maxDepth - 1
44
+ );
45
+ if (relationPopulate) {
46
+ populate[key] = relationPopulate;
47
+ }
48
+ } else if (value.type === "media") {
49
+ populate[key] = true;
50
+ }
51
+ }
52
+ }
53
+ return isEmpty(populate) ? true : { populate };
54
+ };
55
+
56
+ module.exports = getFullPopulateObject;
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
 
3
+ const SUPPORTED_CONTENT_TYPE_FIELDS = require("../models/supported-content-type-fields");
4
+
3
5
  const getModelTree = (attributes, models) => {
4
6
  const currentModelTree = {};
5
7
  for (const attribute in attributes) {
@@ -12,7 +14,7 @@ const getModelTree = (attributes, models) => {
12
14
  ...getModelTree(nestedModel.__schema__.attributes, models),
13
15
  };
14
16
  } else if (
15
- ["string", "text", "richtext"].includes(attributes[attribute].type)
17
+ SUPPORTED_CONTENT_TYPE_FIELDS.includes(attributes[attribute].type)
16
18
  ) {
17
19
  currentModelTree[attribute] = false;
18
20
  } else {
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ const isEmpty = require("lodash/isEmpty");
4
+
5
+ const getAttribute = (model, attribute) => {
6
+ if (isEmpty(model)) {
7
+ return undefined;
8
+ }
9
+
10
+ const attributeObj = model.attributes[attribute];
11
+ return attributeObj;
12
+ };
13
+
14
+ const isComponent = (attributeObj) => {
15
+ return attributeObj.type === "component";
16
+ };
17
+
18
+ const isDynamicZone = (attributeObj) => {
19
+ return attributeObj.type === "dynamiczone";
20
+ }
21
+
22
+ const isRepeatable = (attributeObj) => {
23
+ return isComponent(attributeObj) && !!attributeObj.repeatable;
24
+ };
25
+
26
+ const isRelation = (attributeObj) => {
27
+ return attributeObj.type === "relation"
28
+ && attributeObj.target !== "plugin::upload.file";
29
+ };
30
+
31
+ const findModel = (models, uid) => {
32
+ const model = models.find((model) => model.uid === uid);
33
+ return model;
34
+ };
35
+
36
+ module.exports = {
37
+ getAttribute,
38
+ isComponent,
39
+ isDynamicZone,
40
+ isRepeatable,
41
+ isRelation,
42
+ findModel,
43
+ };
@@ -11,6 +11,13 @@ function omitDeep(collection, excludeKeys) {
11
11
  }
12
12
  }
13
13
 
14
+ if (Array.isArray(collection)) {
15
+ const clonedData = [];
16
+ for (const collectionItem of collection) {
17
+ clonedData.push(cloneDeepWith(collectionItem, omitFn));
18
+ }
19
+ return clonedData;
20
+ }
14
21
  return cloneDeepWith(collection, omitFn);
15
22
  }
16
23
 
@@ -3,24 +3,7 @@
3
3
  const set = require("lodash/set");
4
4
  const get = require("lodash/get");
5
5
  const resetArrayKeysDeep = require("./reset-array-keys-deep");
6
-
7
- const getAttribute = (model, attribute) => {
8
- const attributeObj = model.attributes[attribute];
9
- return attributeObj;
10
- };
11
-
12
- const isComponent = (attributeObj) => {
13
- return attributeObj.type === "component";
14
- };
15
-
16
- const isRepeatable = (attributeObj) => {
17
- return isComponent(attributeObj) && !!attributeObj.repeatable;
18
- };
19
-
20
- const findModel = (models, uid) => {
21
- const model = models.find((model) => model.uid === uid);
22
- return model;
23
- };
6
+ const { getAttribute, isComponent, isDynamicZone, isRepeatable, findModel } = require("./model-utils");
24
7
 
25
8
  const parsedLocalazyEntryToCreateEntry = (
26
9
  models,
@@ -31,6 +14,7 @@ const parsedLocalazyEntryToCreateEntry = (
31
14
  ) => {
32
15
  const createEntry = {};
33
16
  const repeatableComponentsKeystoFilter = [];
17
+ const dynamicZoneComponentKeys = [];
34
18
 
35
19
  const toCreateEntry = (
36
20
  entry,
@@ -40,7 +24,8 @@ const parsedLocalazyEntryToCreateEntry = (
40
24
  key = "",
41
25
  prefix = "",
42
26
  component = "",
43
- isRepeatableComponent = false
27
+ isRepeatableComponent = false,
28
+ isDZ = false,
44
29
  ) => {
45
30
  if (Array.isArray(entry)) {
46
31
  // is array
@@ -64,6 +49,33 @@ const parsedLocalazyEntryToCreateEntry = (
64
49
  if (component && !isRepeatableComponent) {
65
50
  toCreateEntry(value, model, baseEntry, `${prefix}`, `${prefix}`);
66
51
  }
52
+
53
+ if (!component && isRepeatableComponent) {
54
+ // used for indices filtering in dynamic zones
55
+ if (!repeatableComponentsKeystoFilter.includes(prefix)) {
56
+ repeatableComponentsKeystoFilter.push(prefix);
57
+ }
58
+ const baseEntryRepeatableItemId = parseInt(baseEntryItemId);
59
+ const baseEntryRepeateableGroup = get(baseEntry, prefix);
60
+ if (baseEntryRepeateableGroup !== undefined) {
61
+ const localizedEntryRepeatableItemPosition = baseEntryRepeateableGroup.findIndex((repeatableItem) => !!repeatableItem && repeatableItem.id === baseEntryRepeatableItemId);
62
+ if (localizedEntryRepeatableItemPosition > -1) {
63
+ const dzEntry = get(baseEntry, prefix).find((v) => v.id === baseEntryRepeatableItemId);
64
+ const dzEntryComponent = dzEntry.__component;
65
+ const dzEntryComponentModel = findModel(models, dzEntryComponent);
66
+ toCreateEntry(
67
+ value,
68
+ dzEntryComponentModel,
69
+ baseEntry,
70
+ localizedEntryRepeatableItemPosition,
71
+ `${prefix}.${localizedEntryRepeatableItemPosition}`,
72
+ dzEntryComponent,
73
+ true,
74
+ true,
75
+ );
76
+ }
77
+ }
78
+ }
67
79
  }
68
80
  });
69
81
  } else {
@@ -106,12 +118,37 @@ const parsedLocalazyEntryToCreateEntry = (
106
118
  false
107
119
  );
108
120
  }
121
+ } else if (isDynamicZone(attribute)) {
122
+ // behaves sort of like repeatable component
123
+ const newPrefix = prefix
124
+ ? `${prefix}.${objectKey}`
125
+ : `${objectKey}`;
126
+ toCreateEntry(
127
+ value,
128
+ null, // model is evaluated later as it's dynamic (DZ)
129
+ baseEntry,
130
+ objectKey,
131
+ newPrefix,
132
+ "", // component is computed later as it's dynamic (DZ)
133
+ true,
134
+ true,
135
+ );
109
136
  } else {
110
137
  const newPrefix = prefix ? `${prefix}.${objectKey}` : `${objectKey}`;
111
138
  set(createEntry, newPrefix, value);
112
139
 
113
140
  if (component) {
114
- set(createEntry, `${prefix}.__component`, component);
141
+ const componentKeyToSet = `${prefix}.__component`;
142
+ set(createEntry, componentKeyToSet, component);
143
+
144
+ if (isDZ) {
145
+ const isKeyAdded = (typeof dynamicZoneComponentKeys.find((v) => v.key === componentKeyToSet) !== "undefined");
146
+ if (!isKeyAdded)
147
+ dynamicZoneComponentKeys.push({
148
+ key: componentKeyToSet,
149
+ component,
150
+ });
151
+ }
115
152
  }
116
153
  // is not component
117
154
  }
@@ -126,7 +163,7 @@ const parsedLocalazyEntryToCreateEntry = (
126
163
  }
127
164
 
128
165
  resetArrayKeysDeep(createEntry, repeatableComponentsKeystoFilter);
129
- return createEntry;
166
+ return { createEntry, dynamicZoneComponentKeys };
130
167
  };
131
168
 
132
169
  module.exports = parsedLocalazyEntryToCreateEntry;
@@ -3,15 +3,19 @@
3
3
  const merge = require("lodash/merge");
4
4
  const parsedLocalazyEntryToCreateEntry = require("./parsed-localazy-entry-to-create-entry");
5
5
  const omitDeep = require("./omit-deep");
6
+ const populateCreateUpdateEntryWithBaseEntry = require("./populate-create-update-entry-with-base-entry");
7
+ const { set } = require("lodash");
6
8
 
7
- const parsedLocalazyEntryToUpdateEntry = (
9
+ const parsedLocalazyEntryToUpdateEntry = async (
8
10
  allModels,
9
11
  localazyEntry,
12
+ fullyPopulatedLocalizedEntry,
10
13
  currentEntry,
11
14
  baseEntry,
12
- uid
15
+ uid,
16
+ locale
13
17
  ) => {
14
- const createEntry = parsedLocalazyEntryToCreateEntry(
18
+ const { createEntry, dynamicZoneComponentKeys } = parsedLocalazyEntryToCreateEntry(
15
19
  allModels,
16
20
  localazyEntry,
17
21
  baseEntry,
@@ -25,11 +29,47 @@ const parsedLocalazyEntryToUpdateEntry = (
25
29
  "locale",
26
30
  "localizations",
27
31
  "createdAt",
32
+ "createdBy",
28
33
  "updatedAt",
34
+ "updatedBy",
29
35
  "publishedAt",
30
36
  ]);
37
+ // set dynamic zone compoonent keys again
38
+ dynamicZoneComponentKeys.forEach((v) => {
39
+ set(updateEntry, v.key, v.component);
40
+ });
31
41
 
32
- return updateEntry;
42
+ const filteredBaseEntry = omitDeep(baseEntry, [
43
+ // "__component", // do not omit the __component
44
+ "locale",
45
+ "localizations",
46
+ "createdAt",
47
+ "createdBy",
48
+ "updatedAt",
49
+ "updatedBy",
50
+ "publishedAt",
51
+ ]);
52
+ const filteredFullyPopulatedLocalizedEntry = omitDeep(fullyPopulatedLocalizedEntry, [
53
+ // "__component", // do not omit the __component
54
+ "locale",
55
+ "localizations",
56
+ "createdAt",
57
+ "createdBy",
58
+ "updatedAt",
59
+ "updatedBy",
60
+ "publishedAt",
61
+ ]);
62
+ const populatedEntry = await populateCreateUpdateEntryWithBaseEntry(
63
+ allModels,
64
+ updateEntry,
65
+ filteredBaseEntry,
66
+ filteredFullyPopulatedLocalizedEntry,
67
+ uid,
68
+ locale
69
+ );
70
+
71
+ // return updateEntry;
72
+ return populatedEntry;
33
73
  };
34
74
 
35
75
  module.exports = parsedLocalazyEntryToUpdateEntry;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ const isObject = require("lodash/isObject");
3
+ const set = require("lodash/set");
4
+ const includes = require("lodash/includes");
5
+
6
+ const deepLodashPick = (obj, keys, newObj = {}, path = []) => {
7
+ for (let k in obj) {
8
+ const value = obj[k];
9
+
10
+ if (isObject(value)) {
11
+ path.push(k);
12
+ deepLodashPick(value, keys, newObj, path);
13
+ path.pop();
14
+ } else if (includes(keys, k)) {
15
+ set(newObj, [...path, k], value);
16
+ }
17
+ }
18
+
19
+ return newObj;
20
+ }
21
+
22
+ module.exports = deepLodashPick;
@@ -1,12 +1,97 @@
1
1
  "use strict";
2
2
 
3
- const pickEntries = (flatten, pickPaths) => {
3
+ const map = require("lodash/map");
4
+ const pickDeep = require("./pick-deep");
5
+ const flattenObject = require("./flatten-object");
6
+
7
+ const isDynamicZoneKey = (key, transferSetupModel) => {
8
+ const matches = key.match(/\[\d+\]+/g);
9
+
10
+ if (matches.length < 2) {
11
+ return false;
12
+ }
13
+
14
+ const dynamicPropertyEntryIdWithSquareBrackets = matches[1];
15
+ const dynamicPropertyNamePart = key.split(".").find((s, index) => index > 1 && s.includes(dynamicPropertyEntryIdWithSquareBrackets));
16
+ const dynamicPropertyName = dynamicPropertyNamePart.replace(dynamicPropertyEntryIdWithSquareBrackets, "");
17
+
18
+ return Array.isArray(transferSetupModel[dynamicPropertyName]);
19
+ };
20
+
21
+ const getDynamicZoneEntryId = (key, transferSetupModel) => {
22
+ if (!isDynamicZoneKey(key, transferSetupModel)) {
23
+ return undefined;
24
+ }
25
+
26
+ const matches = key.match(/\[\d+\]+/g);
27
+ // remove square brackets
28
+ const id = matches[1].replace(/[\[\]']+/g, '');
29
+ return id;
30
+ }
31
+
32
+ const doesPickPathsIncludeTheComponent = (pickPaths, dzParameterKey) => {
33
+ return pickPaths.some((pp) => pp.includes(dzParameterKey));
34
+ }
35
+
36
+ const pickEntries = (flatten, pickPaths, transferSetupModel) => {
37
+ // * dynamic zones properties must! always be in 1st level (not in component; Strapi restrictions)
38
+
39
+ const dzEntryIdComponentMap = {};
40
+ const flattenComponentProps = Object.fromEntries(Object.entries(flatten).filter(([key]) => key.includes('__component')));
41
+
42
+ const pickedDeep = pickDeep(transferSetupModel, ["__component__"]);
43
+ const flattenedPickedDeep = flattenObject(pickedDeep);
44
+ map(flattenedPickedDeep, (v, i) => {
45
+ const newI = i.replace(".__component__", "");
46
+ flattenedPickedDeep[newI] = v;
47
+ delete flattenedPickedDeep[i];
48
+ });
49
+
50
+ Object.keys(flattenComponentProps).map((key) => {
51
+ if (!isDynamicZoneKey(key, transferSetupModel)) {
52
+ return;
53
+ }
54
+
55
+ const entryId = getDynamicZoneEntryId(key, transferSetupModel);
56
+ dzEntryIdComponentMap[entryId] = {
57
+ component: flattenComponentProps[key],
58
+ key: Object.keys(flattenedPickedDeep).find((fpdk) => flattenedPickedDeep[fpdk] === flattenComponentProps[key]),
59
+ };
60
+ });
61
+
4
62
  const pickedEntries = {};
5
- Object.keys(flatten).forEach((key) => {
63
+ const mappedPickPaths = pickPaths.map((pickPath) => pickPath.replace(/\[\d+\]/g, ""));
64
+
65
+ // filter out "__component"
66
+ const filteredFlatten = Object.fromEntries(Object.entries(flatten).filter(([key]) => !key.includes('__component')));
67
+ Object.keys(filteredFlatten).forEach((key) => {
68
+ // decide whether it is a dynamic zone
69
+ // handle dynamic zones, in case of similarly-named fields across more components
70
+ const isDynamicZone = isDynamicZoneKey(key, transferSetupModel);
71
+ if (isDynamicZone) {
72
+ const dzEntryId = getDynamicZoneEntryId(key, transferSetupModel);
73
+ const dzParameterKey = dzEntryIdComponentMap[dzEntryId].key;
74
+
75
+ if (!doesPickPathsIncludeTheComponent(pickPaths, dzParameterKey)) {
76
+ return;
77
+ }
78
+ }
79
+
6
80
  const filteredKey = key.replace(/\[\d+\]/g, "");
7
- // const filteredKey = key.replace(/\d|[[\]]+/g, "");
8
- if (pickPaths.includes(filteredKey)) {
9
- pickedEntries[key] = flatten[key];
81
+ if (mappedPickPaths.includes(filteredKey)) {
82
+ if (isDynamicZone) {
83
+ const dzEntryId = getDynamicZoneEntryId(key, transferSetupModel);
84
+ const dzParameterKey = dzEntryIdComponentMap[dzEntryId].key;
85
+
86
+ const dzParameterName = dzParameterKey.substring(0, dzParameterKey.indexOf('['));
87
+ const toBeReplaced = `${dzParameterName}[${dzEntryId}]`;
88
+ const dzParameterComponent = dzEntryIdComponentMap[dzEntryId].component;
89
+ const componentAddition = `${dzParameterName}[${dzEntryId};${dzParameterComponent}]`;
90
+ const replacedKey = key.replace(toBeReplaced, componentAddition);
91
+ pickedEntries[replacedKey] = flatten[key];
92
+ } else {
93
+ pickedEntries[key] = flatten[key];
94
+ }
10
95
  }
11
96
  });
12
97
  return pickedEntries;