diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc5783d255e9f744522aba2363bce9e1484be850..502e79f049f750072a0d2bfd1514ccfdc3e2551b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,12 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
+ - Admins and programme coordinators can now add or remove TA coordinators. @rwbackx
### Changed
-
-### Changed
+ - Job information, hiring message, and reject message are now edited through the job details page, to facilitate bigger textareas. @rwbackx
+ - The edition create dialog now has some explanation about what to fill in. @rwbackx
### Fixed
+ - Fixed a bug where anyone without a role in a course edition could see the full job offer details. @rwbackx
## [2.1.2]
@@ -31,8 +33,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Importing job offers now provided better insight in what is wrong with the input file @rwbackx
* CSVs can now be imported with any supported separator, the separator used will be auto-detected @rwbackx
* Teachers can now indicate that applications should respond to the job information @rwbackx
-
-### Changed
* FlexDelft export for extra work now shows approved hours, not maximum hours @toberhuber
* Rejected by student job offers are now displayed if the student rejected after they were offered a position @rwbackx
diff --git a/build.gradle.kts b/build.gradle.kts
index b98d346eddfe05538ecb21618735a649f34abb1a..5e7782bd1b2f376db1f94260563b7cd73c403aaf 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ version = "2.1.2"
val javaVersion = JavaVersion.VERSION_17
-val labradoorVersion = "1.3.5"
+val labradoorVersion = "1.3.6-SNAPSHOT"
val libradorVersion = "1.0.3-SNAPSHOT7"
val chihuahUIVersion = "1.0.0-beta"
val guavaVersion = "31.1-jre"
diff --git a/src/main/java/nl/tudelft/tam/DevDatabaseLoader.java b/src/main/java/nl/tudelft/tam/DevDatabaseLoader.java
index f54b9b212f6f0896bc8802bcb8fa25a77f5570fb..731f47e390db1302656f2b5bff6c6a380bfe8815 100644
--- a/src/main/java/nl/tudelft/tam/DevDatabaseLoader.java
+++ b/src/main/java/nl/tudelft/tam/DevDatabaseLoader.java
@@ -232,11 +232,15 @@ public class DevDatabaseLoader {
.id(9L).emailNotifications(false).build());
csestudent2 = profileRepository.save(nl.tudelft.tam.model.Profile.builder()
.id(10L).emailNotifications(false).build());
+ var admin1 = profileRepository.save(nl.tudelft.tam.model.Profile.builder()
+ .id(1L).emailNotifications(false).build());
// Teacher 1 => Coordinator CSE
coordinatorRepository.save(Coordinator.builder().personId(cseteacher1.getId()).programId(1L).build());
// Teacher 2 => Coordinator EE
coordinatorRepository.save(Coordinator.builder().personId(cseteacher2.getId()).programId(2L).build());
+ // Admin 1 => Coordinator CSE
+ coordinatorRepository.save(Coordinator.builder().personId(admin1.getId()).programId(1L).build());
}
private void initApplications() {
diff --git a/src/main/java/nl/tudelft/tam/controller/CoordinatorController.java b/src/main/java/nl/tudelft/tam/controller/CoordinatorController.java
index 50442bc18ea8fdbd73f2a8eac3c09c5b5671a8c2..1eee365b245784e2398963888585ebf7e44a9a5e 100644
--- a/src/main/java/nl/tudelft/tam/controller/CoordinatorController.java
+++ b/src/main/java/nl/tudelft/tam/controller/CoordinatorController.java
@@ -26,6 +26,7 @@ import nl.tudelft.labracore.api.dto.ProgramDetailsDTO;
import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.tam.dto.patch.CoordinatorDefaultPatchDTO;
+import nl.tudelft.tam.model.Coordinator;
import nl.tudelft.tam.model.CoordinatorDefault;
import nl.tudelft.tam.model.TrainingType;
import nl.tudelft.tam.service.*;
@@ -43,9 +44,15 @@ public class CoordinatorController {
@Autowired
CoordinatorDefaultService coordinatorDefaultService;
+ @Autowired
+ CoordinatorService coordinatorService;
+
@Autowired
ProgramService programService;
+ @Autowired
+ PersonService personService;
+
@Autowired
TrainingTypeService trainingTypeService;
@@ -73,6 +80,12 @@ public class CoordinatorController {
}
model.addAttribute("coordinatingPrograms", coordinatingPrograms);
+ model.addAttribute("coordinators",
+ coordinatingPrograms.stream()
+ .collect(Collectors.toMap(ProgramDetailsDTO::getId,
+ p -> personService
+ .getPeopleById(coordinatorService.getCoordinatorsByProgram(p.getId())
+ .stream().map(Coordinator::getPersonId).toList()))));
return "coordinator/manage";
}
diff --git a/src/main/java/nl/tudelft/tam/controller/EditionController.java b/src/main/java/nl/tudelft/tam/controller/EditionController.java
index 7b6bf1a8028cbb127a46e5c10087e5360170de1a..15458634d18d572e3fbacfe47b4f594e5ad8b902 100644
--- a/src/main/java/nl/tudelft/tam/controller/EditionController.java
+++ b/src/main/java/nl/tudelft/tam/controller/EditionController.java
@@ -67,7 +67,7 @@ public class EditionController {
.name(tamCreateDto.getName())
.course(tamCreateDto.getCourse())
.cohort(tamCreateDto.getCohort())
- .enrollability(tamCreateDto.getEnrollability())
+ .enrollability(EditionCreateDTO.EnrollabilityEnum.OPEN)
.startDate(LocalDateTime.of(tamCreateDto.getStartDate(), LocalTime.of(0, 0)))
.endDate(LocalDateTime.of(tamCreateDto.getEndDate(), LocalTime.of(23, 59)));
diff --git a/src/main/java/nl/tudelft/tam/controller/JobOfferController.java b/src/main/java/nl/tudelft/tam/controller/JobOfferController.java
index 8b6dabf8518879cd10fae3aae106b6b065e7f971..2adb1bf2be7bfbc065692a75b86b09a2f38b7d4b 100644
--- a/src/main/java/nl/tudelft/tam/controller/JobOfferController.java
+++ b/src/main/java/nl/tudelft/tam/controller/JobOfferController.java
@@ -31,6 +31,7 @@ import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.tam.dto.create.JobOfferCreateDTO;
import nl.tudelft.tam.dto.create.MissingEditionsJobOfferImportDTO;
+import nl.tudelft.tam.dto.patch.JobOfferMessagePatchDTO;
import nl.tudelft.tam.dto.patch.JobOfferPatchDTO;
import nl.tudelft.tam.dto.view.details.ApplicationDetailsJobOfferDTO;
import nl.tudelft.tam.dto.view.details.ApplicationDetailsPersonDTO;
@@ -328,6 +329,21 @@ public class JobOfferController {
return "redirect:/job-offer/" + id;
}
+ /**
+ * Edits an existing job offer's message
+ *
+ * @param id The id of the job offer
+ * @param dto The new message data of the job offer
+ * @return The specific job offer page
+ */
+ @PatchMapping("{id}/messages")
+ @PreAuthorize("@authorisationService.isManagerOfJob(#id)")
+ public String patchJobOffer(@PathVariable Long id,
+ @Valid JobOfferMessagePatchDTO dto) {
+ jobOfferService.patchJobOffer(id, dto);
+ return "redirect:/job-offer/" + id;
+ }
+
/**
* Closes a job offer (ie. sets deadline to past)
*
diff --git a/src/main/java/nl/tudelft/tam/controller/ProgramController.java b/src/main/java/nl/tudelft/tam/controller/ProgramController.java
new file mode 100644
index 0000000000000000000000000000000000000000..bfb375761feeb1752f3ae38ae19ce071f788b46d
--- /dev/null
+++ b/src/main/java/nl/tudelft/tam/controller/ProgramController.java
@@ -0,0 +1,55 @@
+/*
+ * TAM
+ * Copyright (C) 2021 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.tam.controller;
+
+import java.util.Arrays;
+
+import nl.tudelft.tam.service.CoordinatorService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping("program")
+public class ProgramController {
+
+ private final CoordinatorService coordinatorService;
+
+ @Autowired
+ public ProgramController(CoordinatorService coordinatorService) {
+ this.coordinatorService = coordinatorService;
+ }
+
+ /**
+ * Sets the coordinators of the programme to a new lists of coordinators.
+ *
+ * @return Redirect to the manage page
+ */
+ @PatchMapping("{id}/coordinators")
+ @PreAuthorize("@authorisationService.canEditCoordinatorsForProgram(#id)")
+ public String updateCoordinators(@PathVariable Long id, String coordinators) {
+ coordinatorService.setCoordinatorsForProgram(id,
+ Arrays.stream(coordinators.split("\n")).map(String::trim).filter(s -> !s.isBlank()).toList());
+ return "redirect:/coordinator/manage";
+ }
+
+}
diff --git a/src/main/java/nl/tudelft/tam/controller/TAMProfileController.java b/src/main/java/nl/tudelft/tam/controller/TAMProfileController.java
index 3a0e8f5b80fb71a0cf232373a65a5c1375d63cfa..5b515064c940bb71adc3ad2365fefdb55ed3c925 100644
--- a/src/main/java/nl/tudelft/tam/controller/TAMProfileController.java
+++ b/src/main/java/nl/tudelft/tam/controller/TAMProfileController.java
@@ -17,18 +17,20 @@
*/
package nl.tudelft.tam.controller;
+import java.util.List;
+
import javax.transaction.Transactional;
import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
+import nl.tudelft.labracore.api.dto.RoleEditionDetailsDTO;
import nl.tudelft.labracore.lib.security.user.AuthenticatedPerson;
import nl.tudelft.labracore.lib.security.user.Person;
+import nl.tudelft.tam.cache.EditionCacheManager;
import nl.tudelft.tam.dto.patch.NotificationPatchDTO;
import nl.tudelft.tam.dto.patch.ShirtPatchDTO;
import nl.tudelft.tam.dto.view.summary.RaiseRequestSummaryDTO;
import nl.tudelft.tam.model.Profile;
-import nl.tudelft.tam.service.PersonService;
-import nl.tudelft.tam.service.ProfileService;
-import nl.tudelft.tam.service.RaiseRequestService;
+import nl.tudelft.tam.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -49,6 +51,12 @@ public class TAMProfileController {
@Autowired
private PersonService personService;
+ @Autowired
+ private RoleService roleService;
+
+ @Autowired
+ private EditionCacheManager editionCache;
+
/**
* Loads the profile page.
*
@@ -70,6 +78,10 @@ public class TAMProfileController {
model.addAttribute("poloPatch", new ShirtPatchDTO());
+ List<RoleEditionDetailsDTO> roles = roleService.getTARolesForPerson(person.getId());
+ editionCache.get(roles.stream().map(r -> r.getEdition().getId()).distinct());
+ model.addAttribute("roles", roles);
+
return "profile/view";
}
@@ -89,6 +101,10 @@ public class TAMProfileController {
model.addAttribute("person", person);
model.addAttribute("teacherView", true);
+ List<RoleEditionDetailsDTO> roles = roleService.getTARolesForPerson(person.getId());
+ editionCache.get(roles.stream().map(r -> r.getEdition().getId()).distinct());
+ model.addAttribute("roles", roles);
+
return "profile/view";
}
diff --git a/src/main/java/nl/tudelft/tam/dto/create/TamEditionCreateDTO.java b/src/main/java/nl/tudelft/tam/dto/create/TamEditionCreateDTO.java
index 15976a5a3390de002cd9f4915012a277c4f8fcb9..80a7a40bca37df78f486ff48e4aaa0798e16f9e7 100644
--- a/src/main/java/nl/tudelft/tam/dto/create/TamEditionCreateDTO.java
+++ b/src/main/java/nl/tudelft/tam/dto/create/TamEditionCreateDTO.java
@@ -28,7 +28,6 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import nl.tudelft.labracore.api.dto.CohortIdDTO;
import nl.tudelft.labracore.api.dto.CourseIdDTO;
-import nl.tudelft.labracore.api.dto.EditionCreateDTO;
import org.springframework.format.annotation.DateTimeFormat;
@@ -47,9 +46,6 @@ public class TamEditionCreateDTO {
@NotNull
private CohortIdDTO cohort;
- @NotNull
- private EditionCreateDTO.EnrollabilityEnum enrollability;
-
@NotNull
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate startDate;
diff --git a/src/main/java/nl/tudelft/tam/dto/patch/JobOfferMessagePatchDTO.java b/src/main/java/nl/tudelft/tam/dto/patch/JobOfferMessagePatchDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..be0be3682cf2ec9eeed47d00613cb7445cfefe90
--- /dev/null
+++ b/src/main/java/nl/tudelft/tam/dto/patch/JobOfferMessagePatchDTO.java
@@ -0,0 +1,51 @@
+/*
+ * TAM
+ * Copyright (C) 2021 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.tam.dto.patch;
+
+import lombok.*;
+import nl.tudelft.librador.dto.patch.Patch;
+import nl.tudelft.tam.model.JobOffer;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+public class JobOfferMessagePatchDTO extends Patch<JobOffer> {
+
+ private String description;
+
+ private String hiringMessage;
+
+ private String rejectMessage;
+
+ @Builder.Default
+ private Boolean requireApplicationText = false;
+
+ @Override
+ protected void applyOneToOne() {
+ updateNonNull(description, data::setDescription);
+ updateNonNull(hiringMessage, data::setHiringMessage);
+ updateNonNull(rejectMessage, data::setRejectMessage);
+ updateNonNull(requireApplicationText, data::setRequireApplicationText);
+ }
+
+ @Override
+ protected void validate() {
+ }
+}
diff --git a/src/main/java/nl/tudelft/tam/dto/patch/JobOfferPatchDTO.java b/src/main/java/nl/tudelft/tam/dto/patch/JobOfferPatchDTO.java
index a5ac560d66cf2f1414e1bab89fddfe72c73d8599..431427738a6b3f14a1d5a8ac562151ad36c083d2 100644
--- a/src/main/java/nl/tudelft/tam/dto/patch/JobOfferPatchDTO.java
+++ b/src/main/java/nl/tudelft/tam/dto/patch/JobOfferPatchDTO.java
@@ -52,15 +52,6 @@ public class JobOfferPatchDTO extends Patch<JobOffer> {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate deadline;
- private String description;
-
- private String hiringMessage;
-
- private String rejectMessage;
-
- @Builder.Default
- private Boolean requireApplicationText = false;
-
@Override
protected void applyOneToOne() {
updateNonNull(name, data::setName);
@@ -70,10 +61,6 @@ public class JobOfferPatchDTO extends Patch<JobOffer> {
updateNonNull(baanCode, data::setBaanCode);
data.setMaxHours(maxHours);
data.setDeadline(deadline);
- data.setDescription(description != null && description.isBlank() ? null : description);
- data.setHiringMessage(hiringMessage);
- data.setRejectMessage(rejectMessage);
- updateNonNull(requireApplicationText, data::setRequireApplicationText);
}
@Override
diff --git a/src/main/java/nl/tudelft/tam/security/AuthorisationService.java b/src/main/java/nl/tudelft/tam/security/AuthorisationService.java
index af0541a3ac62cdd86b11be7c03964b9f4cea8cc6..da770b4921f456a6fc463cca5140ee83445f2cda 100644
--- a/src/main/java/nl/tudelft/tam/security/AuthorisationService.java
+++ b/src/main/java/nl/tudelft/tam/security/AuthorisationService.java
@@ -18,6 +18,7 @@
package nl.tudelft.tam.security;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -68,6 +69,9 @@ public class AuthorisationService {
@Autowired
private CoordinatorService coordinatorService;
+ @Autowired
+ private ProgramService programService;
+
/**
* Returns the authenticated person from the user principal.
*
@@ -124,7 +128,20 @@ public class AuthorisationService {
*/
public boolean isCoordinatorForProgram(Long programId) {
return isAdmin()
- || coordinatorService.existsByPersonIdAndProgramId(getAuthPerson().getId(), programId);
+ || coordinatorService.existsByPersonIdAndProgramId(getAuthPerson().getId(), programId)
+ || programService.get(programId).getCoordinators().stream()
+ .anyMatch(c -> Objects.equals(c.getId(), getAuthPerson().getId()));
+ }
+
+ /**
+ * Checks if the person can edit the coordinators for a programme
+ *
+ * @param programId the id of the programme
+ * @return True iff the authenticated person can edit the coordinators for the programme
+ */
+ public boolean canEditCoordinatorsForProgram(Long programId) {
+ return isAdmin() || programService.get(programId).getCoordinators().stream()
+ .anyMatch(c -> Objects.equals(c.getId(), getAuthPerson().getId()));
}
/**
@@ -186,7 +203,7 @@ public class AuthorisationService {
List<RoleDetailsDTO> roles = roleService
.getRolesById(List.of(editionId),
List.of(getAuthPerson().getId()));
- return roles.size() != 1 || roles.get(0).getType().equals(RoleDetailsDTO.TypeEnum.TEACHER);
+ return roles.size() == 1 && roles.get(0).getType().equals(RoleDetailsDTO.TypeEnum.TEACHER);
}
/**
diff --git a/src/main/java/nl/tudelft/tam/service/CoordinatorService.java b/src/main/java/nl/tudelft/tam/service/CoordinatorService.java
index 41bba00c11d95e84d4692f7cb63097f998337ea8..9bd7a9b8a9d8f051e3acd3ccd1b2bd41adb1e50f 100644
--- a/src/main/java/nl/tudelft/tam/service/CoordinatorService.java
+++ b/src/main/java/nl/tudelft/tam/service/CoordinatorService.java
@@ -18,14 +18,18 @@
package nl.tudelft.tam.service;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
+import nl.tudelft.labracore.api.PersonControllerApi;
+import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
import nl.tudelft.tam.model.Coordinator;
import nl.tudelft.tam.repository.CoordinatorRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
@Service
public class CoordinatorService {
@@ -39,6 +43,9 @@ public class CoordinatorService {
@Autowired
CourseService courseService;
+ @Autowired
+ PersonControllerApi personApi;
+
/**
* Finds all coordinator objects by person id
*
@@ -81,6 +88,16 @@ public class CoordinatorService {
.map(Coordinator::getProgramId).collect(Collectors.toSet());
}
+ /**
+ * Finds all coordinators by program id
+ *
+ * @param programId The program id to search by
+ * @return The list of coordinators
+ */
+ public List<Coordinator> getCoordinatorsByProgram(Long programId) {
+ return coordinatorRepository.findAllByProgramId(programId);
+ }
+
/**
* Finds all coordinators by edition id
*
@@ -92,4 +109,27 @@ public class CoordinatorService {
Long programId = courseService.getOrThrow(courseId).getProgram().getId();
return coordinatorRepository.findAllByProgramId(programId);
}
+
+ /**
+ * Sets the coordinators for the given programme to the people corresponding to the specified usernames.
+ *
+ * @param programId The id of the programme
+ * @param usernames The usernames of the new coordinators
+ */
+ @Transactional
+ public void setCoordinatorsForProgram(Long programId, List<String> usernames) {
+ List<PersonSummaryDTO> coordinators = personApi.searchForPeople(usernames).block().getPeople();
+ for (Coordinator coordinator : coordinatorRepository.findAllByProgramId(programId)) {
+ if (coordinators.stream().noneMatch(c -> Objects.equals(coordinator.getId(), c.getId()))) {
+ coordinatorRepository.delete(coordinator);
+ }
+ }
+ for (PersonSummaryDTO coordinator : coordinators) {
+ if (!coordinatorRepository.existsByPersonIdAndProgramId(coordinator.getId(), programId)) {
+ coordinatorRepository.save(
+ Coordinator.builder().programId(programId).personId(coordinator.getId()).build());
+ }
+ }
+ }
+
}
diff --git a/src/main/java/nl/tudelft/tam/service/JobOfferService.java b/src/main/java/nl/tudelft/tam/service/JobOfferService.java
index 01d16ef4431dcf9c4ee9054fdac9870c720ad962..1a565d898c72d6b991f782aa8eabd959642e311b 100644
--- a/src/main/java/nl/tudelft/tam/service/JobOfferService.java
+++ b/src/main/java/nl/tudelft/tam/service/JobOfferService.java
@@ -29,10 +29,10 @@ import javax.transaction.Transactional;
import javax.validation.constraints.NotNull;
import nl.tudelft.labracore.api.dto.*;
+import nl.tudelft.librador.dto.patch.Patch;
import nl.tudelft.librador.dto.view.View;
import nl.tudelft.tam.dto.create.JobOfferCreateDTO;
import nl.tudelft.tam.dto.create.MissingEditionsJobOfferImportDTO;
-import nl.tudelft.tam.dto.patch.JobOfferPatchDTO;
import nl.tudelft.tam.dto.view.details.JobOfferDetailsDTO;
import nl.tudelft.tam.dto.view.summary.JobOfferSummaryDTO;
import nl.tudelft.tam.enums.Status;
@@ -175,7 +175,7 @@ public class JobOfferService {
* @param dto the changed job offer.
*/
@Transactional
- public void patchJobOffer(Long id, JobOfferPatchDTO dto) {
+ public void patchJobOffer(Long id, Patch<JobOffer> dto) {
dto.apply(jobOfferRepository.findByIdOrThrow(id));
}
diff --git a/src/main/java/nl/tudelft/tam/service/RoleService.java b/src/main/java/nl/tudelft/tam/service/RoleService.java
index a008fc3cf5acb138bc390371fbc23133531a64a1..7c1717c3144e4212e7b6c262cc2bc3cf1231484a 100644
--- a/src/main/java/nl/tudelft/tam/service/RoleService.java
+++ b/src/main/java/nl/tudelft/tam/service/RoleService.java
@@ -17,10 +17,15 @@
*/
package nl.tudelft.tam.service;
+import static nl.tudelft.labracore.api.dto.RoleEditionDetailsDTO.TypeEnum.HEAD_TA;
+import static nl.tudelft.labracore.api.dto.RoleEditionDetailsDTO.TypeEnum.TA;
+
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
+import nl.tudelft.labracore.api.PersonControllerApi;
import nl.tudelft.labracore.api.RoleControllerApi;
import nl.tudelft.labracore.api.dto.*;
@@ -42,6 +47,20 @@ public class RoleService {
@Autowired
RoleControllerApi roleControllerApi;
+ @Autowired
+ PersonControllerApi personApi;
+
+ /**
+ * Get all the TA and Head TA roles for a person.
+ *
+ * @param personId The id of the person
+ * @return The list of roles
+ */
+ public List<RoleEditionDetailsDTO> getTARolesForPerson(Long personId) {
+ return personApi.getRolesForPerson(personId).collectList().block().stream()
+ .filter(r -> Set.of(TA, HEAD_TA).contains(r.getType())).toList();
+ }
+
/**
* Checks if a person has a role in an edition
*
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index 36b4e507bbd4fb8ee6822713513c00ded2ce1b60..24faf53feb3b8ffc99f68d43784ca67165f08a0d 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -58,8 +58,11 @@ edition.cohort = Cohort
edition.cohort.import = Import participants from cohort
edition.startDate = Start Date
edition.startDate.enter = Enter start date... (dd-mm-yyyy HH:mm)
+edition.startDate.tooltip = Enter the date from which you want job offers to be visible.
edition.endDate = End Date
edition.endDate.enter = Enter end date... (dd-mm-yyyy HH:mm)
+edition.endDate.tooltip = Enter a date after the last resit for this edition.
+edition.name.tooltip = Enter only the 'edition' part of the name, e.g. '23/24 Q1'
edition.enrollability = Enrollment Policy
edition.enrollability.open = Open
edition.enrollability.open_to_program = Open to Program
@@ -87,6 +90,8 @@ profile.notifications.emailOff = Emails Disabled
profile.notifications.edit = Edit Notification Preferences
profile.notifications.email = Email Notifications
profile.notifications.frequency = Email Frequency
+profile.experience = Experience
+profile.noExperience = No previous TA experience
jobOffer = Job Offer
jobOffer.many = Job Offers
@@ -106,6 +111,7 @@ jobOffer.retract = Retract
jobOffer.retract.confirm = Confirm Retraction
jobOffer.retract.confirm.desc = Are you sure you would like to retract your application for the following positions:
jobOffer.description = Job Description
+jobOffer.description.none = This job offer has no information
jobOffer.contractName = Contract Name
jobOffer.contractName.enter = Enter a name for the FlexDelft contract...
jobOffer.contractStartDate = Contract Start Date
@@ -203,6 +209,10 @@ application.noContent = No response
application.enterContent = Enter response
application.viewContent = View response
+role = Role
+role.ta = TA
+role.head_ta = Head TA
+
extraWork = Contract Request
extraWork.many = Contract Requests
extraWork.description = Work Description
@@ -363,9 +373,11 @@ training.import.success.title = Successfully Imported Training
training.import.success.desc = Successfully imported training for student(s)!
coordinator = Coordinator
+coordinators = Coordinators
coordinator.manage = Manage Programmes
coordinator.search = Programme...
coordinator.empty = Sadly, you are not a coordinator for any programmes (yet?)
+coordinator.enterUsernames = Enter the NetIDs of the TA coordinators
# Training
coordinator.training = TA Training
coordinator.training.empty = No TA trainings have been created. Try adding one!
diff --git a/src/main/resources/templates/coordinator/manage.html b/src/main/resources/templates/coordinator/manage.html
index c073a7bae6f0aa48cc39e945c0477dec73f4425f..c67a06345ead062bdf4d6b4d6eb70c5795ef2feb 100644
--- a/src/main/resources/templates/coordinator/manage.html
+++ b/src/main/resources/templates/coordinator/manage.html
@@ -61,7 +61,14 @@
data-style="outlined"
th:href="@{|/coordinator/defaults/${program.id}|}"
th:text="|#{general.manage} #{coordinator.defaults}|"></a>
+ <button
+ th:if="${@authorisationService.canEditCoordinatorsForProgram(program.id)}"
+ class="button grow"
+ data-style="outlined"
+ th:data-dialog="|edit-coordinators-${program.id}-overlay|"
+ th:text="|#{general.manage} #{coordinators}|"></button>
</div>
+ <th:block th:replace="~{coordinator/manage_coordinators :: overlay}"></th:block>
</div>
</div>
diff --git a/src/main/resources/templates/coordinator/manage_coordinators.html b/src/main/resources/templates/coordinator/manage_coordinators.html
new file mode 100644
index 0000000000000000000000000000000000000000..d555a965ef22d3d86b1a8490966965a9935bcf66
--- /dev/null
+++ b/src/main/resources/templates/coordinator/manage_coordinators.html
@@ -0,0 +1,58 @@
+<!--
+
+ TAM
+ Copyright (C) 2021 - Delft University of Technology
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+-->
+<!DOCTYPE html>
+<html
+ lang="en"
+ xmlns:th="http://www.thymeleaf.org"
+ xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
+ <body>
+ <dialog
+ th:fragment="overlay"
+ th:if="${@authorisationService.isCoordinatorForProgram(program.id)}"
+ th:id="|edit-coordinators-${program.id}-overlay|"
+ class="dialog">
+ <form
+ th:method="patch"
+ th:action="@{/program/{programId}/coordinators(programId=${program.id})}"
+ class="flex vertical p-7">
+ <h1 class="underlined font-500" th:text="|#{general.manage} #{coordinators}|"></h1>
+
+ <label for="coordinators" th:text="#{coordinator.enterUsernames}"></label>
+ <textarea
+ class="textfield"
+ rows="5"
+ id="coordinators"
+ name="coordinators"
+ th:text="${#strings.listJoin(coordinators[program.id].![username], ' ')}"></textarea>
+
+ <div class="flex space-between">
+ <button
+ type="button"
+ class="button p-less"
+ data-type="error"
+ data-style="outlined"
+ th:text="#{general.cancel}"
+ data-cancel></button>
+ <button type="submit" class="button p-less" th:text="#{general.edit}"></button>
+ </div>
+ </form>
+ </dialog>
+ </body>
+</html>
diff --git a/src/main/resources/templates/edition/create.html b/src/main/resources/templates/edition/create.html
index fc2f06e424e3891f72d6df42375d0cba9de3537c..31cd7c5b659bf30a1067fbf93c9f41bf55af5afb 100644
--- a/src/main/resources/templates/edition/create.html
+++ b/src/main/resources/templates/edition/create.html
@@ -36,7 +36,15 @@
<h1 class="underlined font-500" th:text="#{edition.create}"></h1>
<div class="grid gap-3 align-center" style="grid-template-columns: 8rem 1fr">
- <label for="name" th:text="#{general.name}"></label>
+ <div>
+ <label for="name" th:text="#{general.name}"></label>
+ <div class="tooltip">
+ <span class="tooltip__control fa-solid fa-question"></span>
+ <div role="tooltip">
+ <p th:text="#{edition.name.tooltip}"></p>
+ </div>
+ </div>
+ </div>
<input
id="name"
th:name="name"
@@ -63,7 +71,15 @@
</select>
</div>
- <label for="start-date" th:text="#{edition.startDate}"></label>
+ <div>
+ <label for="start-date" th:text="#{edition.startDate}"></label>
+ <div class="tooltip">
+ <span class="tooltip__control fa-solid fa-question"></span>
+ <div role="tooltip">
+ <p th:text="#{edition.startDate.tooltip}"></p>
+ </div>
+ </div>
+ </div>
<input
id="start-date"
th:name="startDate"
@@ -72,7 +88,15 @@
class="textfield"
required />
- <label for="end-date" th:text="#{edition.endDate}"></label>
+ <div>
+ <label for="end-date" th:text="#{edition.endDate}"></label>
+ <div class="tooltip">
+ <span class="tooltip__control fa-solid fa-question"></span>
+ <div role="tooltip">
+ <p th:text="#{edition.endDate.tooltip}"></p>
+ </div>
+ </div>
+ </div>
<input
id="end-date"
th:name="endDate"
@@ -80,14 +104,6 @@
type="date"
class="textfield"
required />
-
- <label for="enrollment" th:text="#{edition.enrollability}"></label>
- <select id="enrollment" th:name="enrollability" class="textfield">
- <option
- th:each="option : ${T(nl.tudelft.labracore.api.dto.EditionDetailsDTO.EnrollabilityEnum).values()}"
- th:text="#{|edition.enrollability.${#strings.toLowerCase(option.name())}|}"
- th:value="${option}"></option>
- </select>
</div>
<div class="flex space-between">
diff --git a/src/main/resources/templates/job_offer/create.html b/src/main/resources/templates/job_offer/create.html
index fb924845eef8079682a9780683c4986c0b5b8213..152609d3711520fc17aa1d7b75219950f43388fc 100644
--- a/src/main/resources/templates/job_offer/create.html
+++ b/src/main/resources/templates/job_offer/create.html
@@ -60,64 +60,6 @@
class="textfield"
required />
- <label
- for="new-job-offer-description"
- th:text="#{jobOffer.descriptionMessage}"></label>
- <textarea
- id="new-job-offer-description"
- th:name="description"
- class="textfield"
- type="text"
- th:placeholder="#{jobOffer.descriptionMessage.enter}"
- maxlength="2047"></textarea>
- <div></div>
- <div>
- <input
- id="new-job-offer-require-response"
- name="requireApplicationText"
- type="checkbox" />
- <label
- for="new-job-offer-require-response"
- th:text="#{jobOffer.requireResponse}"></label>
- <script>
- const requireResponse = document.getElementById(
- "new-job-offer-require-response"
- );
- const description = document.getElementById(
- "new-job-offer-description"
- );
- requireResponse.addEventListener("change", function () {
- if (requireResponse.checked) {
- description.setAttribute("required", "");
- } else {
- description.removeAttribute("required");
- }
- });
- </script>
- </div>
-
- <label
- for="new-job-offer-hiring-message"
- th:text="#{jobOffer.hiringMessage}"></label>
- <textarea
- id="new-job-offer-hiring-message"
- th:name="hiringMessage"
- class="textfield"
- type="text"
- th:placeholder="#{jobOffer.hiringMessage.enter}"
- maxlength="255"></textarea>
-
- <label
- for="new-job-offer-reject-message"
- th:text="#{jobOffer.rejectMessage}"></label>
- <textarea
- id="new-job-offer-reject-message"
- th:name="rejectMessage"
- class="textfield"
- type="text"
- th:placeholder="#{jobOffer.rejectMessage.enter}"
- maxlength="255"></textarea>
-
<label
for="new-job-offer-contract-name"
th:text="#{jobOffer.contractName}"></label>
diff --git a/src/main/resources/templates/job_offer/edit.html b/src/main/resources/templates/job_offer/edit.html
index 928d412ce31df1b532a17d826e449fb6d1252144..926d053bd1add32876176dcc3e1f6a5da8853148 100644
--- a/src/main/resources/templates/job_offer/edit.html
+++ b/src/main/resources/templates/job_offer/edit.html
@@ -46,69 +46,6 @@
th:value="${offer.name}"
required />
- <label
- for="new-job-offer-description"
- th:text="#{jobOffer.descriptionMessage}"></label>
- <textarea
- id="new-job-offer-description"
- th:name="description"
- class="textfield"
- type="text"
- th:placeholder="#{jobOffer.descriptionMessage.enter}"
- th:text="${offer.description}"
- th:required="${offer.requireApplicationText}"
- maxlength="2047"></textarea>
- <div></div>
- <div>
- <input
- id="new-job-offer-require-response"
- name="requireApplicationText"
- type="checkbox"
- th:checked="${offer.requireApplicationText}" />
- <label
- for="new-job-offer-require-response"
- th:text="#{jobOffer.requireResponse}"></label>
- <script>
- const requireResponse = document.getElementById(
- "new-job-offer-require-response"
- );
- const description = document.getElementById(
- "new-job-offer-description"
- );
- requireResponse.addEventListener("change", function () {
- if (requireResponse.checked) {
- description.setAttribute("required", "");
- } else {
- description.removeAttribute("required");
- }
- });
- </script>
- </div>
-
- <label
- for="new-job-offer-hiring-message"
- th:text="#{jobOffer.hiringMessage}"></label>
- <textarea
- id="new-job-offer-hiring-message"
- th:name="hiringMessage"
- class="textfield"
- type="text"
- th:placeholder="#{jobOffer.hiringMessage.enter}"
- th:text="${offer.hiringMessage}"
- maxlength="255"></textarea>
-
- <label
- for="new-job-offer-reject-message"
- th:text="#{jobOffer.rejectMessage}"></label>
- <textarea
- id="new-job-offer-reject-message"
- th:name="rejectMessage"
- class="textfield"
- type="text"
- th:placeholder="#{jobOffer.rejectMessage.enter}"
- th:text="${offer.rejectMessage}"
- maxlength="255"></textarea>
-
<label
for="new-job-offer-contract-name"
th:text="#{jobOffer.contractName}"></label>
diff --git a/src/main/resources/templates/job_offer/manage.html b/src/main/resources/templates/job_offer/manage.html
index 0ece549026b5b85d3a6fe7f176b1959f435bd690..884b67dd9c1c4a7afc1b08044aa6c94c2d6c767f 100644
--- a/src/main/resources/templates/job_offer/manage.html
+++ b/src/main/resources/templates/job_offer/manage.html
@@ -148,11 +148,18 @@
class="link"
th:href="@{|/job-offer/${offer.id}|}"
th:text="${offer.name}"></a>
- <span
- th:if="${@applicationService.getNumberOfUnhandledApplications(offer.id)} > 0"
- th:text="|${@applicationService.getNumberOfUnhandledApplications(offer.id)} pending|"
- class="chip"
- data-type="warning"></span>
+ <div>
+ <span
+ th:if="${offer.deadline?.isBefore(#temporals.createToday())} ?: false"
+ th:text="#{jobOffer.deadline.closed}"
+ class="chip"
+ data-type="info"></span>
+ <span
+ th:if="${@applicationService.getNumberOfUnhandledApplications(offer.id)} > 0"
+ th:text="|${@applicationService.getNumberOfUnhandledApplications(offer.id)} pending|"
+ class="chip"
+ data-type="warning"></span>
+ </div>
</li>
</ul>
</div>
diff --git a/src/main/resources/templates/job_offer/view_one.html b/src/main/resources/templates/job_offer/view_one.html
index 2882b8365ac71c48e7cb603ce351cf4184d211bc..be04f8f37c8f4d7722b6a777a5031af9c296e749 100644
--- a/src/main/resources/templates/job_offer/view_one.html
+++ b/src/main/resources/templates/job_offer/view_one.html
@@ -113,9 +113,151 @@
</div>
</div>
- <div class="surface" th:if="${offer.description}">
- <h2 class="underlined font-500 mb-5" th:text="#{jobOffer.descriptionMessage}"></h2>
- <div class="article" th:utext="${offer.descriptionHtml}"></div>
+ <div class="surface">
+ <h2 class="underlined font-500 mb-5">Messages</h2>
+ <div class="tabs mb-3" role="tablist">
+ <button
+ role="tab"
+ id="job-information-tab"
+ aria-selected="true"
+ aria-controls="job-information">
+ Job information
+ </button>
+ <button
+ role="tab"
+ id="hiring-message-tab"
+ aria-selected="false"
+ aria-controls="hiring-message">
+ Hiring message
+ </button>
+ <button
+ role="tab"
+ id="rejection-message-tab"
+ aria-selected="false"
+ aria-controls="rejection-message">
+ Rejection message
+ </button>
+ </div>
+
+ <div id="job-information" aria-labelledby="job-information-tab">
+ <div class="flex vertical gap-3" id="view-information">
+ <div
+ th:if="${offer.description}"
+ class="article"
+ th:utext="${offer.descriptionHtml}"></div>
+ <p
+ th:unless="${offer.description}"
+ th:text="#{jobOffer.description.none}"></p>
+ <div>
+ <button id="edit-information-button" class="button">
+ <span class="fa-solid fa-pencil"></span>
+ <span>Edit</span>
+ </button>
+ </div>
+ </div>
+ <form
+ class="flex vertical gap-3 hidden"
+ id="edit-information"
+ th:method="patch"
+ th:action="@{/job-offer/{id}/messages(id=${offer.id})}">
+ <textarea
+ class="textfield"
+ data-style="variant"
+ placeholder="Enter job information"
+ th:text="${offer.description}"
+ rows="4"
+ name="description"
+ aria-label="Job information"
+ id="description"
+ required></textarea>
+ <div>
+ <input
+ id="new-job-offer-require-response"
+ name="requireApplicationText"
+ type="checkbox"
+ th:checked="${offer.requireApplicationText}" />
+ <label
+ for="new-job-offer-require-response"
+ th:text="#{jobOffer.requireResponse}"></label>
+ </div>
+ <div class="flex gap-3">
+ <button
+ id="cancel-edit-information-button"
+ type="button"
+ class="button"
+ data-style="outlined">
+ <span class="fa-solid fa-xmark"></span>
+ <span>Cancel</span>
+ </button>
+ <button type="submit" class="button">
+ <span class="fa-solid fa-save"></span>
+ <span>Save</span>
+ </button>
+ </div>
+ </form>
+ <script>
+ document.addEventListener("DOMContentLoaded", function () {
+ const editButton = document.getElementById("edit-information-button");
+ const cancelButton = document.getElementById(
+ "cancel-edit-information-button"
+ );
+ const viewDesc = document.getElementById("view-information");
+ const editDesc = document.getElementById("edit-information");
+ const descField = document.getElementById("description");
+ let originalDesc;
+ editButton.addEventListener("click", function () {
+ viewDesc.classList.add("hidden");
+ editDesc.classList.remove("hidden");
+ originalDesc = descField.value;
+ });
+ cancelButton.addEventListener("click", function () {
+ viewDesc.classList.remove("hidden");
+ editDesc.classList.add("hidden");
+ descField.value = originalDesc;
+ });
+ });
+ </script>
+ </div>
+
+ <div id="hiring-message" aria-labelledby="hiring-message-tab" hidden>
+ <form
+ class="flex vertical gap-3"
+ th:method="patch"
+ th:action="@{/job-offer/{id}/messages(id=${offer.id})}">
+ <textarea
+ class="textfield"
+ data-style="variant"
+ placeholder="Enter hiring message"
+ th:text="${offer.hiringMessage}"
+ rows="4"></textarea>
+ <div>
+ <button class="button">
+ <span class="fa-solid fa-save"></span>
+ <span>Save</span>
+ </button>
+ </div>
+ </form>
+ </div>
+
+ <div id="rejection-message" aria-labelledby="rejection-message-tab" hidden>
+ <form
+ class="flex vertical gap-3"
+ th:method="patch"
+ th:action="@{/job-offer/{id}/messages(id=${offer.id})}">
+ <textarea
+ class="textfield"
+ data-style="variant"
+ placeholder="Enter rejection message"
+ th:text="${offer.rejectMessage}"
+ rows="4"></textarea>
+ <div>
+ <button class="button">
+ <span class="fa-solid fa-save"></span>
+ <span>Save</span>
+ </button>
+ </div>
+ </form>
+ </div>
</div>
<div class="surface flex space-around">
diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html
index 64d30ab46b73c70510f9e5123feefcb8a90bf6ab..31a0629eac0fbbe48878132c32e55988a6c8ac21 100644
--- a/src/main/resources/templates/layout.html
+++ b/src/main/resources/templates/layout.html
@@ -24,13 +24,13 @@
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<link rel="stylesheet" href="/webjars/font-awesome/css/all.min.css" />
- <link rel="stylesheet" href="/webjars/chihuahui/main.css" />
+ <link rel="stylesheet" href="/webjars/chihuahui/1.0.0/main.css" />
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/momentjs/2.29.4/min/moment.min.js"></script>
- <script type="module" src="/webjars/chihuahui/components.js"></script>
- <script src="/webjars/chihuahui/selectbox.js"></script>
- <script src="/webjars/chihuahui/theme.js"></script>
+ <script type="module" src="/webjars/chihuahui/1.0.0/components.js"></script>
+ <script src="/webjars/chihuahui/1.0.0/selectbox.js"></script>
+ <script src="/webjars/chihuahui/1.0.0/theme.js"></script>
<script th:src="@{/js/main.js}"></script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
diff --git a/src/main/resources/templates/profile/view.html b/src/main/resources/templates/profile/view.html
index 836b23d0a2dbeeac0860fb8e57dd2be7f16476ea..b820434f6fcb59d64e5f0e8a62e0633d573be04e 100644
--- a/src/main/resources/templates/profile/view.html
+++ b/src/main/resources/templates/profile/view.html
@@ -81,7 +81,7 @@
th:unless="${@authorisationService.isStaff() && !teacherView}">
<h2 class="underlined font-500" th:text="#{profile.payscale}"></h2>
<span th:text="${profile.payScale.detailedString()}"></span>
- <div>
+ <div th:unless="${teacherView}">
<button
class="button"
data-style="outlined"
@@ -90,6 +90,11 @@
th:unless="${profile.payScale.equals(T(nl.tudelft.tam.enums.PayScale).SA4)}"></button>
</div>
</div>
+
+ <div class="surface flex vertical" th:if="${roles.isEmpty()}">
+ <h2 class="underlined font-500" th:text="#{profile.experience}"></h2>
+ <p th:text="#{profile.noExperience}"></p>
+ </div>
</div>
<div class="grid auto-fit" style="--col-width: 20rem">
@@ -114,6 +119,22 @@
</div>
</div>
+ <div class="flex vertical" th:unless="${roles.isEmpty()}">
+ <h2 class="underlined font-500" th:text="#{profile.experience}"></h2>
+ <table class="table" data-style="surface">
+ <tr class="table__header">
+ <th class="fit-content" th:text="#{course}"></th>
+ <th th:text="#{role}"></th>
+ </tr>
+ <tr th:each="role : ${roles}">
+ <td
+ class="fit-content single-line"
+ th:text="|${@editionCacheManager.getOrThrow(role.edition.id).course.name} - ${role.edition.name}|"></td>
+ <td th:text="#{|role.${role.type.name().toLowerCase()}|}"></td>
+ </tr>
+ </table>
+ </div>
+
<div th:replace="~{profile/edit_polo :: overlay}" th:unless="${teacherView}"></div>
<div th:replace="~{profile/view_training_details :: overlay}"></div>
<div
diff --git a/src/test/java/nl/tudelft/tam/controller/EditionControllerTest.java b/src/test/java/nl/tudelft/tam/controller/EditionControllerTest.java
index 95a925617ced1b2d21a77474df95251369817ac4..96ddc9d9eefd7fbf88eb6f76a67014ee603a5361 100644
--- a/src/test/java/nl/tudelft/tam/controller/EditionControllerTest.java
+++ b/src/test/java/nl/tudelft/tam/controller/EditionControllerTest.java
@@ -75,7 +75,6 @@ public class EditionControllerTest {
tamEditionCreateDto.setName("Test");
tamEditionCreateDto.setStartDate(LocalDate.now());
tamEditionCreateDto.setEndDate(LocalDate.now().plusYears(1));
- tamEditionCreateDto.setEnrollability(EditionCreateDTO.EnrollabilityEnum.OPEN);
tamEditionCreateDto.setCohort(new CohortIdDTO().id(1L));
tamEditionCreateDto.setCourse(new CourseIdDTO().id(1L));
@@ -83,7 +82,7 @@ public class EditionControllerTest {
.name(tamEditionCreateDto.getName())
.course(tamEditionCreateDto.getCourse())
.cohort(tamEditionCreateDto.getCohort())
- .enrollability(tamEditionCreateDto.getEnrollability())
+ .enrollability(EditionCreateDTO.EnrollabilityEnum.OPEN)
.startDate(LocalDateTime.of(tamEditionCreateDto.getStartDate(), LocalTime.of(0, 0)))
.endDate(LocalDateTime.of(tamEditionCreateDto.getEndDate(), LocalTime.of(23, 59)));
}
diff --git a/src/test/java/nl/tudelft/tam/controller/ProgramControllerTest.java b/src/test/java/nl/tudelft/tam/controller/ProgramControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3b1cbd9ab2de7a62a98c1e73f7d478fbfdf09d0
--- /dev/null
+++ b/src/test/java/nl/tudelft/tam/controller/ProgramControllerTest.java
@@ -0,0 +1,63 @@
+/*
+ * TAM
+ * Copyright (C) 2021 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.tam.controller;
+
+import static org.mockito.Mockito.verify;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import java.util.List;
+
+import javax.transaction.Transactional;
+
+import nl.tudelft.tam.service.CoordinatorService;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithUserDetails;
+import org.springframework.test.web.servlet.MockMvc;
+
+import application.test.TestTAMApplication;
+
+@AutoConfigureMockMvc
+@SpringBootTest(classes = TestTAMApplication.class)
+@Transactional
+public class ProgramControllerTest {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ProgramController programController;
+
+ @MockBean
+ private CoordinatorService coordinatorService;
+
+ @Test
+ @WithUserDetails("admin")
+ void updateCoordinators() throws Exception {
+ mvc.perform(patch("/program/1/coordinators?coordinators=user1\nuser2").with(csrf()))
+ .andExpect(status().is3xxRedirection());
+ verify(coordinatorService).setCoordinatorsForProgram(1L, List.of("user1", "user2"));
+ }
+
+}
diff --git a/src/test/java/nl/tudelft/tam/controller/TAMProfileControllerTest.java b/src/test/java/nl/tudelft/tam/controller/TAMProfileControllerTest.java
index cc2db9cb2c8c4a81308be9112c991de44d42f149..c7c02c71eaa6c798ab0058a27b3e653e54a8f27d 100644
--- a/src/test/java/nl/tudelft/tam/controller/TAMProfileControllerTest.java
+++ b/src/test/java/nl/tudelft/tam/controller/TAMProfileControllerTest.java
@@ -37,6 +37,7 @@ import nl.tudelft.tam.model.Profile;
import nl.tudelft.tam.security.AuthorisationService;
import nl.tudelft.tam.service.PersonService;
import nl.tudelft.tam.service.ProfileService;
+import nl.tudelft.tam.service.RoleService;
import nl.tudelft.tam.test.TestUserDetailsService;
import org.junit.jupiter.api.BeforeEach;
@@ -72,6 +73,9 @@ class TAMProfileControllerTest {
@MockBean
PersonService personService;
+ @MockBean
+ RoleService roleService;
+
Profile profile;
@BeforeEach
diff --git a/src/test/java/nl/tudelft/tam/security/AuthorisationServiceTest.java b/src/test/java/nl/tudelft/tam/security/AuthorisationServiceTest.java
index c5187c8ee758341ac9bc61116a30b6c6b91d8a4b..162b5943307deb2aa0581f23c3d8837d0ed369c9 100644
--- a/src/test/java/nl/tudelft/tam/security/AuthorisationServiceTest.java
+++ b/src/test/java/nl/tudelft/tam/security/AuthorisationServiceTest.java
@@ -19,12 +19,14 @@ package nl.tudelft.tam.security;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.*;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.*;
+import java.util.Collections;
import java.util.List;
+import nl.tudelft.labracore.api.ProgramControllerApi;
import nl.tudelft.labracore.api.dto.CourseSummaryDTO;
+import nl.tudelft.labracore.api.dto.ProgramDetailsDTO;
import nl.tudelft.labracore.lib.security.user.DefaultRole;
import nl.tudelft.labracore.lib.security.user.Person;
import nl.tudelft.tam.service.CoordinatorService;
@@ -32,10 +34,12 @@ import nl.tudelft.tam.service.CourseService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
+import reactor.core.publisher.Mono;
import application.test.TestTAMApplication;
@SpringBootTest(classes = TestTAMApplication.class)
@@ -50,6 +54,9 @@ public class AuthorisationServiceTest {
@MockBean
CourseService courseService;
+ @Autowired
+ ProgramControllerApi programApi;
+
Person admin, teacher, student;
@BeforeEach
@@ -94,6 +101,9 @@ public class AuthorisationServiceTest {
@Test
void isCoordinatorForProgramTest() {
+ when(programApi.getProgramById(anyLong()))
+ .thenReturn(Mono.just(new ProgramDetailsDTO().coordinators(Collections.emptyList())));
+
doReturn(admin).when(service).getAuthPerson();
assertThat(service.isCoordinatorForProgram(1L)).isTrue();
diff --git a/src/test/java/nl/tudelft/tam/service/CoordinatorServiceTest.java b/src/test/java/nl/tudelft/tam/service/CoordinatorServiceTest.java
index c0e39a49bb657b1219f5acd81ee60da101112ba4..e80e3c7b5f9f8d1687768762fbefc58dc016a86d 100644
--- a/src/test/java/nl/tudelft/tam/service/CoordinatorServiceTest.java
+++ b/src/test/java/nl/tudelft/tam/service/CoordinatorServiceTest.java
@@ -18,9 +18,14 @@
package nl.tudelft.tam.service;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.*;
import java.util.List;
+import nl.tudelft.labracore.api.PersonControllerApi;
+import nl.tudelft.labracore.api.dto.PersonSearchDTO;
+import nl.tudelft.labracore.api.dto.PersonSummaryDTO;
import nl.tudelft.tam.model.Coordinator;
import nl.tudelft.tam.repository.CoordinatorRepository;
@@ -30,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
+import reactor.core.publisher.Mono;
import application.test.TestTAMApplication;
@SpringBootTest(classes = TestTAMApplication.class)
@@ -46,6 +52,9 @@ public class CoordinatorServiceTest {
@Autowired
CoordinatorRepository repository;
+ @Autowired
+ PersonControllerApi personApi;
+
@BeforeEach
void setup() {
Coordinator tempCoord = new Coordinator();
@@ -72,4 +81,14 @@ public class CoordinatorServiceTest {
assertThat(service.existsByPersonIdAndProgramId(2L, 2L)).isFalse();
}
+ @Test
+ void setCoordinators() {
+ when(personApi.searchForPeople(anyList()))
+ .thenReturn(Mono.just(new PersonSearchDTO().people(List.of(new PersonSummaryDTO().id(5L)))));
+ service.setCoordinatorsForProgram(1L, List.of("username"));
+ List<Coordinator> coordinators = service.getCoordinatorsByProgram(1L);
+ assertThat(coordinators).hasSize(1);
+ assertThat(coordinators.get(0).getPersonId()).isEqualTo(5L);
+ }
+
}