Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend route voor het kopiëren van een vak #230

Merged
merged 7 commits into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading