From 6d20f0106a87aff72eb6b65652603825797efdbc Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 14:48:21 +0200 Subject: [PATCH 01/13] feat: add formattedCreatedAt method for UI-friendly date display in audit logs --- src/main/java/org/example/alfs/entities/AuditLog.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/org/example/alfs/entities/AuditLog.java b/src/main/java/org/example/alfs/entities/AuditLog.java index 04360b3..9d00090 100644 --- a/src/main/java/org/example/alfs/entities/AuditLog.java +++ b/src/main/java/org/example/alfs/entities/AuditLog.java @@ -51,4 +51,11 @@ public void prePersist() { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; + + public String getFormattedCreatedAt() { + if (createdAt == null) return ""; + return createdAt.format( + java.time.format.DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm") + ); + } } From 70e986883c460f20ce851bd4f637db31f14043d0 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 14:48:31 +0200 Subject: [PATCH 02/13] feat: include user in audit log entries to track who performed actions --- .../org/example/alfs/services/AuditService.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/org/example/alfs/services/AuditService.java b/src/main/java/org/example/alfs/services/AuditService.java index 6b0d80d..89a9438 100644 --- a/src/main/java/org/example/alfs/services/AuditService.java +++ b/src/main/java/org/example/alfs/services/AuditService.java @@ -2,6 +2,7 @@ import org.example.alfs.entities.AuditLog; import org.example.alfs.entities.Ticket; +import org.example.alfs.entities.User; import org.example.alfs.enums.AuditAction; import org.example.alfs.repositories.AuditLogRepository; import org.springframework.stereotype.Service; @@ -15,6 +16,20 @@ public AuditService(AuditLogRepository auditLogRepository) { this.auditLogRepository = auditLogRepository; } + + // new with user + public void log(AuditAction action, String fieldName, String oldValue, String newValue, Ticket ticket, User user) { + AuditLog log = new AuditLog(); + log.setAction(action); + log.setFieldName(fieldName); + log.setOldValue(oldValue); + log.setNewValue(newValue); + log.setTicket(ticket); + log.setUser(user); // 🔥 VIKTIGT + auditLogRepository.save(log); + } + + // keeping old for safety public void log(AuditAction action, String fieldName, String oldValue, String newValue, Ticket ticket) { AuditLog log = new AuditLog(); log.setAction(action); From 4ec307c11532165a71685a859820f3e62adadb02 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 16:28:20 +0200 Subject: [PATCH 03/13] feat: enhance audit logging for status changes and investigator assignment with clearer values --- .../example/alfs/services/TicketService.java | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index 2cd68cf..f916302 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -4,6 +4,7 @@ import org.example.alfs.dto.ticket.TicketViewDTO; import org.example.alfs.entities.Ticket; import org.example.alfs.entities.User; +import org.example.alfs.enums.AuditAction; import org.example.alfs.enums.Role; import org.example.alfs.enums.TicketStatus; import org.example.alfs.mapper.TicketMapper; @@ -26,15 +27,18 @@ public class TicketService { private final TicketMapper ticketMapper; private final SecurityUtils securityUtils; private final UserRepository userRepository; + private final AuditService auditService; public TicketService(TicketRepository ticketRepository, TicketMapper ticketMapper, SecurityUtils securityUtils, - UserRepository userRepository) { + UserRepository userRepository, + AuditService auditService) { this.ticketRepository = ticketRepository; this.ticketMapper = ticketMapper; this.securityUtils = securityUtils; this.userRepository = userRepository; + this.auditService = auditService; } //createNewTicket @@ -58,6 +62,15 @@ public TicketViewDTO createNewTicket(TicketCreateDTO dto) { Ticket saved = ticketRepository.save(ticket); + auditService.log( + AuditAction.CREATED, + "ticket", + null, + "ticketId:" + saved.getId(), + saved, + user + ); + TicketViewDTO view = ticketMapper.entityToViewDTO(saved); if (token != null) { @@ -254,6 +267,15 @@ public TicketViewDTO updateTicketStatus(Long id, TicketStatus newStatus) { ticket.setStatus(newStatus); Ticket savedTicket = ticketRepository.save(ticket); + + auditService.log( + AuditAction.STATUS_CHANGED, + "status", + currentStatus.name(), + newStatus.name(), + savedTicket, + user + ); return ticketMapper.entityToViewDTO(savedTicket); } @@ -303,6 +325,16 @@ public TicketViewDTO assignInvestigator(Long id, Long investigatorId) { ticket.setStatus(TicketStatus.IN_PROGRESS); Ticket savedTicket = ticketRepository.save(ticket); + + auditService.log( + AuditAction.ASSIGNED, + "investigator", + null, + investigator.getUsername(), + savedTicket, + user + ); + return ticketMapper.entityToViewDTO(savedTicket); } @@ -330,6 +362,16 @@ public TicketViewDTO unassignInvestigator(Long id) { ticket.setStatus(TicketStatus.OPEN); Ticket savedTicket = ticketRepository.save(ticket); + + auditService.log( + AuditAction.UNASSIGNED, + "investigator", + ticket.getInvestigator().getUsername(), + null, + savedTicket, + user + ); + return ticketMapper.entityToViewDTO(savedTicket); } } \ No newline at end of file From dab0e2068ab1f3f7db80e65f3d61a5931c3dc087 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 16:28:32 +0200 Subject: [PATCH 04/13] feat: improve audit log for attachments with readable file names instead of object keys --- .../java/org/example/alfs/services/AttachmentService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/alfs/services/AttachmentService.java b/src/main/java/org/example/alfs/services/AttachmentService.java index e7163d3..dc6cd72 100644 --- a/src/main/java/org/example/alfs/services/AttachmentService.java +++ b/src/main/java/org/example/alfs/services/AttachmentService.java @@ -79,8 +79,9 @@ public Attachment uploadToTicket(Long ticketId, MultipartFile file, User user, S AuditAction.ATTACHMENT_ADDED, "attachments", null, - "objectKey:" + objectKey, - ticket + att.getFileName(), + ticket, + user ); return att; From bb8e8aa7131feddb14f8919c56fe30fe0dbc5612 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 16:28:56 +0200 Subject: [PATCH 05/13] feat: improve audit log UI with user, action, details and readable formatting --- src/main/jte/view.jte | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/main/jte/view.jte b/src/main/jte/view.jte index 4cd9c3c..48db70e 100644 --- a/src/main/jte/view.jte +++ b/src/main/jte/view.jte @@ -296,9 +296,48 @@ @if(auditLogs != null) @for(var log : auditLogs) -
- ${log.getAction()} + +
+ + +
+ ${log.getUser() != null ? log.getUser().getUsername() : "System"} +
+ + +
+ @if(log.getAction().toString().equals("ATTACHMENT_ADDED")) + File uploaded + @elseif(log.getAction().toString().equals("STATUS_CHANGED")) + Status updated + @elseif(log.getAction().toString().equals("ASSIGNED")) + Investigator assigned + @elseif(log.getAction().toString().equals("CREATED")) + Ticket created + @else + ${log.getAction().toString().replace("_", " ")} + @endif +
+ + @if(log.getFieldName() != null) +
+ + ${log.getFieldName()}: + + @if(log.getOldValue() != null) + ${log.getOldValue()} → + @endif + ${log.getNewValue()} +
+ @endif + + +
+ ${log.getFormattedCreatedAt()} +
+
+ @endfor @endif
From 394e4c7dec4d09e6578987ba1efb447138e9d75c Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 18:16:47 +0200 Subject: [PATCH 06/13] fix: fix unassign investigator null crash and audit logging --- src/main/java/org/example/alfs/services/TicketService.java | 4 +++- .../java/org/example/alfs/services/TicketServiceTest.java | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index f916302..c600566 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -358,6 +358,8 @@ public TicketViewDTO unassignInvestigator(Long id) { HttpStatus.BAD_REQUEST, "Must be IN_PROGRESS"); } + String oldInvestigator = ticket.getInvestigator().getUsername(); + ticket.setInvestigator(null); ticket.setStatus(TicketStatus.OPEN); @@ -366,7 +368,7 @@ public TicketViewDTO unassignInvestigator(Long id) { auditService.log( AuditAction.UNASSIGNED, "investigator", - ticket.getInvestigator().getUsername(), + oldInvestigator, null, savedTicket, user diff --git a/src/test/java/org/example/alfs/services/TicketServiceTest.java b/src/test/java/org/example/alfs/services/TicketServiceTest.java index 2a48ef1..017272f 100644 --- a/src/test/java/org/example/alfs/services/TicketServiceTest.java +++ b/src/test/java/org/example/alfs/services/TicketServiceTest.java @@ -41,6 +41,9 @@ class TicketServiceTest { @Mock SecurityUtils securityUtils; + @Mock + AuditService auditService; + @InjectMocks TicketService ticketService; From 3b285b0c563def3d3a5befa54e13c1ca47cedb90 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 19:17:56 +0200 Subject: [PATCH 07/13] refactor: improve audit log readability for ticket creation and add transactional support --- src/main/java/org/example/alfs/services/TicketService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index c600566..3c7c986 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -42,6 +42,7 @@ public TicketService(TicketRepository ticketRepository, } //createNewTicket + @Transactional public TicketViewDTO createNewTicket(TicketCreateDTO dto) { Ticket ticket = new Ticket(); @@ -64,9 +65,9 @@ public TicketViewDTO createNewTicket(TicketCreateDTO dto) { auditService.log( AuditAction.CREATED, - "ticket", + "title", null, - "ticketId:" + saved.getId(), + saved.getTitle(), saved, user ); From 5420c9c359ac96be4821532489f898e7e45d233a Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 19:18:39 +0200 Subject: [PATCH 08/13] test: verify audit logging when unassigning investigator --- .../org/example/alfs/services/TicketServiceTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/org/example/alfs/services/TicketServiceTest.java b/src/test/java/org/example/alfs/services/TicketServiceTest.java index 017272f..33cf70c 100644 --- a/src/test/java/org/example/alfs/services/TicketServiceTest.java +++ b/src/test/java/org/example/alfs/services/TicketServiceTest.java @@ -4,6 +4,7 @@ import org.example.alfs.dto.ticket.TicketViewDTO; import org.example.alfs.entities.Ticket; import org.example.alfs.entities.User; +import org.example.alfs.enums.AuditAction; import org.example.alfs.enums.Role; import org.example.alfs.enums.TicketStatus; import org.example.alfs.mapper.TicketMapper; @@ -794,6 +795,15 @@ void unassignInvestigator_shouldSucceed() { assertNull(ticket.getInvestigator()); assertEquals(TicketStatus.OPEN, ticket.getStatus()); verify(ticketRepository).save(ticket); + + verify(auditService).log( + eq(AuditAction.UNASSIGNED), + eq("investigator"), + any(), + isNull(), + any(), + eq(admin) + ); } @Test From 34ab95a4d8d5e9c95f928be2da3b004162a43a0f Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 19:19:10 +0200 Subject: [PATCH 09/13] refactor: reuse DateTimeFormatter for createdAt formatting --- .../java/org/example/alfs/dto/ticket/TicketViewDTO.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java b/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java index b958cd0..4126e34 100644 --- a/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java +++ b/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java @@ -4,6 +4,7 @@ import org.example.alfs.enums.TicketStatus; import java.time.format.DateTimeFormatter; import java.time.LocalDateTime; +import java.util.Locale; /* * DTO returned when retrieving ticket information. @@ -26,9 +27,12 @@ public class TicketViewDTO { private Long assignedInvestigatorId; private String assignedInvestigatorName; + private static final DateTimeFormatter DISPLAY_FORMATTER = + DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm", Locale.ENGLISH); + public String getFormattedCreatedAt() { if (createdAt == null) return ""; - return createdAt.format(DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm")); + return createdAt.format(DISPLAY_FORMATTER); } } From b51a8e2e4345df016415f6912cbdd13e66cc8705 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 19:19:37 +0200 Subject: [PATCH 10/13] refactor: use enum comparison and add readable audit log labels in view.jte --- src/main/jte/view.jte | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/jte/view.jte b/src/main/jte/view.jte index 48db70e..2550062 100644 --- a/src/main/jte/view.jte +++ b/src/main/jte/view.jte @@ -306,16 +306,18 @@
- @if(log.getAction().toString().equals("ATTACHMENT_ADDED")) + @if(log.getAction() == org.example.alfs.enums.AuditAction.ATTACHMENT_ADDED) File uploaded - @elseif(log.getAction().toString().equals("STATUS_CHANGED")) + @elseif(log.getAction() == org.example.alfs.enums.AuditAction.STATUS_CHANGED) Status updated - @elseif(log.getAction().toString().equals("ASSIGNED")) + @elseif(log.getAction() == org.example.alfs.enums.AuditAction.ASSIGNED) Investigator assigned - @elseif(log.getAction().toString().equals("CREATED")) + @elseif(log.getAction() == org.example.alfs.enums.AuditAction.UNASSIGNED) + Investigator unassigned + @elseif(log.getAction() == org.example.alfs.enums.AuditAction.CREATED) Ticket created @else - ${log.getAction().toString().replace("_", " ")} + ${log.getAction().name().replace("_", " ")} @endif
@@ -335,7 +337,6 @@
${log.getFormattedCreatedAt()}
- @endfor From c9e422d670a716e582d11703d10bbbdd68f47723 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 19:52:46 +0200 Subject: [PATCH 11/13] feat: audit status changes on investigator assignment and improve log order --- .../org/example/alfs/services/TicketService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index 3c7c986..fb474a7 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -322,11 +322,22 @@ public TicketViewDTO assignInvestigator(Long id, Long investigatorId) { HttpStatus.BAD_REQUEST, "User is not an investigator"); } + TicketStatus oldStatus = ticket.getStatus(); ticket.setInvestigator(investigator); ticket.setStatus(TicketStatus.IN_PROGRESS); Ticket savedTicket = ticketRepository.save(ticket); + + auditService.log( + AuditAction.STATUS_CHANGED, + "status", + oldStatus.name(), + TicketStatus.IN_PROGRESS.name(), + savedTicket, + user + ); + auditService.log( AuditAction.ASSIGNED, "investigator", @@ -336,6 +347,7 @@ public TicketViewDTO assignInvestigator(Long id, Long investigatorId) { user ); + return ticketMapper.entityToViewDTO(savedTicket); } From b3f1ebed7a7f4de36adcb44835adf1a7cefe1630 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 19:52:56 +0200 Subject: [PATCH 12/13] test: strengthen audit verification in unassignInvestigator test --- src/test/java/org/example/alfs/services/TicketServiceTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/example/alfs/services/TicketServiceTest.java b/src/test/java/org/example/alfs/services/TicketServiceTest.java index 33cf70c..8852b86 100644 --- a/src/test/java/org/example/alfs/services/TicketServiceTest.java +++ b/src/test/java/org/example/alfs/services/TicketServiceTest.java @@ -780,6 +780,7 @@ void unassignInvestigator_shouldSucceed() { Ticket ticket = openTicket(); User admin = adminUser(); User investigator = investigatorUser(); + investigator.setUsername("inv-user"); ticket.setInvestigator(investigator); ticket.setStatus(TicketStatus.IN_PROGRESS); @@ -799,7 +800,7 @@ void unassignInvestigator_shouldSucceed() { verify(auditService).log( eq(AuditAction.UNASSIGNED), eq("investigator"), - any(), + eq("inv-user"), isNull(), any(), eq(admin) From eb56868426a699d843128ce5d7334029272ddf12 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Tue, 21 Apr 2026 19:53:04 +0200 Subject: [PATCH 13/13] fix: allow anonymous access to token-based ticket views and comments --- src/main/java/org/example/alfs/config/SecurityConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/config/SecurityConfig.java b/src/main/java/org/example/alfs/config/SecurityConfig.java index 0c74601..b2e7a36 100644 --- a/src/main/java/org/example/alfs/config/SecurityConfig.java +++ b/src/main/java/org/example/alfs/config/SecurityConfig.java @@ -46,7 +46,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/auth/hash").permitAll() .requestMatchers("/h2-console/**").permitAll() .requestMatchers("/", "/startPage").permitAll() - .requestMatchers(HttpMethod.GET,"/tickets/create").permitAll() + .requestMatchers(HttpMethod.POST, "/tickets/create").permitAll() + .requestMatchers(HttpMethod.GET, "/tickets/create").permitAll() + .requestMatchers("/tickets/ticket-created").permitAll() + .requestMatchers("/tickets/token/**").permitAll() + .requestMatchers(HttpMethod.POST, "/tickets/*/comments").permitAll() .requestMatchers("/error/**").permitAll() //allow access to endpoints during development