diff --git a/service/src/main/java/skills/services/AttachmentService.groovy b/service/src/main/java/skills/services/AttachmentService.groovy index a2a81297c2..71fe1e65c7 100644 --- a/service/src/main/java/skills/services/AttachmentService.groovy +++ b/service/src/main/java/skills/services/AttachmentService.groovy @@ -105,6 +105,9 @@ class AttachmentService { if (description) { UUID_PATTERN.matcher(description).findAll().collect { it[1] }.each { uuid -> Attachment attachment = attachmentRepo.findByUuid(uuid) + if (!attachment) { + throw new IllegalStateException("Failed to find attachment with uuid: [${uuid}]. method params are projectId: [${projectId}], quizId: [${quizId}], skillId: [${skillId}]") + } boolean changed = false if (attachment.projectId != projectId) { attachment.setProjectId(projectId) diff --git a/service/src/main/java/skills/services/admin/ProjectCopyService.groovy b/service/src/main/java/skills/services/admin/ProjectCopyService.groovy index 691625a5d5..7467e00856 100644 --- a/service/src/main/java/skills/services/admin/ProjectCopyService.groovy +++ b/service/src/main/java/skills/services/admin/ProjectCopyService.groovy @@ -130,23 +130,28 @@ class ProjectCopyService { void copyProject(String originalProjectId, ProjectRequest projectRequest) { lockingService.lockProjects() - ProjDef fromProject = loadProject(originalProjectId) - validate(projectRequest) - - ProjDef toProj = saveToProject(projectRequest) - saveProjectSettings(fromProject, toProj) - - pinProjectForRootUser(toProj) - - List allCollectedSkills = [] - def newIcons = customIconFacade.copyIcons(originalProjectId, toProj.projectId) - saveSubjectsAndSkills(projectRequest, fromProject, toProj, allCollectedSkills, newIcons) - updateProjectAndSubjectLevels(fromProject, toProj) - saveBadgesAndTheirSkills(fromProject, toProj, newIcons) - flushEntityCache() - saveDependencies(fromProject, toProj) - saveReusedSkills(allCollectedSkills, fromProject, toProj) - handleQuizBasedUserPointsAndAchievements(toProj) + copiedAttachmentUuidsThreadLocal.set([:]) + try { + ProjDef fromProject = loadProject(originalProjectId) + validate(projectRequest) + + ProjDef toProj = saveToProject(projectRequest) + saveProjectSettings(fromProject, toProj) + + pinProjectForRootUser(toProj) + + List allCollectedSkills = [] + def newIcons = customIconFacade.copyIcons(originalProjectId, toProj.projectId) + saveSubjectsAndSkills(projectRequest, fromProject, toProj, allCollectedSkills, newIcons) + updateProjectAndSubjectLevels(fromProject, toProj) + saveBadgesAndTheirSkills(fromProject, toProj, newIcons) + flushEntityCache() + saveDependencies(fromProject, toProj) + saveReusedSkills(allCollectedSkills, fromProject, toProj) + handleQuizBasedUserPointsAndAchievements(toProj) + } finally { + copiedAttachmentUuidsThreadLocal.set([:]) + } } private void flushEntityCache() { @@ -426,9 +431,10 @@ class ProjectCopyService { } } - private final Map copiedAttachmentUuids = [:] + private final ThreadLocal> copiedAttachmentUuidsThreadLocal = new ThreadLocal<>(); @Profile private String handleAttachmentsInDescription(String description, String newProjectId) { + Map copiedAttachmentUuids = copiedAttachmentUuidsThreadLocal.get() String res = description if (description) { attachmentService.findAttachmentUuids(res).each { String uuid -> @@ -438,6 +444,7 @@ class ProjectCopyService { Attachment copiedAttachment = attachmentService.copyAttachmentWithNewUuid(attachment, newProjectId) copiedUuid = copiedAttachment.uuid copiedAttachmentUuids[uuid] = copiedUuid + copiedAttachmentUuidsThreadLocal.set(copiedAttachmentUuids) } res = res.replaceAll(uuid, copiedUuid) } diff --git a/service/src/test/java/skills/intTests/copyProject/CopyProjectSpecs.groovy b/service/src/test/java/skills/intTests/copyProject/CopyProjectSpecs.groovy index 9f7da6fd66..0783867709 100644 --- a/service/src/test/java/skills/intTests/copyProject/CopyProjectSpecs.groovy +++ b/service/src/test/java/skills/intTests/copyProject/CopyProjectSpecs.groovy @@ -974,6 +974,65 @@ class CopyProjectSpecs extends DefaultIntSpec { copyProj.data.itemId.sort() == [projToCopy.projectId, p1subj1.subjectId, p1Skills[0].skillId].sort() } + def "copy project with an attachment in description, then remove the copy then copy again"() { + def p1 = createProject(1) + skillsService.createProject(p1) + + String contents = 'Test is a test' + String attachmentHref = attachFileAndReturnHref(p1.projectId, contents) + + def p1subj1 = createSubject(1, 1) + p1subj1.description = "Here is a [Link](${attachmentHref})".toString() + skillsService.createSubject(p1subj1) + + when: + def projToCopy = createProject(2) + skillsService.copyProject(p1.projectId, projToCopy) + + def origSubj = skillsService.getSubject([projectId: p1.projectId, subjectId: p1subj1.subjectId]) + def copySubj = skillsService.getSubject([projectId: projToCopy.projectId, subjectId: p1subj1.subjectId]) + + List attachments = attachmentRepo.findAll() + assert attachments.size() == 2 + Attachment originalAttachment = attachments.find { attachmentHref.contains(it.uuid)} + Attachment newAttachment = attachments.find { !attachmentHref.contains(it.uuid)} + + assert origSubj.description == "Here is a [Link](${attachmentHref})" + assert copySubj.description == "Here is a [Link](/api/download/${newAttachment.uuid})" + + assert originalAttachment.projectId == p1.projectId + assert newAttachment.projectId == projToCopy.projectId + + skillsService.deleteProject(projToCopy.projectId) + + List attachmentsAfterDelete = attachmentRepo.findAll() + assert attachmentsAfterDelete.size() == 1 + attachmentsAfterDelete.find { attachmentHref.contains(it.uuid)} + + def secondCopy = createProject(3) + skillsService.copyProject(p1.projectId, secondCopy) + + def origSubjAfterSecondCopy = skillsService.getSubject([projectId: p1.projectId, subjectId: p1subj1.subjectId]) + def copySubjAfterSecondCopy = skillsService.getSubject([projectId: secondCopy.projectId, subjectId: p1subj1.subjectId]) + List attachmentsAfterSecondCopy = attachmentRepo.findAll() + then: + origSubjAfterSecondCopy.description == "Here is a [Link](${attachmentHref})" + + attachmentsAfterSecondCopy.size() == 2 + Attachment originalAttachmentAfterSecondCopy = attachmentsAfterSecondCopy.find { attachmentHref.contains(it.uuid)} + Attachment newAttachmentAfterSecondCopy = attachmentsAfterSecondCopy.find { !attachmentHref.contains(it.uuid)} + + originalAttachmentAfterSecondCopy.projectId == p1.projectId + + newAttachmentAfterSecondCopy.projectId == secondCopy.projectId + copySubjAfterSecondCopy.description == "Here is a [Link](/api/download/${newAttachmentAfterSecondCopy.uuid})" + + SkillsService.FileAndHeaders fileAndHeaders = skillsService.downloadAttachment("/api/download/${newAttachmentAfterSecondCopy.uuid}") + File file = fileAndHeaders.file + file + file.bytes == contents.getBytes() + } + static class Edge { String from