Skip to content
Open
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
4 changes: 3 additions & 1 deletion api/package-lock.json

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

2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"html-to-json-parser": "^2.0.1",
"jsdom": "^24.1.0",
"jsonwebtoken": "^9.0.3",
"lodash": "^4.17.21",
"lodash": "^4.18.1",
"lowdb": "^7.0.1",
"mkdirp": "^3.0.1",
"mysql2": "^3.16.2",
Expand Down
21 changes: 15 additions & 6 deletions api/src/services/wordpress.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,11 @@ async function saveEntry(fields: any, entry: any, file_path: string, assetData
const $ = cheerio.load(xmlData, { xmlMode: true });
const items = $('item');
const entryData: Record<string, any> = {};
const fieldList = Array.isArray(fields) ? fields : [];
const hasField = (uid: string) =>
fieldList.some(
(field: any) => field?.contentstackFieldUid === uid || field?.uid === uid
);

try {
if(entry ){
Expand Down Expand Up @@ -584,16 +589,20 @@ async function saveEntry(fields: any, entry: any, file_path: string, assetData
// Pass individual content to createSchema
entryData[uid] = await createSchema(fields, blocksJson, item?.title, uid, assetData, duplicateBlockMappings);
const categoryReference = extractCategoryReference(item?.['category']);
if (categoryReference?.length > 0) {
entryData[uid]['taxonomies'] = taxonomies;
if (hasField("taxonomies") && categoryReference?.length > 0) {
entryData[uid]["taxonomies"] = taxonomies;
}
const termsReference = extractTermsReference(item?.['category']);
if(termsReference?.length > 0) {
entryData[uid]['terms'] = terms;
if (hasField("terms") && termsReference?.length > 0) {
entryData[uid]["terms"] = terms;
}
entryData[uid]["tags"] = tags?.map((tag: any) => tag?.text) || [];
if (hasField("author")) {
entryData[uid]["author"] =
authorData?.filter((author: any) => author?.uid) || [];
}
entryData[uid]['tags'] = tags?.map((tag: any) => tag?.text);
entryData[uid]['author'] = authorData;
entryData[uid]['locale'] = locale;
entryData[uid]["publish_details"] = [];
entryData[uid]['publish_details'] = [];


Expand Down
4 changes: 3 additions & 1 deletion upload-api/migration-wordpress/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractLocale = exports.extractContentTypes = void 0;
exports.extractEntries = exports.extractLocale = exports.extractContentTypes = void 0;
const contentTypes_1 = __importDefault(require("./libs/contentTypes"));
exports.extractContentTypes = contentTypes_1.default;
//import contentTypeMaker from './libs/contentTypeMapper';
const extractLocale_1 = __importDefault(require("./libs/extractLocale"));
exports.extractLocale = extractLocale_1.default;
const extractEntries_1 = __importDefault(require('./libs/extractEntries'));
exports.extractEntries = extractEntries_1.default;
10 changes: 6 additions & 4 deletions upload-api/migration-wordpress/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import extractContentTypes from './libs/contentTypes';
//import contentTypeMaker from './libs/contentTypeMapper';
import extractLocale from './libs/extractLocale';
import extractEntries from './libs/extractEntries';

export {
extractContentTypes,
//contentTypeMaker,
extractLocale
}
extractContentTypes,
//contentTypeMaker,
extractLocale,
extractEntries
};
117 changes: 117 additions & 0 deletions upload-api/migration-wordpress/libs/extractEntries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"use strict";
const fs = require("fs");
const path = require("path");
const config = require("../config/index.json");

const { contentTypes: contentTypesConfig } = config?.modules ?? {};
const contentTypeFolderPath = path.resolve(config?.data, contentTypesConfig?.dirName);
const EXCLUDED_POST_TYPES = new Set(['attachment', 'wp_global_styles', 'wp_navigation']);

const normalizeArray = (value) => {
if (!value) return [];
return Array.isArray(value) ? value : [value];
};

const idCorrector = (id) => {
const normalized = id?.replace(/[-{}]/g, '');
return normalized ? normalized.toLowerCase() : id;
};

const getEntryName = (item) => {
if (typeof item?.title === 'string' && item.title.trim()) return item.title.trim();
if (item?.title?.text) return String(item.title.text).trim();
if (typeof item?.['wp:post_name'] === 'string' && item['wp:post_name'].trim()) {
return item['wp:post_name'].trim();
}
return 'Untitled Entry';
};

/** Align with api wordpress.service entry uid: idCorrector(`posts_${wp:post_id}`). */
const getSourceEntryUid = (item) => {
const postId = item?.['wp:post_id'];
if (postId != null && String(postId).trim() !== '') {
return idCorrector(`posts_${postId}`);
}
const candidate =
item?.guid?.text ?? item?.guid ?? item?.link ?? getEntryName(item);
return idCorrector(String(candidate || ''));
};

const getEntryLanguage = (item, channelLanguage) => {
const postMeta = normalizeArray(item?.['wp:postmeta']);
const languageMeta = postMeta.find((meta) => {
const key = String(meta?.['wp:meta_key'] || '').toLowerCase();
return key === 'language' || key === '_language' || key === 'locale' || key === '_locale';
});

const metaLanguage = languageMeta?.['wp:meta_value'];
if (typeof metaLanguage === 'string' && metaLanguage.trim()) {
return metaLanguage.trim();
}

if (typeof channelLanguage === 'string' && channelLanguage.trim()) {
return channelLanguage.trim();
}

return 'en-us';
};

const extractEntries = async (filePath, contentTypeData = []) => {
try {
const rawData = await fs.promises.readFile(filePath, 'utf8');
const jsonData = JSON.parse(rawData);
const items = normalizeArray(jsonData?.rss?.channel?.item);
const channelLanguage = jsonData?.rss?.channel?.language;

const groupedByType = items?.reduce((acc, item) => {
const postType = item?.['wp:post_type'] || 'unknown';
if (EXCLUDED_POST_TYPES.has(postType)) return acc;
if (!acc[postType]) acc[postType] = [];
acc[postType].push(item);
return acc;
}, {});

const updatedTypes = contentTypeData.map((ct) => ({ ...ct }));

for (const [type, entries] of Object.entries(groupedByType)) {
const entryMapping = normalizeArray(entries)
.map((item) => {
const otherCmsEntryUid = getSourceEntryUid(item);
if (!otherCmsEntryUid) return null;
return {
contentTypeUid: type,
entryName: getEntryName(item),
otherCmsEntryUid: `posts_${otherCmsEntryUid}`,
otherCmsCTName: type,
language: getEntryLanguage(item, channelLanguage),
isUpdate: false
};
})
.filter(Boolean);

const contentTypeFilePath = path.join(contentTypeFolderPath, `${type.toLowerCase()}.json`);
if (fs.existsSync(contentTypeFilePath)) {
const ctFile = JSON.parse(await fs.promises.readFile(contentTypeFilePath, 'utf8'));
ctFile.entryMapping = entryMapping;
await fs.promises.writeFile(contentTypeFilePath, JSON.stringify(ctFile, null, 4), 'utf8');
}

const index = updatedTypes.findIndex(
(ct) =>
ct?.otherCmsUid?.toLowerCase?.() === type.toLowerCase() ||
ct?.contentstackUid?.toLowerCase?.() === type.toLowerCase()
);
if (index >= 0) {
updatedTypes[index] = { ...updatedTypes[index], entryMapping };
}
}

return updatedTypes;
} catch (error) {
console.error('Error while extracting WordPress entries:', error?.message || error);
return contentTypeData;
}
};

module.exports = extractEntries;
module.exports.default = extractEntries;
126 changes: 126 additions & 0 deletions upload-api/migration-wordpress/libs/extractEntries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import fs from 'fs';
import path from 'path';
import config from '../config/index.json';

const { contentTypes: contentTypesConfig } = config?.modules;
const contentTypeFolderPath = path.resolve(config?.data, contentTypesConfig?.dirName);

const EXCLUDED_POST_TYPES = new Set(['attachment', 'wp_global_styles', 'wp_navigation']);

const normalizeArray = <T>(value: T | T[] | undefined): T[] => {
if (!value) return [];
return Array.isArray(value) ? value : [value];
};

const idCorrector = (id: string) => {
const normalized = id?.replace(/[-{}]/g, '');
return normalized ? normalized.toLowerCase() : id;
};

const getEntryName = (item: any): string => {
if (typeof item?.title === 'string' && item.title.trim()) {
return item.title.trim();
}
if (item?.title?.text) {
return String(item.title.text).trim();
}
if (typeof item?.['wp:post_name'] === 'string' && item['wp:post_name'].trim()) {
return item['wp:post_name'].trim();
}
return 'Untitled Entry';
};

/**
* Must match api WordPress entry keys: idCorrector(`posts_${wp:post_id}`) in wordpress.service.ts.
* Raw post id only caused otherCmsEntryUid to diverge from uid-map / entry JSON keys → entry mapper showed "-".
*/
const getSourceEntryUid = (item: any): string => {
const postId = item?.['wp:post_id'];
if (postId != null && String(postId).trim() !== '') {
return idCorrector(`posts_${postId}`);
}
const candidate =
item?.guid?.text ?? item?.guid ?? item?.link ?? getEntryName(item);
return idCorrector(String(candidate || ''));
};

const getEntryLanguage = (item: any, channelLanguage?: string): string => {
const postMeta = normalizeArray(item?.['wp:postmeta']);
const languageMeta = postMeta.find((meta: any) => {
const key = String(meta?.['wp:meta_key'] || '').toLowerCase();
return key === 'language' || key === '_language' || key === 'locale' || key === '_locale';
});

const metaLanguage = languageMeta?.['wp:meta_value'];
if (typeof metaLanguage === 'string' && metaLanguage.trim()) {
return metaLanguage.trim();
}

if (typeof channelLanguage === 'string' && channelLanguage.trim()) {
return channelLanguage.trim();
}

return 'en-us';
};

const extractEntries = async (filePath: string, contentTypeData: any[] = []) => {
try {
const rawData = await fs.promises.readFile(filePath, 'utf8');
const jsonData = JSON.parse(rawData);
const items = normalizeArray(jsonData?.rss?.channel?.item);
const channelLanguage = jsonData?.rss?.channel?.language;

const groupedByType = items?.reduce((acc: Record<string, any[]>, item: any) => {
const postType = item?.['wp:post_type'] || 'unknown';
if (EXCLUDED_POST_TYPES.has(postType)) return acc;
if (!acc[postType]) acc[postType] = [];
acc[postType].push(item);
return acc;
}, {});

const updatedTypes = contentTypeData?.map((ct) => ({ ...ct }));

for (const [type, entries] of Object.entries(groupedByType)) {
const entryMapping = normalizeArray(entries)
.map((item: any) => {
const otherCmsEntryUid = getSourceEntryUid(item);
if (!otherCmsEntryUid) return null;
return {
contentTypeUid: type,
entryName: getEntryName(item),
otherCmsEntryUid: `posts_${otherCmsEntryUid}`,
otherCmsCTName: type,
language: getEntryLanguage(item, channelLanguage),
isUpdate: false
};
})
.filter(Boolean);

const contentTypeFilePath = path.join(contentTypeFolderPath, `${type.toLowerCase()}.json`);
if (fs.existsSync(contentTypeFilePath)) {
const ctFile = JSON.parse(await fs.promises.readFile(contentTypeFilePath, 'utf8'));
ctFile.entryMapping = entryMapping;
await fs.promises.writeFile(contentTypeFilePath, JSON.stringify(ctFile, null, 4), 'utf8');
}

const index = updatedTypes.findIndex(
(ct: any) =>
ct?.otherCmsUid?.toLowerCase?.() === type.toLowerCase() ||
ct?.contentstackUid?.toLowerCase?.() === type.toLowerCase()
);
if (index >= 0) {
updatedTypes[index] = {
...updatedTypes[index],
entryMapping
};
}
}

return updatedTypes;
} catch (error: any) {
console.error('Error while extracting WordPress entries:', error?.message || error);
return contentTypeData;
}
};

export default extractEntries;
2 changes: 1 addition & 1 deletion upload-api/migration-wordpress/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "migration-v2-wordpress",
"name": "migration-wordpress",
"version": "1.0.0",
"description": "",
"main": "build/index.js",
Expand Down
21 changes: 17 additions & 4 deletions upload-api/migration-wordpress/utils/parseUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,23 @@ const setupWordPressBlocks = (rawContent) => __awaiter(void 0, void 0, void 0, f
// Now import WordPress packages after setting up globals
const wpBlocks = yield Promise.resolve().then(() => __importStar(require('@wordpress/blocks')));
const { parse } = yield Promise.resolve().then(() => __importStar(require('@wordpress/blocks')));
const { registerCoreBlocks } = yield Promise.resolve().then(() => __importStar(require('@wordpress/block-library')));
(_a = wpBlocks.__unstableSetDebugLevel) === null || _a === void 0 ? void 0 : _a.call(wpBlocks, 'none');
registerCoreBlocks();
const blocks = parse(rawContent);
return blocks;
try {
const { registerCoreBlocks } = yield Promise.resolve().then(() => __importStar(require('@wordpress/block-library')));
registerCoreBlocks();
}
catch (error) {
// Some environments cannot fully initialize Gutenberg internals (e.g. getSettings).
// Continue with parser-only mode so mapper generation does not fail.
console.warn('WordPress core blocks registration failed, using parser-only mode:', (error === null || error === void 0 ? void 0 : error.message) || error);
}
try {
const blocks = parse(rawContent);
return Array.isArray(blocks) ? blocks : [];
}
catch (error) {
console.warn('WordPress block parsing failed, returning empty blocks:', (error === null || error === void 0 ? void 0 : error.message) || error);
return [];
}
});
exports.setupWordPressBlocks = setupWordPressBlocks;
Loading
Loading