diff --git a/.changeset/block-personal-workspace-merge.md b/.changeset/block-personal-workspace-merge.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/block-personal-workspace-merge.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/server/src/db/org-merge-db.ts b/server/src/db/org-merge-db.ts index 93d9737385..4d891d8377 100644 --- a/server/src/db/org-merge-db.ts +++ b/server/src/db/org-merge-db.ts @@ -68,7 +68,7 @@ export async function mergeOrganizations( // Validate both organizations exist and fetch all needed fields const orgsResult = await client.query( - `SELECT workos_organization_id, name, prospect_notes, + `SELECT workos_organization_id, name, is_personal, prospect_notes, enrichment_at, enrichment_industry, enrichment_sub_industry, enrichment_employee_count, enrichment_revenue, enrichment_revenue_range, enrichment_country, enrichment_city, enrichment_description @@ -88,6 +88,11 @@ export async function mergeOrganizations( throw new Error('Could not load organization details'); } + // Block merging personal workspaces + if (primaryOrg.is_personal || secondaryOrg.is_personal) { + throw new Error('Cannot merge personal workspaces. Personal workspaces represent individual users and should not be merged with company organizations.'); + } + logger.info( { primary: primaryOrg.name, secondary: secondaryOrg.name }, 'Merging organizations' @@ -662,9 +667,9 @@ export async function previewMerge( }> { const pool = getPool(); - // Get organization names + // Get organization names and personal workspace status const orgsResult = await pool.query( - `SELECT workos_organization_id, name FROM organizations + `SELECT workos_organization_id, name, is_personal FROM organizations WHERE workos_organization_id = ANY($1)`, [[primaryOrgId, secondaryOrgId]] ); @@ -681,6 +686,14 @@ export async function previewMerge( } const warnings: string[] = []; + + // Warn if either organization is a personal workspace + if (primaryOrg.is_personal) { + warnings.unshift(`🔴 PRIMARY IS PERSONAL WORKSPACE: "${primaryOrg.name}" is a personal workspace and should not be merged with company organizations.`); + } + if (secondaryOrg.is_personal) { + warnings.unshift(`🔴 SECONDARY IS PERSONAL WORKSPACE: "${secondaryOrg.name}" is a personal workspace and should not be merged with company organizations.`); + } const estimatedChanges: { table_name: string; rows_to_move: number }[] = []; // Count rows in each table diff --git a/server/src/routes/admin/cleanup.ts b/server/src/routes/admin/cleanup.ts index f81028d453..24afee2c36 100644 --- a/server/src/routes/admin/cleanup.ts +++ b/server/src/routes/admin/cleanup.ts @@ -307,6 +307,15 @@ export function setupCleanupRoutes(apiRouter: Router): void { res.json(result); } catch (error) { logger.error({ err: error }, "Error executing merge"); + + // Return 400 for validation errors (e.g., personal workspace merge attempts) + if (error instanceof Error && error.message.startsWith('Cannot merge personal workspaces')) { + return res.status(400).json({ + error: "Validation error", + message: error.message, + }); + } + res.status(500).json({ error: "Internal server error", message: