From 08badd631e1f43180e06ab93b539f77d344c2415 Mon Sep 17 00:00:00 2001 From: Dmitrii Bocharov Date: Tue, 11 Aug 2026 12:10:13 +0200 Subject: [PATCH] fix: delete branches after content delivery configs in hard delete hardDeleteProject deleted branches before content_delivery_config rows, which reference branch via branch_id, so deleting a project that had branch-scoped content delivery configs failed with a foreign key violation. This aborted the orphan-project purge batch in production, leaving projects of deleted organizations undeleted. Move branchService.deleteAllByProjectId after the ContentDeliveryConfig deletion, and flush right after it so the branch removals (done through the persistence context) reach the DB before the project row is deleted (branch.project_id references project). --- .../project/ProjectHardDeletingServiceTest.kt | 14 ++++++++++++++ .../service/project/ProjectHardDeletingService.kt | 8 +++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/backend/app/src/test/kotlin/io/tolgee/service/project/ProjectHardDeletingServiceTest.kt b/backend/app/src/test/kotlin/io/tolgee/service/project/ProjectHardDeletingServiceTest.kt index 955ee6bd85a..ba46670ec86 100644 --- a/backend/app/src/test/kotlin/io/tolgee/service/project/ProjectHardDeletingServiceTest.kt +++ b/backend/app/src/test/kotlin/io/tolgee/service/project/ProjectHardDeletingServiceTest.kt @@ -10,6 +10,7 @@ import io.tolgee.batch.data.BatchJobType import io.tolgee.batch.request.DeleteKeysRequest import io.tolgee.development.testDataBuilder.data.BaseTestData import io.tolgee.development.testDataBuilder.data.BatchJobsTestData +import io.tolgee.development.testDataBuilder.data.ContentDeliveryConfigBranchingTestData import io.tolgee.development.testDataBuilder.data.ContentDeliveryConfigTestData import io.tolgee.development.testDataBuilder.data.MtSettingsTestData import io.tolgee.development.testDataBuilder.data.ProjectWithQaEntitiesTestData @@ -133,6 +134,19 @@ class ProjectHardDeletingServiceTest : AbstractSpringTest() { } } + @Test + fun `deletes project with content delivery configs on branches`() { + val testData = ContentDeliveryConfigBranchingTestData() + testDataService.saveTestData(testData.root) + executeInNewRepeatableTransaction(platformTransactionManager) { + projectHardDeletingService.hardDeleteProject(testData.projectBuilder.self.refresh()) + } + + executeInNewTransaction { + projectService.find(testData.projectBuilder.self.id).assert.isNull() + } + } + @Test fun `deletes project with webhooks`() { val testData = WebhooksTestData() diff --git a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectHardDeletingService.kt b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectHardDeletingService.kt index b2c0f212825..6047e880480 100644 --- a/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectHardDeletingService.kt +++ b/backend/data/src/main/kotlin/io/tolgee/service/project/ProjectHardDeletingService.kt @@ -147,7 +147,6 @@ class ProjectHardDeletingService( entityManager.clear() bigMetaService.deleteAllByProjectId(projectId) - branchService.deleteAllByProjectId(projectId) projectQaConfigRepository.deleteAllByProjectId(projectId) // Flush and clear the persistence context to ensure deletions are synchronized @@ -183,6 +182,13 @@ class ProjectHardDeletingService( .setParameter("projectId", projectId) .executeUpdate() + // Branches are referenced by content_delivery_config.branch_id, so they must be + // deleted only after the content delivery configs above are gone. deleteAllByProjectId + // removes the entities via the persistence context, so flush to push the DELETEs to the + // DB before the project row is deleted (branch.project_id references project). + branchService.deleteAllByProjectId(projectId) + entityManager.flush() + // Delete ContentStorage children first entityManager .createQuery("DELETE FROM AzureContentStorageConfig a WHERE a.contentStorage.project.id = :projectId")