From 9a70613226e2d659835288d8023b428076eeb258 Mon Sep 17 00:00:00 2001 From: Edvin Karlsson Date: Wed, 15 Apr 2026 10:50:44 +0200 Subject: [PATCH 1/4] fix: return CommentResponse instead of Comment to avoid LazyInitializationException --- .../comment/controller/CommentController.java | 9 +++-- .../comment/dto/CommentResponse.java | 35 +++++++++++++++++++ .../team6backend/comment/entity/Comment.java | 6 ++-- 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/example/team6backend/comment/dto/CommentResponse.java diff --git a/src/main/java/org/example/team6backend/comment/controller/CommentController.java b/src/main/java/org/example/team6backend/comment/controller/CommentController.java index 35e1138..1ec0e49 100644 --- a/src/main/java/org/example/team6backend/comment/controller/CommentController.java +++ b/src/main/java/org/example/team6backend/comment/controller/CommentController.java @@ -2,6 +2,7 @@ import jakarta.validation.Valid; import org.example.team6backend.comment.dto.CommentRequest; +import org.example.team6backend.comment.dto.CommentResponse; import org.example.team6backend.comment.entity.Comment; import org.example.team6backend.comment.service.CommentService; import org.springframework.http.ResponseEntity; @@ -22,8 +23,12 @@ public CommentController(CommentService commentService) { @GetMapping("/incident/{incidentId}") @ResponseBody - public ResponseEntity> getCommentByIncidentId(@PathVariable Long incidentId) { - List comments = commentService.getCommentByIncidentId(incidentId); + public ResponseEntity> getCommentByIncidentId(@PathVariable Long incidentId) { + List comments = commentService.getCommentByIncidentId(incidentId) + .stream() + .map(CommentResponse::fromEntity) + .toList(); + return ResponseEntity.ok(comments); } diff --git a/src/main/java/org/example/team6backend/comment/dto/CommentResponse.java b/src/main/java/org/example/team6backend/comment/dto/CommentResponse.java new file mode 100644 index 0000000..2ea2f48 --- /dev/null +++ b/src/main/java/org/example/team6backend/comment/dto/CommentResponse.java @@ -0,0 +1,35 @@ +package org.example.team6backend.comment.dto; + +import org.example.team6backend.comment.entity.Comment; + +import java.time.Instant; + +public record CommentResponse( + String id, + String message, + Instant createdAt, + CommentUserResponse user + +) { + public static CommentResponse fromEntity(Comment comment) { + return new CommentResponse( + comment.getId(), + comment.getMessage(), + comment.getCreatedAt(), + comment.getUser() != null + ? new CommentUserResponse( + comment.getUser().getId(), + comment.getUser().getName(), + comment.getUser().getEmail() + ) + : null + ); + } + + public record CommentUserResponse( + String id, + String name, + String email + ) { + } +} diff --git a/src/main/java/org/example/team6backend/comment/entity/Comment.java b/src/main/java/org/example/team6backend/comment/entity/Comment.java index cec2b22..90409db 100644 --- a/src/main/java/org/example/team6backend/comment/entity/Comment.java +++ b/src/main/java/org/example/team6backend/comment/entity/Comment.java @@ -6,7 +6,7 @@ import org.example.team6backend.incident.entity.Incident; import org.example.team6backend.user.entity.AppUser; -import java.time.LocalDateTime; +import java.time.Instant; @Entity @Table(name = "comment") @@ -30,11 +30,11 @@ public class Comment { private AppUser user; @Column(name = "created_at", nullable = false) - private LocalDateTime createdAt; + private Instant createdAt; @PrePersist protected void onCreated() { - createdAt = LocalDateTime.now(); + createdAt = Instant.now(); } } From 19f0b46027b23fa787190d86412fd647551e3d32 Mon Sep 17 00:00:00 2001 From: Edvin Karlsson Date: Wed, 15 Apr 2026 10:52:01 +0200 Subject: [PATCH 2/4] Changed all localdate and localdate.now to instant and instant.now --- .../activity/dto/ActivityLogResponse.java | 9 ++++---- .../activity/entity/ActivityLog.java | 10 ++++----- .../team6backend/exception/ErrorResponse.java | 8 +++---- .../exception/GlobalExceptionHandler.java | 12 +++++------ .../incident/dto/IncidentResponse.java | 4 ++-- .../incident/entity/Incident.java | 21 ++++++++++--------- .../incident/service/IncidentService.java | 17 ++++++++------- .../dto/NotificationResponse.java | 8 +++---- .../notification/entity/Notification.java | 10 ++++----- .../team6backend/user/dto/UserResponse.java | 4 ++-- .../team6backend/user/entity/AppUser.java | 10 ++++----- .../user/service/UserServiceTest.java | 6 +++--- 12 files changed, 61 insertions(+), 58 deletions(-) diff --git a/src/main/java/org/example/team6backend/activity/dto/ActivityLogResponse.java b/src/main/java/org/example/team6backend/activity/dto/ActivityLogResponse.java index cb97274..04c1d62 100644 --- a/src/main/java/org/example/team6backend/activity/dto/ActivityLogResponse.java +++ b/src/main/java/org/example/team6backend/activity/dto/ActivityLogResponse.java @@ -1,13 +1,14 @@ package org.example.team6backend.activity.dto; import org.example.team6backend.activity.entity.ActivityLog; -import java.time.LocalDateTime; + +import java.time.Instant; public class ActivityLogResponse { private String action; private String description; - private LocalDateTime createdAt; + private Instant createdAt; private String userName; public static ActivityLogResponse fromEntity(ActivityLog activityLog) { @@ -35,11 +36,11 @@ public void setDescription(String description) { this.description = description; } - public LocalDateTime getCreatedAt() { + public Instant getCreatedAt() { return createdAt; } - public void setCreatedAt(LocalDateTime createdAt) { + public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } diff --git a/src/main/java/org/example/team6backend/activity/entity/ActivityLog.java b/src/main/java/org/example/team6backend/activity/entity/ActivityLog.java index 54f4065..81de6d6 100644 --- a/src/main/java/org/example/team6backend/activity/entity/ActivityLog.java +++ b/src/main/java/org/example/team6backend/activity/entity/ActivityLog.java @@ -4,7 +4,7 @@ import org.example.team6backend.incident.entity.Incident; import org.example.team6backend.user.entity.AppUser; -import java.time.LocalDateTime; +import java.time.Instant; @Entity @Table(name = "activity_log") @@ -16,7 +16,7 @@ public class ActivityLog { private String action; private String description; - private LocalDateTime createdAt; + private Instant createdAt; @ManyToOne @JoinColumn(name = "incident_id", nullable = false) @@ -28,7 +28,7 @@ public class ActivityLog { @PrePersist void onCreated() { - createdAt = LocalDateTime.now(); + createdAt = Instant.now(); } public Long getId() { @@ -55,11 +55,11 @@ public void setDescription(String description) { this.description = description; } - public LocalDateTime getCreatedAt() { + public Instant getCreatedAt() { return createdAt; } - public void setCreatedAt(LocalDateTime createdAt) { + public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } diff --git a/src/main/java/org/example/team6backend/exception/ErrorResponse.java b/src/main/java/org/example/team6backend/exception/ErrorResponse.java index 8055428..89cbb71 100644 --- a/src/main/java/org/example/team6backend/exception/ErrorResponse.java +++ b/src/main/java/org/example/team6backend/exception/ErrorResponse.java @@ -1,13 +1,13 @@ package org.example.team6backend.exception; -import java.time.LocalDateTime; +import java.time.Instant; public class ErrorResponse { private int status; private String message; - private LocalDateTime timestamp; + private Instant timestamp; - public ErrorResponse(int status, String message, LocalDateTime timestamp) { + public ErrorResponse(int status, String message, Instant timestamp) { this.status = status; this.message = message; this.timestamp = timestamp; @@ -19,7 +19,7 @@ public int getStatus() { public String getMessage() { return message; } - public LocalDateTime getTimestamp() { + public Instant getTimestamp() { return timestamp; } } diff --git a/src/main/java/org/example/team6backend/exception/GlobalExceptionHandler.java b/src/main/java/org/example/team6backend/exception/GlobalExceptionHandler.java index 62fca04..5c77f15 100644 --- a/src/main/java/org/example/team6backend/exception/GlobalExceptionHandler.java +++ b/src/main/java/org/example/team6backend/exception/GlobalExceptionHandler.java @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import java.time.LocalDateTime; +import java.time.Instant; @RestControllerAdvice public class GlobalExceptionHandler { @@ -14,25 +14,25 @@ public class GlobalExceptionHandler { @ExceptionHandler(AccessDeniedException.class) public ResponseEntity handleAccessDeniedException(AccessDeniedException ex) { ErrorResponse error = new ErrorResponse(HttpStatus.FORBIDDEN.value(), - "You do not have permission to access this resource!", LocalDateTime.now()); + "You do not have permission to access this resource!", Instant.now()); return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); } @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity handleNotFound(ResourceNotFoundException ex) { - ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage(), LocalDateTime.now()); + ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage(), Instant.now()); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) { - ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage(), LocalDateTime.now()); + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage(), Instant.now()); return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); } @ExceptionHandler(IllegalStateException.class) public ResponseEntity handleIllegalState(IllegalStateException ex) { - ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage(), LocalDateTime.now()); + ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage(), Instant.now()); return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); } @@ -40,7 +40,7 @@ public ResponseEntity handleIllegalState(IllegalStateException ex public ResponseEntity handleGeneral(Exception ex) { ex.printStackTrace(); ErrorResponse error = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Something went wrong!", - LocalDateTime.now()); + Instant.now()); return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/src/main/java/org/example/team6backend/incident/dto/IncidentResponse.java b/src/main/java/org/example/team6backend/incident/dto/IncidentResponse.java index 4590f8a..2c1b2af 100644 --- a/src/main/java/org/example/team6backend/incident/dto/IncidentResponse.java +++ b/src/main/java/org/example/team6backend/incident/dto/IncidentResponse.java @@ -7,7 +7,7 @@ import org.example.team6backend.incident.entity.IncidentStatus; import org.hibernate.Hibernate; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -21,7 +21,7 @@ public class IncidentResponse { private IncidentCategory incidentCategory; private String createdBy; private String assignedTo; - private LocalDateTime createdAt; + private Instant createdAt; private boolean hasDocuments; private List documents; diff --git a/src/main/java/org/example/team6backend/incident/entity/Incident.java b/src/main/java/org/example/team6backend/incident/entity/Incident.java index ec53efd..21576b8 100644 --- a/src/main/java/org/example/team6backend/incident/entity/Incident.java +++ b/src/main/java/org/example/team6backend/incident/entity/Incident.java @@ -3,7 +3,8 @@ import jakarta.persistence.*; import org.example.team6backend.document.entity.Document; import org.example.team6backend.user.entity.AppUser; -import java.time.LocalDateTime; + +import java.time.Instant; import java.util.List; @Entity @@ -36,23 +37,23 @@ public class Incident { private AppUser assignedTo; @Column(name = "created_at") - private LocalDateTime createdAt; + private Instant createdAt; @Column(name = "updated_at") - private LocalDateTime updatedAt; + private Instant updatedAt; @OneToMany(mappedBy = "incident", cascade = CascadeType.ALL, orphanRemoval = true) private List documents; @PrePersist protected void onCreate() { - createdAt = LocalDateTime.now(); - updatedAt = LocalDateTime.now(); + createdAt = Instant.now(); + updatedAt = Instant.now(); } @PreUpdate protected void onUpdate() { - updatedAt = LocalDateTime.now(); + updatedAt = Instant.now(); } public Long getId() { @@ -87,11 +88,11 @@ public AppUser getAssignedTo() { return assignedTo; } - public LocalDateTime getCreatedAt() { + public Instant getCreatedAt() { return createdAt; } - public LocalDateTime getUpdatedAt() { + public Instant getUpdatedAt() { return updatedAt; } @@ -131,11 +132,11 @@ public void setAssignedTo(AppUser assignedTo) { this.assignedTo = assignedTo; } - public void setCreatedAt(LocalDateTime createdAt) { + public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } - public void setUpdatedAt(LocalDateTime updatedAt) { + public void setUpdatedAt(Instant updatedAt) { this.updatedAt = updatedAt; } diff --git a/src/main/java/org/example/team6backend/incident/service/IncidentService.java b/src/main/java/org/example/team6backend/incident/service/IncidentService.java index d294d20..88c56e5 100644 --- a/src/main/java/org/example/team6backend/incident/service/IncidentService.java +++ b/src/main/java/org/example/team6backend/incident/service/IncidentService.java @@ -23,7 +23,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ResponseStatusException; -import java.time.LocalDateTime; + +import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -74,8 +75,8 @@ public Incident createIncident(IncidentRequest incidentRequest, List Date: Wed, 15 Apr 2026 12:08:51 +0200 Subject: [PATCH 3/4] refactor: store timestamp in UTC using Instant and timestampz --- .../comment/controller/CommentController.java | 7 ++-- .../comment/dto/CommentResponse.java | 35 ++++++------------- .../V10__change_timestamp_to_timestampz.sql | 14 ++++++++ src/main/resources/templates/admin.html | 16 +++++++-- src/main/resources/templates/dashboard.html | 16 +++++++-- src/main/resources/templates/incidents.html | 14 +++++++- src/main/resources/templates/profile.html | 23 +++++++++++- .../resources/templates/viewincident.html | 2 +- 8 files changed, 90 insertions(+), 37 deletions(-) create mode 100644 src/main/resources/db/migration/V10__change_timestamp_to_timestampz.sql diff --git a/src/main/java/org/example/team6backend/comment/controller/CommentController.java b/src/main/java/org/example/team6backend/comment/controller/CommentController.java index 1ec0e49..ecfffac 100644 --- a/src/main/java/org/example/team6backend/comment/controller/CommentController.java +++ b/src/main/java/org/example/team6backend/comment/controller/CommentController.java @@ -3,7 +3,6 @@ import jakarta.validation.Valid; import org.example.team6backend.comment.dto.CommentRequest; import org.example.team6backend.comment.dto.CommentResponse; -import org.example.team6backend.comment.entity.Comment; import org.example.team6backend.comment.service.CommentService; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -24,10 +23,8 @@ public CommentController(CommentService commentService) { @GetMapping("/incident/{incidentId}") @ResponseBody public ResponseEntity> getCommentByIncidentId(@PathVariable Long incidentId) { - List comments = commentService.getCommentByIncidentId(incidentId) - .stream() - .map(CommentResponse::fromEntity) - .toList(); + List comments = commentService.getCommentByIncidentId(incidentId).stream() + .map(CommentResponse::fromEntity).toList(); return ResponseEntity.ok(comments); } diff --git a/src/main/java/org/example/team6backend/comment/dto/CommentResponse.java b/src/main/java/org/example/team6backend/comment/dto/CommentResponse.java index 2ea2f48..bd3a1c1 100644 --- a/src/main/java/org/example/team6backend/comment/dto/CommentResponse.java +++ b/src/main/java/org/example/team6backend/comment/dto/CommentResponse.java @@ -4,32 +4,17 @@ import java.time.Instant; -public record CommentResponse( - String id, - String message, - Instant createdAt, - CommentUserResponse user +public record CommentResponse(String id, String message, Instant createdAt, CommentUserResponse user ) { - public static CommentResponse fromEntity(Comment comment) { - return new CommentResponse( - comment.getId(), - comment.getMessage(), - comment.getCreatedAt(), - comment.getUser() != null - ? new CommentUserResponse( - comment.getUser().getId(), - comment.getUser().getName(), - comment.getUser().getEmail() - ) - : null - ); - } + public static CommentResponse fromEntity(Comment comment) { + return new CommentResponse(comment.getId(), comment.getMessage(), comment.getCreatedAt(), + comment.getUser() != null + ? new CommentUserResponse(comment.getUser().getId(), comment.getUser().getName(), + comment.getUser().getEmail()) + : null); + } - public record CommentUserResponse( - String id, - String name, - String email - ) { - } + public record CommentUserResponse(String id, String name, String email) { + } } diff --git a/src/main/resources/db/migration/V10__change_timestamp_to_timestampz.sql b/src/main/resources/db/migration/V10__change_timestamp_to_timestampz.sql new file mode 100644 index 0000000..162b34c --- /dev/null +++ b/src/main/resources/db/migration/V10__change_timestamp_to_timestampz.sql @@ -0,0 +1,14 @@ +ALTER TABLE incident +ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'; + +ALTER TABLE comment +ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'; + +ALTER TABLE activity_log +ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'; + +ALTER TABLE app_user +ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'UTC'; + +ALTER TABLE app_user +ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at AT TIME ZONE 'UTC'; \ No newline at end of file diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html index 7e1c9e9..2926868 100644 --- a/src/main/resources/templates/admin.html +++ b/src/main/resources/templates/admin.html @@ -1196,7 +1196,7 @@

Assign Incident to Handler

${user.active ? 'Active' : 'Inactive'} - ${new Date(user.createdAt).toLocaleDateString()} + ${formatDate(user.createdAt)}
${user.role === 'PENDING' ? @@ -1344,7 +1344,7 @@

Assign Incident to Handler

`Unassigned` } - ${new Date(incident.createdAt).toLocaleDateString()} + ${formatDate(incident.createdAt)}
${actionButton} @@ -1656,6 +1656,18 @@

Assign Incident to Handler

div.textContent = text; return div.innerHTML; } + + function formatDate(dateString) { + if (!dateString) return "-"; + + return new Date(dateString).toLocaleString("sv-SE", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit" + }); + } \ No newline at end of file diff --git a/src/main/resources/templates/dashboard.html b/src/main/resources/templates/dashboard.html index ddbf2cb..dac0831 100644 --- a/src/main/resources/templates/dashboard.html +++ b/src/main/resources/templates/dashboard.html @@ -819,7 +819,7 @@

Recent incidents

${escapeHtml(i.subject)}
-
${new Date(i.createdAt).toLocaleDateString()}
+
${formatDate(i.createdAt)}
${i.incidentStatus}
@@ -949,7 +949,7 @@

Recent incidents

container.innerHTML = notifications.map(notification => ` `).join(''); } catch (error) { @@ -977,6 +977,18 @@

Recent incidents

} } + function formatDate(dateString) { + if (!dateString) return "-"; + + return new Date(dateString).toLocaleString("sv-SE", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit" + }); + } + document.addEventListener('DOMContentLoaded', () => { if (userActive && userRole !== 'PENDING') { loadIncidents(); diff --git a/src/main/resources/templates/incidents.html b/src/main/resources/templates/incidents.html index 957aa37..dd68208 100644 --- a/src/main/resources/templates/incidents.html +++ b/src/main/resources/templates/incidents.html @@ -268,7 +268,7 @@

Loading...