@gudhub/core 1.1.125 → 1.1.127

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.
@@ -1,3 +1,5 @@
1
+ import { GudHub } from "../../gudhub.js";
2
+
1
3
  export default class AppsTemplateService {
2
4
  constructor(gudhub) {
3
5
  this.gudhub = gudhub;
@@ -6,370 +8,332 @@ export default class AppsTemplateService {
6
8
  fields: [],
7
9
  items: [],
8
10
  views: []
9
- }
11
+ };
10
12
  }
11
13
 
12
- createAppsFromTemplate(pack, maxNumberOfInsstalledItems, progressCallback, installFromMaster) {
13
- const self = this;
14
- return new Promise(resolve => {
15
- const stepsCount = pack.apps.length * 6;
16
- const stepPercents = 100 / stepsCount;
17
- let currentStep = 0;
18
- self.createApps(pack, (status) => {
19
- currentStep += 1;
20
- progressCallback ? progressCallback.call(this, {
21
- percent: currentStep * stepPercents,
22
- status
23
- }) : null;
24
- }, installFromMaster).then(success => {
25
- self.createItems(success, maxNumberOfInsstalledItems, (status) => {
26
- if (typeof status === 'string') {
27
- currentStep += 1;
28
- progressCallback ? progressCallback.call(this, {
29
- percent: currentStep * stepPercents,
30
- status
31
- }) : null;
32
- } else if (typeof status === 'object') {
33
- progressCallback ? progressCallback.call(this, {
34
- status: 'Done',
35
- apps: status
36
- }) : null;
37
- resolve();
38
- }
39
- }, installFromMaster)
14
+ async createAppsFromTemplate(pack, remoteServerUrl, remoteServerAuthKey, isInstallFromRemoteServer, maxNumberOfInstalledItems, progressCallback) {
15
+ const stepsCount = pack.apps.length * 6;
16
+ const stepPercents = 100 / stepsCount;
17
+ let currentStep = 0;
18
+
19
+ const remoteServerGudHubInstance = isInstallFromRemoteServer
20
+ ? new GudHub(remoteServerAuthKey, {
21
+ server_url: `${remoteServerUrl}/GudHub`
40
22
  })
41
- });
23
+ : null;
24
+
25
+ const appsList = await remoteServerGudHubInstance.getAppsList();
26
+
27
+ const onAppStep = (status) => {
28
+ currentStep += 1;
29
+ progressCallback?.({ percent: currentStep * stepPercents, status });
30
+ };
31
+
32
+ const onItemStep = (status) => {
33
+ if (typeof status === 'string') {
34
+ currentStep += 1;
35
+ progressCallback?.({ percent: currentStep * stepPercents, status });
36
+ } else if (typeof status === 'object') {
37
+ progressCallback?.({ status: 'Done', apps: status });
38
+ }
39
+ };
40
+
41
+ const createdApps = await this._createApps(pack, remoteServerUrl, remoteServerAuthKey, isInstallFromRemoteServer, remoteServerGudHubInstance, onAppStep);
42
+ await this._createItems(createdApps, remoteServerUrl, remoteServerAuthKey, isInstallFromRemoteServer, remoteServerGudHubInstance, maxNumberOfInstalledItems, onItemStep);
42
43
  }
43
44
 
44
- createItems(create_apps, maxNumberOfInsstalledItems, callback, installFromMaster) {
45
- return new Promise(async (resolve) => {
46
- const MAX_NUMBER_OF_INSTALLED_ITEMS = maxNumberOfInsstalledItems ? maxNumberOfInsstalledItems : 100000;
47
- const self = this;
48
- let createsItems = [];
49
- let promises = [];
45
+ // Fetches source apps, clones them as new ones and builds ID mapping between old and new entities
46
+ async _createApps(pack, remoteServerUrl, remoteServerAuthKey, isInstallFromRemoteServer, remoteServerGudHubInstance, callback) {
47
+ const source_apps = {};
48
+ const created_apps = [];
49
+ const updated_apps = [];
50
50
 
51
- const { accesstoken } = await self.gudhub.req.axiosRequest({
51
+ let accesstoken = null;
52
+
53
+ if (isInstallFromRemoteServer) {
54
+ const response = await this.gudhub.req.axiosRequest({
52
55
  method: 'POST',
53
- url: 'https://app.gudhub.com/GudHub/auth/login',
54
- form: {
55
- auth_key: '3HMOtCbC0M/1/1e4y4Uxo/Kh/aFN4V5LG2//5ixx7TZyiUfMb7IHAAHCj9PsLrOSDrZuuWkbX4BIX23f51H+eA=='
56
- }
56
+ url: `${remoteServerUrl}/GudHub/auth/login`,
57
+ form: { auth_key: remoteServerAuthKey }
57
58
  });
58
59
 
59
- create_apps.forEach(app => {
60
- promises.push(new Promise(async (app_resolve) => {
61
- let result_app;
62
- if(installFromMaster) {
63
- result_app = await self.gudhub.req.axiosRequest({ url: `https://app.gudhub.com/GudHub/api/app/get?token=${accesstoken}&app_id=${self._searchOldAppId(app.app_id, self.appsConnectingMap.apps)}`, method: 'GET' });
64
- } else {
65
- result_app = await self.gudhub.getApp(self._searchOldAppId(app.app_id, self.appsConnectingMap.apps));
66
- }
67
- callback ? callback.call(this, `GET APP: ${result_app.app_name} (Items steps)`) : null;
68
- let source_app = JSON.parse(JSON.stringify(result_app));
69
- let items = source_app.items_list.map(item => {
70
- return {
71
- index_number: item.index_number,
72
- item_id: 0,
73
- fields: []
74
- }
75
- }).filter((item, index) => index <= MAX_NUMBER_OF_INSTALLED_ITEMS);
76
- self.gudhub.addNewItems(app.app_id, items).then(items => {
77
- callback ? callback.call(this, `ADD NEW ITEMS: ${result_app.app_name} (Items steps)`) : null;
60
+ accesstoken = response.accesstoken;
61
+ }
78
62
 
79
- app.items_list = items;
80
- self._updateMap(app.items_list, source_app.items_list);
81
- self._addFieldValue(source_app.items_list, app.items_list, self.appsConnectingMap);
63
+ for (const app_id of pack.apps) {
64
+ let result_app;
82
65
 
83
- createsItems.push(app);
66
+ if (isInstallFromRemoteServer) {
67
+ result_app = await this.gudhub.req.axiosRequest({
68
+ url: `${remoteServerUrl}/GudHub/api/app/get?token=${accesstoken}&app_id=${app_id}`,
69
+ method: 'GET'
70
+ });
71
+ } else {
72
+ result_app = await this.gudhub.getApp(app_id);
73
+ }
84
74
 
85
- app_resolve();
86
- });
87
- }));
88
- });
75
+ if (!result_app) {
76
+ throw new Error(`Failed to get app: ${app_id}`);
77
+ }
89
78
 
90
- Promise.all(promises).then(() => {
79
+ callback?.(`GET APP: ${result_app.app_name} (App steps)`);
91
80
 
92
- createsItems.forEach(app => {
93
- app.items_list.forEach(item => {
94
- item.fields.forEach(field_item => {
95
- self.appsConnectingMap.fields.forEach(field_map => {
96
- if (field_item.field_id === field_map.old_field_id) {
97
- field_item.field_id = field_map.new_field_id;
98
- field_item.element_id = field_map.new_field_id;
99
- }
100
- })
101
- })
102
- })
81
+ const source_app = JSON.parse(JSON.stringify(result_app));
82
+ source_apps[source_app.app_id] = source_app;
83
+
84
+ let appTemplate = this._prepareAppTemplate(source_app);
85
+ appTemplate.privacy = 1;
86
+
87
+ if (pack.data_to_change) {
88
+ if (pack.data_to_change.name) appTemplate.app_name = pack.data_to_change.name;
89
+ if (pack.data_to_change.icon) appTemplate.icon = pack.data_to_change.icon;
90
+ }
91
+
92
+ this._resetViewsIds(appTemplate);
93
+
94
+ const created_app = await this.gudhub.createNewApp(appTemplate);
95
+ callback?.(`CREATE NEW APP: ${result_app.app_name} (App steps)`);
96
+
97
+ created_apps.push(created_app);
98
+ this._mapApp(source_app, created_app, this.appsConnectingMap);
99
+ }
100
+
101
+ this._connectApps(created_apps, this.appsConnectingMap, source_apps);
102
+
103
+ for (const created_app of created_apps) {
104
+ const updated_app = await this.gudhub.updateApp(created_app);
105
+ callback?.(`UPDATE APP: ${updated_app.app_name} (App steps)`);
106
+ updated_apps.push(updated_app);
107
+ }
108
+
109
+ return updated_apps;
110
+ }
111
+
112
+ // Creates items for each app and restores their data with proper ID remapping
113
+ async _createItems(create_apps, remoteServerUrl, remoteServerAuthKey, isInstallFromRemoteServer, remoteServerGudHubInstance, maxNumberOfInstalledItems, callback) {
114
+ const MAX_NUMBER_OF_INSTALLED_ITEMS = maxNumberOfInstalledItems ?? 100000;
115
+
116
+ let accesstoken = null;
117
+
118
+ if (isInstallFromRemoteServer) {
119
+ const response = await this.gudhub.req.axiosRequest({
120
+ method: 'POST',
121
+ url: `${remoteServerUrl}/GudHub/auth/login`,
122
+ form: { auth_key: remoteServerAuthKey }
103
123
  });
124
+ accesstoken = response.accesstoken;
125
+ }
104
126
 
105
- self.deleteField(createsItems);
106
-
107
- self.replaceValue(createsItems, self.appsConnectingMap).then(() => {
108
-
109
- const promises = [];
110
-
111
- createsItems.forEach(created_app => {
112
- promises.push(new Promise(resolve => {
113
- let newItems = created_app.items_list.map(item => {
114
- item.fields.forEach(field => {
115
- delete field.data_id;
116
- });
117
- return item;
118
- });
119
-
120
- self.gudhub.updateItems(created_app.app_id, newItems).then(() => {
121
- callback ? callback.call(this, `REPLACE VALUE: ${created_app.app_name} (Items steps)`) : null;
122
- resolve();
123
- })
124
- }));
125
- });
126
-
127
- Promise.all(promises).then(() => {
128
- callback.call(this, createsItems);
129
- resolve();
127
+ const createdItems = await Promise.all(create_apps.map(async (app) => {
128
+ let result_app;
129
+
130
+ if (isInstallFromRemoteServer) {
131
+ result_app = await this.gudhub.req.axiosRequest({
132
+ url: `${remoteServerUrl}/GudHub/api/app/get?token=${accesstoken}&app_id=${this._searchOldAppId(app.app_id, this.appsConnectingMap.apps)}`,
133
+ method: 'GET'
130
134
  });
135
+ } else {
136
+ result_app = await this.gudhub.getApp(this._searchOldAppId(app.app_id, this.appsConnectingMap.apps));
137
+ }
138
+
139
+ if (!result_app) {
140
+ throw new Error(`Failed to get app in createItems: ${app.app_id}`);
141
+ }
142
+
143
+ callback?.(`GET APP: ${result_app.app_name} (Items steps)`);
144
+
145
+ const source_app = JSON.parse(JSON.stringify(result_app));
146
+ const items = source_app.items_list
147
+ .map(item => ({ index_number: item.index_number, item_id: 0, fields: [] }))
148
+ .filter((_, index) => index <= MAX_NUMBER_OF_INSTALLED_ITEMS);
149
+
150
+ const newItems = await this.gudhub.addNewItems(app.app_id, items);
151
+ callback?.(`ADD NEW ITEMS: ${result_app.app_name} (Items steps)`);
152
+
153
+ app.items_list = newItems;
154
+ this._updateMap(app.items_list, source_app.items_list);
155
+ this._addFieldValue(source_app.items_list, app.items_list, this.appsConnectingMap);
156
+
157
+ return app;
158
+ }));
159
+
160
+ for (const app of createdItems) {
161
+ for (const item of app.items_list) {
162
+ for (const field_item of item.fields) {
163
+ for (const field_map of this.appsConnectingMap.fields) {
164
+ if (field_item.field_id === field_map.old_field_id) {
165
+ field_item.field_id = field_map.new_field_id;
166
+ field_item.element_id = field_map.new_field_id;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
131
172
 
173
+ this._deleteField(createdItems);
174
+ await this._replaceValue(
175
+ createdItems,
176
+ this.appsConnectingMap,
177
+ isInstallFromRemoteServer,
178
+ remoteServerGudHubInstance
179
+ );
180
+
181
+ await Promise.all(createdItems.map(async (created_app) => {
182
+ const newItems = created_app.items_list.map(item => {
183
+ item.fields.forEach(field => { delete field.data_id; });
184
+ return item;
132
185
  });
133
- })
134
- })
186
+
187
+ await this.gudhub.updateItems(created_app.app_id, newItems);
188
+ callback?.(`REPLACE VALUE: ${created_app.app_name} (Items steps)`);
189
+ }));
190
+
191
+ callback?.(createdItems);
135
192
  }
136
193
 
137
- deleteField(apps) {
138
- apps.forEach(app => {
139
- app.items_list.forEach(item => {
140
- item.fields = item.fields.filter(field => {
141
- return app.field_list.findIndex(f => f.field_id == field.field_id) > -1;
142
- })
143
- })
144
- })
194
+ // Ensures items only contain fields that still exist in the app schema
195
+ _deleteField(apps) {
196
+ for (const app of apps) {
197
+ for (const item of app.items_list) {
198
+ item.fields = item.fields.filter(field =>
199
+ app.field_list.findIndex(f => f.field_id == field.field_id) > -1
200
+ );
201
+ }
202
+ }
145
203
  }
146
204
 
147
- replaceValue(apps, map) {
148
- return new Promise(async (resolve) => {
149
- const allPromises = [];
150
- const self = this;
205
+ // Handles post-processing of field values (files, documents, references)
206
+ async _replaceValue(apps, map, isInstallFromRemoteServer, remoteServerGudHubInstance) {
207
+ const allPromises = [];
208
+ const mapAppIds = map?.apps;
151
209
 
152
- apps.forEach(app => {
153
- app.field_list.forEach(field_of_list => {
154
- if (field_of_list.data_type === 'item_ref') {
155
- self._replaceValueItemRef(app, field_of_list, map);
156
- }
210
+ for (const app of apps) {
211
+ for (const field_of_list of app.field_list) {
212
+ if (field_of_list.data_type === 'item_ref') {
213
+ this._replaceValueItemRef(app, field_of_list, map);
214
+ }
157
215
 
158
- allPromises.push(new Promise(resolve => {
159
- self.gudhub.ghconstructor.getInstance(field_of_list.data_type).then(data => {
160
- if(typeof data === 'undefined') {
161
- console.log('ERROR WHILE GETTING INSTANCE OF ', field_of_list.data_type);
162
- resolve({
163
- type: field_of_list.data_type
164
- });
165
- return;
166
- }
167
- if (data.getTemplate().constructor == 'file') {
168
- return self.gudhub.util.fileInstallerHelper(app.app_id, app.items_list, field_of_list.field_id).then(result => {
169
- resolve(result);
170
- });
171
- } else if (data.getTemplate().constructor == 'document') {
172
- self.documentInstallerHelper(app.app_id, app.items_list, field_of_list.field_id).then(result => {
173
- resolve(data);
174
- })
175
- } else {
176
- resolve(data);
177
- }
178
- })
179
- }))
180
- })
181
- });
216
+ allPromises.push(
217
+ this.gudhub.ghconstructor.getInstance(field_of_list.data_type).then(async (data) => {
218
+ if (typeof data === 'undefined') {
219
+ return { type: field_of_list.data_type };
220
+ }
182
221
 
183
- if (allPromises.length > 0) {
222
+ if (data.getTemplate().constructor === 'file') {
223
+ return this.gudhub.util.fileInstallerHelper(
224
+ mapAppIds,
225
+ app.app_id,
226
+ app.items_list,
227
+ field_of_list.field_id,
228
+ isInstallFromRemoteServer,
229
+ remoteServerGudHubInstance
230
+ );
231
+ }
184
232
 
185
- Promise.all(allPromises).then(result => {
186
- resolve(result);
187
- });
233
+ if (data.getTemplate().constructor === 'document') {
234
+ await this._documentInstallerHelper(app.app_id, app.items_list, field_of_list.field_id);
235
+ return data;
236
+ }
188
237
 
189
- } else {
190
- resolve(result);
238
+ return data;
239
+ })
240
+ );
191
241
  }
242
+ }
192
243
 
193
- });
244
+ if (allPromises.length > 0) {
245
+ return Promise.all(allPromises);
246
+ }
194
247
  }
195
248
 
196
- documentInstallerHelper(appId, items, elementId) {
197
- const self = this;
249
+ // Clones documents so new items don’t reference original app data
250
+ async _documentInstallerHelper(appId, items, elementId) {
198
251
  const itemsWithClonedDocument = [];
199
252
 
200
- return new Promise(async (resolve) => {
201
- for(const item of items) {
202
- const itemId = item.item_id;
203
- const field = item.fields.find(field => field.element_id === elementId);
204
-
205
- if (field && field.field_value && field.field_value.length > 0) {
206
- const document = await self.gudhub.getDocument({
207
- _id: field.field_value
208
- });
253
+ for (const item of items) {
254
+ const field = item.fields.find(field => field.element_id === elementId);
209
255
 
210
- if(document && document.data) {
211
-
212
- const newDocument = await self.gudhub.createDocument({
213
- app_id: appId,
214
- item_id: itemId,
215
- element_id: elementId,
216
- data: document.data
217
- });
256
+ if (field?.field_value?.length > 0) {
257
+ const document = await this.gudhub.getDocument({ _id: field.field_value });
218
258
 
219
- field.field_value = newDocument._id;
259
+ if (document?.data) {
260
+ const newDocument = await this.gudhub.createDocument({
261
+ app_id: appId,
262
+ item_id: item.item_id,
263
+ element_id: elementId,
264
+ data: document.data
265
+ });
220
266
 
221
- itemsWithClonedDocument.push(item);
222
- }
267
+ field.field_value = newDocument._id;
268
+ itemsWithClonedDocument.push(item);
223
269
  }
224
270
  }
225
- resolve(itemsWithClonedDocument);
226
- });
271
+ }
272
+
273
+ return itemsWithClonedDocument;
227
274
  }
228
275
 
276
+ // Rewrites item_ref values to point to newly created apps/items instead of old ones
229
277
  _replaceValueItemRef(app, field_of_list, map) {
230
- app.items_list.forEach(item => {
231
- item.fields.forEach(field_of_item => {
278
+ for (const item of app.items_list) {
279
+ for (const field_of_item of item.fields) {
232
280
  if (field_of_item.field_id === field_of_list.field_id && field_of_item.field_value) {
233
- let arr_values = field_of_item.field_value.split(',');
234
-
235
- arr_values.forEach((one_value, key) => {
236
- let value = one_value.split('.');
237
-
238
- map.apps.forEach(app_ids => {
239
- if (value[0] == app_ids.old_app_id) {
240
- value[0] = app_ids.new_app_id;
241
- }
242
- });
281
+ const arr_values = field_of_item.field_value.split(',').map(one_value => {
282
+ const value = one_value.split('.');
243
283
 
244
- map.items.forEach(item_ids => {
245
- if (value[1] == item_ids.old_item_id) {
246
- value[1] = item_ids.new_item_id;
247
- }
248
- });
284
+ for (const app_ids of map.apps) {
285
+ if (value[0] == app_ids.old_app_id) value[0] = app_ids.new_app_id;
286
+ }
249
287
 
250
- arr_values[key] = value.join('.')
288
+ for (const item_ids of map.items) {
289
+ if (value[1] == item_ids.old_item_id) value[1] = item_ids.new_item_id;
290
+ }
251
291
 
292
+ return value.join('.');
252
293
  });
253
294
 
254
295
  field_of_item.field_value = arr_values.join(',');
255
-
256
296
  }
257
- });
258
- })
297
+ }
298
+ }
259
299
  }
260
300
 
301
+ // Copies field values from source items into newly created items
261
302
  _addFieldValue(source_app_items, create_items, appsConnectingMap) {
262
- create_items.forEach(new_item => {
263
- appsConnectingMap.items.forEach(item_of_map => {
303
+ for (const new_item of create_items) {
304
+ for (const item_of_map of appsConnectingMap.items) {
264
305
  if (new_item.item_id === item_of_map.new_item_id) {
265
- source_app_items.forEach(old_item => {
306
+ for (const old_item of source_app_items) {
266
307
  if (item_of_map.old_item_id === old_item.item_id) {
267
308
  new_item.fields = old_item.fields;
268
309
  }
269
- })
310
+ }
270
311
  }
271
- })
272
- })
312
+ }
313
+ }
273
314
  }
274
315
 
316
+ // Builds mapping between old and new item IDs (based on index consistency)
275
317
  _updateMap(new_items, old_items) {
276
- old_items.forEach(old_item => {
277
- new_items.forEach(new_item => {
318
+ for (const old_item of old_items) {
319
+ for (const new_item of new_items) {
278
320
  if (old_item.index_number === new_item.index_number) {
279
321
  this.appsConnectingMap.items.push({
280
322
  old_item_id: old_item.item_id,
281
323
  new_item_id: new_item.item_id
282
- })
324
+ });
283
325
  }
284
- })
285
- })
286
- }
287
-
288
- _searchOldAppId(app_id, apps) {
289
- let findId = 0;
290
-
291
- apps.forEach(value => {
292
- if (value.new_app_id === app_id) {
293
- findId = value.old_app_id;
294
326
  }
295
- });
296
-
297
- return findId;
298
- }
299
-
300
- createApps(pack, callback, installFromMaster) {
301
- const self = this;
302
-
303
- let progress = {
304
- step_size: 100 / (pack.apps.length * 3),
305
- apps: []
306
327
  }
328
+ }
307
329
 
308
- return new Promise(async (resolve) => {
309
- let source_apps = {};
310
- let created_apps = [];
311
- let updated_apps = [];
312
-
313
- const { accesstoken } = await self.gudhub.req.axiosRequest({
314
- method: 'POST',
315
- url: 'https://app.gudhub.com/GudHub/auth/login',
316
- form: {
317
- auth_key: '3HMOtCbC0M/1/1e4y4Uxo/Kh/aFN4V5LG2//5ixx7TZyiUfMb7IHAAHCj9PsLrOSDrZuuWkbX4BIX23f51H+eA=='
318
- }
319
- });
320
-
321
- for(const app_id of pack.apps) {
322
- let result_app;
323
-
324
- if(installFromMaster) {
325
- result_app = await self.gudhub.req.axiosRequest({ url: `https://app.gudhub.com/GudHub/api/app/get?token=${accesstoken}&app_id=${app_id}`, method: 'GET' });
326
- } else {
327
- result_app = await self.gudhub.getApp(app_id);
328
- }
329
-
330
- callback ? callback.call(this, `GET APP: ${result_app.app_name} (App steps)`) : null;
331
- let source_app = JSON.parse(JSON.stringify(result_app));
332
- source_apps[source_app.app_id] = source_app;
333
- progress.apps.push(source_app.icon);
334
-
335
- let appTemplate = self.prepareAppTemplate(source_app);
336
- appTemplate.app_name = appTemplate.app_name;
337
- appTemplate.privacy = 1;
338
- if(pack.data_to_change) {
339
- if(pack.data_to_change.name) {
340
- appTemplate.app_name = pack.data_to_change.name;
341
- }
342
- if(pack.data_to_change.icon) {
343
- appTemplate.icon = pack.data_to_change.icon;
344
- }
345
- }
346
- self.resetViewsIds(appTemplate);
347
-
348
- self.gudhub.createNewApp(appTemplate).then(created_app => {
349
- callback ? callback.call(this, `CREATE NEW APP: ${result_app.app_name} (App steps)`) : null;
350
- created_apps.push(created_app);
351
- self.mapApp(source_app, created_app, self.appsConnectingMap);
352
-
353
- if (pack.apps.length == created_apps.length) {
354
- self.connectApps(created_apps, self.appsConnectingMap, source_apps);
355
-
356
- created_apps.forEach(created_app => {
357
- self.gudhub.updateApp(created_app).then(updated_app => {
358
- callback ? callback.call(this, `UPDATE APP: ${result_app.app_name} (App steps)`) : null;
359
- updated_apps.push(updated_app);
360
-
361
- if (pack.apps.length == updated_apps.length) {
362
- resolve(updated_apps);
363
- }
364
- })
365
- })
366
- }
367
- });
368
- }
369
- });
330
+ // Resolves original app ID from mapping (used when syncing data)
331
+ _searchOldAppId(app_id, apps) {
332
+ return apps.find(value => value.new_app_id === app_id)?.old_app_id ?? 0;
370
333
  }
371
334
 
372
- mapApp(base_app, new_app, appsConnectingMap) {
335
+ // Stores mapping between old/new apps, fields and views for later ID replacement
336
+ _mapApp(base_app, new_app, appsConnectingMap) {
373
337
  appsConnectingMap.apps.push({
374
338
  old_app_id: base_app.app_id,
375
339
  new_app_id: new_app.app_id,
@@ -377,196 +341,179 @@ export default class AppsTemplateService {
377
341
  new_view_init: new_app.view_init
378
342
  });
379
343
 
380
- base_app.field_list.forEach(old_field => {
381
- new_app.field_list.forEach(new_field => {
382
- if (old_field.name_space == new_field.name_space) {
344
+ for (const old_field of base_app.field_list) {
345
+ for (const new_field of new_app.field_list) {
346
+ if (old_field.name_space === new_field.name_space) {
383
347
  appsConnectingMap.fields.push({
384
348
  name_space: old_field.name_space,
385
349
  old_field_id: old_field.field_id,
386
350
  new_field_id: new_field.field_id
387
- })
351
+ });
388
352
  }
389
- })
390
- });
353
+ }
354
+ }
391
355
 
392
- base_app.views_list.forEach(old_view => {
393
- new_app.views_list.forEach(new_view => {
394
- if (old_view.name == new_view.name) {
356
+ for (const old_view of base_app.views_list) {
357
+ for (const new_view of new_app.views_list) {
358
+ if (old_view.name === new_view.name) {
395
359
  appsConnectingMap.views.push({
396
360
  name: old_view.name,
397
361
  old_view_id: old_view.view_id,
398
362
  new_view_id: new_view.view_id
399
- })
363
+ });
400
364
  }
401
- })
402
- })
365
+ }
366
+ }
403
367
  }
404
368
 
405
- crawling(object, action) {
406
- let properties = object === null ? [] : Object.keys(object);
407
- const self = this;
369
+ // Generic recursive walker to traverse deeply nested objects
370
+ _crawling(object, action) {
371
+ if (object === null) return;
408
372
 
409
- if (properties.length > 0) {
410
- properties.forEach(prop => {
411
- let propertyValue = object[prop];
373
+ const properties = Object.keys(object);
412
374
 
413
- if (typeof propertyValue !== 'undefined' && typeof object != 'string') {
414
- action(prop, propertyValue, object);
415
- self.crawling(propertyValue, action);
416
- }
417
- })
375
+ for (const prop of properties) {
376
+ const propertyValue = object[prop];
377
+
378
+ if (typeof propertyValue !== 'undefined' && typeof object !== 'string') {
379
+ action(prop, propertyValue, object);
380
+ this._crawling(propertyValue, action);
381
+ }
418
382
  }
419
383
  }
420
384
 
421
- resetViewsIds(app) {
385
+ // Resets all view-related IDs to avoid collisions when cloning
386
+ _resetViewsIds(app) {
422
387
  app.view_init = 0;
423
388
 
424
- this.crawling(app.views_list, function (prop, value, parent) {
425
- if (prop == 'view_id' || prop == 'container_id') {
389
+ this._crawling(app.views_list, (prop, value, parent) => {
390
+ if (prop === 'view_id' || prop === 'container_id') {
426
391
  parent[prop] = 0;
427
392
  }
428
- })
393
+ });
429
394
  }
430
395
 
431
- connectApps(apps, appsConnectingMap, source_apps) {
432
- const self = this;
396
+ // Reconnects all internal references (fields, apps, views, triggers) after cloning
397
+ _connectApps(apps, appsConnectingMap, source_apps) {
398
+ const hasProp = (prop, propArray) => propArray.some(key => prop.indexOf(key) !== -1);
433
399
 
434
- apps.forEach(app => {
435
- appsConnectingMap.apps.forEach(appMap => {
400
+ for (const app of apps) {
401
+ for (const appMap of appsConnectingMap.apps) {
436
402
  if (app.view_init === appMap.new_view_init) {
437
- appsConnectingMap.views.forEach(view => {
403
+ for (const view of appsConnectingMap.views) {
438
404
  if (appMap.old_view_init === view.old_view_id) {
439
405
  app.view_init = view.new_view_id;
440
406
  }
441
- })
407
+ }
442
408
  }
443
- });
444
-
445
- self.crawling(app.field_list, function (prop, value, parent) {
446
- const fieldProps = ["field_id", "FieldId", "destination_field", "element_id"];
447
- const appProps = ["app_id", "AppId", "destination_app"];
448
- const viewProps = ["view_id"];
449
- const srcProps = ["src"];
409
+ }
450
410
 
451
- const hasProp = (prop, propArray) => propArray.some(key => prop.indexOf(key) !== -1);
411
+ this._crawling(app.field_list, (prop, value, parent) => {
412
+ const fieldProps = ['field_id', 'FieldId', 'destination_field', 'element_id'];
413
+ const appProps = ['app_id', 'AppId', 'destination_app'];
414
+ const viewProps = ['view_id'];
415
+ const srcProps = ['src'];
452
416
 
453
417
  if (hasProp(prop, fieldProps)) {
454
- let fieldsArr = String(value).split(','), newFieldsArr = [];
418
+ const fieldsArr = String(value).split(',');
419
+ const newFieldsArr = [];
455
420
 
456
- appsConnectingMap.fields.forEach(field => {
457
- fieldsArr.forEach(fieldFromValue => {
421
+ for (const field of appsConnectingMap.fields) {
422
+ for (const fieldFromValue of fieldsArr) {
458
423
  if (fieldFromValue == field.old_field_id) {
459
424
  newFieldsArr.push(field.new_field_id);
460
425
  }
461
- })
462
- })
463
-
464
- if (newFieldsArr.length) {
465
- parent[prop] = newFieldsArr.join(',');
426
+ }
466
427
  }
428
+
429
+ if (newFieldsArr.length) parent[prop] = newFieldsArr.join(',');
467
430
  }
468
431
 
469
432
  if (hasProp(prop, appProps)) {
470
- appsConnectingMap.apps.forEach(app => {
471
- if (value == app.old_app_id) {
472
- parent[prop] = app.new_app_id;
473
- }
474
- })
433
+ for (const appMap of appsConnectingMap.apps) {
434
+ if (value == appMap.old_app_id) parent[prop] = appMap.new_app_id;
435
+ }
475
436
  }
476
437
 
477
438
  if (hasProp(prop, viewProps)) {
478
- appsConnectingMap.views.forEach(view => {
479
- if (value == view.old_view_id) {
480
- parent[prop] = view.new_view_id;
481
- }
482
- })
439
+ for (const view of appsConnectingMap.views) {
440
+ if (value == view.old_view_id) parent[prop] = view.new_view_id;
441
+ }
483
442
  }
484
443
 
485
444
  if (hasProp(prop, srcProps) && isFinite(value)) {
486
445
  const pron_name = 'container_id';
487
446
  const old_app_id = appsConnectingMap.apps.find(appMap => appMap.new_app_id === app.app_id).old_app_id;
488
- const path = self.findPath(source_apps[old_app_id].views_list, pron_name, value);
489
-
490
- parent[prop] = `${self.getValueByPath(app.views_list, path, pron_name)}`;
447
+ const path = this._findPath(source_apps[old_app_id].views_list, pron_name, value);
448
+ parent[prop] = `${this._getValueByPath(app.views_list, path, pron_name)}`;
491
449
  }
492
450
 
493
- if (prop.indexOf("trigger") !== -1 && value.model && value.model.nodes) {
451
+ // Trigger nodes keep field IDs as object keys, so we need to remap both keys and connections.
452
+ if (prop.indexOf('trigger') !== -1 && value.model?.nodes) {
494
453
  const nodes = value.model.nodes;
495
- const nodeProperties = ["inputs", "outputs"];
496
454
 
497
455
  for (const index in nodes) {
498
- nodeProperties.forEach((property) => {
456
+ for (const property of ['inputs', 'outputs']) {
499
457
  const node = nodes[index];
500
- const numericProperties = Object.keys(node[property]).filter(prop => prop !== '' && !isNaN(prop)); // field_id as keys
501
-
502
- numericProperties.forEach((old_field_id) => {
503
- const correspondFieldOfConnectingMap = appsConnectingMap.fields.filter((field) => field.old_field_id == old_field_id);
458
+ const numericProperties = Object.keys(node[property]).filter(p => p !== '' && !isNaN(p));
504
459
 
505
- if (correspondFieldOfConnectingMap.length !== 0 && correspondFieldOfConnectingMap[0].new_field_id) {
506
- const new_field_id = correspondFieldOfConnectingMap[0].new_field_id.toString();
460
+ for (const old_field_id of numericProperties) {
461
+ const match = appsConnectingMap.fields.find(f => f.old_field_id == old_field_id);
507
462
 
463
+ if (match?.new_field_id) {
464
+ const new_field_id = match.new_field_id.toString();
508
465
  const connectionObjectCopy = node[property][old_field_id];
509
- connectionObjectCopy.connections.forEach((connection) => {
510
- if (connection.input == old_field_id) {
511
- connection.input = new_field_id;
512
- }
513
- if (connection.output == old_field_id) {
514
- connection.output = new_field_id;
515
- }
516
- });
517
466
 
518
- delete node[property][old_field_id];
467
+ for (const connection of connectionObjectCopy.connections) {
468
+ if (connection.input == old_field_id) connection.input = new_field_id;
469
+ if (connection.output == old_field_id) connection.output = new_field_id;
470
+ }
519
471
 
472
+ delete node[property][old_field_id];
520
473
  node[property][new_field_id] = connectionObjectCopy;
521
474
  }
522
- });
523
- });
524
- };
475
+ }
476
+ }
477
+ }
525
478
  }
526
- })
527
- })
479
+ });
480
+ }
528
481
 
529
482
  return apps;
530
483
  }
531
484
 
532
- getValueByPath(source, path, prop) {
485
+ // Utility to safely access nested value by path
486
+ _getValueByPath(source, path, prop) {
533
487
  let temp = source;
534
-
535
- path.split('.').forEach(item => {
488
+ for (const item of path.split('.')) {
536
489
  temp = temp[item];
537
- })
538
-
490
+ }
539
491
  return temp[prop];
540
492
  }
541
493
 
542
- findPath(source, prop, value) {
543
- const self = this;
544
-
494
+ // Finds path to a specific value inside deeply nested structure
495
+ _findPath(source, prop, value) {
545
496
  let isFound = false;
546
497
  let toClearPath = false;
547
498
  let path = [];
548
499
 
549
- self.crawling(source, function (propSource, valueSource, parentSource) {
500
+ this._crawling(source, (propSource, valueSource, parentSource) => {
550
501
  if (toClearPath) {
551
502
  path.reverse().forEach(item => {
552
503
  if (toClearPath) {
553
504
  item.delete = true;
554
-
555
505
  if (item.parent === parentSource) {
556
506
  toClearPath = false;
557
507
  path = path.filter(item => !item.delete);
558
508
  path.reverse();
559
509
  }
560
510
  }
561
- })
511
+ });
562
512
  }
563
513
 
564
514
  if (!isFound) {
565
515
  if (typeof parentSource[propSource] === 'object') {
566
- path.push({
567
- prop: propSource,
568
- parent: parentSource
569
- });
516
+ path.push({ prop: propSource, parent: parentSource });
570
517
  }
571
518
 
572
519
  if (valueSource == value && prop === propSource) {
@@ -580,24 +527,23 @@ export default class AppsTemplateService {
580
527
  return path.map(item => item.prop).join('.');
581
528
  }
582
529
 
583
- prepareAppTemplate(appToTemplate) {
584
- const self = this;
530
+ // Prepares clean app template by stripping IDs and resetting system fields
531
+ _prepareAppTemplate(appToTemplate) {
532
+ const app = JSON.parse(JSON.stringify(appToTemplate));
585
533
 
586
- let app = JSON.parse(JSON.stringify(appToTemplate));
587
-
588
- self.crawling(app.views_list, function (prop, value, parent) {
589
- if (prop == "element_id") {
534
+ this._crawling(app.views_list, (prop, value, parent) => {
535
+ if (prop === 'element_id') {
590
536
  parent.element_id = -Number(value);
591
537
  parent.create_element = -Number(value);
592
538
  }
593
- })
539
+ });
594
540
 
595
- app.field_list.forEach(field => {
541
+ for (const field of app.field_list) {
596
542
  field.create_field = -Number(field.field_id);
597
543
  field.element_id = -Number(field.field_id);
598
544
  field.field_id = -Number(field.field_id);
599
- field.field_value = ""; /* Should be removed when we update createApp API*/
600
- })
545
+ field.field_value = '';
546
+ }
601
547
 
602
548
  app.items_list = [];
603
549
 
@@ -609,6 +555,5 @@ export default class AppsTemplateService {
609
555
  delete app.file_list;
610
556
 
611
557
  return app;
612
- };
613
-
614
- }
558
+ }
559
+ }