Skip to content

feature/expand-activity-log#13

Merged
VonAdamo merged 3 commits into
mainfrom
feature/expand-activity-log
Apr 24, 2026
Merged

feature/expand-activity-log#13
VonAdamo merged 3 commits into
mainfrom
feature/expand-activity-log

Conversation

@VonAdamo

@VonAdamo VonAdamo commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Activity Log viewer with filtering by activity type, username, ticket, and date
    • Activity history section now displayed on ticket detail pages
    • Document uploads/deletions and ticket status changes now recorded with user context

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces comprehensive activity logging across the application. Controllers now capture authenticated user context via Principal, service methods accept usernames to track who performed actions, and activity logs are recorded for document operations, ticket creation, and status changes. A new activity log viewer UI with filtering capabilities is added, along with updated DTOs, mappers, and exception handling using ResourceNotFoundException.

Changes

Cohort / File(s) Summary
Activity Log Endpoints
src/main/java/org/group1/projectbackend/controller/ActivityLogController.java
Updated default sort direction from ascending to descending for activity log listing.
Authentication Context Integration
src/main/java/org/group1/projectbackend/controller/DocumentController.java, src/main/java/org/group1/projectbackend/controller/SupportTicketController.java, src/main/java/org/group1/projectbackend/controller/web/DocumentViewController.java, src/main/java/org/group1/projectbackend/controller/web/TicketViewController.java
Modified endpoints to accept authenticated Principal parameter and pass username to service layer for user context tracking during document deletion and ticket status updates.
Activity Log Viewer UI
src/main/java/org/group1/projectbackend/controller/web/ActivityLogViewController.java
New Spring controller serving activity log view with GET endpoint at /activitylogs/view, supporting optional filters (activity type, username, ticket, date) and conditional result filtering with descending chronological ordering.
DTO and Mapper Updates
src/main/java/org/group1/projectbackend/dto/activitylog/ActivityLogDto.java, src/main/java/org/group1/projectbackend/mapper/ActivityLogMapper.java
Added username and ticketTitle fields to ActivityLogDto; updated mapper to extract user and ticket information during DTO construction.
Service Layer Enhancements
src/main/java/org/group1/projectbackend/service/ActivityLogService.java, src/main/java/org/group1/projectbackend/service/DocumentService.java, src/main/java/org/group1/projectbackend/service/SupportTicketService.java
Updated service interfaces to include username/Principal parameters; replaced generic exceptions with ResourceNotFoundException; added pre-existence validation for referenced entities.
Activity Logging Implementation
src/main/java/org/group1/projectbackend/service/impl/DocumentServiceImpl.java, src/main/java/org/group1/projectbackend/service/impl/SupportTicketServiceImpl.java, src/main/java/org/group1/projectbackend/service/CommentService.java
Injected ActivityLogService and implemented activity recording for file uploads/deletions, ticket creation, and status changes; centralized error handling with logActivitySafely helper; refactored activity logging in comment operations.
Template and Layout Updates
src/main/resources/templates/activitylogs/list.html, src/main/resources/templates/fragments/layout.html, src/main/resources/templates/tickets/detail.html, src/main/resources/templates/tickets/list.html, src/main/resources/templates/documents/list.html
Added new activity log listing template with filtering UI; added navigation link to activity logs; extended ticket detail page with activity history section; improved title truncation and formatting across pages.
Styling
src/main/resources/static/css/app.css
Enhanced layout with increased container widths, adjusted button/form spacing, added utility classes for table actions and filter highlighting, improved responsive behavior for filter forms.
Test Suite Updates
src/test/java/org/group1/projectbackend/controller/ActivityLogControllerTest.java, src/test/java/org/group1/projectbackend/controller/DocumentControllerTest.java, src/test/java/org/group1/projectbackend/controller/SupportTicketControllerTest.java, src/test/java/org/group1/projectbackend/controller/CommentControllerTest.java, src/test/java/org/group1/projectbackend/mapper/ActivityLogMapperTest.java, src/test/java/org/group1/projectbackend/service/ActivityLogServiceTest.java, src/test/java/org/group1/projectbackend/service/CommentServiceTest.java, src/test/java/org/group1/projectbackend/service/DocumentServiceTest.java, src/test/java/org/group1/projectbackend/service/SupportTicketServiceTest.java
Updated mocks and assertions to verify activity logging invocations, validated username and ticketTitle DTO fields, corrected method signature expectations, added tests for ResourceNotFoundException scenarios, enhanced error-resilience testing.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Controller
    participant Service
    participant ActivityLogService
    participant Repository
    participant Database

    User->>Controller: POST /documents/upload (authenticated)
    Controller->>Service: uploadDocument(username, file, ticketId)
    Service->>Repository: save(document)
    Repository->>Database: INSERT document
    Repository-->>Service: saved document
    Service->>ActivityLogService: createActivityLog(FILE_UPLOADED, description, userId, ticketId)
    ActivityLogService->>Repository: save(activityLog)
    Repository->>Database: INSERT activity_log
    Repository-->>ActivityLogService: saved activity log
    ActivityLogService-->>Service: success
    Service-->>Controller: DocumentResponse
    Controller-->>User: 201 Created

    User->>Controller: PUT /tickets/{id}/status (authenticated)
    Controller->>Service: updateStatus(username, ticketId, request)
    Service->>Repository: findById(ticketId)
    Repository->>Database: SELECT ticket
    Repository-->>Service: ticket with old status
    Service->>Repository: save(updated ticket)
    Repository->>Database: UPDATE ticket
    Service->>ActivityLogService: createActivityLog(TICKET_STATUS_CHANGED, description, userId, ticketId)
    ActivityLogService->>Repository: save(activityLog)
    Repository->>Database: INSERT activity_log
    ActivityLogService-->>Service: success
    Service-->>Controller: TicketResponse
    Controller-->>User: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Poem

🐰 A hop through logs, we trace the way,
Each action marked, the users say—
Who changed that ticket, uploaded file?—
Activity logs reveal the trail with style! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'feature/expand-activity-log' is vague and uses a feature-branch naming convention without describing what was actually expanded or changed. It does not clearly summarize the main changes to a teammate scanning history. Consider a more descriptive title like 'Add user context to document/ticket operations and activity log filtering' or 'Track user actions in activity logs with new filtering UI'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/expand-activity-log

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/java/org/group1/projectbackend/service/CommentService.java (2)

38-96: ⚠️ Potential issue | 🟠 Major

Inconsistent exception types: CommentService still throws raw RuntimeException on missing entities.

This PR migrates ActivityLogService to throw ResourceNotFoundException for missing User/SupportTicket/ActivityLog (and the controller layer is presumably mapping that to 404), but CommentService.createComment (lines 40, 43), getCommentsBySupportTicketId (line 61), deleteComment (line 82), and updateComment (line 96) still throw plain RuntimeException. That will surface as 500 Internal Server Error instead of 404 Not Found from the comment endpoints.

🛡️ Suggested diff
+import org.group1.projectbackend.exception.ResourceNotFoundException;
@@
-                .orElseThrow(() -> new RuntimeException("User not found with id: " + dto.getUserId()));
+                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + dto.getUserId()));
@@
-                .orElseThrow(() -> new RuntimeException("Support ticket not found with id: " + dto.getSupportTicketId()));
+                .orElseThrow(() -> new ResourceNotFoundException("Support ticket not found with id: " + dto.getSupportTicketId()));
@@
-                .orElseThrow(() -> new RuntimeException("Support ticket not found with id: " + ticketId));
+                .orElseThrow(() -> new ResourceNotFoundException("Support ticket not found with id: " + ticketId));
@@
-                .orElseThrow(() -> new RuntimeException("Comment not found with id: " + id));
+                .orElseThrow(() -> new ResourceNotFoundException("Comment not found with id: " + id));
@@
-                .orElseThrow(() -> new RuntimeException("Comment not found with id: " + id));
+                .orElseThrow(() -> new ResourceNotFoundException("Comment not found with id: " + id));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/group1/projectbackend/service/CommentService.java` around
lines 38 - 96, The service currently throws raw RuntimeException when entities
are missing; update createComment, getCommentsBySupportTicketId, deleteComment,
and updateComment to throw ResourceNotFoundException instead of RuntimeException
for user/supportTicket/comment lookups (the findById(...) orElseThrow(...) calls
in those methods), preserving the same messages (e.g., "User not found with id:
...", "Support ticket not found with id: ...", "Comment not found with id:
..."); ensure you import ResourceNotFoundException and keep the
logActivitySafely calls unchanged so controllers return 404s consistently.

80-106: ⚠️ Potential issue | 🔴 Critical

Add @Transactional to deleteComment and updateComment methods.

The DB constraints (nullable = false on both user_id and ticket_id columns) and JPA mappings correctly enforce that comment.getUser() and comment.getTicket() are non-null. However, both associations use FetchType.LAZY and the service methods lack @Transactional annotations. When logActivitySafely attempts to access comment.getUser().getId() or comment.getTicket().getId() after the repository operation, the Hibernate session will have closed, causing a LazyInitializationException at runtime. Both deleteComment (line 80) and updateComment (line 94) must be annotated with @Transactional to keep the session open for lazy initialization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/group1/projectbackend/service/CommentService.java` around
lines 80 - 106, The deleteComment and updateComment methods should be annotated
with `@Transactional` so the Hibernate session remains open when logActivitySafely
accesses lazy associations (comment.getUser().getId(),
comment.getTicket().getId()); add `@Transactional` above both methods (or at the
class level) and ensure the import for
org.springframework.transaction.annotation.Transactional is present so
repository operations and subsequent lazy access occur within a transactional
context.
src/main/java/org/group1/projectbackend/controller/web/DocumentViewController.java (1)

27-41: ⚠️ Potential issue | 🟡 Minor

Possible null Principal masked by the broad catch.

