Skip to content

Commit

Permalink
Merge pull request #230 from SELab-2/229-kopiëren-van-een-vak
Browse files Browse the repository at this point in the history
Backend route voor het kopiëren van een vak
  • Loading branch information
Aqua-sc authored Apr 28, 2024
2 parents f17d6e9 + c17a2b8 commit 69d4c92
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.MethodNotSupportedException;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.server.MethodNotAllowedException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

Expand Down Expand Up @@ -45,6 +49,24 @@ public ResponseEntity<ApiErrorReponse> handleHttpMessageNotFoundException(HttpSe
"Endpoint doesn't exist", path));
}

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ApiErrorReponse> handleMethodNotSupportedException(HttpServletRequest request, Exception ex) {
logError(ex);
String path = request.getRequestURI();
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
return ResponseEntity.status(status).body(new ApiErrorReponse(OffsetDateTime.now(), status.value(), status.getReasonPhrase(),
"Method not supported", path));
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ApiErrorReponse> handleMethodArgumentTypeMismatchException(HttpServletRequest request, Exception ex) {
logError(ex);
String path = request.getRequestURI();
HttpStatus status = HttpStatus.BAD_REQUEST;
return ResponseEntity.status(status).body(new ApiErrorReponse(OffsetDateTime.now(), status.value(), status.getReasonPhrase(),
"Invalid url argument type", path));
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ApiErrorReponse> handleException(HttpServletRequest request, Exception ex) {
logError(ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,6 @@ public ResponseEntity<?> deleteCourse(@PathVariable long courseId, Auth auth) {
}

List<GroupClusterEntity> clusters = groupClusterRepository.findByCourseId(courseId);
Optional<GroupClusterEntity> individualCluster = groupClusterRepository.findIndividualClusterByCourseId(courseId);
individualCluster.ifPresent(clusters::add);

// Delete all groupclusters linked to the course
for (GroupClusterEntity groupCluster : clusters) {
Expand Down Expand Up @@ -683,5 +681,30 @@ public ResponseEntity<String> deleteCourseKey(Auth auth, @PathVariable Long cour
return ResponseEntity.ok("");
}

@PostMapping(ApiRoutes.COURSE_BASE_PATH + "/{courseId}/copy")
@Roles({UserRole.teacher})
@Transactional
public ResponseEntity<?> copyCourse(@PathVariable long courseId, Auth auth) {
try {
CheckResult<Pair<CourseEntity, CourseRelation>> checkResult = courseUtil.getCourseIfUserInCourse(courseId, auth.getUserEntity());
if (checkResult.getStatus() != HttpStatus.OK) {
return ResponseEntity.status(checkResult.getStatus()).body(checkResult.getMessage());
}
if (!checkResult.getData().getSecond().equals(CourseRelation.creator)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Only the creator of a course can copy it");
}

CourseEntity course = checkResult.getData().getFirst();

CheckResult<CourseEntity> copyCheckRes = commonDatabaseActions.copyCourse(course, auth.getUserEntity().getId());
CourseEntity newCourse = copyCheckRes.getData();

return ResponseEntity.ok(entityToJsonConverter.courseEntityToCourseWithInfo(newCourse, courseUtil.getJoinLink(newCourse.getJoinKey(), "" + newCourse.getId()), false));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}



}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@


import com.ugent.pidgeon.postgre.models.*;
import com.ugent.pidgeon.postgre.models.types.CourseRelation;
import com.ugent.pidgeon.postgre.repository.*;
import java.nio.file.Path;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.hibernate.annotations.Check;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -30,6 +38,12 @@ public class CommonDatabaseActions {
private TestRepository testRepository;
@Autowired
private FileUtil fileUtil;
@Autowired
private FileRepository fileRepository;
@Autowired
private CourseRepository courseRepository;
@Autowired
private CourseUserRepository courseUserRepository;


/**
Expand Down Expand Up @@ -124,9 +138,12 @@ public CheckResult<Void> deleteProject(long projectId) {
if (testEntity == null) {
return new CheckResult<>(HttpStatus.NOT_FOUND, "Test not found", null);
}
return deleteTestById(projectEntity, testEntity);
CheckResult<Void> delRes = deleteTestById(projectEntity, testEntity);
return delRes;
}



return new CheckResult<>(HttpStatus.OK, "", null);
} catch (Exception e) {
System.out.println(e.getMessage());
Expand Down Expand Up @@ -161,8 +178,6 @@ public CheckResult<Void> deleteSubmissionById(long submissionId) {
*/
public CheckResult<Void> deleteTestById(ProjectEntity projectEntity, TestEntity testEntity) {
try {
projectEntity.setTestId(null);
projectRepository.save(projectEntity);
testRepository.deleteById(testEntity.getId()) ;
CheckResult<Void> checkAndDeleteRes = fileUtil.deleteFileById(testEntity.getStructureTestId());
if (!checkAndDeleteRes.getStatus().equals(HttpStatus.OK)) {
Expand Down Expand Up @@ -192,4 +207,189 @@ public CheckResult<Void> deleteClusterById(long clusterId) {
return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error while deleting cluster", null);
}
}


/**
* Copy a course and all its related data. Assumes that permissions are already checked
* @param course course to copy
* @return CheckResult with the status of the copy and the new course
*/
public CheckResult<CourseEntity> copyCourse(CourseEntity course, long userId) {
// Copy the course
CourseEntity newCourse = new CourseEntity(course.getName(), course.getDescription(), course.getCourseYear());
// Change the createdAt, archivedAt and joinKey
newCourse.setCreatedAt(OffsetDateTime.now());
newCourse.setArchivedAt(null);
newCourse.setJoinKey(UUID.randomUUID().toString());

newCourse = courseRepository.save(newCourse);

Map<Long, Long> groupClusterMap = new HashMap<>();
// Copy the group(clusters) linked to the course
GroupClusterEntity groupCluster = groupClusterRepository.findIndividualClusterByCourseId(
course.getId()).orElse(null);
if (groupCluster != null) {
CheckResult<GroupClusterEntity> checkResult = copyGroupCluster(groupCluster, newCourse.getId(), false);
if (!checkResult.getStatus().equals(HttpStatus.OK)) {
return new CheckResult<>(checkResult.getStatus(), checkResult.getMessage(), null);
}
groupClusterMap.put(groupCluster.getId(), checkResult.getData().getId());
} else {
return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error while copying course", null);
}

List<GroupClusterEntity> groupClusters = groupClusterRepository.findClustersWithoutInvidualByCourseId(course.getId());
for (GroupClusterEntity cluster : groupClusters) {
CheckResult<GroupClusterEntity> checkResult = copyGroupCluster(cluster,
newCourse.getId(), true);
if (!checkResult.getStatus().equals(HttpStatus.OK)) {
return new CheckResult<>(checkResult.getStatus(), checkResult.getMessage(), null);
}
groupClusterMap.put(cluster.getId(), checkResult.getData().getId());
}

// Copy the projects linked to the course
List<ProjectEntity> projects = projectRepository.findByCourseId(course.getId());
for (ProjectEntity project : projects) {
CheckResult<ProjectEntity> checkResult = copyProject(project, newCourse.getId(), groupClusterMap.get(project.getGroupClusterId()));
if (!checkResult.getStatus().equals(HttpStatus.OK)) {
return new CheckResult<>(checkResult.getStatus(), checkResult.getMessage(), null);
}
}

// Add user to course
CourseUserEntity courseUserEntity = new CourseUserEntity(newCourse.getId(), userId, CourseRelation.creator);
courseUserRepository.save(courseUserEntity);

return new CheckResult<>(HttpStatus.OK, "", newCourse);
}

/**
* Copy a group cluster and all its related data. Assumes that permissions are already checked
* @param groupCluster group cluster that needs to be copied
* @return CheckResult with the status of the copy and the new group cluster
*/
public CheckResult<GroupClusterEntity> copyGroupCluster(GroupClusterEntity groupCluster, long courseId, boolean copyGroups) {
GroupClusterEntity newGroupCluster = new GroupClusterEntity(
courseId,
groupCluster.getMaxSize(),
groupCluster.getName(),
groupCluster.getGroupAmount()
);
newGroupCluster.setCreatedAt(OffsetDateTime.now());

newGroupCluster = groupClusterRepository.save(newGroupCluster);
if (copyGroups) {
List<GroupEntity> groups = groupRepository.findAllByClusterId(groupCluster.getId());
for (GroupEntity group : groups) {
GroupEntity newGroup = new GroupEntity(group.getName(), newGroupCluster.getId());
groupRepository.save(newGroup);
}
}

return new CheckResult<>(HttpStatus.OK, "", newGroupCluster);
}



/**
* Copy a project and all its related data. Assumes that permissions are already checked
* @param project project that needs to be copied
* @param courseId id of the course the project is linked to
* @param clusterId id of the cluster the project is linked to
* @return CheckResult with the status of the copy and the new project
*/
public CheckResult<ProjectEntity> copyProject(ProjectEntity project, long courseId, long clusterId) {
// Copy the project
ProjectEntity newProject = new ProjectEntity(
courseId,
project.getName(),
project.getDescription(),
clusterId,
null,
project.isVisible(),
project.getMaxScore(),
project.getDeadline());

newProject = projectRepository.save(newProject);


// Copy the test linked to the project
if (project.getTestId() != null) {
TestEntity test = testRepository.findById(project.getTestId()).orElse(null);
if (test != null) {
CheckResult<TestEntity> checkResult = copyTest(test, newProject.getId());
if (!checkResult.getStatus().equals(HttpStatus.OK)) {
return new CheckResult<>(checkResult.getStatus(), checkResult.getMessage(), null);
}
newProject.setTestId(checkResult.getData().getId());
newProject = projectRepository.save(newProject);
} else {
return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error while copying project", null);
}
}


return new CheckResult<>(HttpStatus.OK, "", newProject);
}

/**
* Copy a test and all its related data. Assumes that permissions are already checked
* @param test test that needs to be copied
* @param projectId id of the project the test is linked to
* @return CheckResult with the status of the copy and the new test
*/
public CheckResult<TestEntity> copyTest(TestEntity test, long projectId) {
// Copy the test
TestEntity newTest = new TestEntity(
test.getDockerImage(),
test.getDockerTestId(),
test.getStructureTestId()
);

// Copy the files linked to the test
try {
FileEntity dockerFile = fileRepository.findById(test.getDockerTestId()).orElse(null);
FileEntity structureFile = fileRepository.findById(test.getStructureTestId()).orElse(null);
if (dockerFile == null || structureFile == null) {
return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error while copying test", null);
}

CheckResult<FileEntity> copyDockRes = copyTestFile(dockerFile, projectId);
if (!copyDockRes.getStatus().equals(HttpStatus.OK)) {
return new CheckResult<>(copyDockRes.getStatus(), copyDockRes.getMessage(), null);
}
newTest.setDockerTestId(copyDockRes.getData().getId());

CheckResult<FileEntity> copyStructRes = copyTestFile(structureFile, projectId);
if (!copyStructRes.getStatus().equals(HttpStatus.OK)) {
return new CheckResult<>(copyStructRes.getStatus(), copyStructRes.getMessage(), null);
}
newTest.setStructureTestId(copyStructRes.getData().getId());
} catch (Exception e) {
return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error while copying test", null);
}

newTest = testRepository.save(newTest);
return new CheckResult<>(HttpStatus.OK, "", newTest);
}


/**
* Copy a file and all its related data. Assumes that permissions are already checked
* @param file file to copy
* @param projectId id of the project the file is linked to
* @return CheckResult with the status of the copy and the new file
*/
public CheckResult<FileEntity> copyTestFile(FileEntity file, long projectId) {
// Copy the file
try {
Path newPath = Filehandler.copyTest(Path.of(file.getPath()), projectId);
FileEntity newFile = new FileEntity(newPath.getFileName().toString(), newPath.toString(), file.getUploadedBy());
newFile = fileRepository.save(newFile);
return new CheckResult<>(HttpStatus.OK, "", newFile);
} catch (Exception e) {
return new CheckResult<>(HttpStatus.INTERNAL_SERVER_ERROR, "Error while copying file", null);
}
}
}
28 changes: 28 additions & 0 deletions backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,34 @@ public static Path saveTest(MultipartFile file, long projectId) throws IOExcepti
return filePath;
}

/**
* Copy a file to the server project directory.
* @param sourceFilePath the path of the file to copy
* @param projectId the ID of the project
* @return the path of the copied file
* @throws IOException if an error occurs while copying the file
*/
public static Path copyTest(Path sourceFilePath, long projectId) throws IOException {
// Check if the source file exists
if (!Files.exists(sourceFilePath)) {
throw new IOException("Source file does not exist");
}

// Create project directory if it doesn't exist
Path projectDirectory = getTestPath(projectId);
if (!Files.exists(projectDirectory)) {
Files.createDirectories(projectDirectory);
}

// Resolve destination file path
Path destinationFilePath = projectDirectory.resolve(sourceFilePath.getFileName());

// Copy the file to the project directory
Files.copy(sourceFilePath, destinationFilePath);

return destinationFilePath;
}

/**
* Get the structure test file contents as string
* @param path path of the structure test file
Expand Down

0 comments on commit 69d4c92

Please sign in to comment.