Skip to content

Commit a8669f3

Browse files
committed
added extension
1 parent f864674 commit a8669f3

File tree

6 files changed

+172
-25
lines changed

6 files changed

+172
-25
lines changed

api/src/constants/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ export const LOCALE_MAPPER: any = {
183183
}
184184
export const CHUNK_SIZE = 1048576;
185185

186+
export const LIST_EXTENSION_UID = 'bltc44e51cc9f4b0d80';
187+
186188
export const KEYTOREMOVE = [
187189
"update", "fetch", "delete", "oauth", "hosting", "install", "reinstall",
188190
"upgrade", "getRequests", "authorize", "authorization", "listInstallations"
@@ -209,11 +211,14 @@ export const MIGRATION_DATA_CONFIG = {
209211

210212
CONTENT_TYPES_DIR_NAME: "content_types",
211213
EXTENSIONS_MAPPER_DIR_NAME: "extension-mapper.json",
214+
CUSTOM_MAPPER_FILE_NAME: "custmon-mapper.json",
212215
CONTENT_TYPES_FILE_NAME: "contenttype.json",
213216
CONTENT_TYPES_MASTER_FILE: "contenttypes.json",
214217
CONTENT_TYPES_SCHEMA_FILE: "schema.json",
215218
MARKETPLACE_APPS_DIR_NAME: "marketplace_apps",
216219
MARKETPLACE_APPS_FILE_NAME: "marketplace_apps.json",
220+
EXTENSION_APPS_DIR_NAME: "extensions",
221+
EXTENSION_APPS_FILE_NAME: "extensions.json",
217222
REFERENCES_DIR_NAME: "reference",
218223
REFERENCES_FILE_NAME: "reference.json",
219224

api/src/services/contentful.service.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import _ from "lodash";
66
import axios from "axios";
77
import jsonpath from "jsonpath";
88
import pLimit from 'p-limit';
9-
import { jsonToHtml, jsonToMarkdown } from '@contentstack/json-rte-serializer';
9+
import { JSDOM } from "jsdom";
10+
import { jsonToHtml, jsonToMarkdown, htmlToJson } from '@contentstack/json-rte-serializer';
1011

1112

1213
import { CHUNK_SIZE, MIGRATION_DATA_CONFIG } from "../constants/index.js";
@@ -200,14 +201,17 @@ const processField = (
200201
case 'text': {
201202
return lang_value;
202203
}
204+
203205
case 'json': {
204206
return processRTEOrNestedObject(lang_value, lang, destination_stack_id);
205207
}
208+
206209
case 'dropdown':
207210
case 'radio': {
208211
const isPresent = fieldData?.advanced?.options?.find((option: any) => lang_value === option?.value);
209212
return isPresent?.value ?? fieldData?.advanced?.default_value;
210213
}
214+
211215
case 'file': {
212216
if (fieldData?.advanced?.multiple) {
213217
const assetsData: any = [];
@@ -225,17 +229,31 @@ const processField = (
225229
return null;
226230
}
227231
}
232+
228233
case 'reference': {
234+
if (Array?.isArray?.(lang_value) && fieldData?.advanced?.multiple) {
235+
const refs = [];
236+
for (const entry of lang_value) {
237+
const id = entry?.sys?.id;
238+
if (id in entryId) {
239+
refs?.push(entryId?.[id]);
240+
}
241+
}
242+
return refs;
243+
}
229244
const id = lang_value?.sys?.id;
230-
if (lang_value?.sys?.linkType === "Entry" && id in entryId) return [entryId?.[id]];
231-
return [];
245+
if (id in entryId) return [[entryId?.[id]]];
246+
return null;
232247
}
248+
233249
case 'app': {
234250
return damApp(fieldData?.otherCmsType, lang_value)
235251
}
252+
236253
case 'boolean': {
237254
return lang_value;
238255
}
256+
239257
case 'number': {
240258
if (typeof lang_value === 'string') {
241259
return parseInt?.(lang_value)
@@ -275,21 +293,33 @@ const processField = (
275293
return jsonToMarkdown(jsonValue);
276294
}
277295

296+
case 'extension': {
297+
if (['listInput', 'tagEditor']?.includes(fieldData?.otherCmsType)) {
298+
if (Array.isArray(lang_value) && lang_value?.length) {
299+
return { value: lang_value?.map((element: any) => ({ key: element, value: element })) }
300+
}
301+
return { value: [] };
302+
}
303+
break;
304+
}
305+
306+
case 'group': {
307+
if (lang_value.lat) return lang_value;
308+
break;
309+
}
310+
278311
default: {
279312
if (Array.isArray(lang_value)) {
280313
return processArrayFields(lang_value, entryId, assetId);
281314
}
282-
console.info("🚀 ~ fieldData?.otherCmsType:", fieldData?.contentstackFieldType)
315+
if (typeof lang_value !== "object") {
316+
return typeof lang_value === "number" ? lang_value
317+
: cleanBrackets(lang_value);
318+
}
319+
console.info("Missing ===>", fieldData?.contentstackFieldType)
283320
break;
284321
}
285322
}
286-
// If lang_value is not an object
287-
if (typeof lang_value !== "object") {
288-
return typeof lang_value === "number" ? lang_value
289-
: cleanBrackets(lang_value);
290-
}
291-
// Check if it's a location (lat/lon)
292-
if (lang_value.lat) return lang_value;
293323
};
294324

295325
// Helper function to clean up brackets in non-numeric lang_value
@@ -324,10 +354,12 @@ const processArrayFields = (array: any, entryId: any, assetId: any) => {
324354

325355
// Helper function to process Rich Text Editor (RTE) or nested object
326356
const processRTEOrNestedObject = (lang_value: any, lang: any, destination_stack_id: string) => {
327-
if (lang_value.data) {
357+
if (lang_value?.data) {
328358
return jsonRTE(lang_value, lang.toLowerCase(), destination_stack_id);
329359
} else {
330-
return lang_value;
360+
const dom = new JSDOM(lang_value);
361+
const htmlDoc = dom.window.document.querySelector("body");
362+
return htmlToJson(htmlDoc);
331363
}
332364
};
333365

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import path from "path";
2+
import fs from 'fs';
3+
import { MIGRATION_DATA_CONFIG, LIST_EXTENSION_UID } from "../constants/index.js";
4+
5+
const {
6+
CUSTOM_MAPPER_FILE_NAME,
7+
EXTENSION_APPS_DIR_NAME,
8+
EXTENSION_APPS_FILE_NAME
9+
} = MIGRATION_DATA_CONFIG;
10+
11+
const writeExtFile = async ({ destinationStackId, extensionData }: any) => {
12+
const dirPath = path.join(process.cwd(), MIGRATION_DATA_CONFIG.DATA, destinationStackId, EXTENSION_APPS_DIR_NAME);
13+
try {
14+
await fs.promises.access(dirPath);
15+
} catch (err) {
16+
try {
17+
await fs.promises.mkdir(dirPath, { recursive: true });
18+
} catch (mkdirErr) {
19+
console.error("🚀 ~ fs.mkdir ~ err:", mkdirErr);
20+
return;
21+
}
22+
}
23+
try {
24+
const filePath = path.join(dirPath, EXTENSION_APPS_FILE_NAME);
25+
await fs.promises.writeFile(filePath, JSON.stringify(extensionData, null, 2));
26+
} catch (writeErr) {
27+
console.error("🚀 ~ fs.writeFile ~ err:", writeErr);
28+
}
29+
}
30+
31+
const getExtension = ({ uid, destinationStackId }: any) => {
32+
if (uid === LIST_EXTENSION_UID) {
33+
return {
34+
"stackHeaders": { "api_key": destinationStackId },
35+
"urlPath": `/extensions/${destinationStackId}`,
36+
"uid": LIST_EXTENSION_UID,
37+
"created_at": "2025-02-18T14:45:22.630Z",
38+
"updated_at": "2025-02-18T14:45:22.630Z",
39+
"created_by": "bltba052dc70a273dd2",
40+
"updated_by": "bltba052dc70a273dd2",
41+
"tags": [],
42+
"_version": 1,
43+
"title": "Key-value Field",
44+
"config": {},
45+
"type": "field",
46+
"data_type": "json",
47+
"multiple": false,
48+
"srcdoc": "<!doctype html>\n<html ng-app=\"keyValuePair\">\n\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular.min.js\"></script>\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/angular-ui-tree/2.22.6/angular-ui-tree.min.js\"></script>\n <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdnjs.cloudflare.com/ajax/libs/angular-ui-tree/2.22.6/angular-ui-tree.min.css\"/>\n <script\n src=\"https://unpkg.com/@contentstack/[email protected]/dist/ui-extension-sdk.js\"\n integrity=\"sha512-Zvd/rx8MHdudDeY916W50kakd+G/7Z/L4VvG0A/NBWVtmAlD7FPHcWDHc5a4WpGSXoXDgy2SLLDVrASxM7IpNQ==\"\n crossorigin=\"anonymous\"></script>\n <link\n rel=\"stylesheet\"\n type=\"text/css\"\n href=\"https://unpkg.com/@contentstack/[email protected]/dist/ui-extension-sdk.css\"\n integrity=\"sha512-YFrH8bTpkhIRTf8jgGmJDWvd56LA9GnRzirfMr/K78ldxsyrfedaMxMCZMC9A9d0LzuVFhkkHWo10HWEoAgjjg==\"\n crossorigin=\"anonymous\"/>\n <style>\n .cs-text-box {\n margin-bottom: 5px;\n margin-left: 10px;\n }\n\n .sort-list {\n display: inline-block;\n width: 100%;\n position: relative;\n }\n\n .sort-list:hover>.drag-icon {\n visibility: visible;\n }\n\n .main-div {\n width: 450px;\n }\n .ml-0 {\n margin-left: 0;\n }\n </style>\n</head>\n\n<body>\n <div ng-controller=\"keyValueCtrl\">\n <div ui-tree=\"treeOptions\">\n <div ui-tree-nodes ng-model=\"list.value\" class=\"main-div\">\n <div ui-tree-node data-ng-repeat=\"x in list.value\" class=\"sort-list\">\n <input type=\"text\" class=\"cs-text-box\" ng-model=\"x.key\" ng-change=\"setValue()\" ng-focus=\"setFocus()\" placeholder=\"Key\" ng-value=\"x.key\"\n /> :\n <input type=\"text\" class=\"cs-text-box ml-0\" ng-model=\"x.value\" ng-change=\"setValue()\" ng-focus=\"setFocus()\" placeholder=\"Value\"\n ng-value=\"x.value\" />\n <i ng-click=\"removeKey($index);setFocus()\" class=\"minus-sign\" ng-show=\"list.value.length !== 1\"></i>\n <i ng-click=\"addMoreKey();setFocus()\" class=\"plus-sign\" ng-show=\"$last\"></i>\n <div class=\"drag-icon\" ng-show=\"list.value.length !== 1\" ui-tree-handle></div>\n </div>\n </div>\n </div>\n\n </div>\n <script>\n var app = angular.module(\"keyValuePair\", ['ui.tree']);\n app.controller(\"keyValueCtrl\", function ($scope, $timeout) {\n\n $scope.addMoreKey = function () {\n $scope.list.value.push({ \"key\": \"\", \"value\": \"\" });\n $scope.setValue();\n };\n\n $scope.removeKey = function (index) {\n $scope.list.value.splice(index, 1);\n $scope.setValue();\n };\n\n $scope.setFocus = function () {\n extensionField.field.setFocus();\n };\n\n $scope.setValue = function () {\n extensionField.field.setData($scope.list);\n\n };\n\n $scope.treeOptions = {\n dragStop: function () {\n $timeout(function () {\n $scope.setValue();\n }, 10);\n }\n };\n\n\n\n ContentstackUIExtension.init().then(function (extension) {\n extensionField = extension;\n $scope.$apply(function () {\n $scope.list = (extension && extension.field && extension.field.getData()) ? extension.field.getData() : {};\n if (angular.equals($scope.list, {})) {\n $scope.list = {\n \"value\": [{ \"key\": \"\", \"value\": \"\" }]\n }\n }\n })\n\n extensionField.window.updateHeight();\n extensionField.window.enableAutoResizing();\n })\n });\n\n </script>\n</body>\n\n</html>"
49+
}
50+
}
51+
return null;
52+
}
53+
54+
const createExtension = async ({ destinationStackId }: any) => {
55+
const extensionPath = path.join(MIGRATION_DATA_CONFIG.DATA, destinationStackId, CUSTOM_MAPPER_FILE_NAME);
56+
const extMapper: any = await fs.promises.readFile(extensionPath, "utf-8").catch(async () => { });
57+
if (extMapper !== undefined) {
58+
const extensionData: any = {};
59+
const extJson = JSON?.parse(extMapper);
60+
const uniqueExtUids: any = [...new Set(extJson?.map?.((item: any) => item.extensionUid))];
61+
for await (const extUid of uniqueExtUids ?? []) {
62+
const extData = getExtension({ uid: extUid, destinationStackId });
63+
if (extData) {
64+
extensionData[extUid] = extData;
65+
}
66+
}
67+
await writeExtFile({ destinationStackId, extensionData })
68+
}
69+
}
70+
71+
72+
73+
export const extensionService = {
74+
createExtension
75+
}

api/src/services/migration.service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { setLogFilePath } from "../server.js";
1919
import fs from 'fs';
2020
import { contentfulService } from "./contentful.service.js";
2121
import { marketPlaceAppService } from "./marketplace.service.js";
22+
import { extensionService } from "./extension.service.js";
2223

2324

2425

@@ -223,6 +224,7 @@ const startTestMigration = async (req: Request): Promise<any> => {
223224
await setLogFilePath(loggerPath);
224225
const contentTypes = await fieldAttacher({ orgId, projectId, destinationStackId: project?.current_test_stack_id, region, user_id });
225226
await marketPlaceAppService?.createAppManifest({ orgId, destinationStackId: project?.current_test_stack_id, region, userId: user_id });
227+
await extensionService?.createExtension({ destinationStackId: project?.current_test_stack_id });
226228
switch (cms) {
227229
case CMS.SITECORE_V8:
228230
case CMS.SITECORE_V9:
@@ -256,9 +258,9 @@ const startTestMigration = async (req: Request): Promise<any> => {
256258
await contentfulService?.createRefrence(file_path, project?.current_test_stack_id, projectId);
257259
await contentfulService?.createWebhooks(file_path, project?.current_test_stack_id, projectId);
258260
await contentfulService?.createEnvironment(file_path, project?.current_test_stack_id, projectId);
259-
// await contentfulService?.createAssets(file_path, project?.current_test_stack_id, projectId);
261+
await contentfulService?.createAssets(file_path, project?.current_test_stack_id, projectId);
260262
await contentfulService?.createEntry(file_path, project?.current_test_stack_id, projectId, contentTypes);
261-
// await contentfulService?.createVersionFile(project?.current_test_stack_id, projectId);
263+
await contentfulService?.createVersionFile(project?.current_test_stack_id, projectId);
262264
break;
263265
}
264266
default:

api/src/utils/content-type-creator.utils.ts

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from 'path';
33
import _ from 'lodash';
44
import customLogger from './custom-logger.utils.js';
55
import { getLogMessage } from './index.js';
6-
import { MIGRATION_DATA_CONFIG } from '../constants/index.js';
6+
import { LIST_EXTENSION_UID, MIGRATION_DATA_CONFIG } from '../constants/index.js';
77
import { contentMapperService } from "../services/contentMapper.service.js";
88
import appMeta from '../constants/app/index.json';
99

@@ -12,7 +12,8 @@ const {
1212
GLOBAL_FIELDS_DIR_NAME,
1313
CONTENT_TYPES_DIR_NAME,
1414
CONTENT_TYPES_SCHEMA_FILE,
15-
EXTENSIONS_MAPPER_DIR_NAME
15+
EXTENSIONS_MAPPER_DIR_NAME,
16+
CUSTOM_MAPPER_FILE_NAME
1617
} = MIGRATION_DATA_CONFIG;
1718

1819
interface Group {
@@ -73,7 +74,7 @@ const arrangGroups = ({ schema, newStack }: any) => {
7374
return dtSchema;
7475
}
7576

76-
const saveAppMapper = async ({ marketPlacePath, data }: any) => {
77+
const saveAppMapper = async ({ marketPlacePath, data, fileName }: any) => {
7778
try {
7879
await fs.promises.access(marketPlacePath);
7980
} catch (err) {
@@ -84,7 +85,7 @@ const saveAppMapper = async ({ marketPlacePath, data }: any) => {
8485
return;
8586
}
8687
}
87-
const marketPlaceFilePath = path.join(marketPlacePath, EXTENSIONS_MAPPER_DIR_NAME);
88+
const marketPlaceFilePath = path.join(marketPlacePath, fileName);
8889
const newData: any = await fs.promises.readFile(marketPlaceFilePath, "utf-8").catch(async () => {
8990
await fs.promises.writeFile(marketPlaceFilePath, JSON.stringify([data]));
9091
});
@@ -475,20 +476,50 @@ const convertToSchemaFormate = ({ field, advanced = true, marketPlacePath }: any
475476
const title = field?.title?.split?.(' ')?.[0];
476477
const appDetails = appMeta?.entries?.find?.((item: any) => item?.title === appName);
477478
if (appDetails?.uid) {
478-
saveAppMapper({ marketPlacePath, data: { appUid: appDetails?.app_uid, extensionUid: `${appDetails?.uid}-cs.cm.stack.custom_field` } });
479+
saveAppMapper({
480+
marketPlacePath,
481+
data: { appUid: appDetails?.app_uid, extensionUid: `${appDetails?.uid}-cs.cm.stack.custom_field` },
482+
fileName: EXTENSIONS_MAPPER_DIR_NAME
483+
});
479484
return {
480485
"display_name": title,
481486
"extension_uid": appDetails?.uid,
482487
"field_metadata": {
483488
"extension": true
484489
},
485490
"uid": field?.uid,
486-
"mandatory": false,
487-
"non_localizable": false,
488-
"unique": false,
489491
"config": {},
490492
"data_type": "json",
491-
"multiple": false
493+
"multiple": field?.advanced?.multiple ?? false,
494+
"mandatory": field?.advanced?.mandatory ?? false,
495+
"unique": field?.advanced?.unique ?? false,
496+
"non_localizable": field.advanced?.nonLocalizable ?? false,
497+
}
498+
}
499+
break;
500+
}
501+
502+
case 'extension': {
503+
if (['listInput', 'tagEditor']?.includes(field?.otherCmsType)) {
504+
const extensionUid = LIST_EXTENSION_UID;
505+
saveAppMapper({
506+
marketPlacePath,
507+
data: { extensionUid },
508+
fileName: CUSTOM_MAPPER_FILE_NAME
509+
});
510+
return {
511+
"display_name": field?.title,
512+
"uid": field?.uid,
513+
"extension_uid": extensionUid,
514+
"field_metadata": {
515+
"extension": true
516+
},
517+
"config": {},
518+
"multiple": field?.advanced?.multiple ?? false,
519+
"mandatory": field?.advanced?.mandatory ?? false,
520+
"unique": field?.advanced?.unique ?? false,
521+
"non_localizable": field.advanced?.nonLocalizable ?? false,
522+
"data_type": "json",
492523
}
493524
}
494525
break;

upload-api/migration-contentful/libs/contentTypeMapper.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,10 @@ const contentTypeMapper = (data) => {
290290
acc.push(createDropdownOrRadioFieldObject(item, item.widgetId));
291291
break;
292292
case 'tagEditor':
293-
acc.push(createFieldObject(item, 'json', 'json'));
293+
case 'listInput': {
294+
acc.push(createFieldObject(item, 'extension', 'extension'))
294295
break;
296+
}
295297
}
296298
break;
297299
case 'Boolean':

0 commit comments

Comments
 (0)