Skip to content

Feature/extend logger to user activity#42

Merged
Martin-E-Karlsson merged 10 commits into
mainfrom
feature/extend-logger-to-user-activity
Apr 23, 2026
Merged

Feature/extend logger to user activity#42
Martin-E-Karlsson merged 10 commits into
mainfrom
feature/extend-logger-to-user-activity

Conversation

@Martin-E-Karlsson

@Martin-E-Karlsson Martin-E-Karlsson commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Audit logging split into separate Visa and User logs; sysadmin dashboard shows distinct "Visa Log" and "User Log" sections.
    • Sysadmin can update another user's authorization via a dedicated endpoint; profile edit shows authorization controls only to authorized users.
    • Audit entries now record actor vs. affected user for clearer trails.
  • Bug Fixes

    • Profile edit preserves authorization selection and repopulates form after validation errors.
    • Access-denied errors now render a user-friendly 403 error page.

- Created UserLogDTO, UserEventType, UserLog, UserLogMapper, UserLogRepository, UserLogService by mirroring the existing AuditLog files
- Renamed all AuditLog files to VisaLog to differentiate from UserLog
- Updated UserService to record UserLog events

Fix:
- Renamed all usages of AuditLog in all files to VisaLog
- Fixed tests failing because of AuditLog mentions or not taking UserService changes into consideration.
- UserLogMapper
- UserLogService
- VisaLogMapper
- VisaLogService
@Martin-E-Karlsson Martin-E-Karlsson linked an issue Apr 22, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8eaa6261-df17-46ac-ac4a-fdc1b39ae09b

📥 Commits

Reviewing files that changed from the base of the PR and between bcc70f8 and 543276f.

📒 Files selected for processing (6)
  • src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java
  • src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java
  • src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java
  • src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceIntegrationTest.java
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java
  • src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java
  • src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java
  • src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java

📝 Walkthrough

Walkthrough

Split the generic audit subsystem into two domain-specific paths: user logs (UserLog/UserEventType) and visa logs (VisaLog/VisaEventType). Adds entities, DTOs, mappers, repositories, services, controller endpoints, template updates, tests, and an AccessDeniedException handler. Mutating APIs now accept an explicit actor userId.

Changes