If the /documents/** route is reachable anonymously, principal will be null, the NPE will bubble into the RuntimeException catch, and users will see "Dokumentet kunde inte tas bort." with no indication that it's an auth problem. Ensure the route requires authentication and/or add an explicit null check before calling principal.getName().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/group1/projectbackend/controller/web/DocumentViewController.java`
around lines 27 - 41, The deleteDocument handler currently calls
principal.getName() inside deleteDocument and masks null-principal NPEs in a
broad RuntimeException catch; add an explicit null check for the Principal at
the start of the deleteDocument method (or enforce authentication via an
annotation like `@PreAuthorize/`@Secured on the controller/method) and handle the
null case by setting an appropriate redirectAttributes.documentError (e.g.,
"Authentication required") and returning the redirect to /documents (or forward
to login) before calling documentService.deleteDocument; ensure you still catch
and log other RuntimeExceptions separately so auth-related nulls are not
swallowed.
🧹 Nitpick comments (14)
src/test/java/org/group1/projectbackend/controller/CommentControllerTest.java (1)

27-27: Minor: prefer an import over the fully qualified name.

The field type was changed to use the fully qualified org.group1.projectbackend.service.CommentService while the rest of the file uses imports. For consistency and readability, consider restoring the explicit import instead.

♻️ Suggested diff
 import org.group1.projectbackend.dto.comment.CommentDto;
+import org.group1.projectbackend.service.CommentService;
 import org.junit.jupiter.api.BeforeEach;
@@
     `@MockitoBean`
-    private org.group1.projectbackend.service.CommentService commentService;
+    private CommentService commentService;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/group1/projectbackend/controller/CommentControllerTest.java`
at line 27, Replace the fully-qualified field type with an import to match the
file's style: add an import for CommentService and change the field declaration
in CommentControllerTest from "private
org.group1.projectbackend.service.CommentService commentService;" to use the
simple type "CommentService" so the class uses consistent imports and improves
readability.
src/main/java/org/group1/projectbackend/service/ActivityLogService.java (1)

67-91: Inconsistent existence check between ticket-scoped and user-scoped log queries.

getActivityLogsBySupportTicketId now throws ResourceNotFoundException when the ticket doesn't exist (good), but getActivityLogsByUserId silently returns an empty list for a non-existent userId. For API consistency and to help clients distinguish "user has no activity" from "user does not exist", consider adding the same userRepository.existsById(userId) pre-check.

🛡️ Suggested diff
 public List<ActivityLogDto> getActivityLogsByUserId(Long userId, String sortDirection) {
+    if (!userRepository.existsById(userId)) {
+        throw new ResourceNotFoundException("User not found with id: " + userId);
+    }
+
     Sort sort = "desc".equalsIgnoreCase(sortDirection)
             ? Sort.by("createdAt").descending()
             : Sort.by("createdAt").ascending();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/group1/projectbackend/service/ActivityLogService.java`
around lines 67 - 91, Add the same existence check used in
getActivityLogsBySupportTicketId to getActivityLogsByUserId: call
userRepository.existsById(userId) before building the Sort and querying
activityLogRepository; if it returns false throw new
ResourceNotFoundException("User not found with id: " + userId) so the API
consistently signals a missing user rather than returning an empty list. Ensure
you reference getActivityLogsByUserId, userRepository.existsById, and
ResourceNotFoundException when making the change and keep the existing sort
logic intact.
src/main/java/org/group1/projectbackend/dto/activitylog/ActivityLogDto.java (1)

17-24: Positional-constructor coupling — caveat for callers.

Adding username and ticketTitle and reordering fields changes the @AllArgsConstructor positional signature. The mapper was updated to match, but any future caller using the all-args constructor (tests, serializers, JSON binding with constructor detection) must align with the new field order. Consider using @Builder at call sites to make these constructions resilient to further field additions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/group1/projectbackend/dto/activitylog/ActivityLogDto.java`
around lines 17 - 24, The positional AllArgsConstructor for ActivityLogDto
changed when fields (username, ticketTitle, field order) were modified; to avoid
callers breaking on constructor argument order, add Lombok's `@Builder` to the
ActivityLogDto class (in addition to keeping existing constructors/annotations
like `@AllArgsConstructor/`@NoArgsConstructor) and update
callers/tests/serializers that currently call the all-args constructor to use
ActivityLogDto.builder().username(...).ticketTitle(...).activityType(...).description(...).createdAt(...).supportTicketId(...)
so future field reordering/additions won't break positional construction.
src/main/java/org/group1/projectbackend/service/DocumentService.java (1)

10-18: Inconsistent user-identity contract within DocumentService.

deleteDocument now uses String username (server-side resolved from Principal), while uploadDocument still takes a client-supplied Long uploadedByUserId. Per the retrieved learning, the uploadedByUserId parameter is intentional until auth lands, but now that Principal is being wired into controllers for activity logging, consider flipping uploadDocument to also accept String username so both methods resolve the acting user server-side. This would remove the last spoofable identity input on this interface and unify the activity-log provenance.

Based on learnings: DocumentService.uploadDocument uses a client-supplied uploadedByUserId intentionally until Spring Security auth is wired in — and this PR is part of that transition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/group1/projectbackend/service/DocumentService.java` around
lines 10 - 18, The interface has an inconsistent user identity contract:
DocumentService.uploadDocument(Long uploadedByUserId, ...) accepts a
client-supplied user ID while deleteDocument(String username, ...) uses
server-resolved username; change uploadDocument to accept String username
instead of Long uploadedByUserId so both methods resolve the acting user
server-side. Update the DocumentService interface signature for uploadDocument,
then update all implementing classes (e.g., DocumentServiceImpl.uploadDocument)
and callers (controllers that currently pass uploadedByUserId) to pass
Principal.getName() or the server-resolved username; adjust unit/integration
tests and any DTO mapping that referenced uploadedByUserId accordingly. Ensure
method name and parameter order remain otherwise unchanged
(DocumentService.uploadDocument(String username, MultipartFile file, Long
ticketId) or matching existing parameter order) so callers compile. Make sure to
remove or deprecate the old uploadedByUserId usage to prevent duplicate identity
inputs.
src/test/java/org/group1/projectbackend/service/CommentServiceTest.java (1)

49-49: Nit: redundant fully-qualified class name.

CommentService lives in the same package as this test (org.group1.projectbackend.service), so the FQCN is unnecessary and just adds visual noise. A plain private CommentService commentService; is enough.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/group1/projectbackend/service/CommentServiceTest.java` at
line 49, The field declaration uses a redundant fully-qualified name; change the
field declaration "private org.group1.projectbackend.service.CommentService
commentService;" to use the simple type name "CommentService" (i.e., "private
CommentService commentService;") to match the test's package and remove visual
noise while keeping the same symbol name commentService.
src/main/java/org/group1/projectbackend/service/impl/SupportTicketServiceImpl.java (1)

102-113: Replace System.err.println with a proper logger, and consider extracting the duplicated helper.

Two related concerns in logActivitySafely:

  1. System.err.println bypasses the application's logging pipeline (no levels, no MDC, no timestamps/thread info, no structured output). Use SLF4J and log at warn with the exception so the stack trace is preserved — otherwise a silently-swallowed bug in ActivityLogService will be very hard to diagnose in production.
  2. This exact helper is duplicated in CommentService.logActivitySafely (per the provided snippet from src/main/java/org/group1/projectbackend/service/CommentService.java:112-123). As more services adopt activity logging, the pattern will keep multiplying. Consider extracting a small SafeActivityLogger component that wraps ActivityLogService and exposes a single logSafely(...) method.
♻️ Minimal diff: switch to SLF4J
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 `@Service`
 public class SupportTicketServiceImpl implements SupportTicketService {
 
+    private static final Logger log = LoggerFactory.getLogger(SupportTicketServiceImpl.class);
+
     private final SupportTicketRepository supportTicketRepository;
     private void logActivitySafely(ActivityType activityType, String description, Long userId, Long ticketId) {
         try {
             activityLogService.createActivityLog(new CreateActivityLogDto(
                     activityType,
                     description,
                     userId,
                     ticketId
             ));
         } catch (RuntimeException ex) {
-            System.err.println("Failed to create activity log: " + ex.getMessage());
+            log.warn("Failed to create activity log (type={}, userId={}, ticketId={})",
+                    activityType, userId, ticketId, ex);
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/group1/projectbackend/service/impl/SupportTicketServiceImpl.java`
around lines 102 - 113, Replace the System.err.println in
SupportTicketServiceImpl.logActivitySafely with an SLF4J logger call and
preserve the exception (e.g., logger.warn("Failed to create activity log for
ticketId={}, userId={}", ticketId, userId, ex)); then extract the duplicated
logic from SupportTicketServiceImpl.logActivitySafely and
CommentService.logActivitySafely into a single reusable component/class (e.g.,
SafeActivityLogger) that wraps ActivityLogService and exposes a
logSafely(ActivityType, String, Long userId, Long ticketId) method; update both
services to inject and use SafeActivityLogger instead of their private helpers
so logging uses SLF4J and the helper is not duplicated.
src/test/java/org/group1/projectbackend/controller/DocumentControllerTest.java (1)

167-173: Consider verifying the forwarded principal name.

The stub uses any() for the username, so a regression where the controller sends null or a hard-coded value would not be caught. Consider verify(documentService).deleteDocument(eq("user"), eq(100L)); (default @WithMockUser username is user) to pin the contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/group1/projectbackend/controller/DocumentControllerTest.java`
around lines 167 - 173, Update the test shouldDeleteDocument to assert the
controller forwards the authenticated principal to the service: replace the
loose doNothing/mock verification that uses any() for the username with a strict
verify call on documentService.deleteDocument(eq("user"), eq(100L)) (the default
`@WithMockUser` username is "user") so the test fails if the controller passes
null or a hard-coded value; keep the existing
mockMvc.perform(delete("/api/documents/100").with(csrf())) and status assertion
but add the verify(documentService).deleteDocument(...) after the request to
enforce the principal contract.
src/main/java/org/group1/projectbackend/controller/web/ActivityLogViewController.java (2)

67-70: Ticket-id filter produces false positives.

String.valueOf(id).contains(expected) makes e.g. ticket=2 match ids 12, 20, 200. If the intent is "match by ID", prefer an exact parse (Long.parseLong(expected)) and fall back to the title substring match only when parsing fails.

♻️ Proposed tweak
-    private boolean matchesTicketId(ActivityLogDto activityLog, String expected) {
-        return activityLog.getSupportTicketId() != null
-                && String.valueOf(activityLog.getSupportTicketId()).contains(expected);
-    }
+    private boolean matchesTicketId(ActivityLogDto activityLog, String expected) {
+        if (activityLog.getSupportTicketId() == null) {
+            return false;
+        }
+        try {
+            return activityLog.getSupportTicketId().equals(Long.parseLong(expected));
+        } catch (NumberFormatException ex) {
+            return false;
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/group1/projectbackend/controller/web/ActivityLogViewController.java`
around lines 67 - 70, The matchesTicketId method currently uses
String.valueOf(activityLog.getSupportTicketId()).contains(expected) which causes
false positives; change it to attempt parsing the expected string to a Long
(e.g., using Long.parseLong(expected)) and compare for equality with
activityLog.getSupportTicketId() (via ActivityLogDto.getSupportTicketId()); if
parsing throws/ fails, fall back to the previous substring behavior but applied
to the ticket title (e.g.,
ActivityLogDto.getSupportTicketTitle().contains(expected)) or to a safe string
conversion only as a fallback so numeric ID matching is exact while non-numeric
queries still allow title substring matching.

34-43: Move filtering/sorting to the repository layer.

Fetching all activity logs into memory then sorting and filtering in a stream won't scale and is vulnerable to LazyInitializationException when the mapper reads user.username/supportTicket.title outside a transaction. Consider a repository query with JPA Specification/derived queries and a @Query using JOIN FETCH (or @EntityGraph) on user and supportTicket, plus passing Sort.by("createdAt").descending() and pagination.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/group1/projectbackend/controller/web/ActivityLogViewController.java`
around lines 34 - 43, The controller currently loads all logs via
activityLogService.getAllActivityLogs() and applies stream-based
sorting/filtering which causes poor scalability and LazyInitializationException;
move this logic into the data layer by adding repository-level query methods
(e.g., in ActivityLogRepository) that accept a JPA Specification or derived
parameters and return results with Sort/Pageable (use
Sort.by("createdAt").descending() and Pageable for pagination). Ensure the
repository query uses JOIN FETCH or an `@EntityGraph` to eagerly fetch user and
supportTicket to avoid lazy-load outside a transaction, add a service method
like findActivityLogs(Specification<ActivityLog>, Pageable) that the
ActivityLogViewController calls with built Specifications for type, username,
ticket and date, and return the sorted/paged list instead of filtering in the
controller.
src/main/resources/templates/tickets/list.html (1)

39-42: Consider CSS truncation instead of substring.

#strings.substring truncates by UTF-16 code units, which can split surrogate pairs/emoji and always shows "..." even when not needed. A pure-CSS approach (max-width + white-space: nowrap; overflow: hidden; text-overflow: ellipsis;) is more robust and keeps the full text selectable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/tickets/list.html` around lines 39 - 42, Replace
the server-side substring truncation in the table cell (the expression using
`#strings.substring` and the conditional) with a plain full title rendering (keep
th:title="${ticket.title()}" and set th:text="${ticket.title()}"), and add a CSS
class (e.g. class="truncate") to that <td> so truncation is handled in CSS; then
implement the CSS rules for .truncate: max-width (or width), white-space:
nowrap, overflow: hidden, and text-overflow: ellipsis so the displayed title is
visually truncated without cutting surrogate pairs and the full text remains
selectable via the title attribute.
src/main/resources/templates/activitylogs/list.html (2)

29-34: Nit: placeholder text inside <option> is redundant.

th:text="${activityType.name()}" already sets the content at render time; the TICKET_CREATED literal is only visible at design-time and can mislead static-HTML previews. Minor, keep if you rely on preview tooling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/activitylogs/list.html` around lines 29 - 34,
Remove the static placeholder text "TICKET_CREATED" inside the <option> element
since th:text="${activityType.name()}" will render the option label at runtime;
update the template block using th:each, th:value, th:text and th:selected (the
option with th:each="activityType : ${activityTypes}" and
th:value/th:text/th:selected attributes) to have no inner literal content so
design-time previews don't show misleading static text.

74-84: Filter-summary separator logic is brittle.

The hand-rolled comma separators between filter tokens replicate the conjunction logic four times and will be tedious to maintain if new filters are added. Consider joining active filter labels via a controller-supplied list or a Thymeleaf utility like #strings.listJoin(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/activitylogs/list.html` around lines 74 - 84,
The template's comma/separator logic for showing active filters (using
selectedType, selectedUsername, selectedTicket, selectedDate in list.html) is
brittle and duplicated; instead prepare an ordered list of active filter labels
in the controller (e.g., create List<String> activeFilters from the same
selection logic) or build the list in Thymeleaf and join with a utility (e.g.,
`#strings.listJoin`(activeFilters, ', ')), then render a single span that prints
the joined string (remove the repeated span/separator blocks). Ensure the
controller or template produces labels like "Typ: X", "Användare: Y", "Ticket:
Z", "Datum: D" and pass them as the variable used by the join.
src/test/java/org/group1/projectbackend/service/DocumentServiceTest.java (1)

123-128: Consider adding a negative-path test for activity-log failures.

The tests cover the happy path for createActivityLog invocations. Since DocumentServiceImpl.logActivitySafely is designed to swallow RuntimeException thrown by ActivityLogService, it would be valuable to add one test stubbing activityLogService.createActivityLog(...) to throw, and asserting that uploadDocument/deleteDocument still complete successfully (document saved/deleted, storage object handled). This locks in the intended resilience guarantee.

Also applies to: 190-195

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/group1/projectbackend/service/DocumentServiceTest.java`
around lines 123 - 128, Add negative-path unit tests that verify
DocumentServiceImpl.logActivitySafely swallows runtime exceptions from
ActivityLogService: stub activityLogService.createActivityLog(...) to throw a
RuntimeException and then call uploadDocument (and a separate test for
deleteDocument) and assert the primary operation still completes successfully
(document persisted or deleted) and storage interactions (e.g.,
storageClient.putObject or storageClient.deleteObject) are invoked as expected;
also verify no exception escapes and that activity logging was attempted (verify
createActivityLog was called) even though it threw.
src/main/java/org/group1/projectbackend/controller/web/TicketViewController.java (1)

61-61: Consider replacing "desc"/"asc" magic strings with an enum or constants.

activityLogService.getActivityLogsBySupportTicketId(id, "desc") relies on a free-form string for sort direction. A dedicated SortDirection enum (or reuse of Spring's Sort.Direction) would be safer and self-documenting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/group1/projectbackend/controller/web/TicketViewController.java`
at line 61, Replace the "desc"/"asc" magic string usage in
TicketViewController's call to
activityLogService.getActivityLogsBySupportTicketId(id, "desc") by switching to
a typed enum or Spring's Sort.Direction: update the activityLogService method
signature (e.g., getActivityLogsBySupportTicketId(Long id, Sort.Direction
direction) or use your own SortDirection enum) and change the controller call to
pass Sort.Direction.DESC (or SortDirection.DESC) instead of the literal "desc";
update any downstream usages and converters/parsers accordingly so the service
and controller use the enum type rather than free-form strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/org/group1/projectbackend/controller/ActivityLogController.java`:
- Line 47: The controller changed the default sortDirection from "asc" to "desc"
for the activity-log endpoint, which is a behavioral API change; either align
the other user-pivot endpoint in ActivityLogController so both
`@RequestParam`(defaultValue = "desc") String sortDirection use the same
newest-first default, or explicitly document the deliberate difference in the
API docs/changelog and tests; update the other occurrence of the sortDirection
default (currently "asc") in ActivityLogController to "desc" if you choose
alignment, and add a changelog/API docs note calling out the changed default
behavior so clients that omit the parameter are aware.

In
`@src/main/java/org/group1/projectbackend/controller/web/ActivityLogViewController.java`:
- Line 35: The current sort lambda in ActivityLogViewController (.sorted((left,
right) -> right.getCreatedAt().compareTo(left.getCreatedAt()))) can NPE when
createdAt is null; replace it with a null-safe comparator such as
Comparator.comparing(ActivityLogDto::getCreatedAt,
Comparator.nullsLast(Comparator.naturalOrder())).reversed() or ensure createdAt
is non-null at persistence (e.g., add `@CreationTimestamp/`@Column(nullable =
false)) and then use the safer comparator to prevent null pointer exceptions
during sorting.

In `@src/main/java/org/group1/projectbackend/service/CommentService.java`:
- Around line 112-123: Replace the System.err usage in
CommentService.logActivitySafely with an SLF4J logger: add a private static
final Logger (LoggerFactory.getLogger(CommentService.class)) and change the
catch to logger.error or logger.warn with a descriptive message and pass the
exception (e.g., logger.error("Failed to create activity log for userId={}
ticketId={}", userId, ticketId, ex)); apply the same fix to
DocumentServiceImpl.logActivitySafely and consider extracting a shared utility
method to avoid duplication.

In
`@src/main/java/org/group1/projectbackend/service/impl/DocumentServiceImpl.java`:
- Around line 196-207: The catch around activityLogService.createActivityLog in
logActivitySafely swallows RuntimeException (which still marks the outer
`@Transactional` uploadDocument as rollback-only) and uses System.err; fix by
isolating the activity write or deferring it and replace the stderr call with
SLF4J: either change ActivityLogService.createActivityLog to run in its own
transaction (annotate with `@Transactional`(propagation = REQUIRES_NEW) on the
method) so failures don't mark the caller txn, or schedule the
CreateActivityLogDto via
TransactionSynchronizationManager.registerSynchronization(...).afterCommit(...)
from uploadDocument so logging runs post-commit; additionally replace
System.err.println with a logger (use a private static final Logger and call
logger.error(...) including exception) in logActivitySafely and any related code
paths.

In `@src/main/resources/templates/fragments/layout.html`:
- Line 14: Replace the English anchor text "Activity Log" in the navigation link
(the <a> element with th:href="@{/activitylogs/view}") with the Swedish label
"Aktivitetslogg" so it matches the other Swedish nav items in layout.html;
update only the anchor's inner text to "Aktivitetslogg" (or the corresponding
i18n key if the project uses message bundles) to maintain consistency.

In `@src/main/resources/templates/tickets/list.html`:
- Line 47: Replace the invalid nested <a> inside <button> by using a single
interactive element: either turn the outer element into the anchor (keep
th:href="@{/tickets/{id}(id=${ticket.id()})}" and the link text "Visa", adding a
CSS class like "btn" for button styling) or keep a <button type="button"> and
wire navigation via th:onclick (using the same URL expression with ticket.id()).
Locate the element with th:href and ticket.id() in the template and remove the
nested structure so only one interactive element (anchor or button) remains.

---

Outside diff comments:
In
`@src/main/java/org/group1/projectbackend/controller/web/DocumentViewController.java`:
- Around line 27-41: The deleteDocument handler currently calls
principal.getName() inside deleteDocument and masks null-principal NPEs in a
broad RuntimeException catch; add an explicit null check for the Principal at
the start of the deleteDocument method (or enforce authentication via an
annotation like `@PreAuthorize/`@Secured on the controller/method) and handle the
null case by setting an appropriate redirectAttributes.documentError (e.g.,
"Authentication required") and returning the redirect to /documents (or forward
to login) before calling documentService.deleteDocument; ensure you still catch
and log other RuntimeExceptions separately so auth-related nulls are not
swallowed.

In `@src/main/java/org/group1/projectbackend/service/CommentService.java`:
- Around line 38-96: The service currently throws raw RuntimeException when
entities are missing; update createComment, getCommentsBySupportTicketId,
deleteComment, and updateComment to throw ResourceNotFoundException instead of
RuntimeException for user/supportTicket/comment lookups (the findById(...)
orElseThrow(...) calls in those methods), preserving the same messages (e.g.,
"User not found with id: ...", "Support ticket not found with id: ...", "Comment
not found with id: ..."); ensure you import ResourceNotFoundException and keep
the logActivitySafely calls unchanged so controllers return 404s consistently.
- Around line 80-106: The deleteComment and updateComment methods should be
annotated with `@Transactional` so the Hibernate session remains open when
logActivitySafely accesses lazy associations (comment.getUser().getId(),
comment.getTicket().getId()); add `@Transactional` above both methods (or at the
class level) and ensure the import for
org.springframework.transaction.annotation.Transactional is present so
repository operations and subsequent lazy access occur within a transactional
context.

---

Nitpick comments:
In
`@src/main/java/org/group1/projectbackend/controller/web/ActivityLogViewController.java`:
- Around line 67-70: The matchesTicketId method currently uses
String.valueOf(activityLog.getSupportTicketId()).contains(expected) which causes
false positives; change it to attempt parsing the expected string to a Long
(e.g., using Long.parseLong(expected)) and compare for equality with
activityLog.getSupportTicketId() (via ActivityLogDto.getSupportTicketId()); if
parsing throws/ fails, fall back to the previous substring behavior but applied
to the ticket title (e.g.,
ActivityLogDto.getSupportTicketTitle().contains(expected)) or to a safe string
conversion only as a fallback so numeric ID matching is exact while non-numeric
queries still allow title substring matching.
- Around line 34-43: The controller currently loads all logs via
activityLogService.getAllActivityLogs() and applies stream-based
sorting/filtering which causes poor scalability and LazyInitializationException;
move this logic into the data layer by adding repository-level query methods
(e.g., in ActivityLogRepository) that accept a JPA Specification or derived
parameters and return results with Sort/Pageable (use
Sort.by("createdAt").descending() and Pageable for pagination). Ensure the
repository query uses JOIN FETCH or an `@EntityGraph` to eagerly fetch user and
supportTicket to avoid lazy-load outside a transaction, add a service method
like findActivityLogs(Specification<ActivityLog>, Pageable) that the
ActivityLogViewController calls with built Specifications for type, username,
ticket and date, and return the sorted/paged list instead of filtering in the
controller.

In
`@src/main/java/org/group1/projectbackend/controller/web/TicketViewController.java`:
- Line 61: Replace the "desc"/"asc" magic string usage in TicketViewController's
call to activityLogService.getActivityLogsBySupportTicketId(id, "desc") by
switching to a typed enum or Spring's Sort.Direction: update the
activityLogService method signature (e.g., getActivityLogsBySupportTicketId(Long
id, Sort.Direction direction) or use your own SortDirection enum) and change the
controller call to pass Sort.Direction.DESC (or SortDirection.DESC) instead of
the literal "desc"; update any downstream usages and converters/parsers
accordingly so the service and controller use the enum type rather than
free-form strings.

In `@src/main/java/org/group1/projectbackend/dto/activitylog/ActivityLogDto.java`:
- Around line 17-24: The positional AllArgsConstructor for ActivityLogDto
changed when fields (username, ticketTitle, field order) were modified; to avoid
callers breaking on constructor argument order, add Lombok's `@Builder` to the
ActivityLogDto class (in addition to keeping existing constructors/annotations
like `@AllArgsConstructor/`@NoArgsConstructor) and update
callers/tests/serializers that currently call the all-args constructor to use
ActivityLogDto.builder().username(...).ticketTitle(...).activityType(...).description(...).createdAt(...).supportTicketId(...)
so future field reordering/additions won't break positional construction.

In `@src/main/java/org/group1/projectbackend/service/ActivityLogService.java`:
- Around line 67-91: Add the same existence check used in
getActivityLogsBySupportTicketId to getActivityLogsByUserId: call
userRepository.existsById(userId) before building the Sort and querying
activityLogRepository; if it returns false throw new
ResourceNotFoundException("User not found with id: " + userId) so the API
consistently signals a missing user rather than returning an empty list. Ensure
you reference getActivityLogsByUserId, userRepository.existsById, and
ResourceNotFoundException when making the change and keep the existing sort
logic intact.

In `@src/main/java/org/group1/projectbackend/service/DocumentService.java`:
- Around line 10-18: The interface has an inconsistent user identity contract:
DocumentService.uploadDocument(Long uploadedByUserId, ...) accepts a
client-supplied user ID while deleteDocument(String username, ...) uses
server-resolved username; change uploadDocument to accept String username
instead of Long uploadedByUserId so both methods resolve the acting user
server-side. Update the DocumentService interface signature for uploadDocument,
then update all implementing classes (e.g., DocumentServiceImpl.uploadDocument)
and callers (controllers that currently pass uploadedByUserId) to pass
Principal.getName() or the server-resolved username; adjust unit/integration
tests and any DTO mapping that referenced uploadedByUserId accordingly. Ensure
method name and parameter order remain otherwise unchanged
(DocumentService.uploadDocument(String username, MultipartFile file, Long
ticketId) or matching existing parameter order) so callers compile. Make sure to
remove or deprecate the old uploadedByUserId usage to prevent duplicate identity
inputs.

In
`@src/main/java/org/group1/projectbackend/service/impl/SupportTicketServiceImpl.java`:
- Around line 102-113: Replace the System.err.println in
SupportTicketServiceImpl.logActivitySafely with an SLF4J logger call and
preserve the exception (e.g., logger.warn("Failed to create activity log for
ticketId={}, userId={}", ticketId, userId, ex)); then extract the duplicated
logic from SupportTicketServiceImpl.logActivitySafely and
CommentService.logActivitySafely into a single reusable component/class (e.g.,
SafeActivityLogger) that wraps ActivityLogService and exposes a
logSafely(ActivityType, String, Long userId, Long ticketId) method; update both
services to inject and use SafeActivityLogger instead of their private helpers
so logging uses SLF4J and the helper is not duplicated.

In `@src/main/resources/templates/activitylogs/list.html`:
- Around line 29-34: Remove the static placeholder text "TICKET_CREATED" inside
the <option> element since th:text="${activityType.name()}" will render the
option label at runtime; update the template block using th:each, th:value,
th:text and th:selected (the option with th:each="activityType :
${activityTypes}" and th:value/th:text/th:selected attributes) to have no inner
literal content so design-time previews don't show misleading static text.
- Around line 74-84: The template's comma/separator logic for showing active
filters (using selectedType, selectedUsername, selectedTicket, selectedDate in
list.html) is brittle and duplicated; instead prepare an ordered list of active
filter labels in the controller (e.g., create List<String> activeFilters from
the same selection logic) or build the list in Thymeleaf and join with a utility
(e.g., `#strings.listJoin`(activeFilters, ', ')), then render a single span that
prints the joined string (remove the repeated span/separator blocks). Ensure the
controller or template produces labels like "Typ: X", "Användare: Y", "Ticket:
Z", "Datum: D" and pass them as the variable used by the join.

In `@src/main/resources/templates/tickets/list.html`:
- Around line 39-42: Replace the server-side substring truncation in the table
cell (the expression using `#strings.substring` and the conditional) with a plain
full title rendering (keep th:title="${ticket.title()}" and set
th:text="${ticket.title()}"), and add a CSS class (e.g. class="truncate") to
that <td> so truncation is handled in CSS; then implement the CSS rules for
.truncate: max-width (or width), white-space: nowrap, overflow: hidden, and
text-overflow: ellipsis so the displayed title is visually truncated without
cutting surrogate pairs and the full text remains selectable via the title
attribute.

In
`@src/test/java/org/group1/projectbackend/controller/CommentControllerTest.java`:
- Line 27: Replace the fully-qualified field type with an import to match the
file's style: add an import for CommentService and change the field declaration
in CommentControllerTest from "private
org.group1.projectbackend.service.CommentService commentService;" to use the
simple type "CommentService" so the class uses consistent imports and improves
readability.

In
`@src/test/java/org/group1/projectbackend/controller/DocumentControllerTest.java`:
- Around line 167-173: Update the test shouldDeleteDocument to assert the
controller forwards the authenticated principal to the service: replace the
loose doNothing/mock verification that uses any() for the username with a strict
verify call on documentService.deleteDocument(eq("user"), eq(100L)) (the default
`@WithMockUser` username is "user") so the test fails if the controller passes
null or a hard-coded value; keep the existing
mockMvc.perform(delete("/api/documents/100").with(csrf())) and status assertion
but add the verify(documentService).deleteDocument(...) after the request to
enforce the principal contract.

In `@src/test/java/org/group1/projectbackend/service/CommentServiceTest.java`:
- Line 49: The field declaration uses a redundant fully-qualified name; change
the field declaration "private org.group1.projectbackend.service.CommentService
commentService;" to use the simple type name "CommentService" (i.e., "private
CommentService commentService;") to match the test's package and remove visual
noise while keeping the same symbol name commentService.

In `@src/test/java/org/group1/projectbackend/service/DocumentServiceTest.java`:
- Around line 123-128: Add negative-path unit tests that verify
DocumentServiceImpl.logActivitySafely swallows runtime exceptions from
ActivityLogService: stub activityLogService.createActivityLog(...) to throw a
RuntimeException and then call uploadDocument (and a separate test for
deleteDocument) and assert the primary operation still completes successfully
(document persisted or deleted) and storage interactions (e.g.,
storageClient.putObject or storageClient.deleteObject) are invoked as expected;
also verify no exception escapes and that activity logging was attempted (verify
createActivityLog was called) even though it threw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1303e569-4096-4f40-ace8-e237cde69b11

📥 Commits

Reviewing files that changed from the base of the PR and between de97871 and e20f352.

📒 Files selected for processing (29)
  • src/main/java/org/group1/projectbackend/controller/ActivityLogController.java
  • src/main/java/org/group1/projectbackend/controller/DocumentController.java
  • src/main/java/org/group1/projectbackend/controller/SupportTicketController.java
  • src/main/java/org/group1/projectbackend/controller/web/ActivityLogViewController.java
  • src/main/java/org/group1/projectbackend/controller/web/DocumentViewController.java
  • src/main/java/org/group1/projectbackend/controller/web/TicketViewController.java
  • src/main/java/org/group1/projectbackend/dto/activitylog/ActivityLogDto.java
  • src/main/java/org/group1/projectbackend/mapper/ActivityLogMapper.java
  • src/main/java/org/group1/projectbackend/service/ActivityLogService.java
  • src/main/java/org/group1/projectbackend/service/CommentService.java
  • src/main/java/org/group1/projectbackend/service/DocumentService.java
  • src/main/java/org/group1/projectbackend/service/SupportTicketService.java
  • src/main/java/org/group1/projectbackend/service/impl/DocumentServiceImpl.java
  • src/main/java/org/group1/projectbackend/service/impl/SupportTicketServiceImpl.java
  • src/main/resources/static/css/app.css
  • src/main/resources/templates/activitylogs/list.html
  • src/main/resources/templates/documents/list.html
  • src/main/resources/templates/fragments/layout.html
  • src/main/resources/templates/tickets/detail.html
  • src/main/resources/templates/tickets/list.html
  • src/test/java/org/group1/projectbackend/controller/ActivityLogControllerTest.java
  • src/test/java/org/group1/projectbackend/controller/CommentControllerTest.java
  • src/test/java/org/group1/projectbackend/controller/DocumentControllerTest.java
  • src/test/java/org/group1/projectbackend/controller/SupportTicketControllerTest.java
  • src/test/java/org/group1/projectbackend/mapper/ActivityLogMapperTest.java
  • src/test/java/org/group1/projectbackend/service/ActivityLogServiceTest.java
  • src/test/java/org/group1/projectbackend/service/CommentServiceTest.java
  • src/test/java/org/group1/projectbackend/service/DocumentServiceTest.java
  • src/test/java/org/group1/projectbackend/service/SupportTicketServiceTest.java

Comment thread src/main/java/org/group1/projectbackend/service/CommentService.java
Comment thread src/main/resources/templates/fragments/layout.html
Comment thread src/main/resources/templates/tickets/list.html
@VonAdamo
VonAdamo merged commit 1e3ae2c into main Apr 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant