Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@
"typescript": "^5.4.3"
},
"keywords": []
}
}
100 changes: 85 additions & 15 deletions api/src/services/aem.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,14 +356,14 @@ export async function fetchFilesByQuery(

function addUidToEntryMapping(
entryMapping: Record<string, string[]>,
contentType: ContentType | undefined,
resolvedCtUid: string,
uid: string
) {
if (!contentType?.contentstackUid) return;
if (!entryMapping[contentType.contentstackUid]) {
entryMapping[contentType.contentstackUid] = [];
if (!resolvedCtUid) return;
if (!entryMapping[resolvedCtUid]) {
entryMapping[resolvedCtUid] = [];
}
entryMapping[contentType.contentstackUid].push(uid);
entryMapping[resolvedCtUid].push(uid);
}

function uidCorrector(str: string): string {
Expand Down Expand Up @@ -587,17 +587,73 @@ function processFieldsRecursive(
for (const field of fields) {
switch (field?.contentstackFieldType) {
case 'modular_blocks': {
const modularData = items?.[field?.uid] ? items?.[field?.uid] : items?.[':items'];
const uid = getLastKey(field?.contentstackFieldUid);
const modularBlocksArray: any[] = [];
const aemSourcePath = field?.backupFieldUid || field?.otherCmsField?.replace?.(/ > /g, '.') || '';
const aemSourceField = getLastKey(aemSourcePath);
const sourceData = aemSourceField && aemSourceField in items
? items[aemSourceField]
: items;

if (Array.isArray(field?.schema)) {
const itemsData = modularData?.[':items'] ?? modularData;
const value = processFieldsRecursive(field.schema, itemsData, title, pathToUidMap, assetDetailsMap);
const uid = getLastKey(field?.contentstackFieldUid);
obj[uid] = value;
// Process each child block schema
for (const childBlockSchema of field.schema) {
if (childBlockSchema?.contentstackFieldType === 'modular_blocks_child') {
const blockTypeUid = getLastKey(childBlockSchema?.contentstackFieldUid);
const aemChildPath = childBlockSchema?.backupFieldUid ||
childBlockSchema?.otherCmsField?.replace?.(/ > /g, '.') || '';
const aemBlockUid = getLastKey(aemChildPath);

// Find matching items in the source data
const itemsList: any[] = (() => {
if (Array.isArray(sourceData)) return sourceData;
const order = Array.isArray(sourceData?.[':itemsOrder']) ? sourceData[':itemsOrder'] : null;
const map = sourceData?.[':items'] || sourceData;
if (order && map) return order.map((k: string) => map?.[k]).filter(Boolean);
return Object.values(map || {});
})();

// Process each item that matches current block type
for (const item of itemsList) {
if (!item || typeof item !== 'object') continue;

const typeValue = (item as any)[':type'] || '';
const getTypeComp = getLastKey(typeValue, '/');
const isMatch = (aemBlockUid && getTypeComp === aemBlockUid) ||
getTypeComp === blockTypeUid ||
(aemBlockUid && item?.['sling:resourceType']?.includes(aemBlockUid)) ||
item?.['sling:resourceType']?.includes(blockTypeUid);

if (isMatch) {
// Process the fields within this child block
if (Array.isArray(childBlockSchema?.schema)) {
const blockData = processFieldsRecursive(
childBlockSchema.schema,
item,
title,
pathToUidMap,
assetDetailsMap
);

if (blockData && Object.keys(blockData).length) {
const blockEntry: any = {};
blockEntry[blockTypeUid] = blockData;
modularBlocksArray.push(blockEntry);
}
}
}
}
}
}
}

obj[uid] = modularBlocksArray;
break;
}

case 'modular_blocks_child': {
// This case is handled in the modular_blocks case above
// This is for backwards compatibility
const list: any[] = (() => {
if (Array.isArray(items)) return items;
const order = Array.isArray(items?.[':itemsOrder']) ? items[':itemsOrder'] : null;
Expand All @@ -615,7 +671,14 @@ function processFieldsRecursive(
const getTypeComp = getLastKey(typeValue, '/');
if (getTypeComp !== blockTypeUid) continue;

const compValue = processFieldsRecursive(field.schema, value, title, pathToUidMap, assetDetailsMap);
const compValue = processFieldsRecursive(
field.schema,
value,
title,
pathToUidMap,
assetDetailsMap
);

if (compValue && Object.keys(compValue).length) {
const objData: any = {};
objData[uid] = compValue;
Expand Down Expand Up @@ -1206,7 +1269,8 @@ const createEntry = async ({
contentTypes,
destinationStackId,
projectId,
project
project,
keyMapper
}: CreateEntryOptions) => {
const srcFunc = 'createEntry';
const baseDir = path.join(baseDirName, destinationStackId);
Expand Down Expand Up @@ -1262,9 +1326,15 @@ const createEntry = async ({
data.publish_details = [];

if (contentType?.contentstackUid && data && mappedLocale) {
const mappedValue = (keyMapper as Record<string, string> | undefined)?.[contentType.contentstackUid];
const resolvedCtUid: string =
mappedValue && mappedValue !== ''
? mappedValue
: contentType.contentstackUid;

const message = getLogMessage(
srcFunc,
`Entry title "${data?.title}"(${contentType?.contentstackUid}) in the ${mappedLocale} locale has been successfully transformed.`,
`Entry title "${data?.title}"(${resolvedCtUid}) in the ${mappedLocale} locale has been successfully transformed.`,
{}
);
await customLogger(
Expand All @@ -1273,8 +1343,8 @@ const createEntry = async ({
'info',
message
);
addEntryToEntriesData(entriesData, contentType.contentstackUid, data, mappedLocale);
addUidToEntryMapping(entryMapping, contentType, uid);
addEntryToEntriesData(entriesData, resolvedCtUid, data, mappedLocale);
addUidToEntryMapping(entryMapping, resolvedCtUid, uid);
}
}
}
Expand Down
103 changes: 84 additions & 19 deletions api/src/utils/content-type-creator.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,11 @@ function getLastSegmentNew(str: string, separator: string): string {
}

export function buildSchemaTree(fields: any[], parentUid = '', parentType = '', oldParentUid = ''): any[] {

if (!Array.isArray(fields)) {
console.warn('buildSchemaTree called with invalid fields:', fields);
return [];
}

// Build a lookup map for O(1) access
const fieldMap = new Map<string, any>();
fields?.forEach(f => {
Expand All @@ -267,7 +267,7 @@ export function buildSchemaTree(fields: any[], parentUid = '', parentType = '',
return remainder && !remainder?.includes('.');
}

// Fallback: check if field is a direct child of oldPrentUid (if provided and different)
// Fallback: check if field is a direct child of oldParentUid (if provided and different from parentUid)
if (oldParentUid && oldParentUid !== parentUid && fieldUid?.startsWith(oldParentUid + '.')) {
const remainder = fieldUid?.substring(oldParentUid.length + 1);
// Verify it's exactly one level deeper (no more dots in remainder)
Expand All @@ -293,24 +293,24 @@ export function buildSchemaTree(fields: any[], parentUid = '', parentType = '',
const fieldUid = field?.contentstackFieldUid;
const fieldType = field?.contentstackFieldType;
const oldFieldUid = field?.backupFieldUid;

// Check if this field has direct children (exactly one level deeper)
const hasChildren = fields.some(f => {
const fUid = f?.contentstackFieldUid || '';
if (!fUid) return false;

// Check if field starts with current fieldUid and is exactly one level deeper
if (fieldUid && fUid?.startsWith(fieldUid + '.')) {
const remainder = fUid?.substring(fieldUid.length + 1);
return remainder && !remainder?.includes('.');
}
// Check if field starts with oldFieldtUid and is exactly one level deeper

// Check if field starts with oldFieldUid and is exactly one level deeper
if (oldFieldUid && fUid?.startsWith(oldFieldUid + '.')) {
const remainder = fUid?.substring(oldFieldUid.length + 1);
return remainder && !remainder?.includes('.');
}

return false;
});

Expand All @@ -332,6 +332,7 @@ export function buildSchemaTree(fields: any[], parentUid = '', parentType = '',
...child,
uid: childUid,
display_name: childDisplay,
// Recursively build schema for fields inside this child block
schema: buildSchemaTree(fields, child.contentstackFieldUid, 'modular_blocks_child', child?.backupFieldUid)
};
});
Expand Down Expand Up @@ -969,7 +970,7 @@ const existingCtMapper = async ({ keyMapper, contentTypeUid, projectId, region,
const ctUid = keyMapper?.[contentTypeUid];

if(type === 'global_field') {

const req: any = {
params: {
projectId,
Expand Down Expand Up @@ -1028,22 +1029,86 @@ const mergeTwoCts = async (ct: any, mergeCts: any) => {
"singleton": false,
}
}

for await (const field of ctData?.schema ?? []) {
// Handle regular groups
if (field?.data_type === 'group') {
const currentGroup = mergeCts?.schema?.find((grp: any) => grp?.uid === field?.uid &&
grp?.data_type === 'group');
const group = [];
for await (const fieldGp of currentGroup?.schema ?? []) {
const fieldNst = field?.schema?.find((fld: any) => fld?.uid === fieldGp?.uid &&
fld?.data_type === fieldGp?.data_type);
if (fieldNst === undefined) {
group?.push(fieldGp);
const currentGroup = mergeCts?.schema?.find((grp: any) =>
grp?.uid === field?.uid && grp?.data_type === 'group'
);

if (currentGroup) {
const group = [];
for await (const fieldGp of currentGroup?.schema ?? []) {
const fieldNst = field?.schema?.find((fld: any) =>
fld?.uid === fieldGp?.uid && fld?.data_type === fieldGp?.data_type
);
if (fieldNst === undefined) {
group?.push(fieldGp);
}
}
field.schema = removeDuplicateFields([...field?.schema ?? [], ...group]);
}
}

// Handle modular blocks
if (field?.data_type === 'blocks') {
const currentModularBlock = mergeCts?.schema?.find((mb: any) =>
mb?.uid === field?.uid && mb?.data_type === 'blocks'
);

if (currentModularBlock && currentModularBlock?.blocks) {
// Iterate through each child block in the source
for (const sourceBlock of field?.blocks ?? []) {
// Find matching child block in target by UID
const targetBlock = currentModularBlock?.blocks?.find((tb: any) =>
tb?.uid === sourceBlock?.uid
);

if (targetBlock && targetBlock?.schema) {
// Merge the schemas of matching child blocks
const additionalFields = [];

for (const targetField of targetBlock?.schema ?? []) {
// Check if this field already exists in source block
const existsInSource = sourceBlock?.schema?.find((sf: any) =>
sf?.uid === targetField?.uid && sf?.data_type === targetField?.data_type
);

if (!existsInSource) {
additionalFields.push(targetField);
}
}

// Merge source and target fields, removing duplicates
sourceBlock.schema = removeDuplicateFields([
...sourceBlock?.schema ?? [],
...additionalFields
]);
}
}

// Add any child blocks from target that don't exist in source
const additionalBlocks = [];
for (const targetBlock of currentModularBlock?.blocks ?? []) {
const existsInSource = field?.blocks?.find((sb: any) =>
sb?.uid === targetBlock?.uid
);

if (!existsInSource) {
additionalBlocks.push(targetBlock);
}
}

field.blocks = removeDuplicateFields([
...field?.blocks ?? [],
...additionalBlocks
]);
}
field.schema = removeDuplicateFields([...field?.schema ?? [], ...group]);
}
}
ctData.schema = await mergeArrays(ctData?.schema, mergeCts?.schema) ?? [];

return ctData;
}

Expand All @@ -1066,7 +1131,7 @@ export const contenTypeMaker = async ({ contentType, destinationStackId, project

// Safe: ensures we never pass undefined to the builder
const ctData: any[] = buildSchemaTree(contentType?.fieldMapping || []);

// Use the deep converter that properly handles groups & modular blocks
for (const item of ctData) {
if (item?.isDeleted === true) continue;
Expand Down Expand Up @@ -1098,4 +1163,4 @@ export const contenTypeMaker = async ({ contentType, destinationStackId, project
} else {
console.info(contentType?.contentstackUid, 'missing');
}
};
}
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@
"postcss": ">=8.4.31",
"serialize-javascript": ">=6.0.2",
"@babel/runtime": ">=7.26.10",
"tmp": "^0.2.5"
"tmp": "^0.2.5",
"lodash": "^4.17.23",
"lodash-es": "^4.17.23",
"undici": "^7.18.2"
},
"husky": {
"hooks": {
Expand All @@ -55,4 +58,4 @@
"tmp": "^0.2.5",
"validator": "^13.15.26"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export interface FieldMetadata {
allow_json_rte?: boolean;
}
export interface ContentTypesSchema {
blocks?: ContentTypesSchema[];
display_type: string;
data_type?:
| 'text'
Expand All @@ -116,7 +117,9 @@ export interface ContentTypesSchema {
| 'boolean'
| 'link'
| 'Marketplace app'
| 'Extension';
| 'Extension'
| 'blocks'
| 'modular_blocks_child';
display_name: string;
enum?: any;
error_messages?: ErrorMessages;
Expand Down
Loading