diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/AssignmentFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/AssignmentFacade.java
index 4c342b9339..17baa431b8 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/AssignmentFacade.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/AssignmentFacade.java
@@ -73,8 +73,8 @@
import org.jboss.resteasy.annotations.GZIP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateAssignmentException;
import uk.ac.cam.cl.dtg.isaac.api.managers.AssignmentManager;
-import uk.ac.cam.cl.dtg.isaac.api.managers.DuplicateAssignmentException;
import uk.ac.cam.cl.dtg.isaac.api.managers.GameManager;
import uk.ac.cam.cl.dtg.isaac.api.services.AssignmentService;
import uk.ac.cam.cl.dtg.isaac.dos.LightweightQuestionValidationResponse;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java
index fc420a4c6d..9db85ce15b 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacade.java
@@ -92,14 +92,14 @@
import org.jboss.resteasy.annotations.GZIP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import uk.ac.cam.cl.dtg.isaac.api.managers.DuplicateBookingException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateBookingException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventBookingUpdateException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventDeadlineException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventGroupReservationLimitException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsCancelledException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsFullException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsNotFullException;
import uk.ac.cam.cl.dtg.isaac.api.managers.EventBookingManager;
-import uk.ac.cam.cl.dtg.isaac.api.managers.EventBookingUpdateException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.EventDeadlineException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.EventGroupReservationLimitException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.EventIsCancelledException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.EventIsFullException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.EventIsNotFullException;
import uk.ac.cam.cl.dtg.isaac.dos.EventStatus;
import uk.ac.cam.cl.dtg.isaac.dos.eventbookings.BookingStatus;
import uk.ac.cam.cl.dtg.isaac.dos.users.Role;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/GameboardsFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/GameboardsFacade.java
index e459c5f57a..12a6754c0c 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/GameboardsFacade.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/GameboardsFacade.java
@@ -56,10 +56,10 @@
import org.jboss.resteasy.annotations.GZIP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import uk.ac.cam.cl.dtg.isaac.api.managers.DuplicateGameboardException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateGameboardException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.InvalidGameboardException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.NoWildcardException;
import uk.ac.cam.cl.dtg.isaac.api.managers.GameManager;
-import uk.ac.cam.cl.dtg.isaac.api.managers.InvalidGameboardException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.NoWildcardException;
import uk.ac.cam.cl.dtg.isaac.dos.GameboardCreationMethod;
import uk.ac.cam.cl.dtg.isaac.dos.IsaacWildcard;
import uk.ac.cam.cl.dtg.isaac.dos.QuestionValidationResponse;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/IsaacController.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/IsaacController.java
index 1072bd3808..12e43e2d26 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/IsaacController.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/IsaacController.java
@@ -43,8 +43,10 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
+import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
@@ -65,7 +67,10 @@
import org.jboss.resteasy.annotations.GZIP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.InvalidTimestampException;
+import uk.ac.cam.cl.dtg.isaac.api.requests.PrivacyPolicyRequest;
import uk.ac.cam.cl.dtg.isaac.api.services.ContentSummarizerService;
+import uk.ac.cam.cl.dtg.isaac.api.services.PrivacyPolicyService;
import uk.ac.cam.cl.dtg.isaac.dos.IUserStreaksManager;
import uk.ac.cam.cl.dtg.isaac.dto.ResultsWrapper;
import uk.ac.cam.cl.dtg.isaac.dto.SegueErrorResponse;
@@ -85,6 +90,7 @@
import uk.ac.cam.cl.dtg.segue.dao.content.GitContentManager;
import uk.ac.cam.cl.dtg.util.PropertiesLoader;
+
/**
* Isaac Controller
*
@@ -107,6 +113,7 @@ public class IsaacController extends AbstractIsaacFacade {
private final UserBadgeManager userBadgeManager;
private final IUserStreaksManager userStreaksManager;
private final ContentSummarizerService contentSummarizerService;
+ private final PrivacyPolicyService privacyPolicyService;
private static long lastQuestionCount = 0L;
@@ -152,7 +159,8 @@ public IsaacController(final PropertiesLoader propertiesLoader, final ILogManage
final GitContentManager contentManager, final UserAssociationManager associationManager,
@Named(CONTENT_INDEX) final String contentIndex, final IUserStreaksManager userStreaksManager,
final UserBadgeManager userBadgeManager,
- final ContentSummarizerService contentSummarizerService) {
+ final ContentSummarizerService contentSummarizerService,
+ final PrivacyPolicyService privacyPolicyService) {
super(propertiesLoader, logManager);
this.statsManager = statsManager;
this.userManager = userManager;
@@ -162,6 +170,7 @@ public IsaacController(final PropertiesLoader propertiesLoader, final ILogManage
this.userBadgeManager = userBadgeManager;
this.userStreaksManager = userStreaksManager;
this.contentSummarizerService = contentSummarizerService;
+ this.privacyPolicyService = privacyPolicyService;
}
/**
@@ -489,6 +498,30 @@ public final Response getCurrentUserSnapshot(@Context final HttpServletRequest r
return Response.ok(userSnapshot).cacheControl(getCacheControl(NEVER_CACHE_WITHOUT_ETAG_CHECK, false)).build();
}
+ @POST
+ @Path("/users/accept-privacy-policy")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Operation(summary = "Accept the privacy policy for the current user.")
+ public Response acceptPrivacyPolicy(@Context final HttpServletRequest request,
+ PrivacyPolicyRequest privacyPolicyRequest) {
+ try {
+ privacyPolicyService.acceptPrivacyPolicy(request, privacyPolicyRequest);
+ return Response.ok().build();
+
+ } catch (NoUserLoggedInException e) {
+ return SegueErrorResponse.getNotLoggedInResponse();
+
+ } catch (InvalidTimestampException e) {
+ log.warn("Invalid timestamp provided: {}", e.getMessage());
+ return new SegueErrorResponse(Status.BAD_REQUEST, "Invalid timestamp: " + e.getMessage()).toResponse();
+
+ } catch (SegueDatabaseException e) {
+ log.error("Database error during privacy policy acceptance", e);
+ return new SegueErrorResponse(Status.INTERNAL_SERVER_ERROR, "Database error").toResponse();
+ }
+ }
+
/**
* Get some statistics out of how many questions the user has completed.
*
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/QuizFacade.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/QuizFacade.java
index 5a10c63e48..34427c1bca 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/QuizFacade.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/QuizFacade.java
@@ -71,10 +71,10 @@
import org.jboss.resteasy.annotations.GZIP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import uk.ac.cam.cl.dtg.isaac.api.managers.AssignmentCancelledException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.AttemptCompletedException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.DueBeforeNowException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.DuplicateAssignmentException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.AssignmentCancelledException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.AttemptCompletedException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DueBeforeNowException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateAssignmentException;
import uk.ac.cam.cl.dtg.isaac.api.managers.QuizAssignmentManager;
import uk.ac.cam.cl.dtg.isaac.api.managers.QuizAttemptManager;
import uk.ac.cam.cl.dtg.isaac.api.managers.QuizManager;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AssignmentCancelledException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/AssignmentCancelledException.java
similarity index 74%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AssignmentCancelledException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/AssignmentCancelledException.java
index 63ceb62021..94e0e313d2 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AssignmentCancelledException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/AssignmentCancelledException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
public class AssignmentCancelledException extends Exception {
private static final long serialVersionUID = 4862878947772927260L;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AttemptCompletedException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/AttemptCompletedException.java
similarity index 94%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AttemptCompletedException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/AttemptCompletedException.java
index 402a544433..7a710588fc 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AttemptCompletedException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/AttemptCompletedException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
public class AttemptCompletedException extends Exception {
private static final long serialVersionUID = -1062901117738824285L;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DueBeforeNowException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DueBeforeNowException.java
similarity index 96%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DueBeforeNowException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DueBeforeNowException.java
index 0bab79948f..0291dc9fe9 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DueBeforeNowException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DueBeforeNowException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DuplicateAssignmentException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DuplicateAssignmentException.java
similarity index 95%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DuplicateAssignmentException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DuplicateAssignmentException.java
index 1d4ffb7cf0..1299b50628 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DuplicateAssignmentException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DuplicateAssignmentException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DuplicateBookingException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DuplicateBookingException.java
similarity index 90%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DuplicateBookingException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DuplicateBookingException.java
index 9cd41be20e..e590bb9b29 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DuplicateBookingException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DuplicateBookingException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* DuplicateBookingException
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DuplicateGameboardException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DuplicateGameboardException.java
similarity index 91%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DuplicateGameboardException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DuplicateGameboardException.java
index aaafa275f2..a34a40a6c4 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/DuplicateGameboardException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/DuplicateGameboardException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* An exception that indicates a gameboard already exists with the id provided.
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingUpdateException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventBookingUpdateException.java
similarity index 80%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingUpdateException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventBookingUpdateException.java
index 8d7b43df81..6c14fe4852 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingUpdateException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventBookingUpdateException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* Created by sac92 on 07/06/2016.
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventDeadlineException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventDeadlineException.java
similarity index 88%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventDeadlineException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventDeadlineException.java
index 97fd6e9e4d..4b28aeda10 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventDeadlineException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventDeadlineException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* Indicates the event booking deadline has passed.
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventGroupReservationLimitException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventGroupReservationLimitException.java
similarity index 89%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventGroupReservationLimitException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventGroupReservationLimitException.java
index 5255cb36a0..a0e3336241 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventGroupReservationLimitException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventGroupReservationLimitException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* Indicates that the user is trying to request reservations exceeding the limit specified.
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventIsCancelledException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventIsCancelledException.java
similarity index 92%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventIsCancelledException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventIsCancelledException.java
index 3fba380b6a..b998cf527e 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventIsCancelledException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventIsCancelledException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* EventCancelledException.
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventIsFullException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventIsFullException.java
similarity index 88%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventIsFullException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventIsFullException.java
index 52a17fd1ef..6cc61b767a 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventIsFullException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventIsFullException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* Indicates the event is full.
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventIsNotFullException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventIsNotFullException.java
similarity index 88%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventIsNotFullException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventIsNotFullException.java
index 71668a80e3..8e537e5ac8 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventIsNotFullException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/EventIsNotFullException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* Indicates the event is NOT full.
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/InvalidGameboardException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/InvalidGameboardException.java
similarity index 91%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/InvalidGameboardException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/InvalidGameboardException.java
index 9f93794610..ac047d0706 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/InvalidGameboardException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/InvalidGameboardException.java
@@ -1,4 +1,4 @@
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* An exception that indicates an invalid gameboard has been provided.
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/InvalidTimestampException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/InvalidTimestampException.java
new file mode 100644
index 0000000000..64e333b2c1
--- /dev/null
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/InvalidTimestampException.java
@@ -0,0 +1,7 @@
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
+
+public class InvalidTimestampException extends Exception {
+ public InvalidTimestampException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/NoWildcardException.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/NoWildcardException.java
similarity index 94%
rename from src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/NoWildcardException.java
rename to src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/NoWildcardException.java
index e51accbc28..9fd0353f96 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/NoWildcardException.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/exceptions/NoWildcardException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package uk.ac.cam.cl.dtg.isaac.api.managers;
+package uk.ac.cam.cl.dtg.isaac.api.exceptions;
/**
* Exception for when an IsaacWildcard cannot be found/provided.
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AssignmentManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AssignmentManager.java
index 0dc714da15..1c9f95319f 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AssignmentManager.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/AssignmentManager.java
@@ -32,6 +32,7 @@
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateAssignmentException;
import uk.ac.cam.cl.dtg.isaac.api.services.EmailService;
import uk.ac.cam.cl.dtg.isaac.dao.IAssignmentPersistenceManager;
import uk.ac.cam.cl.dtg.isaac.dto.AssignmentDTO;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingManager.java
index 298fcc53b6..31d9a80d83 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingManager.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingManager.java
@@ -61,6 +61,13 @@
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateBookingException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventBookingUpdateException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventDeadlineException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventGroupReservationLimitException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsCancelledException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsFullException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsNotFullException;
import uk.ac.cam.cl.dtg.isaac.dao.EventBookingPersistenceManager;
import uk.ac.cam.cl.dtg.isaac.dos.AssociationToken;
import uk.ac.cam.cl.dtg.isaac.dos.EventStatus;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java
index 2727ea000b..c6a12b8c29 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/GameManager.java
@@ -67,6 +67,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.cam.cl.dtg.isaac.api.Constants;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateGameboardException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.InvalidGameboardException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.NoWildcardException;
import uk.ac.cam.cl.dtg.isaac.dao.GameboardPersistenceManager;
import uk.ac.cam.cl.dtg.isaac.dos.AudienceContext;
import uk.ac.cam.cl.dtg.isaac.dos.GameboardContentDescriptor;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAssignmentManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAssignmentManager.java
index 698a8a96f8..741754c392 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAssignmentManager.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAssignmentManager.java
@@ -29,6 +29,9 @@
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.AssignmentCancelledException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DueBeforeNowException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateAssignmentException;
import uk.ac.cam.cl.dtg.isaac.api.services.EmailService;
import uk.ac.cam.cl.dtg.isaac.dao.IQuizAssignmentPersistenceManager;
import uk.ac.cam.cl.dtg.isaac.dto.IAssignmentLike;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAttemptManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAttemptManager.java
index bd050688fa..2aa11342fa 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAttemptManager.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAttemptManager.java
@@ -24,6 +24,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.AttemptCompletedException;
import uk.ac.cam.cl.dtg.isaac.dao.IQuizAttemptPersistenceManager;
import uk.ac.cam.cl.dtg.isaac.dto.IsaacQuizDTO;
import uk.ac.cam.cl.dtg.isaac.dto.QuizAssignmentDTO;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/requests/PrivacyPolicyRequest.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/requests/PrivacyPolicyRequest.java
new file mode 100644
index 0000000000..e6b5616a24
--- /dev/null
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/requests/PrivacyPolicyRequest.java
@@ -0,0 +1,20 @@
+package uk.ac.cam.cl.dtg.isaac.api.requests;
+
+import java.time.Instant;
+
+
+public class PrivacyPolicyRequest {
+ private long privacyPolicyAcceptedTime;
+
+ public Instant getPrivacyPolicyAcceptedTimeInstant() {
+ return Instant.ofEpochMilli(privacyPolicyAcceptedTime);
+ }
+
+ public long getPrivacyPolicyAcceptedTime() {
+ return privacyPolicyAcceptedTime;
+ }
+
+ public void setPrivacyPolicyAcceptedTime(long privacyPolicyAcceptedTime) {
+ this.privacyPolicyAcceptedTime = privacyPolicyAcceptedTime;
+ }
+}
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/PrivacyPolicyService.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/PrivacyPolicyService.java
new file mode 100644
index 0000000000..ab44b9ff66
--- /dev/null
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/api/services/PrivacyPolicyService.java
@@ -0,0 +1,50 @@
+package uk.ac.cam.cl.dtg.isaac.api.services;
+
+import com.google.inject.Inject;
+import jakarta.servlet.http.HttpServletRequest;
+import java.time.Instant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.InvalidTimestampException;
+import uk.ac.cam.cl.dtg.isaac.api.requests.PrivacyPolicyRequest;
+import uk.ac.cam.cl.dtg.isaac.dto.users.RegisteredUserDTO;
+import uk.ac.cam.cl.dtg.segue.api.managers.UserAccountManager;
+import uk.ac.cam.cl.dtg.segue.auth.exceptions.NoUserLoggedInException;
+import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException;
+
+public class PrivacyPolicyService {
+
+ private static final Logger log = LoggerFactory.getLogger(PrivacyPolicyService.class);
+
+ private final UserAccountManager userManager;
+
+ @Inject
+ public PrivacyPolicyService(UserAccountManager userManager) {
+ this.userManager = userManager;
+ }
+
+ /**
+ * Accept privacy policy with timestamp validation.
+ *
+ * @param request HTTP request to get current user
+ * @param privacyPolicyRequest Request containing the timestamp
+ * @throws NoUserLoggedInException if no user is logged in
+ * @throws SegueDatabaseException if database error occurs
+ * @throws InvalidTimestampException if timestamp is too far from current time
+ */
+ public void acceptPrivacyPolicy(HttpServletRequest request, PrivacyPolicyRequest privacyPolicyRequest)
+ throws NoUserLoggedInException, SegueDatabaseException, InvalidTimestampException {
+
+ RegisteredUserDTO user = userManager.getCurrentRegisteredUser(request);
+
+ Instant providedTime = privacyPolicyRequest.getPrivacyPolicyAcceptedTimeInstant();
+
+ Instant now = Instant.now();
+ Instant privacyPolicyTime = providedTime.isAfter(now) ? now : providedTime;
+
+ userManager.updatePrivacyPolicyAcceptedTime(user, privacyPolicyTime);
+
+ log.info("User {} accepted privacy policy at {}", user.getEmail(), privacyPolicyTime);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/IQuizAssignmentPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/IQuizAssignmentPersistenceManager.java
index 76f096be8d..49ca2baf1f 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/IQuizAssignmentPersistenceManager.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/IQuizAssignmentPersistenceManager.java
@@ -17,7 +17,7 @@
package uk.ac.cam.cl.dtg.isaac.dao;
import java.util.List;
-import uk.ac.cam.cl.dtg.isaac.api.managers.AssignmentCancelledException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.AssignmentCancelledException;
import uk.ac.cam.cl.dtg.isaac.dto.QuizAssignmentDTO;
import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java
index 392465857c..424f4d5364 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dao/PgQuizAssignmentPersistenceManager.java
@@ -31,7 +31,7 @@
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import uk.ac.cam.cl.dtg.isaac.api.managers.AssignmentCancelledException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.AssignmentCancelledException;
import uk.ac.cam.cl.dtg.isaac.dos.QuizAssignmentDO;
import uk.ac.cam.cl.dtg.isaac.dos.QuizFeedbackMode;
import uk.ac.cam.cl.dtg.isaac.dto.QuizAssignmentDTO;
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/users/RegisteredUser.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/users/RegisteredUser.java
index 163a6a4252..012a6583ae 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/users/RegisteredUser.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dos/users/RegisteredUser.java
@@ -15,18 +15,17 @@
*/
package uk.ac.cam.cl.dtg.isaac.dos.users;
-
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Instant;
import java.util.List;
+import java.util.Objects;
/**
* Data Object to represent a user of the system. This object will be persisted in the database.
*/
public class RegisteredUser extends AbstractSegueUser {
private Long id;
-
private String givenName;
private String familyName;
private String email;
@@ -38,40 +37,29 @@ public class RegisteredUser extends AbstractSegueUser {
private String schoolOther;
private List registeredContexts;
private Instant registeredContextsLastConfirmed;
-
private String emailVerificationToken;
private String emailToVerify;
private EmailVerificationStatus emailVerificationStatus;
private Boolean teacherPending;
-
private Instant lastUpdated;
private Instant lastSeen;
+ private Instant privacyPolicyAcceptedTime;
/**
* Full constructor for the User object.
- *
- * @param id Our database Unique ID
- * @param givenName Equivalent to firstname
- * @param familyName Equivalent to second name
- * @param email primary e-mail address
- * @param role role description
- * @param dateOfBirth date of birth to help with monitoring
- * @param gender gender of the user
- * @param registrationDate date of registration
- * @param lastUpdated the date this user was last updated.
- * @param emailToVerify the most recent email for which a token has been generated
- * @param emailVerificationToken the most recent token generated to verify email addresses
- * @param emailVerificationStatus whether the user has verified their email or not
- * @param teacherPending the teacherPending flag value
*/
@JsonCreator
public RegisteredUser(
@JsonProperty("id") final Long id,
- @JsonProperty("givenName") final String givenName, @JsonProperty("familyName") final String familyName,
- @JsonProperty("email") final String email, @JsonProperty("role") final Role role,
- @JsonProperty("dateOfBirth") final Instant dateOfBirth, @JsonProperty("gender") final Gender gender,
+ @JsonProperty("givenName") final String givenName,
+ @JsonProperty("familyName") final String familyName,
+ @JsonProperty("email") final String email,
+ @JsonProperty("role") final Role role,
+ @JsonProperty("dateOfBirth") final Instant dateOfBirth,
+ @JsonProperty("gender") final Gender gender,
@JsonProperty("registrationDate") final Instant registrationDate,
@JsonProperty("lastUpdated") final Instant lastUpdated,
+ @JsonProperty("privacyPolicyAcceptedTime") final Instant privacyPolicyAcceptedTime,
@JsonProperty("emailToVerify") final String emailToVerify,
@JsonProperty("emailVerificationToken") final String emailVerificationToken,
@JsonProperty("emailVerificationStatus") final EmailVerificationStatus emailVerificationStatus,
@@ -80,333 +68,167 @@ public RegisteredUser(
this.id = id;
this.familyName = familyName;
this.givenName = givenName;
- this.email = email;
this.role = role;
this.dateOfBirth = dateOfBirth;
this.gender = gender;
this.registrationDate = registrationDate;
this.lastUpdated = lastUpdated;
- this.emailToVerify = emailToVerify;
+ this.privacyPolicyAcceptedTime = privacyPolicyAcceptedTime;
this.emailVerificationToken = emailVerificationToken;
this.emailVerificationStatus = emailVerificationStatus;
this.teacherPending = teacherPending;
+
+ setEmail(email);
+ setEmailToVerify(emailToVerify);
}
/**
* Default constructor required for Jackson.
*/
public RegisteredUser() {
-
}
- /**
- * Gets the id (integer form).
- *
- * @return the id
- * @deprecated use getId
- */
+ // ID Management
@JsonProperty("_id")
@Deprecated
- // TODO: Deprecate all usage of old mongo ids e.g. _id
public Long getLegacyId() {
- return this.getId();
+ return this.id;
}
-
- /**
- * Gets the id (integer form).
- *
- * @return the id
- */
@JsonProperty("id")
public Long getId() {
return id;
}
- /**
- * Sets the id.
- *
- * @param id the id to set
- */
@JsonProperty("_id")
public void setId(final Long id) {
this.id = id;
}
- /**
- * Gets the givenName.
- *
- * @return the givenName
- */
- public final String getGivenName() {
+ public String getGivenName() {
return givenName;
}
- /**
- * Sets the givenName.
- *
- * @param givenName the givenName to set
- */
- public final void setGivenName(final String givenName) {
+ public void setGivenName(final String givenName) {
this.givenName = givenName;
}
- /**
- * Gets the familyName.
- *
- * @return the familyName
- */
- public final String getFamilyName() {
+ public String getFamilyName() {
return familyName;
}
- /**
- * Sets the familyName.
- *
- * @param familyName the familyName to set
- */
- public final void setFamilyName(final String familyName) {
+ public void setFamilyName(final String familyName) {
this.familyName = familyName;
}
- /**
- * Gets the email.
- *
- * @return the email
- */
- public final String getEmail() {
+ public String getEmail() {
return email;
}
- /**
- * Sets the email.
- *
- * @param email the email to set
- */
- public final void setEmail(final String email) {
- if (email != null) {
- this.email = email.trim();
- } else {
- this.email = email;
- }
+ public void setEmail(final String email) {
+ this.email = trimIfNotNull(email);
}
- /**
- * Gets the role.
- *
- * @return the role
- */
- public final Role getRole() {
+ public Role getRole() {
return role;
}
- /**
- * Sets the role.
- *
- * @param role the role to set
- */
- public final void setRole(final Role role) {
+ public void setRole(final Role role) {
this.role = role;
}
- /**
- * Gets the dateOfBirth.
- *
- * @return the dateOfBirth
- */
- public final Instant getDateOfBirth() {
+ public Instant getDateOfBirth() {
return dateOfBirth;
}
- /**
- * Sets the dateOfBirth.
- *
- * @param dateOfBirth the dateOfBirth to set
- */
- public final void setDateOfBirth(final Instant dateOfBirth) {
+ public void setDateOfBirth(final Instant dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
- /**
- * Gets the gender.
- *
- * @return the gender
- */
- public final Gender getGender() {
+ public Gender getGender() {
return gender;
}
- /**
- * Sets the gender.
- *
- * @param gender the gender to set
- */
- public final void setGender(final Gender gender) {
+ public void setGender(final Gender gender) {
this.gender = gender;
}
- /**
- * Gets the registrationDate.
- *
- * @return the registrationDate
- */
- public final Instant getRegistrationDate() {
+ public Instant getRegistrationDate() {
return registrationDate;
}
- /**
- * Sets the registrationDate.
- *
- * @param registrationDate the registrationDate to set
- */
- public final void setRegistrationDate(final Instant registrationDate) {
+ public void setRegistrationDate(final Instant registrationDate) {
this.registrationDate = registrationDate;
}
- /**
- * Gets the schoolId.
- *
- * @return the schoolId
- */
- public final String getSchoolId() {
+ public String getSchoolId() {
return schoolId;
}
- /**
- * Sets the schoolId.
- *
- * @param schoolId the schoolId to set
- */
- public final void setSchoolId(final String schoolId) {
+ public void setSchoolId(final String schoolId) {
this.schoolId = schoolId;
}
- /**
- * Gets the schoolOther.
- *
- * @return the schoolOther
- */
public String getSchoolOther() {
return schoolOther;
}
- /**
- * Sets the schoolOther.
- *
- * @param schoolOther the schoolOther to set
- */
public void setSchoolOther(final String schoolOther) {
this.schoolOther = schoolOther;
}
- /**
- * Gets the email.
- *
- * @return the email to verify
- */
- public final String getEmailToVerify() {
+ public String getEmailToVerify() {
return emailToVerify;
}
- /**
- * Sets the email.
- *
- * @param emailToVerify the email to verify
- */
- public final void setEmailToVerify(final String emailToVerify) {
- if (emailToVerify != null) {
- this.emailToVerify = emailToVerify.trim();
- } else {
- this.emailToVerify = emailToVerify;
- }
+ public void setEmailToVerify(final String emailToVerify) {
+ this.emailToVerify = trimIfNotNull(emailToVerify);
}
- /**
- * Sets the email verification token.
- *
- * @param verificationToken token created by authenticator
- */
- public final void setEmailVerificationToken(final String verificationToken) {
- this.emailVerificationToken = verificationToken;
+ public String getEmailVerificationToken() {
+ return emailVerificationToken;
}
- /**
- * Gets the email verification token.
- *
- * @return the email verification token
- */
- public final String getEmailVerificationToken() {
- return this.emailVerificationToken;
+ public void setEmailVerificationToken(final String emailVerificationToken) {
+ this.emailVerificationToken = emailVerificationToken;
}
- /**
- * Get the verification status of the provided email address.
- *
- * @return the EmailVerificationStatus
- */
public EmailVerificationStatus getEmailVerificationStatus() {
return emailVerificationStatus;
}
- /**
- * Set the verification status of the provided email address.
- *
- * @param status sets the EmailVerificationStatus
- */
- public void setEmailVerificationStatus(final EmailVerificationStatus status) {
- this.emailVerificationStatus = status;
+ public void setEmailVerificationStatus(final EmailVerificationStatus emailVerificationStatus) {
+ this.emailVerificationStatus = emailVerificationStatus;
}
- /**
- * Gets the lastUpdated.
- *
- * @return the lastUpdated
- */
public Instant getLastUpdated() {
return lastUpdated;
}
- /**
- * Sets the lastUpdated.
- *
- * @param lastUpdated the lastUpdated to set
- */
public void setLastUpdated(final Instant lastUpdated) {
this.lastUpdated = lastUpdated;
}
- /**
- * Gets the lastSeen.
- *
- * @return the lastSeen
- */
+ public Instant getPrivacyPolicyAcceptedTime() {
+ return privacyPolicyAcceptedTime;
+ }
+
+ public void setPrivacyPolicyAcceptedTime(final Instant privacyPolicyAcceptedTime) {
+ this.privacyPolicyAcceptedTime = privacyPolicyAcceptedTime;
+ }
+
public Instant getLastSeen() {
return lastSeen;
}
- /**
- * Sets the lastSeen.
- *
- * @param lastSeen the lastSeen to set
- */
public void setLastSeen(final Instant lastSeen) {
this.lastSeen = lastSeen;
}
- /**
- * Gets the teacherPending flag.
- *
- * @return the teacherPending flag
- */
public Boolean getTeacherPending() {
return teacherPending;
}
- /**
- * Sets the teacherPending flag.
- *
- * @param teacherPending the teacherPending flag value to set
- */
public void setTeacherPending(final Boolean teacherPending) {
this.teacherPending = teacherPending;
}
@@ -427,12 +249,12 @@ public void setRegisteredContextsLastConfirmed(final Instant registeredContextsL
this.registeredContextsLastConfirmed = registeredContextsLastConfirmed;
}
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((id == null) ? 0 : id.hashCode());
- return result;
+ /**
+ * Utility method to trim strings consistently, handling null values.
+ * Eliminates duplicated trimming logic in email setters.
+ */
+ private String trimIfNotNull(final String value) {
+ return value != null ? value.trim() : null;
}
@Override
@@ -440,41 +262,27 @@ public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof RegisteredUser other)) {
- return false;
- }
- if (id == null) {
- if (other.id != null) {
- return false;
- }
- } else if (!id.equals(other.id)) {
- return false;
- }
- return true;
+ if (!(obj instanceof RegisteredUser other)) return false;
+ return Objects.equals(id, other.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
}
@Override
public String toString() {
- return "RegisteredUser{"
- + "id=" + id
- + ", givenName='" + givenName + '\''
- + ", familyName='" + familyName + '\''
- + ", email='" + email + '\''
- + ", role=" + role
- + ", dateOfBirth=" + dateOfBirth
- + ", gender=" + gender
- + ", registrationDate=" + registrationDate
- + ", schoolId='" + schoolId + '\''
- + ", schoolOther='" + schoolOther + '\''
- + ", emailVerificationToken='" + emailVerificationToken + '\''
- + ", emailToVerify='" + emailToVerify + '\''
- + ", emailVerificationStatus=" + emailVerificationStatus
- + ", teacherPending=" + teacherPending
- + ", lastUpdated=" + lastUpdated
- + ", lastSeen=" + lastSeen
- + '}';
- }
-}
+ return String.format(
+ "RegisteredUser{id=%d, givenName='%s', familyName='%s', email='%s', role=%s, "
+ + "dateOfBirth=%s, gender=%s, registrationDate=%s, schoolId='%s', schoolOther='%s', "
+ + "emailVerificationToken='%s', emailToVerify='%s', emailVerificationStatus=%s, "
+ + "teacherPending=%s, lastUpdated=%s, privacyPolicyAcceptedTime=%s, lastSeen=%s, "
+ + "registeredContexts=%s, registeredContextsLastConfirmed=%s}",
+ id, givenName, familyName, email, role, dateOfBirth, gender, registrationDate,
+ schoolId, schoolOther, emailVerificationToken, emailToVerify, emailVerificationStatus,
+ teacherPending, lastUpdated, privacyPolicyAcceptedTime, lastSeen,
+ registeredContexts, registeredContextsLastConfirmed
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/RegisteredUserDTO.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/RegisteredUserDTO.java
index ce455ba335..b1b1665083 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/RegisteredUserDTO.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/RegisteredUserDTO.java
@@ -20,6 +20,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Instant;
import java.util.List;
+import java.util.Objects;
import uk.ac.cam.cl.dtg.isaac.dos.users.EmailVerificationStatus;
import uk.ac.cam.cl.dtg.isaac.dos.users.Gender;
import uk.ac.cam.cl.dtg.isaac.dos.users.Role;
@@ -30,7 +31,6 @@
*/
public class RegisteredUserDTO extends AbstractSegueUserDTO {
private Long id;
-
private String givenName;
private String familyName;
private String email;
@@ -38,30 +38,19 @@ public class RegisteredUserDTO extends AbstractSegueUserDTO {
private Instant dateOfBirth;
private Gender gender;
private Instant registrationDate;
-
private String schoolId;
private String schoolOther;
private List registeredContexts;
private Instant registeredContextsLastConfirmed;
-
private boolean firstLogin = false;
private Instant lastUpdated;
+ private Instant privacyPolicyAcceptedTime;
private Instant lastSeen;
private EmailVerificationStatus emailVerificationStatus;
private Boolean teacherPending;
/**
* Full constructor for the User object.
- *
- * @param givenName Equivalent to firstname
- * @param familyName Equivalent to second name
- * @param email primary e-mail address
- * @param emailVerificationStatus verification status of email address
- * @param dateOfBirth date of birth to help with monitoring
- * @param gender gender of the user
- * @param registrationDate date of registration
- * @param schoolId the list of linked authentication provider accounts.
- * @param teacherPending the teacherPending flag value to set
*/
@JsonCreator
public RegisteredUserDTO(
@@ -86,309 +75,150 @@ public RegisteredUserDTO(
this.teacherPending = teacherPending;
}
-
/**
* Default constructor required for Jackson.
*/
public RegisteredUserDTO() {
-
}
- /**
- * Gets the id.
- *
- * @return the id
- */
@JsonProperty("id")
public Long getId() {
return id;
}
-
- /**
- * Sets the id.
- *
- * @param id the id to set
- */
@JsonProperty("id")
public void setId(final Long id) {
this.id = id;
}
- /**
- * Gets the id.
- *
- * @return the id
- * @deprecated - TODO need to remove _id from frontend
- */
@JsonProperty("_id")
@Deprecated
public Long getLegacyId() {
return this.getId();
}
-
- /**
- * Sets the id.
- *
- * @param id the id to set
- * @deprecated - TODO need to remove _id from frontend
- */
@JsonProperty("_id")
@Deprecated
public void setLegacyId(final Long id) {
this.setId(id);
}
- /**
- * Gets the givenName.
- *
- * @return the givenName
- */
public String getGivenName() {
return givenName;
}
- /**
- * Sets the givenName.
- *
- * @param givenName the givenName to set
- */
public void setGivenName(final String givenName) {
this.givenName = givenName;
}
- /**
- * Gets the familyName.
- *
- * @return the familyName
- */
public String getFamilyName() {
return familyName;
}
- /**
- * Sets the familyName.
- *
- * @param familyName the familyName to set
- */
public void setFamilyName(final String familyName) {
this.familyName = familyName;
}
- /**
- * Gets the email.
- *
- * @return the email
- */
public String getEmail() {
return email;
}
- /**
- * Sets the email.
- *
- * @param email the email to set
- */
public void setEmail(final String email) {
this.email = email;
}
- /**
- * Gets the role.
- *
- * @return the role
- */
public Role getRole() {
return role;
}
- /**
- * Sets the role.
- *
- * @param role the role to set
- */
public void setRole(final Role role) {
this.role = role;
}
- /**
- * Gets the dateOfBirth.
- *
- * @return the dateOfBirth
- */
public Instant getDateOfBirth() {
return dateOfBirth;
}
- /**
- * Sets the dateOfBirth.
- *
- * @param dateOfBirth the dateOfBirth to set
- */
public void setDateOfBirth(final Instant dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
-
- /**
- * Get the verification status of the provided email address.
- *
- * @return the EmailVerificationStatus
- */
public EmailVerificationStatus getEmailVerificationStatus() {
return this.emailVerificationStatus;
}
- /**
- * Set the verification status of the provided email address.
- *
- * @param emailVerificationStatus sets the EmailVerificationStatus
- */
public void setEmailVerificationStatus(final EmailVerificationStatus emailVerificationStatus) {
this.emailVerificationStatus = emailVerificationStatus;
}
- /**
- * Gets the gender.
- *
- * @return the gender
- */
public Gender getGender() {
return gender;
}
- /**
- * Sets the gender.
- *
- * @param gender the gender to set
- */
public void setGender(final Gender gender) {
this.gender = gender;
}
- /**
- * Gets the registrationDate.
- *
- * @return the registrationDate
- */
public Instant getRegistrationDate() {
return registrationDate;
}
- /**
- * Sets the registrationDate.
- *
- * @param registrationDate the registrationDate to set
- */
public void setRegistrationDate(final Instant registrationDate) {
this.registrationDate = registrationDate;
}
- /**
- * Gets the schoolId.
- *
- * @return the schoolId
- */
public String getSchoolId() {
return schoolId;
}
- /**
- * Sets the schoolId.
- *
- * @param schoolId the schoolId to set
- */
public void setSchoolId(final String schoolId) {
this.schoolId = schoolId;
}
- /**
- * Gets the schoolOther.
- *
- * @return the schoolOther
- */
public String getSchoolOther() {
return schoolOther;
}
- /**
- * Sets the schoolOther.
- *
- * @param schoolOther the schoolOther to set
- */
public void setSchoolOther(final String schoolOther) {
this.schoolOther = schoolOther;
}
- /**
- * Gets the firstLogin.
- *
- * @return the firstLogin
- */
public boolean isFirstLogin() {
return firstLogin;
}
- /**
- * Sets the firstLogin.
- *
- * @param firstLogin the firstLogin to set
- */
public void setFirstLogin(final boolean firstLogin) {
this.firstLogin = firstLogin;
}
- /**
- * Gets the lastUpdated.
- *
- * @return the lastUpdated
- */
public Instant getLastUpdated() {
return lastUpdated;
}
- /**
- * Sets the lastUpdated.
- *
- * @param lastUpdated the lastUpdated to set
- */
public void setLastUpdated(final Instant lastUpdated) {
this.lastUpdated = lastUpdated;
}
- /**
- * Gets the lastSeen.
- *
- * @return the lastSeen
- */
+ public Instant getPrivacyPolicyAcceptedTime() {
+ return privacyPolicyAcceptedTime;
+ }
+
+ public void setPrivacyPolicyAcceptedTime(final Instant privacyPolicyAcceptedTime) {
+ this.privacyPolicyAcceptedTime = privacyPolicyAcceptedTime;
+ }
+
public Instant getLastSeen() {
return lastSeen;
}
- /**
- * Sets the lastSeen.
- *
- * @param lastSeen the lastSeen to set
- */
public void setLastSeen(final Instant lastSeen) {
this.lastSeen = lastSeen;
}
- /**
- * Gets the teacherPending flag.
- *
- * @return the teacherPending flag
- */
public Boolean getTeacherPending() {
return teacherPending;
}
- /**
- * Sets the teacherPending flag.
- *
- * @param teacherPending the teacherPending flag value to set
- */
public void setTeacherPending(final Boolean teacherPending) {
this.teacherPending = teacherPending;
}
@@ -409,158 +239,63 @@ public void setRegisteredContextsLastConfirmed(final Instant registeredContextsL
this.registeredContextsLastConfirmed = registeredContextsLastConfirmed;
}
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((id == null) ? 0 : id.hashCode());
- return result;
- }
-
+ // Object Methods - Using modern Java and Objects utility
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof RegisteredUserDTO other)) {
- return false;
- }
- if (id == null) {
- if (other.id != null) {
- return false;
- }
- } else if (!id.equals(other.id)) {
- return false;
- }
- return true;
+ if (!(obj instanceof RegisteredUserDTO other)) return false;
+ return Objects.equals(id, other.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
}
/**
* A method that tests if each field in the object is equal to each in the other.
- *
- * @param obj to check
- * @return true if the same false if not.
+ * Optimized using Objects.equals for cleaner null handling.
*/
public boolean strictEquals(final Object obj) {
if (this == obj) {
return true;
}
- if (obj == null) {
- return false;
- }
- if (!(obj instanceof RegisteredUserDTO other)) {
- return false;
- }
- if (id == null) {
- if (other.id != null) {
- return false;
- }
- } else if (!id.equals(other.id)) {
- return false;
- }
- if (dateOfBirth == null) {
- if (other.dateOfBirth != null) {
- return false;
- }
- } else if (!dateOfBirth.equals(other.dateOfBirth)) {
- return false;
- }
- if (email == null) {
- if (other.email != null) {
- return false;
- }
- } else if (!email.equals(other.email)) {
- return false;
- }
- if (emailVerificationStatus == null) {
- if (other.emailVerificationStatus != null) {
- return false;
- }
- } else if (emailVerificationStatus.compareTo(other.emailVerificationStatus) != 0) {
- return false;
- }
- if (familyName == null) {
- if (other.familyName != null) {
- return false;
- }
- } else if (!familyName.equals(other.familyName)) {
- return false;
- }
- if (firstLogin != other.firstLogin) {
- return false;
- }
- if (gender != other.gender) {
- return false;
- }
- if (givenName == null) {
- if (other.givenName != null) {
- return false;
- }
- } else if (!givenName.equals(other.givenName)) {
- return false;
- }
- if (lastUpdated == null) {
- if (other.lastUpdated != null) {
- return false;
- }
- } else if (!lastUpdated.equals(other.lastUpdated)) {
- return false;
- }
-
- if (registrationDate == null) {
- if (other.registrationDate != null) {
- return false;
- }
- } else if (!registrationDate.equals(other.registrationDate)) {
- return false;
- }
- if (role != other.role) {
- return false;
- }
- if (schoolId == null) {
- if (other.schoolId != null) {
- return false;
- }
- } else if (!schoolId.equals(other.schoolId)) {
- return false;
- }
- if (schoolOther == null) {
- if (other.schoolOther != null) {
- return false;
- }
- } else if (!schoolOther.equals(other.schoolOther)) {
- return false;
- }
- if (teacherPending == null) {
- if (other.teacherPending != null) {
- return false;
- }
- } else if (!teacherPending.equals(other.teacherPending)) {
- return false;
- }
- return true;
+ if (!(obj instanceof RegisteredUserDTO other)) return false;
+
+ return Objects.equals(id, other.id) &&
+ Objects.equals(dateOfBirth, other.dateOfBirth) &&
+ Objects.equals(email, other.email) &&
+ Objects.equals(emailVerificationStatus, other.emailVerificationStatus) &&
+ Objects.equals(familyName, other.familyName) &&
+ firstLogin == other.firstLogin &&
+ gender == other.gender &&
+ Objects.equals(givenName, other.givenName) &&
+ Objects.equals(lastUpdated, other.lastUpdated) &&
+ Objects.equals(privacyPolicyAcceptedTime, other.privacyPolicyAcceptedTime) &&
+ Objects.equals(registrationDate, other.registrationDate) &&
+ role == other.role &&
+ Objects.equals(schoolId, other.schoolId) &&
+ Objects.equals(schoolOther, other.schoolOther) &&
+ Objects.equals(teacherPending, other.teacherPending) &&
+ Objects.equals(lastSeen, other.lastSeen) &&
+ Objects.equals(registeredContexts, other.registeredContexts) &&
+ Objects.equals(registeredContextsLastConfirmed, other.registeredContextsLastConfirmed);
}
@Override
public String toString() {
- return "RegisteredUserDTO ["
- + "id=" + id
- + ", givenName=" + givenName
- + ", familyName=" + familyName
- + ", email=" + email
- + ", role=" + role
- + ", dateOfBirth=" + dateOfBirth
- + ", gender=" + gender
- + ", registrationDate=" + registrationDate
- + ", schoolId=" + schoolId
- + ", schoolOther=" + schoolOther
- + ", emailVerificationStatus=" + emailVerificationStatus
- + ", teacherPending=" + teacherPending
- + ", firstLogin=" + firstLogin
- + ", lastUpdated=" + lastUpdated
- + "]";
- }
-}
+ return String.format(
+ "RegisteredUserDTO{id=%d, givenName='%s', familyName='%s', email='%s', role=%s, "
+ + "dateOfBirth=%s, gender=%s, registrationDate=%s, schoolId='%s', schoolOther='%s', "
+ + "emailVerificationStatus=%s, teacherPending=%s, firstLogin=%s, lastUpdated=%s, "
+ + "privacyPolicyAcceptedTime=%s, lastSeen=%s, registeredContexts=%s, "
+ + "registeredContextsLastConfirmed=%s}",
+ id, givenName, familyName, email, role, dateOfBirth, gender, registrationDate,
+ schoolId, schoolOther, emailVerificationStatus, teacherPending, firstLogin,
+ lastUpdated, privacyPolicyAcceptedTime, lastSeen, registeredContexts,
+ registeredContextsLastConfirmed
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/UserSummaryDTO.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/UserSummaryDTO.java
index d41d2a2606..a61cf80c16 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/UserSummaryDTO.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/UserSummaryDTO.java
@@ -17,6 +17,7 @@
package uk.ac.cam.cl.dtg.isaac.dto.users;
import com.fasterxml.jackson.annotation.JsonProperty;
+import java.time.Instant;
import java.util.List;
import uk.ac.cam.cl.dtg.isaac.dos.users.EmailVerificationStatus;
import uk.ac.cam.cl.dtg.isaac.dos.users.Role;
@@ -34,6 +35,7 @@ public class UserSummaryDTO extends AbstractSegueUserDTO {
private EmailVerificationStatus emailVerificationStatus;
private Boolean teacherPending;
private List registeredContexts;
+ private Instant privacyPolicyAcceptedTime;
/**
* UserSummaryDTO.
@@ -179,6 +181,24 @@ public void setRegisteredContexts(final List registeredContexts) {
this.registeredContexts = registeredContexts;
}
+ /**
+ * Gets the privacyPolicyAcceptedTime.
+ *
+ * @return the privacyPolicyAcceptedTime
+ */
+ public Instant getPrivacyPolicyAcceptedTime() {
+ return privacyPolicyAcceptedTime;
+ }
+
+ /**
+ * Sets the privacyPolicyAcceptedTime.
+ *
+ * @param privacyPolicyAcceptedTime the privacyPolicyAcceptedTime to set
+ */
+ public void setPrivacyPolicyAcceptedTime(final Instant privacyPolicyAcceptedTime) {
+ this.privacyPolicyAcceptedTime = privacyPolicyAcceptedTime;
+ }
+
@Override
public String toString() {
return "UserSummaryDTO ["
@@ -190,6 +210,7 @@ public String toString() {
+ ", emailVerificationStatus=" + emailVerificationStatus
+ ", teacherPending=" + teacherPending
+ ", registeredContexts=" + registeredContexts
+ + ", privacyPolicyAcceptedTime=" + privacyPolicyAcceptedTime
+ ']';
}
}
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/UserSummaryForAdminUsersDTO.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/UserSummaryForAdminUsersDTO.java
index 0179598abb..99c3ad8188 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/UserSummaryForAdminUsersDTO.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/dto/users/UserSummaryForAdminUsersDTO.java
@@ -53,6 +53,7 @@ public void setLastUpdated(final Instant lastUpdated) {
this.lastUpdated = lastUpdated;
}
+
/**
* Gets the lastSeen.
*
diff --git a/src/main/java/uk/ac/cam/cl/dtg/isaac/mappers/UserMapper.java b/src/main/java/uk/ac/cam/cl/dtg/isaac/mappers/UserMapper.java
index a86418cc56..8b026b692e 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/isaac/mappers/UserMapper.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/isaac/mappers/UserMapper.java
@@ -180,6 +180,7 @@ default T map(UserSummaryDTO source, Class targetClass) {
@Mapping(target = "registeredContextsLastConfirmed", ignore = true)
@Mapping(target = "registeredContexts", ignore = true)
@Mapping(target = "lastUpdated", ignore = true)
+ @Mapping(target = "privacyPolicyAcceptedTime", ignore = true)
@Mapping(target = "lastSeen", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "emailVerificationToken", ignore = true)
diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java
index b16441853b..dbca46de44 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/segue/api/managers/UserAccountManager.java
@@ -1985,6 +1985,18 @@ private void updateLastSeen(final RegisteredUser user) throws SegueDatabaseExcep
}
}
+ /**
+ * Update the users' last policy accepted field.
+ *
+ * @param userDTO of interest
+ * @throws SegueDatabaseException if an error occurs with the update.
+ */
+ public void updatePrivacyPolicyAcceptedTime(final RegisteredUserDTO userDTO, Instant privacyPolicyAcceptedTime)
+ throws SegueDatabaseException {
+ RegisteredUser user = findUserById(userDTO.getId());
+ this.database.updatePrivacyPolicyAcceptedTime(user, privacyPolicyAcceptedTime);
+ }
+
/**
* Generate a verification link to be sent to a new user's email address.
*
diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/IUserDataManager.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/IUserDataManager.java
index 1e3c57b76d..e98cb6bb73 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/IUserDataManager.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/IUserDataManager.java
@@ -251,6 +251,14 @@ boolean linkAuthProviderToAccount(RegisteredUser user, AuthenticationProvider pr
*/
void createSessionToken(RegisteredUser user, Integer newTokenValue) throws SegueDatabaseException;
+ /**
+ * Update the privacy policy acceptance time for a user.
+ *
+ * @param user - the user to update
+ * @throws SegueDatabaseException - if there is a database error
+ */
+ void updatePrivacyPolicyAcceptedTime(RegisteredUser user, Instant policyAcceptedTime) throws SegueDatabaseException;
+
/**
* Update the session token of a user object in the data store to a randomly generated value.
*
diff --git a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/PgUsers.java b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/PgUsers.java
index 5de1ca14ad..3c80aa4657 100644
--- a/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/PgUsers.java
+++ b/src/main/java/uk/ac/cam/cl/dtg/segue/dao/users/PgUsers.java
@@ -441,8 +441,9 @@ public List findUsers(final List usersToLocate) throws Seg
}
String inParams = String.join(",", Collections.nCopies(usersToLocate.size(), "?"));
- String query = String.format(
- "SELECT * FROM users WHERE id IN (%s) AND NOT deleted ORDER BY family_name, given_name", inParams);
+ String query =
+ String.format("SELECT * FROM users WHERE id IN (%s) AND NOT deleted ORDER BY family_name, given_name",
+ inParams);
try (Connection conn = database.getDatabaseConnection();
PreparedStatement pst = conn.prepareStatement(query)) {
@@ -680,6 +681,7 @@ public void updateUserLastSeen(final RegisteredUser user) throws SegueDatabaseEx
@Override
public void updateUserLastSeen(final RegisteredUser user, final Instant date) throws SegueDatabaseException {
+
requireNonNull(user);
String query = "UPDATE users SET last_seen = ? WHERE id = ?";
@@ -694,6 +696,32 @@ public void updateUserLastSeen(final RegisteredUser user, final Instant date) th
}
}
+ @Override
+ public void updatePrivacyPolicyAcceptedTime(final RegisteredUser user, final Instant acceptedTime)
+ throws SegueDatabaseException {
+
+ requireNonNull(user);
+ requireNonNull(acceptedTime);
+
+ String query = "UPDATE users SET updated_privacy_policy_accepted = ?, last_updated = ? WHERE id = ?";
+ try (Connection conn = database.getDatabaseConnection();
+ PreparedStatement pst = conn.prepareStatement(query)
+ ) {
+ Instant now = Instant.now();
+ Instant privacyPolicyAcceptedTime = acceptedTime.isAfter(now) ? now : acceptedTime;
+ pst.setTimestamp(1, Timestamp.from(privacyPolicyAcceptedTime));
+ pst.setTimestamp(2, Timestamp.from(now));
+ pst.setLong(3, user.getId());
+ pst.execute();
+
+ // Update the user object as well
+ user.setPrivacyPolicyAcceptedTime(now);
+ user.setLastUpdated(now);
+ } catch (SQLException e) {
+ throw new SegueDatabaseException("Unable to update privacy policy acceptance time", e);
+ }
+ }
+
@Override
public Integer regenerateSessionToken(final RegisteredUser user) throws SegueDatabaseException {
Integer newSessionTokenValue = generateRandomTokenInteger();
@@ -827,8 +855,8 @@ private RegisteredUser createUser(final RegisteredUser userToCreate) throws Segu
String query = "INSERT INTO users(family_name, given_name, email, role, date_of_birth, gender,"
+ " registration_date, school_id, school_other, last_updated, email_verification_status, last_seen,"
+ " email_verification_token, email_to_verify, teacher_pending, registered_contexts,"
- + " registered_contexts_last_confirmed)"
- + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+ + " registered_contexts_last_confirmed, updated_privacy_policy_accepted)"
+ + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
try (Connection conn = database.getDatabaseConnection();
PreparedStatement pst = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)
) {
@@ -859,6 +887,8 @@ private RegisteredUser createUser(final RegisteredUser userToCreate) throws Segu
pst.setArray(FIELD_CREATE_UPDATE_USER_REGISTERED_CONTEXTS, userContexts);
setValueHelper(pst, FIELD_CREATE_UPDATE_USER_REGISTERED_CONTEXTS_LAST_CONFIRMED,
userToCreate.getRegisteredContextsLastConfirmed());
+ setValueHelper(pst, FIELD_CREATE_UPDATE_USER_UPDATED_PRIVACY_POLICY_ACCEPTED,
+ userToCreate.getPrivacyPolicyAcceptedTime());
if (pst.executeUpdate() == 0) {
throw new SegueDatabaseException("Unable to save user.");
@@ -922,7 +952,9 @@ private RegisteredUser updateUser(final Connection conn, final RegisteredUser us
String query = "UPDATE users SET family_name = ?, given_name = ?, email = ?, role = ?, date_of_birth = ?,"
+ " gender = ?, registration_date = ?, school_id = ?, school_other = ?, last_updated = ?,"
+ " email_verification_status = ?, last_seen = ?, email_verification_token = ?, email_to_verify = ?,"
- + " teacher_pending = ?, registered_contexts = ?, registered_contexts_last_confirmed = ? WHERE id = ?;";
+ + " teacher_pending = ?, registered_contexts = ?, registered_contexts_last_confirmed = ?,"
+ + " updated_privacy_policy_accepted = ?"
+ + " WHERE id = ?;";
try (PreparedStatement pst = conn.prepareStatement(query)) {
setValueHelper(pst, FIELD_CREATE_UPDATE_USER_FAMILY_NAME, userToCreate.getFamilyName());
setValueHelper(pst, FIELD_CREATE_UPDATE_USER_GIVEN_NAME, userToCreate.getGivenName());
@@ -940,6 +972,8 @@ private RegisteredUser updateUser(final Connection conn, final RegisteredUser us
setValueHelper(pst, FIELD_CREATE_UPDATE_USER_EMAIL_VERIFICATION_TOKEN, userToCreate.getEmailVerificationToken());
setValueHelper(pst, FIELD_CREATE_UPDATE_USER_EMAIL_TO_VERIFY, userToCreate.getEmailToVerify());
setValueHelper(pst, FIELD_CREATE_UPDATE_USER_TEACHER_PENDING, userToCreate.getTeacherPending());
+ setValueHelper(pst, FIELD_CREATE_UPDATE_USER_UPDATED_PRIVACY_POLICY_ACCEPTED,
+ userToCreate.getPrivacyPolicyAcceptedTime());
List userContextsJsonb = Lists.newArrayList();
if (userToCreate.getRegisteredContexts() != null) {
for (UserContext registeredContext : userToCreate.getRegisteredContexts()) {
@@ -1008,6 +1042,7 @@ private RegisteredUser buildRegisteredUser(final ResultSet results) throws SQLEx
u.setEmailVerificationStatus(results.getString("email_verification_status") != null ? EmailVerificationStatus
.valueOf(results.getString("email_verification_status")) : null);
u.setTeacherPending(results.getBoolean("teacher_pending"));
+ u.setPrivacyPolicyAcceptedTime(getInstantFromTimestamp(results, "updated_privacy_policy_accepted"));
return u;
}
@@ -1165,5 +1200,6 @@ private static RegisteredUser removePIIFromUserDO(final RegisteredUser user) {
private static final int FIELD_CREATE_UPDATE_USER_TEACHER_PENDING = 15;
private static final int FIELD_CREATE_UPDATE_USER_REGISTERED_CONTEXTS = 16;
private static final int FIELD_CREATE_UPDATE_USER_REGISTERED_CONTEXTS_LAST_CONFIRMED = 17;
- private static final int FIELD_UPDATE_USER_USER_ID = 18;
+ private static final int FIELD_CREATE_UPDATE_USER_UPDATED_PRIVACY_POLICY_ACCEPTED = 18;
+ private static final int FIELD_UPDATE_USER_USER_ID = 19;
}
diff --git a/src/main/resources/db_scripts/postgres-rutherford-create-script.sql b/src/main/resources/db_scripts/postgres-rutherford-create-script.sql
index f68a7189e2..11d9eaf5ef 100644
--- a/src/main/resources/db_scripts/postgres-rutherford-create-script.sql
+++ b/src/main/resources/db_scripts/postgres-rutherford-create-script.sql
@@ -732,7 +732,8 @@ CREATE TABLE public.users (
email_to_verify text,
email_verification_token text,
deleted boolean DEFAULT false NOT NULL,
- teacher_pending boolean DEFAULT false NOT NULL
+ teacher_pending boolean DEFAULT false NOT NULL,
+ updated_privacy_policy_accepted timestamp without time zone
);
diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeTest.java
index 2e8e43e9b0..0549574f06 100644
--- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeTest.java
+++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/EventsFacadeTest.java
@@ -27,8 +27,8 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import uk.ac.cam.cl.dtg.isaac.api.managers.EventBookingManager;
-import uk.ac.cam.cl.dtg.isaac.api.managers.EventBookingUpdateException;
-import uk.ac.cam.cl.dtg.isaac.api.managers.EventIsCancelledException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventBookingUpdateException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsCancelledException;
import uk.ac.cam.cl.dtg.isaac.dos.EventStatus;
import uk.ac.cam.cl.dtg.isaac.dos.eventbookings.BookingStatus;
import uk.ac.cam.cl.dtg.isaac.dto.IsaacEventPageDTO;
diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingManagerTest.java
index c5277a23e1..a1cea9ea9e 100644
--- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingManagerTest.java
+++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/EventBookingManagerTest.java
@@ -1,6 +1,5 @@
package uk.ac.cam.cl.dtg.isaac.api.managers;
-import static java.time.ZoneId.getAvailableZoneIds;
import static java.time.ZoneOffset.UTC;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
@@ -32,7 +31,6 @@
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -46,6 +44,12 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateBookingException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventDeadlineException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventGroupReservationLimitException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsCancelledException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsFullException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.EventIsNotFullException;
import uk.ac.cam.cl.dtg.isaac.dao.EventBookingPersistenceManager;
import uk.ac.cam.cl.dtg.isaac.dos.AssociationToken;
import uk.ac.cam.cl.dtg.isaac.dos.EventStatus;
diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAssignmentManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAssignmentManagerTest.java
index 93127dccd5..6a7782699e 100644
--- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAssignmentManagerTest.java
+++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAssignmentManagerTest.java
@@ -33,6 +33,8 @@
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DueBeforeNowException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.DuplicateAssignmentException;
import uk.ac.cam.cl.dtg.isaac.api.services.EmailService;
import uk.ac.cam.cl.dtg.isaac.dao.IQuizAssignmentPersistenceManager;
import uk.ac.cam.cl.dtg.isaac.dos.QuizFeedbackMode;
diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAttemptManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAttemptManagerTest.java
index d30df8e4b7..9ec2bd71a1 100644
--- a/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAttemptManagerTest.java
+++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/api/managers/QuizAttemptManagerTest.java
@@ -31,6 +31,7 @@
import org.easymock.IArgumentMatcher;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.AttemptCompletedException;
import uk.ac.cam.cl.dtg.isaac.dao.IQuizAttemptPersistenceManager;
import uk.ac.cam.cl.dtg.isaac.dto.QuizAttemptDTO;
import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException;
diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/app/GameboardsFacadeTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/app/GameboardsFacadeTest.java
index 6a6c3baef8..d3bea5f87e 100644
--- a/src/test/java/uk/ac/cam/cl/dtg/isaac/app/GameboardsFacadeTest.java
+++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/app/GameboardsFacadeTest.java
@@ -30,7 +30,7 @@
import org.junit.jupiter.api.Test;
import uk.ac.cam.cl.dtg.isaac.api.GameboardsFacade;
import uk.ac.cam.cl.dtg.isaac.api.managers.GameManager;
-import uk.ac.cam.cl.dtg.isaac.api.managers.NoWildcardException;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.NoWildcardException;
import uk.ac.cam.cl.dtg.isaac.dto.GameFilter;
import uk.ac.cam.cl.dtg.isaac.dto.users.AbstractSegueUserDTO;
import uk.ac.cam.cl.dtg.isaac.dto.users.AnonymousUserDTO;
diff --git a/src/test/java/uk/ac/cam/cl/dtg/isaac/service/PrivacyPolicyServiceTest.java b/src/test/java/uk/ac/cam/cl/dtg/isaac/service/PrivacyPolicyServiceTest.java
new file mode 100644
index 0000000000..0ca4862228
--- /dev/null
+++ b/src/test/java/uk/ac/cam/cl/dtg/isaac/service/PrivacyPolicyServiceTest.java
@@ -0,0 +1,200 @@
+package uk.ac.cam.cl.dtg.isaac.service;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import jakarta.servlet.http.HttpServletRequest;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import org.easymock.EasyMock;
+import org.easymock.EasyMockExtension;
+import org.easymock.IMocksControl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import uk.ac.cam.cl.dtg.isaac.api.exceptions.InvalidTimestampException;
+import uk.ac.cam.cl.dtg.isaac.api.requests.PrivacyPolicyRequest;
+import uk.ac.cam.cl.dtg.isaac.api.services.PrivacyPolicyService;
+import uk.ac.cam.cl.dtg.isaac.dto.users.RegisteredUserDTO;
+import uk.ac.cam.cl.dtg.segue.api.managers.UserAccountManager;
+import uk.ac.cam.cl.dtg.segue.auth.exceptions.NoUserLoggedInException;
+import uk.ac.cam.cl.dtg.segue.dao.SegueDatabaseException;
+
+@ExtendWith(EasyMockExtension.class)
+class PrivacyPolicyServiceTest {
+
+ private IMocksControl control;
+ private HttpServletRequest mockRequest;
+ private PrivacyPolicyRequest mockPrivacyPolicyRequest;
+ private UserAccountManager mockUserManager;
+ private RegisteredUserDTO mockUser;
+
+ private PrivacyPolicyService privacyPolicyService;
+
+ @BeforeEach
+ void setUp() {
+ control = EasyMock.createControl();
+ mockRequest = control.createMock(HttpServletRequest.class);
+ mockPrivacyPolicyRequest = control.createMock(PrivacyPolicyRequest.class);
+ mockUserManager = control.createMock(UserAccountManager.class);
+ mockUser = control.createMock(RegisteredUserDTO.class);
+
+ privacyPolicyService = new PrivacyPolicyService(mockUserManager);
+ }
+
+ @Test
+ void acceptPrivacyPolicy_WithValidPastTime_ShouldUseProvidedTime()
+ throws NoUserLoggedInException, SegueDatabaseException, InvalidTimestampException {
+
+ Instant pastTime = Instant.now().minus(1, ChronoUnit.HOURS);
+ String userEmail = "test@example.com";
+
+ expect(mockUserManager.getCurrentRegisteredUser(mockRequest)).andReturn(mockUser);
+ expect(mockPrivacyPolicyRequest.getPrivacyPolicyAcceptedTimeInstant()).andReturn(pastTime);
+ expect(mockUser.getEmail()).andReturn(userEmail);
+
+ mockUserManager.updatePrivacyPolicyAcceptedTime(mockUser, pastTime);
+ expectLastCall();
+
+ expectLastCall();
+
+ control.replay();
+
+ privacyPolicyService.acceptPrivacyPolicy(mockRequest, mockPrivacyPolicyRequest);
+
+ control.verify();
+ }
+
+ @Test
+ void acceptPrivacyPolicy_WithFutureTime_ShouldUseCurrentTime()
+ throws NoUserLoggedInException, SegueDatabaseException, InvalidTimestampException {
+
+ Instant futureTime = Instant.now().plus(1, ChronoUnit.HOURS);
+ String userEmail = "test@example.com";
+
+ expect(mockUserManager.getCurrentRegisteredUser(mockRequest)).andReturn(mockUser);
+ expect(mockPrivacyPolicyRequest.getPrivacyPolicyAcceptedTimeInstant()).andReturn(futureTime);
+ expect(mockUser.getEmail()).andReturn(userEmail);
+
+ mockUserManager.updatePrivacyPolicyAcceptedTime(eq(mockUser), anyObject(Instant.class));
+ expectLastCall();
+
+ expectLastCall();
+
+ control.replay();
+
+ privacyPolicyService.acceptPrivacyPolicy(mockRequest, mockPrivacyPolicyRequest);
+
+ control.verify();
+ }
+
+ @Test
+ void acceptPrivacyPolicy_WithCurrentTime_ShouldUseProvidedTime()
+ throws NoUserLoggedInException, SegueDatabaseException, InvalidTimestampException {
+
+ Instant currentTime = Instant.now();
+ String userEmail = "test@example.com";
+
+ expect(mockUserManager.getCurrentRegisteredUser(mockRequest)).andReturn(mockUser);
+ expect(mockPrivacyPolicyRequest.getPrivacyPolicyAcceptedTimeInstant()).andReturn(currentTime);
+ expect(mockUser.getEmail()).andReturn(userEmail);
+
+ mockUserManager.updatePrivacyPolicyAcceptedTime(mockUser, currentTime);
+ expectLastCall();
+
+ expectLastCall();
+
+ control.replay();
+
+ privacyPolicyService.acceptPrivacyPolicy(mockRequest, mockPrivacyPolicyRequest);
+
+ control.verify();
+ }
+
+ @Test
+ void acceptPrivacyPolicy_WhenNoUserLoggedIn_ShouldThrowException()
+ throws NoUserLoggedInException {
+
+ expect(mockUserManager.getCurrentRegisteredUser(mockRequest))
+ .andThrow(new NoUserLoggedInException());
+
+ control.replay();
+
+ assertThrows(NoUserLoggedInException.class, () -> {
+ privacyPolicyService.acceptPrivacyPolicy(mockRequest, mockPrivacyPolicyRequest);
+ });
+
+ control.verify();
+ }
+
+ @Test
+ void acceptPrivacyPolicy_WhenDatabaseException_ShouldThrowException()
+ throws NoUserLoggedInException, SegueDatabaseException, InvalidTimestampException {
+
+ // Arrange
+ Instant pastTime = Instant.now().minus(1, ChronoUnit.HOURS);
+
+ expect(mockUserManager.getCurrentRegisteredUser(mockRequest)).andReturn(mockUser);
+ expect(mockPrivacyPolicyRequest.getPrivacyPolicyAcceptedTimeInstant()).andReturn(pastTime);
+
+ mockUserManager.updatePrivacyPolicyAcceptedTime(mockUser, pastTime);
+ expectLastCall().andThrow(new SegueDatabaseException("Database error"));
+
+ control.replay();
+
+ assertThrows(SegueDatabaseException.class, () -> {
+ privacyPolicyService.acceptPrivacyPolicy(mockRequest, mockPrivacyPolicyRequest);
+ });
+
+ control.verify();
+ }
+
+ @Test
+ void acceptPrivacyPolicy_LogsCorrectInformation()
+ throws NoUserLoggedInException, SegueDatabaseException, InvalidTimestampException {
+
+ Instant pastTime = Instant.now().minus(30, ChronoUnit.MINUTES);
+ String userEmail = "user@test.com";
+
+ expect(mockUserManager.getCurrentRegisteredUser(mockRequest)).andReturn(mockUser);
+ expect(mockPrivacyPolicyRequest.getPrivacyPolicyAcceptedTimeInstant()).andReturn(pastTime);
+ expect(mockUser.getEmail()).andReturn(userEmail);
+
+ mockUserManager.updatePrivacyPolicyAcceptedTime(mockUser, pastTime);
+ expectLastCall();
+
+ expectLastCall();
+
+ control.replay();
+
+ privacyPolicyService.acceptPrivacyPolicy(mockRequest, mockPrivacyPolicyRequest);
+
+ control.verify();
+ }
+
+ @Test
+ void acceptPrivacyPolicy_WithBoundaryTime_JustBeforeNow_ShouldUseProvidedTime()
+ throws NoUserLoggedInException, SegueDatabaseException, InvalidTimestampException {
+
+ Instant almostNow = Instant.now().minus(1, ChronoUnit.MILLIS);
+ String userEmail = "boundary@example.com";
+
+ expect(mockUserManager.getCurrentRegisteredUser(mockRequest)).andReturn(mockUser);
+ expect(mockPrivacyPolicyRequest.getPrivacyPolicyAcceptedTimeInstant()).andReturn(almostNow);
+ expect(mockUser.getEmail()).andReturn(userEmail);
+
+ mockUserManager.updatePrivacyPolicyAcceptedTime(mockUser, almostNow);
+ expectLastCall();
+
+ expectLastCall();
+
+ control.replay();
+
+ privacyPolicyService.acceptPrivacyPolicy(mockRequest, mockPrivacyPolicyRequest);
+
+ control.verify();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java
index 39f64c9ee2..961aa4aa6c 100644
--- a/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java
+++ b/src/test/java/uk/ac/cam/cl/dtg/segue/api/managers/UserManagerTest.java
@@ -194,7 +194,7 @@ void getCurrentUser_IsAuthenticatedWithValidHMAC_userIsReturned()
int sessionToken = 7;
RegisteredUser returnUser = new RegisteredUser(validUserId, "TestFirstName", "TestLastName", "", Role.STUDENT,
- Instant.now(), Gender.MALE, Instant.now(), null, null, null, null, false);
+ Instant.now(), Gender.MALE, Instant.now(), null, null, null, null, null, false);
returnUser.setId(validUserId);
Map sessionInformation =
@@ -382,7 +382,7 @@ void authenticateCallback_checkNewUserIsAuthenticated_createInternalUserAccount(
.atLeastOnce();
RegisteredUser mappedUser = new RegisteredUser(null, "TestFirstName", "testLastName", "test@test.com", Role.STUDENT,
- Instant.now(), Gender.MALE, Instant.now(), null, null, null, null, false);
+ Instant.now(), Gender.MALE, Instant.now(), null, null, null, null, null, false);
expect(dummyDatabase.getAuthenticationProvidersByUsers(Collections.singletonList(mappedUser)))
.andReturn(new HashMap>() {