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>() {