diff --git a/CHANGELOG.md b/CHANGELOG.md index e7716a4141b69299010bf893bb8fe9544d81a1c7..28de2a961793a3296438424764a6ebeb1871c7ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Fixed + - Students can no longer enqueue twice. [@rwbackx](https://gitlab.ewi.tudelft.nl/rwbackx) ### Deprecated diff --git a/src/main/java/nl/tudelft/queue/service/RequestService.java b/src/main/java/nl/tudelft/queue/service/RequestService.java index f6c047af11a8f187ddbeef997b5a7cd9daff8bdb..f787e95afc0c5c9aaef5a7168ca62919430f68a6 100644 --- a/src/main/java/nl/tudelft/queue/service/RequestService.java +++ b/src/main/java/nl/tudelft/queue/service/RequestService.java @@ -19,6 +19,7 @@ package nl.tudelft.queue.service; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @@ -57,6 +58,7 @@ import reactor.core.publisher.Mono; @Service public class RequestService { + @Autowired private RequestRepository rr; @@ -109,7 +111,8 @@ public class RequestService { @Autowired private PermissionService permissionService; - private static final ReentrantLock lock = new ReentrantLock(); + private static final ReentrantLock requestTakingLock = new ReentrantLock(); + private static final Set<Long> currentlyEnqueuing = ConcurrentHashMap.newKeySet(); /** * Creates a request in the request database based on the person that posted the request and the @@ -132,39 +135,44 @@ public class RequestService { throw new AccessDeniedException("Request not allowed"); } - if (request instanceof LabRequest) { - var labRequest = (LabRequest) request; - /* - * TODO in the future, delegate to another service layer component, - * and let it generate a link for the respective online mode. - * This is for the far future, when more online modes need to be supported. - */ - - if (labRequest.getTimeSlot() != null && !labRequest.getTimeSlot().canTakeSlot()) { - throw new AccessDeniedException("Time slot is not available"); - } - - if (((LabRequest) request).getOnlineMode() == OnlineMode.JITSI) { - labRequest.setJitsiRoom(js.createJitsiRoomName(labRequest)); - } - if (((LabRequest) request).getSession().getEnableExperimental()) { - ((LabRequest) request).setQuestionId(qApi.addQuestion(new QuestionCreateDTO() - .edition(new EditionIdDTO() - .id(mCache.getOrThrow(dto.getModule()).getEdition().getId())) - .question(((LabRequest) request).getQuestion())).block()); + try { + if (!currentlyEnqueuing.add(personId)) + return; + if (request instanceof LabRequest labRequest) { + /* + * TODO in the future, delegate to another service layer component, + * and let it generate a link for the respective online mode. + * This is for the far future, when more online modes need to be supported. + */ + + if (labRequest.getTimeSlot() != null && !labRequest.getTimeSlot().canTakeSlot()) { + throw new AccessDeniedException("Time slot is not available"); + } + + if (((LabRequest) request).getOnlineMode() == OnlineMode.JITSI) { + labRequest.setJitsiRoom(js.createJitsiRoomName(labRequest)); + } + if (((LabRequest) request).getSession().getEnableExperimental()) { + ((LabRequest) request).setQuestionId(qApi.addQuestion(new QuestionCreateDTO() + .edition(new EditionIdDTO() + .id(mCache.getOrThrow(dto.getModule()).getEdition().getId())) + .question(((LabRequest) request).getQuestion())).block()); + } } - } - request = rr.save(request); - var event = rer.applyAndSave(new RequestCreatedEvent(request)); + request = rr.save(request); + var event = rer.applyAndSave(new RequestCreatedEvent(request)); - if (request instanceof SelectionRequest) { - ((SelectionRequest) request).getSession().getRequests().add((SelectionRequest) request); - checkFcfsSelection((SelectionRequest) request); - } + if (request instanceof SelectionRequest) { + ((SelectionRequest) request).getSession().getRequests().add((SelectionRequest) request); + checkFcfsSelection((SelectionRequest) request); + } - if (sendEvent) { - wss.sendRequestCreated(event); + if (sendEvent) { + wss.sendRequestCreated(event); + } + } finally { + currentlyEnqueuing.remove(personId); } } @@ -343,7 +351,7 @@ public class RequestService { @Transactional(Transactional.TxType.REQUIRES_NEW) public Optional<LabRequest> takeNextRequestFromTimeSlot(Person assistant, ClosableTimeSlot timeSlot) { - lock.lock(); + requestTakingLock.lock(); try { // If the person is already working on a request, no new event should be created. @@ -362,7 +370,7 @@ public class RequestService { return request; } finally { - lock.unlock(); + requestTakingLock.unlock(); } } @@ -379,11 +387,11 @@ public class RequestService { */ public Optional<LabRequest> takeNextRequest(Person assistant, Lab lab, RequestTableFilterDTO filter) { - lock.lock(); + requestTakingLock.lock(); try { return getNextRequest(assistant, lab, filter); } finally { - lock.unlock(); + requestTakingLock.unlock(); } } @@ -415,7 +423,7 @@ public class RequestService { */ @Transactional(Transactional.TxType.REQUIRES_NEW) public LabRequest pickRequest(Person assistant, LabRequest request) { - lock.lock(); + requestTakingLock.lock(); try { Optional<LabRequest> oldRequests = lrr.findCurrentlyProcessingRequest(assistant, request.getSession()); @@ -427,7 +435,7 @@ public class RequestService { return request; } finally { - lock.unlock(); + requestTakingLock.unlock(); } } diff --git a/src/main/resources/templates/lab/enqueue.html b/src/main/resources/templates/lab/enqueue.html index e60b8c5cd0d95d46652c3e8e84cb075133d66452..47f5575c3339da9f02e5af149abcccd81e8c5f45 100644 --- a/src/main/resources/templates/lab/enqueue.html +++ b/src/main/resources/templates/lab/enqueue.html @@ -48,7 +48,7 @@ </div> <form th:action="@{/lab/{id}/enqueue/{rType}(id=${qSession.id}, rType=${rType})}" th:object="${request}" method="post" - class="form-horizontal"> + class="form-horizontal" id="enqueue-form"> <th:block layout:fragment="enqueue-body"> </th:block> @@ -61,6 +61,13 @@ </div> </div> </form> + <script> + document.addEventListener("DOMContentLoaded", function () { + document.getElementById("enqueue-form").addEventListener("submit", function () { + document.getElementById("enqueue").setAttribute("disabled", ""); + }); + }); + </script> </section> </body> </html>