Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8ce8233
feat(cmg-703): stack-to-stack migration with audit report
May 27, 2026
82225a0
Fixed test cases
May 27, 2026
22106a2
Fixed test cases
May 27, 2026
9f2a3c8
Fixed test cases
May 27, 2026
39dcd57
Fixed Snyk issues
May 28, 2026
e74fdf2
feat(tests): update auth and config tests for improved error handling…
May 28, 2026
4742101
fix(snyk): gate exportPath before validate/audit reads
May 28, 2026
1e70b9f
fix(snyk): char-allowlist rebuild in assertExportPathInAllowedRoot
May 28, 2026
f666e06
chore(pr-review): add ?. chaining + route exportCli stdio through logger
Jun 1, 2026
0c1095d
fix(pr-review): audit URL regions, magic numbers, audit gate, step nav
Jun 2, 2026
d48a93c
fix(exportCli): timeout + stdio through logger
Jun 2, 2026
a07bc8d
fix(upload-api): use saveZip/saveJson actual path for mapper
Jun 2, 2026
284fdc0
fix(contentful): don't drop entries when source locale isn't mapped
Jun 2, 2026
661c17f
refactor(pr-review): consolidate constants, switch case, drop en-us f…
Jun 3, 2026
8b00472
refactor(runCli): own backup lifecycle, drop broken cleanup scanner
Jun 3, 2026
fdea5d3
fix: post-rebase test fixes (duplicate import, missing it block)
Jun 4, 2026
f32cf05
fix: gate CMS-specific pre-import + iteration defaults for stack-to-s…
Jun 4, 2026
ede2404
Merge branch 'dev' into feature/cmg-703-stack-to-stack
chetan-contentstack Jun 5, 2026
c2015b8
fix(pr-review): address copilot review comments
Jun 8, 2026
5c20cd1
feat(source-session): persist regional source token on backend user r…
Jun 8, 2026
ac0c73d
fix(upload-api): annotate handleFileProcessing return type
Jun 8, 2026
91a4d18
Merge remote-tracking branch 'origin/dev' into feature/cmg-703-stack-…
Jun 8, 2026
485140c
test: cover source-session endpoints + migrationSourceSession utility
Jun 8, 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