Cohort / File(s) Summary
Enums
src/main/java/.../audit/UserEventType.java, src/main/java/.../audit/VisaEventType.java
Added UserEventType; renamed AuditEventTypeVisaEventType (constants preserved).
DTOs
src/main/java/.../audit/dto/UserLogDTO.java, src/main/java/.../audit/dto/VisaLogDTO.java
Added UserLogDTO; renamed AuditDTOVisaLogDTO and switched event field to VisaEventType.
Entities
src/main/java/.../audit/entity/UserLog.java, src/main/java/.../audit/entity/VisaLog.java
Added UserLog entity; AuditLogVisaLog with column/name updates and enum field type change to VisaEventType. Review JPA column names and auditing annotations.
Mappers
src/main/java/.../audit/mapper/UserLogMapper.java, src/main/java/.../audit/mapper/VisaLogMapper.java, src/main/java/.../audit/mapper/*
Removed generic AuditMapper; added UserLogMapper and VisaLogMapper. Pay attention to null handling and field mappings.
Repositories
src/main/java/.../audit/repository/UserLogRepository.java, src/main/java/.../audit/repository/VisaLogRepository.java
Replaced AuditRepository with UserLogRepository; added VisaLogRepository. Confirm DI points and repository types in callers.
Services
src/main/java/.../audit/service/UserLogService.java, src/main/java/.../audit/service/VisaLogService.java, src/main/java/.../audit/service/*
Removed AuditService; added UserLogService and VisaLogService. New services are used by several callers—check constructor injections and transactional boundaries.
Callers / Domain services
src/main/java/.../user/service/UserService.java, src/main/java/.../visa/service/VisaService.java, src/main/java/.../comment/service/CommentService.java
Swapped audit dependency to domain services; method signatures now include actor userId; logging calls updated to domain event types. Update all call sites and tests to match new signatures.
Controllers & Views
src/main/java/.../user/controller/UserViewController.java, src/main/resources/templates/dashboard/sysadmin.html, src/main/resources/templates/profile/edit.html
Controller DI updated to new services; added POST endpoint for authorization changes; templates now render visaLogs and userLogs and conditional authorization UI. Verify permission checks and model population paths.
Exception Handling
src/main/java/.../exception/GlobalExceptionHandler.java
Added handler for AccessDeniedException returning 403 and standard error view.
Tests
src/test/java/.../audit/..., src/test/java/.../user/..., src/test/java/.../visa/...
Added mapper/service tests for new log types; updated many unit/integration/controller tests to new service signatures, event types, and actor parameters. Check test coverage for controller authorization flows.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Controller as UserViewController
    participant Service as UserService
    participant LogService as UserLogService
    participant Mapper as UserLogMapper
    participant Repo as UserLogRepository
    participant DB as Database

    Client->>Controller: POST /profile/edit/{userId}/authorization
    Controller->>Service: updateUserAuthorization(actorUserId, targetUserId, newAuth)
    Service->>Service: validate & persist user (UserRepository)
    Service->>LogService: createUserLog(actorUserId, targetUserId, AUTHORIZATION_CHANGED, desc)
    LogService->>Mapper: toEntity(actorUserId, targetUserId, UserEventType, desc)
    Mapper-->>LogService: UserLog entity
    LogService->>Repo: save(UserLog)
    Repo->>DB: INSERT user_log
    DB-->>Repo: OK
    Repo-->>LogService: saved UserLog
    LogService-->>Service: completed
    Service-->>Controller: redirect/view
    Controller-->>Client: HTTP 302 / updated page
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • eeebbaandersson
  • EdvinSandgren
  • dinbah18

"I nibble logs and hop with glee,
Two trails now split for you and me,
Mappers, repos, services set—
Each event caught and safely kept,
A rabbit cheers: recorded neatly!" 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: extending audit logging to track user activity by introducing UserEventType, UserLog entity/DTO, UserLogService, and related components alongside refactored visa logging.
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/extend-logger-to-user-activity

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: 3

Caution

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

⚠️ Outside diff range comments (2)
src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java (1)

161-170: ⚠️ Potential issue | 🟠 Major

Sysadmin dashboard omits user-activity logs.

sysAdminDashboard exposes only visaLogs in the model. Given this PR introduces UserLogService specifically for user-activity auditing, consider also injecting UserLogService and adding model.addAttribute("userLogs", userLogService.findAll()) (and surfacing them in dashboard/sysadmin.html). Otherwise, user audit logs are written but never viewable, making the new infrastructure effectively dark. (Downstream impact also noted in dashboard/sysadmin.html.)

♻️ Sketch of the change
     private final VisaService visaService;
     private final UserService userService;
     private final VisaLogService visaLogService;
+    private final UserLogService userLogService;

     public UserViewController(VisaService visaService,
                               UserService userService,
-                              VisaLogService visaLogService) {
+                              VisaLogService visaLogService,
+                              UserLogService userLogService) {
         this.visaService = visaService;
         this.userService = userService;
         this.visaLogService = visaLogService;
+        this.userLogService = userLogService;
     }
...
         model.addAttribute("visaLogs", visaLogService.findAll());
+        model.addAttribute("userLogs", userLogService.findAll());
         return "dashboard/sysadmin";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java`
around lines 161 - 170, The sysAdminDashboard method currently only exposes
visaLogs; inject the new UserLogService into the controller (e.g., add a
constructor or field for UserLogService) and in sysAdminDashboard(...) call
model.addAttribute("userLogs", userLogService.findAll()) so user activity audit
records are available to the view (also ensure dashboard/sysadmin.html is
updated to render the userLogs). Locate the sysAdminDashboard method and the
controller class to add the UserLogService dependency and the model attribute.
src/main/resources/templates/dashboard/sysadmin.html (1)

188-229: ⚠️ Potential issue | 🟠 Major

User activity logs are not surfaced on the sysadmin dashboard.

The PR splits the monolithic audit log into UserLog and VisaLog, but the sysadmin dashboard now only displays visaLogs. User-activity events written through UserLogService (created/updated/deleted users, authorization changes) have no UI, which partially defeats the "extend logger to user activity" goal. Consider adding a sibling "User Log" section iterating over a userLogs attribute populated from userLogService.findAll() in UserViewController.sysAdminDashboard.

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

In `@src/main/resources/templates/dashboard/sysadmin.html` around lines 188 - 229,
The dashboard only renders visaLogs; add a parallel "User Log" section and wire
the controller to supply userLogs so user activity appears. In
UserViewController.sysAdminDashboard call UserLogService.findAll() (or
equivalent method) and add the returned list to the model as "userLogs"; then
update the sysadmin.html template to include a new container similar to the Visa
Log block that iterates th:each="log : ${userLogs}" and renders fields from
UserLog (e.g., log.timeStamp(), log.userId(), log.eventType(),
log.description()) to match the existing table structure. Ensure the new section
uses the same empty-state logic and pluralized row-count expression as the Visa
Log.
🧹 Nitpick comments (5)
src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceIntegrationTest.java (1)

56-56: Tighten this audit-log assertion.

isNotEmpty() would still pass if the service wrote the wrong visa event, user, or case. Since this test covers the new visa-audit path, assert the CREATED log belongs to the saved visa and applicant.

Proposed test assertion
-        assertThat(visaLogService.findAll()).isNotEmpty();
+        var logs = visaLogService.findAll();
+        Visa savedVisa = savedVisas.get(0);
+        assertThat(logs).anyMatch(log ->
+                savedVisa.getId().equals(log.visaCaseId()) &&
+                        user.getId().equals(log.userId()) &&
+                        log.visaEventType() == VisaEventType.CREATED
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceIntegrationTest.java`
at line 56, Replace the loose isNotEmpty() assertion with one that verifies the
audit log contains a CREATED entry for the visa we saved and the applicant who
acted: query visaLogService.findAll() and assert it containsAnyMatch/log entry
where log.getEvent() == CREATED (or Event.CREATED),
log.getSubjectId().equals(savedVisa.getId()) and
log.getActorId().equals(savedApplicant.getId()) (or equivalent getter names used
in the test), so the test ensures the correct event, subject (visa id) and actor
(applicant/user id) are recorded.
src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java (1)

58-111: Consider asserting audit-log side effects.

Since the whole point of this PR is to record user activity, these integration tests for updateUser, updateUserAuthorization, and deleteUser are an ideal place to assert that a corresponding UserLog row is persisted (e.g., autowire UserLogRepository and check count + userEventType/actorUserId/targetUserId). Right now the audit side effect is completely untested at the integration layer.

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

In
`@src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java`
around lines 58 - 111, Add assertions that the audit UserLog row is persisted
for each integration test: autowire UserLogRepository into
UserServiceIntegrationTest, record the initial count (or query for logs for
targetUserId) before calling userService methods in
updateUser_shouldUpdateUserFields_WhenDataIsValid,
updateUserAuthorization_shouldChangeAuthorization_WhenRequestedBySysAdmin, and
deleteUser_shouldRemoveUser_WhenRequestedBySysAdmin, then after the Act assert
the repository contains one new UserLog with the expected userEventType (e.g.,
UPDATE_PROFILE / UPDATE_AUTHORIZATION / DELETE_USER), actorUserId equals the
acting user's id (user.getId() or sysAdmin.getId()), and targetUserId equals the
affected user's id (user.getId() or targetUser.getId()); prefer querying the
latest log for the target user and assert its fields rather than relying solely
on total count.
src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java (1)

66-72: Consider adding a dedicated COMMENT_ADDED event type to VisaEventType if comment events warrant distinct audit logging.

Using VisaEventType.UPDATED for comment creation conflates it with actual visa-status updates, making the audit log less precise. The VisaEventType enum currently has no comment-specific event type (only CREATED, ASSIGNED, UPDATED, DELETED, GRANTED, REJECTED), so adding COMMENT_ADDED would require enum changes. If comment logging should be tracked separately from visa updates, this refactoring is worth considering; otherwise, the current approach using UPDATED is acceptable.

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

In
`@src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java`
around lines 66 - 72, Change the audit event for comment creation to a dedicated
event type: add COMMENT_ADDED to the VisaEventType enum and update the call in
CommentService where visaLogService.createVisaLog(...) is invoked (the block
that currently passes VisaEventType.UPDATED with message "Comment added by ...")
to use VisaEventType.COMMENT_ADDED instead; ensure any switch/case or
serialization that handles VisaEventType is updated to recognize COMMENT_ADDED
and add a migration or tests if enum persistence is used.
src/main/java/org/example/visacasemanagementsystem/audit/service/UserLogService.java (1)

12-33: Same @Transactional suggestion as VisaLogService.

Apply the same explicit transactional boundaries here (@Transactional on createUserLog, @Transactional(readOnly = true) on findAll) for consistency and clarity across the two audit services.

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

In
`@src/main/java/org/example/visacasemanagementsystem/audit/service/UserLogService.java`
around lines 12 - 33, Add explicit transactional boundaries to UserLogService by
annotating createUserLog with `@Transactional` and findAll with
`@Transactional`(readOnly = true); modify the UserLogService class to import and
apply these annotations to the corresponding methods (createUserLog and findAll)
to match the VisaLogService pattern and ensure consistent transaction semantics.
src/main/java/org/example/visacasemanagementsystem/audit/service/VisaLogService.java (1)

12-33: Consider adding explicit @Transactional boundaries.

createVisaLog performs a write and findAll is read-only, yet the service has no transactional annotations. Relying on SimpleJpaRepository's internal transactions works, but explicit boundaries are clearer and ensure that when callers don't supply a transaction, behavior is predictable (and findAll benefits from readOnly=true).

♻️ Proposed refactor
 `@Service`
 public class VisaLogService {
@@
-    public void createVisaLog(Long userId, Long visaCaseId, VisaEventType visaEventType, String description) {
+    `@Transactional`
+    public void createVisaLog(Long userId, Long visaCaseId, VisaEventType visaEventType, String description) {
         VisaLog visaLog = visaLogMapper.toEntity(userId, visaCaseId, visaEventType, description);
         visaLogRepository.save(visaLog);
     }
 
-    public List<VisaLogDTO> findAll() {
+    `@Transactional`(readOnly = true)
+    public List<VisaLogDTO> findAll() {
         return visaLogRepository.findAll()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/audit/service/VisaLogService.java`
around lines 12 - 33, Add explicit transactional boundaries to VisaLogService:
annotate createVisaLog with `@Transactional` (default readOnly=false) and annotate
findAll with `@Transactional`(readOnly = true); ensure the
org.springframework.transaction.annotation.Transactional import is added and put
the annotations on the methods (or annotate the class with `@Transactional` and
override readOnly=true on findAll) so writes go through an explicit transaction
and reads use a read-only transaction.
🤖 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/example/visacasemanagementsystem/audit/entity/UserLog.java`:
- Around line 32-41: The Spotless formatting check fails because the `@NotNull`
annotations for the fields actorUserId and targetUserId in the UserLog entity
are on their own lines; update the declarations in class UserLog so the
annotations are inline with the fields (e.g., change from "@NotNull" on a
separate line to "@NotNull private Long actorUserId;" and similarly for
targetUserId), matching the style used in VisaLog; you can run mvn
spotless:apply afterwards to verify formatting.

In
`@src/main/java/org/example/visacasemanagementsystem/user/service/UserService.java`:
- Around line 109-121: The updateUserAuthorization method must validate the
incoming newAuth before modifying or saving the entity and before creating the
audit log; check that newAuth is not null (and optionally a supported enum
value) at the start of updateUserAuthorization, throw a suitable exception
(e.g., IllegalArgumentException or a validation exception) if invalid, and only
then call user.setUserAuthorization(newAuth), userRepository.save(user), and
userLogService.createUserLog (so the savedUser and log never record a null or
invalid authorization).
- Around line 69-82: The catch block around userRepository.save currently also
wraps the subsequent userLogService.createUserLog call so any audit failures are
misreported as duplicate-email; update UserService to only catch
DataIntegrityViolationException from the persistence/save step
(userRepository.save) and let userLogService.createUserLog run outside that try
or be handled by its own try/catch that logs audit errors without rethrowing as
IllegalArgumentException. Specifically, isolate the save into its own try that
throws the "A user with this email already exists" IllegalArgumentException on
DataIntegrityViolationException, then call
userLogService.createUserLog(savedUser.getId(), savedUser.getId(),
UserEventType.CREATED, ...) afterward (or catch/log audit exceptions separately)
and finally return userMapper.toDTO(savedUser).

---

Outside diff comments:
In
`@src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java`:
- Around line 161-170: The sysAdminDashboard method currently only exposes
visaLogs; inject the new UserLogService into the controller (e.g., add a
constructor or field for UserLogService) and in sysAdminDashboard(...) call
model.addAttribute("userLogs", userLogService.findAll()) so user activity audit
records are available to the view (also ensure dashboard/sysadmin.html is
updated to render the userLogs). Locate the sysAdminDashboard method and the
controller class to add the UserLogService dependency and the model attribute.

In `@src/main/resources/templates/dashboard/sysadmin.html`:
- Around line 188-229: The dashboard only renders visaLogs; add a parallel "User
Log" section and wire the controller to supply userLogs so user activity
appears. In UserViewController.sysAdminDashboard call UserLogService.findAll()
(or equivalent method) and add the returned list to the model as "userLogs";
then update the sysadmin.html template to include a new container similar to the
Visa Log block that iterates th:each="log : ${userLogs}" and renders fields from
UserLog (e.g., log.timeStamp(), log.userId(), log.eventType(),
log.description()) to match the existing table structure. Ensure the new section
uses the same empty-state logic and pluralized row-count expression as the Visa
Log.

---

Nitpick comments:
In
`@src/main/java/org/example/visacasemanagementsystem/audit/service/UserLogService.java`:
- Around line 12-33: Add explicit transactional boundaries to UserLogService by
annotating createUserLog with `@Transactional` and findAll with
`@Transactional`(readOnly = true); modify the UserLogService class to import and
apply these annotations to the corresponding methods (createUserLog and findAll)
to match the VisaLogService pattern and ensure consistent transaction semantics.

In
`@src/main/java/org/example/visacasemanagementsystem/audit/service/VisaLogService.java`:
- Around line 12-33: Add explicit transactional boundaries to VisaLogService:
annotate createVisaLog with `@Transactional` (default readOnly=false) and annotate
findAll with `@Transactional`(readOnly = true); ensure the
org.springframework.transaction.annotation.Transactional import is added and put
the annotations on the methods (or annotate the class with `@Transactional` and
override readOnly=true on findAll) so writes go through an explicit transaction
and reads use a read-only transaction.

In
`@src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java`:
- Around line 66-72: Change the audit event for comment creation to a dedicated
event type: add COMMENT_ADDED to the VisaEventType enum and update the call in
CommentService where visaLogService.createVisaLog(...) is invoked (the block
that currently passes VisaEventType.UPDATED with message "Comment added by ...")
to use VisaEventType.COMMENT_ADDED instead; ensure any switch/case or
serialization that handles VisaEventType is updated to recognize COMMENT_ADDED
and add a migration or tests if enum persistence is used.

In
`@src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java`:
- Around line 58-111: Add assertions that the audit UserLog row is persisted for
each integration test: autowire UserLogRepository into
UserServiceIntegrationTest, record the initial count (or query for logs for
targetUserId) before calling userService methods in
updateUser_shouldUpdateUserFields_WhenDataIsValid,
updateUserAuthorization_shouldChangeAuthorization_WhenRequestedBySysAdmin, and
deleteUser_shouldRemoveUser_WhenRequestedBySysAdmin, then after the Act assert
the repository contains one new UserLog with the expected userEventType (e.g.,
UPDATE_PROFILE / UPDATE_AUTHORIZATION / DELETE_USER), actorUserId equals the
acting user's id (user.getId() or sysAdmin.getId()), and targetUserId equals the
affected user's id (user.getId() or targetUser.getId()); prefer querying the
latest log for the target user and assert its fields rather than relying solely
on total count.

In
`@src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceIntegrationTest.java`:
- Line 56: Replace the loose isNotEmpty() assertion with one that verifies the
audit log contains a CREATED entry for the visa we saved and the applicant who
acted: query visaLogService.findAll() and assert it containsAnyMatch/log entry
where log.getEvent() == CREATED (or Event.CREATED),
log.getSubjectId().equals(savedVisa.getId()) and
log.getActorId().equals(savedApplicant.getId()) (or equivalent getter names used
in the test), so the test ensures the correct event, subject (visa id) and actor
(applicant/user id) are recorded.
🪄 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: 19c5de37-c089-4b51-bd50-441059bb9e0f

📥 Commits

Reviewing files that changed from the base of the PR and between 14a5c60 and a82cb16.

📒 Files selected for processing (28)
  • src/main/java/org/example/visacasemanagementsystem/audit/UserEventType.java
  • src/main/java/org/example/visacasemanagementsystem/audit/VisaEventType.java
  • src/main/java/org/example/visacasemanagementsystem/audit/dto/UserLogDTO.java
  • src/main/java/org/example/visacasemanagementsystem/audit/dto/VisaLogDTO.java
  • src/main/java/org/example/visacasemanagementsystem/audit/entity/UserLog.java
  • src/main/java/org/example/visacasemanagementsystem/audit/entity/VisaLog.java
  • src/main/java/org/example/visacasemanagementsystem/audit/mapper/AuditMapper.java
  • src/main/java/org/example/visacasemanagementsystem/audit/mapper/UserLogMapper.java
  • src/main/java/org/example/visacasemanagementsystem/audit/mapper/VisaLogMapper.java
  • src/main/java/org/example/visacasemanagementsystem/audit/repository/UserLogRepository.java
  • src/main/java/org/example/visacasemanagementsystem/audit/repository/VisaLogRepository.java
  • src/main/java/org/example/visacasemanagementsystem/audit/service/AuditService.java
  • src/main/java/org/example/visacasemanagementsystem/audit/service/UserLogService.java
  • src/main/java/org/example/visacasemanagementsystem/audit/service/VisaLogService.java
  • src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java
  • src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java
  • src/main/java/org/example/visacasemanagementsystem/user/service/UserService.java
  • src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java
  • src/main/resources/templates/dashboard/sysadmin.html
  • src/test/java/org/example/visacasemanagementsystem/audit/mapper/UserLogMapperTest.java
  • src/test/java/org/example/visacasemanagementsystem/audit/mapper/VisaLogMapperTest.java
  • src/test/java/org/example/visacasemanagementsystem/audit/service/UserLogServiceTest.java
  • src/test/java/org/example/visacasemanagementsystem/audit/service/VisaLogServiceTest.java
  • src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java
  • src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceIntegrationTest.java
  • src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceIntegrationTest.java
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceTest.java
💤 Files with no reviewable changes (2)
  • src/main/java/org/example/visacasemanagementsystem/audit/mapper/AuditMapper.java
  • src/main/java/org/example/visacasemanagementsystem/audit/service/AuditService.java

Comment thread src/main/java/org/example/visacasemanagementsystem/audit/entity/UserLog.java Outdated

@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: 2

🧹 Nitpick comments (5)
src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java (1)

59-65: Consider asserting no audit log is emitted on failed operations.

These failure-path tests verify exceptions, but they don’t protect against accidental userLogService.createUserLog(...) calls before validation, lookup, or persistence succeeds.

🧪 Suggested test hardening
 assertThatThrownBy(() -> userService.createUser(dto))
         .isInstanceOf(IllegalArgumentException.class)
         .hasMessageContaining("email already exists");
+verifyNoInteractions(userLogService);

Apply the same pattern to the other failure-path tests where the operation should not create an audit record.

Also applies to: 82-84, 128-130, 148-150, 191-193, 241-243

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

In
`@src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java`
around lines 59 - 65, The tests currently assert exceptions and repository
non-interaction but miss asserting no audit log was written; update each
failure-path test (e.g., the createUser negative-case that calls
userService.createUser(dto)) to also verify that userLogService was not invoked
by adding a Mockito assertion such as verifyNoInteractions(userLogService) or
verify(userLogService, never()).method(any()) after the exception assertion
(mirror this change for other failure-path tests that validate inputs or early
failures to ensure userLogService.createUserLog(...) is never called).
src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java (1)

159-169: Duplicated isSysAdmin computation — already computed in viewProfile.

The sysadmin-detection logic (streaming authorities and matching ROLE_SYSADMIN) appears in both viewProfile (lines 91-92) and addAuthorizationFormAttributes. Consider hoisting to a small helper on UserPrincipal (e.g., principal.isSysAdmin()) or a private static method here to keep the check in one place.

♻️ Suggested refactor
-    private void addAuthorizationFormAttributes(Model model, UserPrincipal principal, Long userId) {
-        boolean isOwnProfile = principal.getUserId().equals(userId);
-        boolean isSysAdmin = principal.getAuthorities().stream()
-                .anyMatch(a -> Objects.equals(a.getAuthority(), "ROLE_SYSADMIN"));
-        model.addAttribute("canChangeAuthorization", isSysAdmin && !isOwnProfile);
-        model.addAttribute("authorizations", UserAuthorization.values());
-    }
+    private static boolean isSysAdmin(UserPrincipal principal) {
+        return principal.getAuthorities().stream()
+                .anyMatch(a -> Objects.equals(a.getAuthority(), "ROLE_SYSADMIN"));
+    }
+
+    private void addAuthorizationFormAttributes(Model model, UserPrincipal principal, Long userId) {
+        boolean isOwnProfile = principal.getUserId().equals(userId);
+        model.addAttribute("canChangeAuthorization", isSysAdmin(principal) && !isOwnProfile);
+        model.addAttribute("authorizations", UserAuthorization.values());
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java`
around lines 159 - 169, Duplicate sysadmin check appears in
UserViewController.viewProfile and
UserViewController.addAuthorizationFormAttributes; hoist it to a single helper
to avoid duplication. Add a boolean helper like UserPrincipal.isSysAdmin()
(preferred) or a private static method in UserViewController that encapsulates
principal.getAuthorities().stream().anyMatch(a ->
Objects.equals(a.getAuthority(), "ROLE_SYSADMIN")), then replace the inline
checks in both viewProfile and addAuthorizationFormAttributes with calls to that
helper (update references to isSysAdmin variable usage accordingly).
src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java (1)

404-422: Consider also asserting the userLogs model attribute on the sysadmin dashboard test.

sysAdminDashboard now populates both visaLogs and userLogs (controller lines 211-212), and the template iterates userLogs. The updated test at lines 366-382 only stubs visaLogService.findAll() and asserts visaLogs exists. Adding a userLogService.findAll() stub and a model().attributeExists("userLogs") assertion would keep coverage aligned with the template's requirements and prevent a silent regression if userLogs is ever dropped from the model.

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

In
`@src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java`
around lines 404 - 422, Update the sysAdmin dashboard test in
UserViewControllerTest to also stub the userLogService and assert the userLogs
model attribute: add a when(userLogService.findAll()).thenReturn(...) alongside
the existing when(visaLogService.findAll()) stub, and add
.andExpect(model().attributeExists("userLogs")) to the mockMvc assertion chain
in the test method that exercises sysAdminDashboard (look for the test method
that calls "/admin" or is named sysAdminDashboard); also verify any interactions
with userLogService if other tests follow that pattern.
src/main/java/org/example/visacasemanagementsystem/exception/GlobalExceptionHandler.java (1)

32-40: Duplicated handler body — consider delegating to a shared helper.

handleAccessDeniedException is byte-identical to handleUnauthorizedException (same log message, same two model attributes, same view). Minor DRY concern; easy to let them drift over time (e.g., adding the pending dashboardUrl TODO to only one).

♻️ Suggested refactor
-    `@ResponseStatus`(value = HttpStatus.FORBIDDEN)
-    `@ExceptionHandler`(UnauthorizedException.class)
-    public String handleUnauthorizedException(UnauthorizedException exception, Model model) {
-        log.error("Access denied: {}", exception.getMessage());
-        model.addAttribute("errorMessage","You do not have permission to perform this action.");
-        model.addAttribute("errorTitle", "⚠️Access Denied.");
-        // TODO: ...
-        return "error/error";
-    }
-
-    // Handles method-level `@PreAuthorize` denials which throw AccessDeniedException
-    `@ResponseStatus`(value = HttpStatus.FORBIDDEN)
-    `@ExceptionHandler`(AccessDeniedException.class)
-    public String handleAccessDeniedException(AccessDeniedException exception, Model model) {
-        log.error("Access denied: {}", exception.getMessage());
-        model.addAttribute("errorMessage", "You do not have permission to perform this action.");
-        model.addAttribute("errorTitle", "⚠️Access Denied.");
-        return "error/error";
-    }
+    `@ResponseStatus`(value = HttpStatus.FORBIDDEN)
+    `@ExceptionHandler`({UnauthorizedException.class, AccessDeniedException.class})
+    public String handleForbidden(Exception exception, Model model) {
+        log.error("Access denied: {}", exception.getMessage());
+        model.addAttribute("errorMessage", "You do not have permission to perform this action.");
+        model.addAttribute("errorTitle", "⚠️Access Denied.");
+        return "error/error";
+    }

Worth confirming this is reachable by @ControllerAdvice at all: Spring Security's AccessDeniedException thrown by @PreAuthorize via AuthorizationManagerBeforeMethodInterceptor is normally handled by the Spring Security filter chain's ExceptionTranslationFilter / AccessDeniedHandler before @ExceptionHandler advice gets a chance, especially for filter-level denials. For method-security denials thrown inside the controller invocation, @ExceptionHandler does pick them up. Please confirm with a manual test that hitting a @PreAuthorize-denied endpoint (e.g., /dashboard/sysadmin as a USER) actually renders error/error with errorTitle = "⚠️Access Denied." rather than Spring Security's default 403 page — the existing userListView_AsNonSysAdmin_ShouldReturnForbidden test only asserts status().isForbidden() and won't catch this.

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

In
`@src/main/java/org/example/visacasemanagementsystem/exception/GlobalExceptionHandler.java`
around lines 32 - 40, Both handleAccessDeniedException and
handleUnauthorizedException in GlobalExceptionHandler have identical bodies;
extract the shared logic into a single private helper (e.g., a method like
renderErrorView(Model model, Exception e, String title, String message)) and
have both exception handlers call that helper to set the log message, model
attributes ("errorMessage", "errorTitle") and return the "error/error" view;
update handleAccessDeniedException and handleUnauthorizedException to delegate
to this helper so future changes (like adding dashboardUrl) are made in one
place and avoid drift, and after refactor run the manual test described to
confirm the ControllerAdvice handler is actually reached for method-level
AccessDeniedException.
src/main/resources/templates/dashboard/sysadmin.html (1)

192-272: Null-safety on visaLogs / userLogs lookups.

${visaLogs.isEmpty()}, ${!visaLogs.isEmpty()}, ${#lists.size(visaLogs) ...} (and the userLogs equivalents) will NPE if either attribute is ever missing/null in the model. Today sysAdminDashboard always sets both, so this is latent rather than broken — but it's cheap defensive rendering in case an error path or future caller omits them.

♻️ Suggested hardening
-        <div th:if="${visaLogs.isEmpty()}" class="empty-state">
+        <div th:if="${`#lists.isEmpty`(visaLogs)}" class="empty-state">
             No visa events recorded yet.
         </div>

-        <table th:if="${!visaLogs.isEmpty()}">
+        <table th:if="${!#lists.isEmpty(visaLogs)}">

(Same pattern for userLogs.) #lists.isEmpty(...) / #lists.size(...) handle null gracefully.

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

In `@src/main/resources/templates/dashboard/sysadmin.html` around lines 192 - 272,
The template currently calls visaLogs.isEmpty() and userLogs.isEmpty() which can
NPE if the model omits those attributes; replace those checks with the null-safe
Thymeleaf list utilities: use `#lists.isEmpty`(visaLogs) wherever
visaLogs.isEmpty() or !visaLogs.isEmpty() is used (e.g. the th:if checks for the
empty-state and the table) and do the same for userLogs (use
`#lists.isEmpty`(userLogs) and negated form for showing the table); leave the
existing `#lists.size`(visaLogs) / `#lists.size`(userLogs) usage as-is because those
are already null-safe.
🤖 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/example/visacasemanagementsystem/user/controller/UserViewController.java`:
- Around line 127-138: When rebuilding the UserDTO in UserViewController's catch
for IllegalArgumentException, guard against userService.findById(userId)
returning empty by falling back to any existing user authorization already
present in the model or request instead of null; i.e., obtain currentAuth via
userService.findById(userId).map(UserDTO::userAuthorization).orElseGet(() -> {
UserDTO existing = (UserDTO) model.getAttribute("user"); return existing != null
? existing.userAuthorization() : null; }) and then use that currentAuth when
constructing the new UserDTO and calling addAuthorizationFormAttributes so the
authorization dropdown never becomes null/empty.
- Around line 144-157: The updateAuthorization method currently lets
service-thrown IllegalArgumentException bubble to the global handler (rendering
the generic error page); catch IllegalArgumentException around the call to
userService.updateUserAuthorization in updateAuthorization and handle it the
same way updateProfile does — add a user-friendly error message (from the
exception) to RedirectAttributes or the model and return/redirect back to the
profile edit page (e.g., "redirect:/profile/edit/{userId}" or the edit view) so
validation/service errors show inline; keep the existing UnauthorizedException
self-change guard and still rethrow it unchanged.

---

Nitpick comments:
In
`@src/main/java/org/example/visacasemanagementsystem/exception/GlobalExceptionHandler.java`:
- Around line 32-40: Both handleAccessDeniedException and
handleUnauthorizedException in GlobalExceptionHandler have identical bodies;
extract the shared logic into a single private helper (e.g., a method like
renderErrorView(Model model, Exception e, String title, String message)) and
have both exception handlers call that helper to set the log message, model
attributes ("errorMessage", "errorTitle") and return the "error/error" view;
update handleAccessDeniedException and handleUnauthorizedException to delegate
to this helper so future changes (like adding dashboardUrl) are made in one
place and avoid drift, and after refactor run the manual test described to
confirm the ControllerAdvice handler is actually reached for method-level
AccessDeniedException.

In
`@src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java`:
- Around line 159-169: Duplicate sysadmin check appears in
UserViewController.viewProfile and
UserViewController.addAuthorizationFormAttributes; hoist it to a single helper
to avoid duplication. Add a boolean helper like UserPrincipal.isSysAdmin()
(preferred) or a private static method in UserViewController that encapsulates
principal.getAuthorities().stream().anyMatch(a ->
Objects.equals(a.getAuthority(), "ROLE_SYSADMIN")), then replace the inline
checks in both viewProfile and addAuthorizationFormAttributes with calls to that
helper (update references to isSysAdmin variable usage accordingly).

In `@src/main/resources/templates/dashboard/sysadmin.html`:
- Around line 192-272: The template currently calls visaLogs.isEmpty() and
userLogs.isEmpty() which can NPE if the model omits those attributes; replace
those checks with the null-safe Thymeleaf list utilities: use
`#lists.isEmpty`(visaLogs) wherever visaLogs.isEmpty() or !visaLogs.isEmpty() is
used (e.g. the th:if checks for the empty-state and the table) and do the same
for userLogs (use `#lists.isEmpty`(userLogs) and negated form for showing the
table); leave the existing `#lists.size`(visaLogs) / `#lists.size`(userLogs) usage
as-is because those are already null-safe.

In
`@src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java`:
- Around line 404-422: Update the sysAdmin dashboard test in
UserViewControllerTest to also stub the userLogService and assert the userLogs
model attribute: add a when(userLogService.findAll()).thenReturn(...) alongside
the existing when(visaLogService.findAll()) stub, and add
.andExpect(model().attributeExists("userLogs")) to the mockMvc assertion chain
in the test method that exercises sysAdminDashboard (look for the test method
that calls "/admin" or is named sysAdminDashboard); also verify any interactions
with userLogService if other tests follow that pattern.

In
`@src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java`:
- Around line 59-65: The tests currently assert exceptions and repository
non-interaction but miss asserting no audit log was written; update each
failure-path test (e.g., the createUser negative-case that calls
userService.createUser(dto)) to also verify that userLogService was not invoked
by adding a Mockito assertion such as verifyNoInteractions(userLogService) or
verify(userLogService, never()).method(any()) after the exception assertion
(mirror this change for other failure-path tests that validate inputs or early
failures to ensure userLogService.createUserLog(...) is never called).
🪄 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: 89d8793c-f38e-4ea6-9de3-7e0484edbc30

📥 Commits

Reviewing files that changed from the base of the PR and between a82cb16 and bcc70f8.

📒 Files selected for processing (8)
  • src/main/java/org/example/visacasemanagementsystem/audit/entity/UserLog.java
  • src/main/java/org/example/visacasemanagementsystem/exception/GlobalExceptionHandler.java
  • src/main/java/org/example/visacasemanagementsystem/user/controller/UserViewController.java
  • src/main/java/org/example/visacasemanagementsystem/user/service/UserService.java
  • src/main/resources/templates/dashboard/sysadmin.html
  • src/main/resources/templates/profile/edit.html
  • src/test/java/org/example/visacasemanagementsystem/user/controller/UserViewControllerTest.java
  • src/test/java/org/example/visacasemanagementsystem/user/service/UserServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/example/visacasemanagementsystem/audit/entity/UserLog.java
  • src/main/java/org/example/visacasemanagementsystem/user/service/UserService.java

…r form and page redirection on AccessDenied error
@eeebbaandersson eeebbaandersson removed a link to an issue Apr 23, 2026
…ivity

# Conflicts:
#	src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java
#	src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceIntegrationTest.java
@Martin-E-Karlsson
Martin-E-Karlsson merged commit a66d5ef into main Apr 23, 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