diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentService.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentService.java index 60c8455..d13303b 100644 --- a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentService.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentService.java @@ -13,6 +13,8 @@ import java.util.UUID; public interface AssignmentService { Optional getAssignment(UUID id); + void archive(Assignment assignment); + @Transactional void acceptAssigment(Assignment assignment, ClassroomUser student) throws GitLabApiException, GitLabException; diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentServiceImpl.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentServiceImpl.java index 39f9426..0537dc0 100644 --- a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentServiceImpl.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AssignmentServiceImpl.java @@ -45,6 +45,20 @@ class AssignmentServiceImpl implements AssignmentService { return this.assignmentRepository.findById(id); } + @Override + public void archive(Assignment assignment){ + var studentAssignments = this.studentAssignmentRepository.findAllByAssignment(assignment); + studentAssignments.stream() + .map(it -> (StudentExerciseAssignment)it) + .forEach(studentExerciseAssignment -> { + try { + this.gitlab.archiveProject(studentExerciseAssignment); + } catch (GitLabException e) { + System.out.println(e.getMessage()); + } + }); + } + @Override @Transactional public void acceptAssigment(Assignment assignment, ClassroomUser student) throws GitLabApiException, GitLabException { diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ExerciseAssignmentRestController.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ExerciseAssignmentRestController.java new file mode 100644 index 0000000..bfd8819 --- /dev/null +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ExerciseAssignmentRestController.java @@ -0,0 +1,62 @@ +package fr.univ_lille.gitlab.classrooms.assignments; + +import jakarta.annotation.security.RolesAllowed; +import org.gitlab4j.api.GitLabApi; +import org.gitlab4j.api.GitLabApiException; +import org.gitlab4j.api.models.Project; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.server.ResponseStatusException; + +import java.util.UUID; +import java.util.stream.Collectors; + +@Controller +@RolesAllowed("TEACHER") +public class ExerciseAssignmentRestController { + + private AssignmentService assignmentService; + + private GitLabApi gitlab; + + public ExerciseAssignmentRestController(AssignmentService assignmentService, GitLabApi gitlab) { + this.assignmentService = assignmentService; + this.gitlab = gitlab; + } + + @GetMapping(value = "/assignments/{id}/clone-script", produces = "text/plain") + @ResponseBody + String buildGitlabCloneScript(@PathVariable UUID id) { + var assignment = this.assignmentService.getAssignment(id).orElseThrow(); + + var studentAssignments = this.assignmentService.getAssignmentResults(assignment); + + var urls = studentAssignments.stream() + .map(it -> (StudentExerciseAssignment) it) + .map(StudentExerciseAssignment::getGitlabProjectId) + .map(it -> { + try { + return gitlab.getProjectApi().getProject(it); + } catch (GitLabApiException e) { + throw new RuntimeException(e); + } + }) + .map(Project::getSshUrlToRepo) + .map(it -> "git clone " + it); + + return urls.collect(Collectors.joining("\n")); + } + + @GetMapping("/assignments/{assignmentId}/archive") + String archiveAssignment(@PathVariable UUID assignmentId, Model model) { + var assignment = this.assignmentService.getAssignment(assignmentId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + this.assignmentService.archive(assignment); + + return "redirect:/classrooms/" + assignment.getClassroom().getId(); + } + +} diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/Gitlab.java b/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/Gitlab.java index 82815d7..0220259 100644 --- a/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/Gitlab.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/Gitlab.java @@ -1,6 +1,7 @@ package fr.univ_lille.gitlab.classrooms.gitlab; import fr.univ_lille.gitlab.classrooms.assignments.ExerciseAssignment; +import fr.univ_lille.gitlab.classrooms.assignments.StudentExerciseAssignment; import fr.univ_lille.gitlab.classrooms.classrooms.Classroom; import fr.univ_lille.gitlab.classrooms.users.ClassroomUser; import org.gitlab4j.api.GitLabApiException; @@ -46,4 +47,5 @@ public interface Gitlab { */ Project createStudentProject(ExerciseAssignment exerciseAssignment, ClassroomUser student) throws GitLabApiException, GitLabException; + void archiveProject(StudentExerciseAssignment studentExerciseAssignment) throws GitLabException; } diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/GitlabImpl.java b/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/GitlabImpl.java index 243be92..c162f61 100644 --- a/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/GitlabImpl.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/gitlab/GitlabImpl.java @@ -1,6 +1,7 @@ package fr.univ_lille.gitlab.classrooms.gitlab; import fr.univ_lille.gitlab.classrooms.assignments.ExerciseAssignment; +import fr.univ_lille.gitlab.classrooms.assignments.StudentExerciseAssignment; import fr.univ_lille.gitlab.classrooms.classrooms.Classroom; import fr.univ_lille.gitlab.classrooms.users.ClassroomUser; import org.gitlab4j.api.GitLabApi; @@ -115,6 +116,17 @@ class GitlabImpl implements Gitlab { return project; } + @Override + public void archiveProject(StudentExerciseAssignment studentExerciseAssignment) throws GitLabException { + var projectId = studentExerciseAssignment.getGitlabProjectId(); + + try { + this.gitLabApi.getProjectApi().archiveProject(projectId); + } catch (GitLabApiException e) { + throw new GitLabException("Unable to archive GitLab project with id '%s'".formatted(projectId), e); + } + } + private Project ensureStudentProjectExists(GitLabApi teacherGitlabApi, ExerciseAssignment exerciseAssignment, ClassroomUser student) throws GitLabException { var groupId = exerciseAssignment.getGitlabGroupId(); var group = teacherGitlabApi.getGroupApi().getOptionalGroup(groupId).orElseThrow(() -> new GitLabException("Group with id %s doest not exists".formatted(groupId))); diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/security/GitlabIDTokenSecurityConfiguration.java b/src/main/java/fr/univ_lille/gitlab/classrooms/security/GitlabIDTokenSecurityConfiguration.java index 5d735df..20dda24 100644 --- a/src/main/java/fr/univ_lille/gitlab/classrooms/security/GitlabIDTokenSecurityConfiguration.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/security/GitlabIDTokenSecurityConfiguration.java @@ -21,4 +21,12 @@ public class GitlabIDTokenSecurityConfiguration { .build(); } + @Bean + SecurityFilterChain apiKeyFilterChain(HttpSecurity http) throws Exception { + return http + .securityMatcher("/api/**") + .authorizeHttpRequests(authorize -> authorize.requestMatchers("/api/**").authenticated()) + .build(); + } + } -- 2.51.2 From 3a30c58629c3a641e55ad3d3bb626bd4eb41a82f Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Mon, 30 Dec 2024 13:40:24 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8=20:=20auto-archive=20assignments?= =?UTF-8?q?=20with=20due=20date?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignments/ArchiveAssignmentUseCase.java | 32 +++++++++++++++++ .../assignments/ArchiveRestController.java | 33 ++++++++++++++++++ .../classrooms/assignments/Assignment.java | 21 ++++++++++++ .../assignments/AutoArchiveJob.java | 34 +++++++++++++++++++ .../ExerciseAssignmentRestController.java | 11 ------ .../V24__ASSIGNMENT_AUTO_ARCHIVE.sql | 5 +++ 6 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ArchiveAssignmentUseCase.java create mode 100644 src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ArchiveRestController.java create mode 100644 src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AutoArchiveJob.java create mode 100644 src/main/resources/db/migration/V24__ASSIGNMENT_AUTO_ARCHIVE.sql diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ArchiveAssignmentUseCase.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ArchiveAssignmentUseCase.java new file mode 100644 index 0000000..05a7f91 --- /dev/null +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ArchiveAssignmentUseCase.java @@ -0,0 +1,32 @@ +package fr.univ_lille.gitlab.classrooms.assignments; + +import fr.univ_lille.gitlab.classrooms.gitlab.GitLabException; +import fr.univ_lille.gitlab.classrooms.gitlab.Gitlab; +import org.springframework.stereotype.Component; + +@Component +public class ArchiveAssignmentUseCase { + + private final StudentAssignmentRepository studentAssignmentRepository; + + private final Gitlab gitlab; + + ArchiveAssignmentUseCase(StudentAssignmentRepository studentAssignmentRepository, Gitlab gitlab) { + this.studentAssignmentRepository = studentAssignmentRepository; + this.gitlab = gitlab; + } + + public void archive(Assignment assignment){ + var studentAssignments = this.studentAssignmentRepository.findAllByAssignment(assignment); + studentAssignments.stream() + .map(it -> (StudentExerciseAssignment)it) + .forEach(studentExerciseAssignment -> { + try { + this.gitlab.archiveProject(studentExerciseAssignment); + } catch (GitLabException e) { + System.out.println(e.getMessage()); + } + }); + } + +} diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ArchiveRestController.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ArchiveRestController.java new file mode 100644 index 0000000..2f83d2c --- /dev/null +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ArchiveRestController.java @@ -0,0 +1,33 @@ +package fr.univ_lille.gitlab.classrooms.assignments; + +import jakarta.annotation.security.RolesAllowed; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.server.ResponseStatusException; + +import java.util.UUID; + +@Controller +@RolesAllowed("TEACHER") +public class ArchiveRestController { + + private AssignmentService assignmentService; + + private ArchiveAssignmentUseCase archiveAssignmentUseCase; + + public ArchiveRestController(AssignmentService assignmentService, ArchiveAssignmentUseCase archiveAssignmentUseCase) { + this.assignmentService = assignmentService; + this.archiveAssignmentUseCase = archiveAssignmentUseCase; + } + + @GetMapping("/assignments/{assignmentId}/archive") + String archiveAssignment(@PathVariable UUID assignmentId, Model model) { + var assignment = this.assignmentService.getAssignment(assignmentId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + this.archiveAssignmentUseCase.archive(assignment); + + return "redirect:/classrooms/" + assignment.getClassroom().getId(); + } +} diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/Assignment.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/Assignment.java index 34d7d0a..88d3c9a 100644 --- a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/Assignment.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/Assignment.java @@ -4,6 +4,7 @@ import fr.univ_lille.gitlab.classrooms.classrooms.Classroom; import fr.univ_lille.gitlab.classrooms.users.ClassroomUser; import jakarta.persistence.*; +import java.time.ZonedDateTime; import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -26,6 +27,10 @@ public abstract class Assignment { @ManyToOne private Classroom classroom; + private ZonedDateTime dueDate; + + private boolean autoArchive = false; + public UUID getId() { return id; } @@ -69,4 +74,20 @@ public abstract class Assignment { public void setClassroom(Classroom classroom) { this.classroom = classroom; } + + public ZonedDateTime getDueDate() { + return dueDate; + } + + public void setDueDate(ZonedDateTime dueDate) { + this.dueDate = dueDate; + } + + public boolean isAutoArchive() { + return autoArchive; + } + + public void setAutoArchive(boolean autoArchive) { + this.autoArchive = autoArchive; + } } diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AutoArchiveJob.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AutoArchiveJob.java new file mode 100644 index 0000000..e789926 --- /dev/null +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/AutoArchiveJob.java @@ -0,0 +1,34 @@ +package fr.univ_lille.gitlab.classrooms.assignments; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.ZonedDateTime; +import java.util.concurrent.TimeUnit; + +@Component +public class AutoArchiveJob { + + private AssignmentRepository assignmentRepository; + + private ArchiveAssignmentUseCase archiveAssignmentUseCase; + + private static final Logger LOGGER = LoggerFactory.getLogger(AutoArchiveJob.class.getName()); + + public AutoArchiveJob(AssignmentRepository assignmentRepository, ArchiveAssignmentUseCase archiveAssignmentUseCase) { + this.assignmentRepository = assignmentRepository; + this.archiveAssignmentUseCase = archiveAssignmentUseCase; + } + + @Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES) + void autoArchiveAssignments(){ + LOGGER.info("Auto archiving assignments"); + this.assignmentRepository.findAll() + .stream() + .filter(Assignment::isAutoArchive) + .filter(it -> it.getDueDate().isBefore(ZonedDateTime.now())) + .forEach(it -> this.archiveAssignmentUseCase.archive(it)); + } +} diff --git a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ExerciseAssignmentRestController.java b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ExerciseAssignmentRestController.java index bfd8819..779142a 100644 --- a/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ExerciseAssignmentRestController.java +++ b/src/main/java/fr/univ_lille/gitlab/classrooms/assignments/ExerciseAssignmentRestController.java @@ -4,13 +4,10 @@ import jakarta.annotation.security.RolesAllowed; import org.gitlab4j.api.GitLabApi; import org.gitlab4j.api.GitLabApiException; import org.gitlab4j.api.models.Project; -import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.server.ResponseStatusException; import java.util.UUID; import java.util.stream.Collectors; @@ -51,12 +48,4 @@ public class ExerciseAssignmentRestController { return urls.collect(Collectors.joining("\n")); } - @GetMapping("/assignments/{assignmentId}/archive") - String archiveAssignment(@PathVariable UUID assignmentId, Model model) { - var assignment = this.assignmentService.getAssignment(assignmentId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - this.assignmentService.archive(assignment); - - return "redirect:/classrooms/" + assignment.getClassroom().getId(); - } - } diff --git a/src/main/resources/db/migration/V24__ASSIGNMENT_AUTO_ARCHIVE.sql b/src/main/resources/db/migration/V24__ASSIGNMENT_AUTO_ARCHIVE.sql new file mode 100644 index 0000000..7feaf6e --- /dev/null +++ b/src/main/resources/db/migration/V24__ASSIGNMENT_AUTO_ARCHIVE.sql @@ -0,0 +1,5 @@ +ALTER TABLE assignment + ADD column if not exists auto_archive bool; + +ALTER TABLE assignment + ADD column if not exists due_date TIMESTAMP WITH TIME ZONE; \ No newline at end of file