Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f2b2a3f
feat(cmg-703): stack-to-stack migration with audit report
May 27, 2026
279ac3d
Fixed test cases
May 27, 2026
66a1390
Fixed test cases
May 27, 2026
4fa2f23
Fixed test cases
May 27, 2026
684eb08
Fixed Snyk issues
May 28, 2026
c2f6712
feat(tests): update auth and config tests for improved error handling…
May 28, 2026
bcc30f8
fix(snyk): gate exportPath before validate/audit reads
May 28, 2026
2f82fd8
fix(snyk): char-allowlist rebuild in assertExportPathInAllowedRoot
May 28, 2026
ea6bc7f
chore(pr-review): add ?. chaining + route exportCli stdio through logger
Jun 1, 2026
af84afd
fix(pr-review): audit URL regions, magic numbers, audit gate, step nav
Jun 2, 2026
a4f783c
fix(exportCli): timeout + stdio through logger
Jun 2, 2026
bebd527
fix(upload-api): use saveZip/saveJson actual path for mapper
Jun 2, 2026
89b1858
fix(contentful): don't drop entries when source locale isn't mapped
Jun 2, 2026
5870de1
refactor(pr-review): consolidate constants, switch case, drop en-us f…
Jun 3, 2026
e08a548
refactor(runCli): own backup lifecycle, drop broken cleanup scanner
Jun 3, 2026
c3f2771
fix: post-rebase test fixes (duplicate import, missing it block)
Jun 4, 2026
dd4936d
fix: gate CMS-specific pre-import + iteration defaults for stack-to-s…
Jun 4, 2026
3b2df68
fix(pr-review): address copilot review comments
Jun 8, 2026
52acac5
feat(source-session): persist regional source token on backend user r…
Jun 8, 2026
a699778
fix(upload-api): annotate handleFileProcessing return type
Jun 8, 2026
aad3065
test: cover source-session endpoints + migrationSourceSession utility
Jun 8, 2026
f9a9ce0
fix(migration): stub persistAutoMappedContentMapper after rebase
Jun 11, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,4 @@ app.json

# Test coverage (global)
coverage/
export-stack/
45 changes: 33 additions & 12 deletions api/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,26 @@ export const regionalApiHosts = {
AU: 'au-api.contentstack.com',
GCP_EU: 'gcp-eu-api.contentstack.com',
};

/** Web-app base URLs per region (used for building Contentstack management URLs). */
export const regionalAppHosts: Record<string, string> = {
NA: 'https://app.contentstack.com',
EU: 'https://eu-app.contentstack.com',
AZURE_NA: 'https://azure-na-app.contentstack.com',
AZURE_EU: 'https://azure-eu-app.contentstack.com',
GCP_NA: 'https://gcp-na-app.contentstack.com',
GCP_EU: 'https://gcp-eu-app.contentstack.com',
AU: 'https://au-app.contentstack.com',
};
export const CMS = {
CONTENTFUL: 'contentful',
SITECORE_V8: 'sitecore v8',
SITECORE_V9: 'sitecore v9',
SITECORE_V10: 'sitecore v10',
WORDPRESS: 'wordpress',
DRUPAL: 'drupal',
AEM: 'aem',
CONTENTSTACK: "contentstack",
CONTENTFUL: "contentful",
SITECORE_V8: "sitecore v8",
SITECORE_V9: "sitecore v9",
SITECORE_V10: "sitecore v10",
WORDPRESS: "wordpress",
DRUPAL: "drupal",
AEM: "aem",
};
export const MODULES = [
'Project',
Expand Down Expand Up @@ -95,6 +107,9 @@ export const HTTP_TEXTS = {
FILE_FORMAT_UPDATED: "Project's migration file format updated successfully",
DESTINATION_STACK_UPDATED:
"Project's migration destination stack updated successfully",
AUDIT_SELECTIONS_UPDATED: 'Audit selections updated successfully',
CS_SOURCE_EXPORT_PATH_REQUIRED:
'Source export path is required for Contentstack stack migration',
DESTINATION_STACK_NOT_FOUND: 'Destination stack does not exist',
DESTINATION_STACK_ERROR: 'Error occurred during verifying destination stack',
INVALID_ID: 'Provided $ ID is invalid.',
Expand Down Expand Up @@ -127,7 +142,7 @@ export const HTTP_TEXTS = {
CONTENTMAPPER_NOT_FOUND:
'Sorry, the requested content mapper id does not exists.',
ADMIN_LOGIN_ERROR:
"Sorry, You Don't have admin access in any of the Organisation",
'You are not a member of any Contentstack organization in this region (or organization list is empty).',
PROJECT_DELETE: 'Project Deleted Successfully',
PROJECT_REVERT: 'Project Reverted Successfully',
LOGS_NOT_FOUND: 'Sorry, no logs found for requested stack migration.',
Expand Down Expand Up @@ -175,9 +190,10 @@ export const PROJECT_STATUS = {
export const STEPPER_STEPS: any = {
LEGACY_CMS: 1,
DESTINATION_STACK: 2,
CONTENT_MAPPING: 3,
TESTING: 4,
MIGRATION: 5,
AUDIT_REPORT: 3,
CONTENT_MAPPING: 4,
TESTING: 5,
MIGRATION: 6,
};
export const PREDEFINED_STATUS = [
'Draft',
Expand All @@ -186,7 +202,7 @@ export const PREDEFINED_STATUS = [
'Failed',
'Success',
];
export const PREDEFINED_STEPS = [1, 2, 3, 4, 5];
export const PREDEFINED_STEPS = [1, 2, 3, 4, 5, 6];

export const NEW_PROJECT_STATUS = {
0: 0, //DRAFT
Expand Down Expand Up @@ -334,6 +350,11 @@ export const DATABASE_FILES = {
ASSET_METADATA: 'asset-metadata.json',
};


export const DB_CONFIG = {
AUDIT_THRESHOLD: 250,
AUDIT_DIR: "database/audit",
};
export const GET_AUDIT_DATA = {
MIGRATION: 'migration-v2',
API_DIR: 'api',
Expand Down
34 changes: 30 additions & 4 deletions api/src/controllers/migration.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ const getAuditData = async (req: Request, res: Response): Promise<void> => {
const resp = await migrationService.getAuditData(req);
res.status(resp?.status).json(resp);
};

const exportSourceStack = async (req: Request, res: Response): Promise<void> => {
const resp = await migrationService.exportSourceStack(req);
res.status(resp?.status).json(resp);
};

const validateSourceExport = async (req: Request, res: Response): Promise<void> => {
const resp = await migrationService.validateSourceExport(req);
res.status(resp?.status).json(resp);
};

const runSourceAudit = async (req: Request, res: Response): Promise<void> => {
const resp = await migrationService.runSourceAudit(req);
res.status(resp?.status).json(resp);
};

const getSourceAuditSummary = async (req: Request, res: Response): Promise<void> => {
const resp = await migrationService.getSourceAuditSummary(req);
res.status(resp?.status).json(resp);
};
/**
* Start Test Migartion.
*
Expand All @@ -24,8 +44,8 @@ const getAuditData = async (req: Request, res: Response): Promise<void> => {
* @returns {Promise<void>} - A Promise that resolves when the stack is deleted.
*/
const startTestMigration = async (req: Request, res: Response): Promise<void> => {
const resp = migrationService.startTestMigration(req);
res.status(200).json(resp);
const resp = await migrationService.startTestMigration(req);
res.status(resp?.status).json(resp);
};


Expand All @@ -37,8 +57,10 @@ const startTestMigration = async (req: Request, res: Response): Promise<void> =>
* @returns {Promise<void>} - A Promise that resolves when the stack is deleted.
*/
const startMigration = async (req: Request, res: Response): Promise<void> => {
const resp = migrationService.startMigration(req);
res.status(200).json(resp);
const resp = await migrationService.startMigration(req);
// Service may return void (e.g. when running a long async import) — default
// to 200 so Express doesn't crash with "Invalid status code: undefined".
res.status(resp?.status ?? 200).json(resp ?? { status: 200 });
};

/**
Expand Down Expand Up @@ -75,6 +97,10 @@ const restartMigration = async (req: Request, res: Response): Promise<void> => {

export const migrationController = {
createTestStack,
exportSourceStack,
validateSourceExport,
runSourceAudit,
getSourceAuditSummary,
deleteTestStack,
startTestMigration,
startMigration,
Expand Down
16 changes: 16 additions & 0 deletions api/src/controllers/projects.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Request, Response } from "express";
import { projectService } from "../services/projects.service.js";
import { HTTP_TEXTS } from "../constants/index.js";

/**
* Retrieves all projects.
Expand Down Expand Up @@ -96,6 +97,11 @@ const updateFileFormat = async (req: Request, res: Response) => {
res.status(resp.status).json(resp.data);
};

const updateSourceConfig = async (req: Request, res: Response) => {
const resp = await projectService.updateSourceConfig(req);
res.status(resp?.status).json(resp?.data);
};

/**
* Handles the file format confirmation request.
*
Expand Down Expand Up @@ -176,6 +182,14 @@ const getMigratedStacks = async (req: Request, res: Response): Promise<void> =>
res.status(project.status).json(project);
}

/**
* Updates audit report selections for a project
*/
const updateAuditSelections = async (req: Request, res: Response): Promise<void> => {
const project = await projectService.updateAuditSelections(req);
res.status(200).json({ data: project, message: HTTP_TEXTS.AUDIT_SELECTIONS_UPDATED });
};

export const projectController = {
getAllProjects,
getProject,
Expand All @@ -185,6 +199,7 @@ export const projectController = {
updateAffix,
affixConfirmation,
updateFileFormat,
updateSourceConfig,
fileformatConfirmation,
updateDestinationStack,
updateCurrentStep,
Expand All @@ -193,4 +208,5 @@ export const projectController = {
updateStackDetails,
updateMigrationExecution,
getMigratedStacks,
updateAuditSelections,
};
21 changes: 21 additions & 0 deletions api/src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ const getUserProfile = async (req: Request, res: Response) => {
res.status(resp?.status).json(resp?.data);
};

/** Returns the persisted regional source-login session, if any. */
const getSourceSession = async (req: Request, res: Response) => {
const resp = await userService.getSourceSession(req);
res.status(resp?.status).json(resp?.data);
};

/** Persists the regional source-login session on the current user's record. */
const setSourceSession = async (req: Request, res: Response) => {
const resp = await userService.setSourceSession(req);
res.status(resp?.status).json(resp?.data);
};

/** Clears the persisted regional source-login session for the current user. */
const clearSourceSession = async (req: Request, res: Response) => {
const resp = await userService.clearSourceSession(req);
res.status(resp?.status).json(resp?.data);
};

export const userController = {
getUserProfile,
getSourceSession,
setSourceSession,
clearSourceSession,
};
Loading
Loading