Skip to content

Feature/visa view controller#27

Merged
eeebbaandersson merged 22 commits into
mainfrom
feature/visa-view-controller
Apr 14, 2026
Merged

Feature/visa view controller#27
eeebbaandersson merged 22 commits into
mainfrom
feature/visa-view-controller

Conversation

@eeebbaandersson

@eeebbaandersson eeebbaandersson commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • New visa UI: dashboard, apply/edit forms, details view, and full case workflows (apply, edit, assign, approve, request info, reject)
    • Application now requires passport number and travel date; travel date validated and shown in lists/details; handler name, status notes, and audit timestamps added
    • App startup seeds demo users
  • Bug Fixes

    • Global fallback logs unexpected server errors and returns a consistent 500 response
    • Removed legacy "UNASSIGNED" status value
  • Tests

    • Added unit tests for mapping and service workflows (apply/update/admin actions)
  • Chores

    • Basic security/filter configuration added

…templates

- Added endpoints for to show and manage visa applications
- Added missing fields from Visa Entity to DTO and mapper
- Added start of thymeleaf templates to display visa cases, apply for a new visa and show further details about a case
… role-based views

- Removed UNASSIGNED from VisaStatus for improved flow
- Replaced Reject reason with statusInformation
- Added logic for admin users to request further information from applicants
…endpoint

- Added missing @PostMapping for request-info in VisaViewController
- Fixed HTML form issue where the reason parameter was not correctly sent to the server
- Updated logic to sort applications by last updated for admins
- Implemented chronological sorting for regular users
…tails

- UI: Updated Dashboard, Details and Apply-form to dark/minimal themed design.
- Added passportNumber and travelDate to Visa entity and DTOs.
- Added travel date validation in VisaService
- Implement server-side validation using @Valid and BindingResult
- Add error message handling in VisaViewController
- Map Service-layer exceptions to specific form fields
- Removed @NotNull from applicantId in CreateVisaDTO to avoid binding issues with Records and @Valid.
- Aligned Dashboard table columns.
- Created new edit-form.html to allow applicants to update their visa details.
- Added showEditForm and processUpdate to VisaViewController
- Added "Action Required" alert in details view for incomplete applications
- Integrated handler feedback directly into the edit form for better overview.
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Thymeleaf UI and MVC controller for visa workflows, extends Visa domain with passport/travelDate/statusInformation/audit timestamps, tightens DTO validation, updates mapper/repository/service logic and tests, seeds users at startup, introduces permissive security config, and adds a global exception handler with logging.

Changes

Cohort / File(s) Summary
Exception Handling
src/main/java/org/example/visacasemanagementsystem/exception/GlobalExceptionHandler.java
Added Lombok @Slf4j and a catch‑all @ExceptionHandler(Exception.class) that logs the exception and returns HTTP 500 with fixed body.
Domain & DTOs
src/main/java/org/example/visacasemanagementsystem/visa/VisaStatus.java, src/main/java/org/example/visacasemanagementsystem/visa/entity/Visa.java, src/main/java/org/example/visacasemanagementsystem/visa/dto/CreateVisaDTO.java, src/main/java/org/example/visacasemanagementsystem/visa/dto/UpdateVisaDTO.java, src/main/java/org/example/visacasemanagementsystem/visa/dto/VisaDTO.java
Removed UNASSIGNED status; added passportNumber and travelDate; replaced rejectionReason with statusInformation; added createdAt/updatedAt; tightened/added validation annotations on DTOs; update entity to use LocalDate and date format.
Controller / Views
src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java, src/main/resources/templates/visa/apply-form.html, src/main/resources/templates/visa/dashboard.html, src/main/resources/templates/visa/details.html, src/main/resources/templates/visa/edit-form.html
New VisaViewController implementing dashboard, apply/edit flows, details and state-change endpoints; four Thymeleaf templates implementing forms, validation, role-based UI and actions. Review authorization checks and form-binding for new fields.
Mapping & Repository
src/main/java/org/example/visacasemanagementsystem/visa/mapper/VisaMapper.java, src/main/java/org/example/visacasemanagementsystem/visa/repository/VisaRepository.java
Mapper now maps passportNumber, travelDate, handlerName, createdAt/updatedAt, and statusInformation; Create/Update mapping updated. Repository adds findByApplicant_Id(Long, Sort).
Business Logic
src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java
Added travel-date validation, updateVisa, findVisaDtoById, findVisasByApplicant, requestMoreInformation; updated status transitions to use statusInformation, auto-assign handler on admin actions, and sort findAll by updatedAt desc. Complex transactional and authorization logic added—high review attention.
Application Init & Security
src/main/java/org/example/visacasemanagementsystem/VisaCaseManagementSystemApplication.java, src/main/java/org/example/visacasemanagementsystem/config/SecurityConfig.java
Added CommandLineRunner to seed users at startup; added permissive SecurityFilterChain bean (all requests allowed, CSRF disabled, frame options disabled).
Tests
src/test/java/org/example/visacasemanagementsystem/visa/VisaMapperTest.java, src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceTest.java
Added unit tests for mapper mappings and extensive VisaService behavior covering apply, update, approve, reject, request-info, assign, validation and authorization scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant VisaViewController
    participant VisaService
    participant VisaRepository
    participant AuditService

    Browser->>VisaViewController: POST /visas/apply (CreateVisaDTO)
    VisaViewController->>VisaService: applyForVisa(dto, currentUserId)
    VisaService->>VisaRepository: save(Visa entity)
    VisaRepository-->>VisaService: saved Visa
    VisaService->>AuditService: record AuditEvent(SUBMITTED)
    AuditService-->>VisaService: ack
    VisaService-->>VisaViewController: VisaDTO / redirect
    VisaViewController-->>Browser: Redirect /visas/dashboard?currentUserId=...
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Issue #18 — Implements the VisaViewController and Thymeleaf templates described in the issue.

Possibly related PRs

  • PR #6 — Also modifies GlobalExceptionHandler; overlaps on exception handling changes.
  • PR #16 — Modifies GlobalExceptionHandler (additional handlers); likely related to merged handler behavior.
  • PR #1 — Adds Lombok dependency enabling use of @Slf4j used in this PR.

Suggested reviewers

  • Martin-E-Karlsson
  • dinbah18

Poem

🐰 I hopped through forms and fields with cheer,

Collected passports, dates, and kept them near,
Controllers guide, services log each claim,
Dashboards glow and templates call my name,
A rabbit cheers — the workflows hum with fame.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "Feature/visa view controller" is partially related to the changeset. While a new VisaViewController is added, the PR contains extensive changes across multiple components including DTOs, entities, services, mappers, templates, configuration, and test coverage that go significantly beyond just adding a view controller.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/visa-view-controller

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

🧹 Nitpick comments (4)
src/test/java/org/example/visacasemanagementsystem/visa/VisaMapperTest.java (1)

62-71: Add an assertion for handlerName fallback behavior.

In this test, handler is unset, so asserting dto.handlerName() equals "Unassigned" would verify the new mapper contract explicitly.

🤖 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/VisaMapperTest.java`
around lines 62 - 71, The test VisaMapperTest currently checks fields from
visaMapper.toDTO(visa) but misses asserting the handlerName fallback; update the
test to assert that dto.handlerName() equals "Unassigned" when the source Visa
has no handler set, ensuring the mapper's fallback behavior is explicitly
verified (locate the assertion block after the existing assertions on dto.id(),
dto.visaType(), etc., and add the dto.handlerName() check).
src/main/java/org/example/visacasemanagementsystem/visa/dto/CreateVisaDTO.java (1)

15-15: Consider removing applicantId from the client-facing create DTO.

Line 15 keeps applicant identity writable by clients. Since apply flow already receives server-side user context, this field is safer to derive on the backend only.

Suggested refactor
 public record CreateVisaDTO(
         `@NotNull`(message = "Visa type must be specified") VisaType visaType,
         `@NotBlank`(message = "Nationality must be specified") String nationality,
         `@NotBlank`(message = "Passport number must be provided") String passportNumber,
-        `@NotNull`(message = "Travel date must be provided") `@DateTimeFormat`(iso = DateTimeFormat.ISO.DATE) LocalDate travelDate,
-        Long applicantId
+        `@NotNull`(message = "Travel date must be provided") `@DateTimeFormat`(iso = DateTimeFormat.ISO.DATE) LocalDate travelDate
 ) {
 }

Based on learnings: In this repository, trusting client-provided identity fields (like authorId) is a known deferred security gap and should be replaced by resolving the authenticated principal server-side when possible.

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

In
`@src/main/java/org/example/visacasemanagementsystem/visa/dto/CreateVisaDTO.java`
at line 15, Remove the mutable applicant identity from the client-facing create
DTO by deleting the applicantId field from CreateVisaDTO and updating any usages
(e.g., constructors, builders, validation code) that accept or map applicantId;
ensure server-side code that processes visa creation (controller/servlet method
handling the create request and service method that accepts CreateVisaDTO)
instead resolves the applicant identity from the authenticated principal/session
and sets it onto the domain model before persisting.
src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceTest.java (1)

368-381: Test method name is misleading.

The method name validateHandler_shouldThrowEntityNotFoundException_WhenUserIsApplicant suggests it tests the applicant scenario, but it actually tests when the user doesn't exist. Consider renaming for clarity.

✏️ Suggested rename
-    void validateHandler_shouldThrowEntityNotFoundException_WhenUserIsApplicant() {
+    void validateHandler_shouldThrowEntityNotFoundException_WhenUserDoesNotExist() {
🤖 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/VisaServiceTest.java`
around lines 368 - 381, The test method
validateHandler_shouldThrowEntityNotFoundException_WhenUserIsApplicant has a
misleading name; rename it to reflect that it covers the non-existent user case
(for example
validateHandler_shouldThrowEntityNotFoundException_WhenUserNotFound) and update
any references to the method name in the test class; keep the test body (mocking
userRepository.findById(nonExistingUserId) to return Optional.empty(), asserting
EntityNotFoundException with message "User not found.", and
verifyNoInteractions(visaRepository)) unchanged.
src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java (1)

269-291: Consider validating infoText parameter in requestMoreInformation.

Unlike rejectVisa which validates that reason is not blank (line 244-246), requestMoreInformation accepts any infoText including null or empty strings. This inconsistency could lead to INCOMPLETE visas with no actionable feedback for applicants.

♻️ Add validation for consistency
     `@Transactional`
     public VisaDTO requestMoreInformation(Long visaId, Long adminId, String infoText) {
+        if (infoText == null || infoText.isBlank()) {
+            throw new IllegalArgumentException("Information request text cannot be null or blank");
+        }
         User  admin = validateHandler(adminId);
         Visa visa = findVisaById(visaId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java`
around lines 269 - 291, requestMoreInformation currently accepts null or blank
infoText which can create INCOMPLETE visas with no feedback; add a validation at
the start of requestMoreInformation to ensure infoText is non-null and not blank
(trim it and check length), and if invalid throw the same type of
validation/BadRequest exception your service uses elsewhere (mirror the check
used in rejectVisa), then proceed to call validateHandler(adminId),
findVisaById(visaId), set the handler/status/info, save via visaRepository.save,
and create the audit log as before; reference the method requestMoreInformation
and reuse the same validation style/exception as rejectVisa for consistency.
🤖 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/exception/GlobalExceptionHandler.java`:
- Around line 36-41: The handler GlobalExceptionHandler.handleAllUncaughtErrors
currently returns exception.getMessage(), which can leak internals/PII; change
it to return a generic 500 message (e.g., "Internal server error" or "An
unexpected error occurred") instead, keep the existing log.error("Unexpected
Error: ", exception) to record details, and optionally include a
correlation/error id in both the log and the response if you want traceability;
update the method return to the generic string and do not expose
exception.getMessage().

In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`:
- Around line 107-109: In VisaViewController where the authorization check
throws new UnauthorizedException("You can only edit ypur own applications."),
correct the typo in the exception message to "your" so the thrown
UnauthorizedException reads "You can only edit your own applications."; update
the string in the throw statement inside the method performing the applicantId
vs currentUserId check.
- Around line 138-151: Validation error paths don't re-add the
"statusInformation" model attribute, so update both the
bindingResult.hasErrors() branch and the IllegalArgumentException catch branch
in VisaViewController.edit flow to re-add statusInformation before returning the
view; call model.addAttribute("statusInformation",
visaService.getStatusInformation(currentUserId)) (or the equivalent method that
returns the current user's status info) after prepareApplyModel(...) and before
model.addAttribute("isEdit", true) so the "Feedback from Handler" banner renders
on the re-displayed "visa/edit-form".
- Around line 187-204: The viewDetails method in VisaViewController currently
returns any visa by id without authorization; update viewDetails to verify that
the requesting user (obtained via userService.findById(currentUserId)) is
allowed to see the VisaDTO returned by visaService.findVisaDtoById(id) by
checking either that the user has an admin/STAFF role or that
visa.getApplicantId() (or equivalent owner field on VisaDTO) equals
currentUserId, and if not throw an AccessDeniedException or return a 403
response; ensure the check happens before adding the visa to the model so only
authorized users can see passport/nationality data.

In `@src/main/java/org/example/visacasemanagementsystem/visa/entity/Visa.java`:
- Around line 90-93: The Visa.toString() currently prints full sensitive and
heavy objects (passportNumber, applicant, handler); change it to mask
passportNumber (e.g., replace all but last 2–4 chars with asterisks or a fixed
token) and avoid embedding full User objects—include only their identifiers
(e.g., applicant.getId() and handler.getId()) or null-safe id getters; update
the toString() implementation in class Visa to use the maskedPassport (derived
from passportNumber) and applicantId/handlerId instead of applicant and handler
to prevent PII leakage and large recursive logs.

In
`@src/main/java/org/example/visacasemanagementsystem/visa/repository/VisaRepository.java`:
- Line 27: The repository method findByApplicantId in VisaRepository doesn't
match the Visa entity (Visa has a User applicant reference), so rename the
derived query to findByApplicant_Id (keeping the Sort parameter) so Spring Data
JPA resolves the path to the associated applicant's id; update the method
signature in VisaRepository to use findByApplicant_Id(Long applicantId, Sort
sort).

In
`@src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java`:
- Around line 307-309: The audit message in VisaService that constructs the
ASSIGNED event uses string concatenation "Admin" + admin.getFullName(),
producing "AdminJohn Doe"; update the concatenation to include a space (e.g.,
"Admin " + admin.getFullName()) or use a formatted string when creating the
AuditEventType.ASSIGNED message so the audit log reads "Admin John Doe has been
assigned to case and status is now ASSIGNED."

In `@src/main/resources/templates/visa/dashboard.html`:
- Around line 155-161: The status cell's <td> wrapping the <span class="status"
th:classappend="${'status-' + `#strings.toLowerCase`(visa.visaStatus)}"
th:text="${visa.visaStatus}"> is not closed, causing malformed table HTML; fix
by adding the missing closing </td> after the span (i.e., close the <td> that
contains the status badge before the next <td> that renders travelDate) so the
table row's cells are properly balanced.
- Around line 125-126: The template links the Sign Out button to /visas/logout
but no handler exists; either add a logout endpoint in VisaViewController (e.g.,
create a method like logout or handleLogout with a mapping for "/visas/logout"
that delegates to SecurityContextLogoutHandler or redirects to the security
logout URL) or modify the template anchor to use Spring Security's default
logout endpoint by changing th:href to "@{/logout}"; update the controller or
template consistently and ensure any CSRF token or POST logout requirements are
handled per your security configuration.

In `@src/main/resources/templates/visa/details.html`:
- Around line 196-218: The request-more-info flow allows an empty reason because
the shared `#infoReason` input sits outside the form so client-side required
validation is bypassed and VisaService.requestMoreInformation does not validate
blank infoText; add server-side validation in VisaService.requestMoreInformation
to reject empty or whitespace-only infoText (same pattern used by the rejectVisa
path), returning/throwing a validation error so the controller can respond
appropriately, and optionally ensure the form wiring uses `#form-info` and
hidden-info consistently to avoid client-side bypass.
- Around line 237-244: The form won't work because CommentController expects
JSON via `@RequestBody` and CreateCommentDTO uses field name text; fix by adding a
view handler in VisaViewController (e.g., addComment with
`@PostMapping`("/{id}/comments") that accepts `@RequestParam` Long visaId,
`@RequestParam` Long authorId, `@RequestParam` String text and calls
commentService.createComment(new CreateCommentDTO(visaId, authorId, text)) then
redirects back to the visa details page), and update the template form to POST
to that view endpoint and rename the textarea from name="content" to name="text"
so the field names match CreateCommentDTO.

---

Nitpick comments:
In
`@src/main/java/org/example/visacasemanagementsystem/visa/dto/CreateVisaDTO.java`:
- Line 15: Remove the mutable applicant identity from the client-facing create
DTO by deleting the applicantId field from CreateVisaDTO and updating any usages
(e.g., constructors, builders, validation code) that accept or map applicantId;
ensure server-side code that processes visa creation (controller/servlet method
handling the create request and service method that accepts CreateVisaDTO)
instead resolves the applicant identity from the authenticated principal/session
and sets it onto the domain model before persisting.

In
`@src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java`:
- Around line 269-291: requestMoreInformation currently accepts null or blank
infoText which can create INCOMPLETE visas with no feedback; add a validation at
the start of requestMoreInformation to ensure infoText is non-null and not blank
(trim it and check length), and if invalid throw the same type of
validation/BadRequest exception your service uses elsewhere (mirror the check
used in rejectVisa), then proceed to call validateHandler(adminId),
findVisaById(visaId), set the handler/status/info, save via visaRepository.save,
and create the audit log as before; reference the method requestMoreInformation
and reuse the same validation style/exception as rejectVisa for consistency.

In `@src/test/java/org/example/visacasemanagementsystem/visa/VisaMapperTest.java`:
- Around line 62-71: The test VisaMapperTest currently checks fields from
visaMapper.toDTO(visa) but misses asserting the handlerName fallback; update the
test to assert that dto.handlerName() equals "Unassigned" when the source Visa
has no handler set, ensuring the mapper's fallback behavior is explicitly
verified (locate the assertion block after the existing assertions on dto.id(),
dto.visaType(), etc., and add the dto.handlerName() check).

In
`@src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceTest.java`:
- Around line 368-381: The test method
validateHandler_shouldThrowEntityNotFoundException_WhenUserIsApplicant has a
misleading name; rename it to reflect that it covers the non-existent user case
(for example
validateHandler_shouldThrowEntityNotFoundException_WhenUserNotFound) and update
any references to the method name in the test class; keep the test body (mocking
userRepository.findById(nonExistingUserId) to return Optional.empty(), asserting
EntityNotFoundException with message "User not found.", and
verifyNoInteractions(visaRepository)) unchanged.
🪄 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: f4941141-b20f-48b1-b30b-a138963ca573

📥 Commits

Reviewing files that changed from the base of the PR and between 44500d2 and 77e4e58.

📒 Files selected for processing (16)
  • src/main/java/org/example/visacasemanagementsystem/exception/GlobalExceptionHandler.java
  • src/main/java/org/example/visacasemanagementsystem/visa/VisaStatus.java
  • src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java
  • src/main/java/org/example/visacasemanagementsystem/visa/dto/CreateVisaDTO.java
  • src/main/java/org/example/visacasemanagementsystem/visa/dto/UpdateVisaDTO.java
  • src/main/java/org/example/visacasemanagementsystem/visa/dto/VisaDTO.java
  • src/main/java/org/example/visacasemanagementsystem/visa/entity/Visa.java
  • src/main/java/org/example/visacasemanagementsystem/visa/mapper/VisaMapper.java
  • src/main/java/org/example/visacasemanagementsystem/visa/repository/VisaRepository.java
  • src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java
  • src/main/resources/templates/visa/apply-form.html
  • src/main/resources/templates/visa/dashboard.html
  • src/main/resources/templates/visa/details.html
  • src/main/resources/templates/visa/edit-form.html
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaMapperTest.java
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceTest.java
💤 Files with no reviewable changes (1)
  • src/main/java/org/example/visacasemanagementsystem/visa/VisaStatus.java

Comment thread src/main/java/org/example/visacasemanagementsystem/visa/entity/Visa.java Outdated
Comment on lines +125 to +126
<a th:href="@{/visas/logout}" class="btn-new "> Sign Out
</a>

@coderabbitai coderabbitai Bot Apr 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for logout endpoint configuration
rg -n "logout" --type=java --type=html -C2

Repository: ithsjava25/visa-case-management-system

Length of output: 498


🏁 Script executed:

# Find the VisaViewController class
find . -name "*VisaViewController*" -o -name "*Controller*" | grep -i visa

Repository: ithsjava25/visa-case-management-system

Length of output: 353


🏁 Script executed:

# Look for Spring Security configuration
find . -name "*Security*" -o -name "*Config*" | grep -v target | head -20

Repository: ithsjava25/visa-case-management-system

Length of output: 167


🏁 Script executed:

# Check for any `@GetMapping`, `@PostMapping`, or `@RequestMapping` related to logout or visas
rg -n "@(Get|Post|Request)Mapping.*logout|logout.*@" --type=java

Repository: ithsjava25/visa-case-management-system

Length of output: 64


🏁 Script executed:

# Examine the VisaViewController more thoroughly
find . -type f -name "*.java" | xargs grep -l "VisaViewController" | head -5

Repository: ithsjava25/visa-case-management-system

Length of output: 174


🏁 Script executed:

# Read the VisaViewController to see all endpoints
cat -n ./src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java

Repository: ithsjava25/visa-case-management-system

Length of output: 9812


🏁 Script executed:

# Search for Spring Security configuration more broadly
rg -l "Security\|WebSecurityConfigurerAdapter\|SecurityFilterChain\|@EnableWebSecurity" --type=java

Repository: ithsjava25/visa-case-management-system

Length of output: 64


🏁 Script executed:

# Check for logout handling in application.properties or yml
find . -name "application*.properties" -o -name "application*.yml" | xargs cat 2>/dev/null

Repository: ithsjava25/visa-case-management-system

Length of output: 586


🏁 Script executed:

# Search for any Spring Security configuration class
rg -n "class.*Config|@Configuration|SecurityFilterChain|WebSecurityConfigurerAdapter" --type=java | head -20

Repository: ithsjava25/visa-case-management-system

Length of output: 204


🏁 Script executed:

# Check for Spring Security dependencies
find . -name "pom.xml" -o -name "build.gradle" | xargs cat 2>/dev/null | grep -i security

Repository: ithsjava25/visa-case-management-system

Length of output: 453


🏁 Script executed:

# Search for any bean configuration related to logout
rg -n "logout\|Logout" --type=java

Repository: ithsjava25/visa-case-management-system

Length of output: 64


🏁 Script executed:

# Check for any XML configuration files
find . -name "*.xml" | grep -v target | xargs grep -l "logout\|security" 2>/dev/null

Repository: ithsjava25/visa-case-management-system

Length of output: 91


🏁 Script executed:

# Check what files are in the visa controller package
find ./src/main/java/org/example/visacasemanagementsystem/visa -type f -name "*.java"

Repository: ithsjava25/visa-case-management-system

Length of output: 885


🏁 Script executed:

# Search for any other potential logout handlers or controllers
rg -n "logout|Logout" --type=java -i

Repository: ithsjava25/visa-case-management-system

Length of output: 64


🏁 Script executed:

# Check pom.xml for Spring Security configuration details
cat -n ./pom.xml | grep -A5 -B5 "spring-boot-starter-security"

Repository: ithsjava25/visa-case-management-system

Length of output: 1685


The /visas/logout endpoint is not implemented.

The "Sign Out" button links to /visas/logout, but no such endpoint exists in VisaViewController or Spring Security configuration. Implement a logout handler in VisaViewController or update the template to use Spring Security's default /logout endpoint.

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

In `@src/main/resources/templates/visa/dashboard.html` around lines 125 - 126, The
template links the Sign Out button to /visas/logout but no handler exists;
either add a logout endpoint in VisaViewController (e.g., create a method like
logout or handleLogout with a mapping for "/visas/logout" that delegates to
SecurityContextLogoutHandler or redirects to the security logout URL) or modify
the template anchor to use Spring Security's default logout endpoint by changing
th:href to "@{/logout}"; update the controller or template consistently and
ensure any CSRF token or POST logout requirements are handled per your security
configuration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This will be solved later on when we implement Spring Security to the project

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@eeebbaandersson, understood! I'll note this for future reviews.


✏️ Learnings added
Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 27
File: src/main/resources/templates/visa/dashboard.html:125-126
Timestamp: 2026-04-14T12:26:05.436Z
Learning: In the `visa-case-management-system` repository, the "Sign Out" link in `src/main/resources/templates/visa/dashboard.html` (line ~126) points to `/visas/logout`, which is not yet implemented. This is intentionally deferred until Spring Security is fully configured. When implemented, the link should either use Spring Security's default `/logout` endpoint (with CSRF token handling via a POST form) or a dedicated `VisaViewController` method that delegates to `SecurityContextLogoutHandler`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 16
File: src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java:42-43
Timestamp: 2026-04-09T11:13:00.080Z
Learning: In the `visa-case-management-system` repository, the `authorId` field in `CreateCommentDTO` is currently trusted directly in `CommentService.createComment()` (file: `src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java`, around line 42). This is a known security gap intentionally deferred until Spring Security is integrated. Once Spring Security is added, the author should be resolved from the authenticated principal (e.g., via `SecurityContextHolder`) rather than from `dto.authorId()`, and the `authorId` field should be removed from `CreateCommentDTO` or restricted to admin use only.

Learnt from: Martin-E-Karlsson
Repo: ithsjava25/visa-case-management-system PR: 26
File: src/main/java/org/example/visacasemanagementsystem/user/dto/CreateUserDTO.java:11-11
Timestamp: 2026-04-13T17:12:15.296Z
Learning: In the `visa-case-management-system` repository, Spring Security has been integrated (via `SecurityUser` and `UserDetailsServiceImpl` in PR `#26`), but password hashing in `UserService.createUser()` (file: `src/main/java/org/example/visacasemanagementsystem/user/service/UserService.java`, line ~54) still uses plaintext (`user.setPassword(dto.password())`). BCrypt encoding is intentionally deferred to a future pull request. When implemented, a `BCryptPasswordEncoder` `Bean` should be configured and injected into `UserService` via constructor injection, replacing the plaintext assignment with `passwordEncoder.encode(dto.password())`.

Comment thread src/main/resources/templates/visa/dashboard.html
Comment on lines +196 to +218
<div class="decision-form" style="margin-top: 20px; border-top: 1px solid #eee; padding-top: 20px;">
<input type="text" id="infoReason"
placeholder="Write reason for rejection or what info is missing..."
style="width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px;" required>

<div style="display: flex; gap: 10px;">
<form th:action="@{/visas/{id}/request-info(id=${visa.id}, currentUserId=${currentUser.id})}" method="post" id="form-info" style="flex: 1;">
<input type="hidden" name="reason" id="hidden-info">
<button type="submit" onclick="document.getElementById('hidden-info').value = document.getElementById('infoReason').value"
style="width: 100%">
Request More Info
</button>
</form>

<form th:action="@{/visas/{id}/reject(id=${visa.id}, currentUserId=${currentUser.id})}" method="post" id="form-reject" style="flex: 1;">
<input type="hidden" name="reason" id="hidden-reject">
<button type="submit" onclick="document.getElementById('hidden-reject').value = document.getElementById('infoReason').value"
style="width: 100%;">
Reject Visa
</button>
</form>
</div>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Empty reason can be submitted for "Request More Info" action.

The shared #infoReason input has required attribute, but since it's outside the forms, HTML5 validation won't trigger. While rejectVisa validates blank reasons server-side, requestMoreInformation in VisaService (lines 270-291) does not validate empty infoText, allowing empty feedback to be saved.

🛡️ Suggested server-side validation in VisaService
 `@Transactional`
 public VisaDTO requestMoreInformation(Long visaId, Long adminId, String infoText) {
+    if (infoText == null || infoText.isBlank()) {
+        throw new IllegalArgumentException("Information request text cannot be null or blank");
+    }
     User  admin = validateHandler(adminId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/visa/details.html` around lines 196 - 218, The
request-more-info flow allows an empty reason because the shared `#infoReason`
input sits outside the form so client-side required validation is bypassed and
VisaService.requestMoreInformation does not validate blank infoText; add
server-side validation in VisaService.requestMoreInformation to reject empty or
whitespace-only infoText (same pattern used by the rejectVisa path),
returning/throwing a validation error so the controller can respond
appropriately, and optionally ensure the form wiring uses `#form-info` and
hidden-info consistently to avoid client-side bypass.

Comment on lines +237 to +244
<div class="comment-form">
<form th:action="@{/api/comments}" method="post">
<input type="hidden" name="visaId" th:value="${visa.id}">
<input type="hidden" name="authorId" th:value="${currentUser.id}">
<textarea name="content" placeholder="Write a comment..."></textarea>
<button type="submit" class="btn-submit">Send</button>
</form>
</div>

@coderabbitai coderabbitai Bot Apr 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Comment form will fail due to content-type and field name mismatch.

Two issues prevent this form from working:

  1. Content-type mismatch: The CommentController is a @RestController expecting @RequestBody JSON (see src/main/java/org/example/visacasemanagementsystem/comment/controller/CommentController.java:13-26), but this form submits application/x-www-form-urlencoded.

  2. Field name mismatch: The form sends content (line 241) but CreateCommentDTO expects text field (see src/main/java/org/example/visacasemanagementsystem/comment/dto/CreateCommentDTO.java:4).

🐛 Recommended fix options

Option 1: Add a view controller endpoint that accepts form data:

// In VisaViewController
`@PostMapping`("/{id}/comments")
public String addComment(`@PathVariable` Long id,
                         `@RequestParam` Long authorId,
                         `@RequestParam` String text,
                         `@RequestParam` Long currentUserId) {
    commentService.createComment(new CreateCommentDTO(id, authorId, text));
    return "redirect:/visas/" + id + "?currentUserId=" + currentUserId;
}

Then update the form:

-<form th:action="@{/api/comments}" method="post">
-    <input type="hidden" name="visaId" th:value="${visa.id}">
-    <input type="hidden" name="authorId" th:value="${currentUser.id}">
-    <textarea name="content" placeholder="Write a comment..."></textarea>
+<form th:action="@{/visas/{id}/comments(id=${visa.id}, currentUserId=${currentUser.id})}" method="post">
+    <input type="hidden" name="authorId" th:value="${currentUser.id}">
+    <textarea name="text" placeholder="Write a comment..."></textarea>

Option 2: Use JavaScript/fetch to submit as JSON to the existing REST endpoint.

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

In `@src/main/resources/templates/visa/details.html` around lines 237 - 244, The
form won't work because CommentController expects JSON via `@RequestBody` and
CreateCommentDTO uses field name text; fix by adding a view handler in
VisaViewController (e.g., addComment with `@PostMapping`("/{id}/comments") that
accepts `@RequestParam` Long visaId, `@RequestParam` Long authorId, `@RequestParam`
String text and calls commentService.createComment(new CreateCommentDTO(visaId,
authorId, text)) then redirects back to the visa details page), and update the
template form to POST to that view endpoint and rename the textarea from
name="content" to name="text" so the field names match CreateCommentDTO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This issue will be solved later by using JavaScript/fetch to display comments in visa form

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@eeebbaandersson, understood! Using JavaScript/fetch to submit comments as JSON to the existing /api/comments REST endpoint is a clean approach that avoids the need for an additional view controller endpoint. When you implement it, remember to:

  1. Use fetch('/api/comments', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ visaId, authorId, text }) }) — note the field name should be text (matching CreateCommentDTO), not content.
  2. Handle the response to dynamically append the new comment to the comments list without a full page reload.

Feel free to revisit this comment when the implementation is in place.


✏️ Learnings added
Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 27
File: src/main/resources/templates/visa/details.html:237-244
Timestamp: 2026-04-14T12:40:25.740Z
Learning: In `visa-case-management-system`, the comment form in `src/main/resources/templates/visa/details.html` (around lines 237-244) currently submits to `/api/comments` with form fields `visaId`, `authorId`, and `content` (wrong field name). The intended fix is to replace the HTML form with JavaScript/fetch to POST JSON to the existing REST endpoint `/api/comments`. The correct JSON body field for the comment text is `text` (matching `CreateCommentDTO`), not `content`. This is a known deferred issue to be addressed in a future PR.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 27
File: src/main/resources/templates/visa/dashboard.html:125-126
Timestamp: 2026-04-14T12:26:05.436Z
Learning: In the `visa-case-management-system` templates, ensure the “Sign Out” link/button uses the actual Spring Security logout mechanism once configured. Don’t leave it pointing to an unimplemented `/visas/logout` endpoint. When logout is ready, either (a) use Spring Security’s standard `/logout` endpoint via a POST form (including CSRF token handling), or (b) route to a dedicated controller method (e.g., a `VisaViewController` handler) that delegates to `SecurityContextLogoutHandler`.

Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 16
File: src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java:42-43
Timestamp: 2026-04-09T11:13:00.080Z
Learning: In the `visa-case-management-system` repository, the `authorId` field in `CreateCommentDTO` is currently trusted directly in `CommentService.createComment()` (file: `src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java`, around line 42). This is a known security gap intentionally deferred until Spring Security is integrated. Once Spring Security is added, the author should be resolved from the authenticated principal (e.g., via `SecurityContextHolder`) rather than from `dto.authorId()`, and the `authorId` field should be removed from `CreateCommentDTO` or restricted to admin use only.

@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

♻️ Duplicate comments (2)
src/main/java/org/example/visacasemanagementsystem/visa/entity/Visa.java (1)

96-96: ⚠️ Potential issue | 🟠 Major

Omit raw statusInformation from toString().

statusInformation now carries free-text rejection reasons / info requests, so printing it here leaks case notes into logs. Keep only a presence flag, length, or omit it entirely.

🔐 Suggested change
-                ", statusInformation='" + statusInformation + '\'' +
+                ", hasStatusInformation=" + (statusInformation != null && !statusInformation.isBlank()) +
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/visacasemanagementsystem/visa/entity/Visa.java` at
line 96, The Visa.toString method currently appends the raw statusInformation
field which may leak sensitive free-text case notes; update the Visa.toString
implementation (the toString method on class Visa) to stop including the raw
statusInformation value and instead include a safe indicator such as a boolean
presence flag (e.g., statusInformation != null && !statusInformation.isEmpty())
or the character length (e.g., statusInformationLength) or omit it entirely;
locate the concatenation that references statusInformation and replace it with
the chosen safe representation and update any related string labels accordingly.
src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java (1)

146-152: ⚠️ Potential issue | 🟡 Minor

Re-add statusInformation in the service-validation error branch.

The IllegalArgumentException path re-renders visa/edit-form without repopulating statusInformation, so the handler feedback banner disappears specifically for travel-date errors.

🧩 Suggested change
         } catch (IllegalArgumentException e) {
             bindingResult.rejectValue("travelDate", "error.travelDate", e.getMessage());
             prepareApplyModel(currentUserId, model);
             model.addAttribute("isEdit", true);
+            VisaDTO visa = visaService.findVisaDtoById(id);
+            model.addAttribute("statusInformation", visa.statusInformation());
             return "visa/edit-form";
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`
around lines 146 - 152, When catching IllegalArgumentException in
VisaViewController.updateVisa handling, the code repopulates the model via
prepareApplyModel(...) but fails to re-add the statusInformation attribute so
the feedback banner disappears; modify the IllegalArgumentException catch block
(the branch that calls bindingResult.rejectValue("travelDate", ...),
prepareApplyModel(currentUserId, model), model.addAttribute("isEdit", true),
return "visa/edit-form") to also fetch and add the same statusInformation value
used elsewhere (the attribute prepared for the edit form, e.g., by calling the
same helper or retrieving from visaService) to the model before returning so the
feedback banner is preserved.
🤖 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/visa/controller/VisaViewController.java`:
- Around line 38-41: Stop trusting request-supplied currentUserId: remove the
`@RequestParam` Long currentUserId from all affected controller handler methods
(showDashboard, and the methods referenced around lines 62–64, 77–80, 101–103,
131–135, 157–166, 174–185, 189–196, 216–218) and instead resolve the
authenticated user via Spring Security
(SecurityContextHolder.getContext().getAuthentication() or an injected
Principal) to obtain the current user's id/username, then use userService (e.g.,
userService.findById or findByUsername) to load UserDTO and pass that into
visaService.validateHandler(...) and other authorization/ownership checks; also
update method signatures to remove currentUserId from request surfaces.
Additionally, in the catch block that handles IllegalArgumentException from
visaService.updateVisa(), ensure you add model.addAttribute("statusInformation",
...) so the edit form has the missing dropdown (use the same source used
elsewhere for statusInformation or call the method that prepares it before
returning prepareApplyModel(...) with isEdit=true).

In
`@src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java`:
- Around line 188-191: Change updateVisa to accept the path id and use it as the
authoritative target instead of trusting dto.id(): modify VisaService.updateVisa
signature to updateVisa(Long id, UpdateVisaDTO dto, Long userId) and load the
entity with visaRepository.findById(id).orElseThrow(...), then apply fields from
UpdateVisaDTO to the loaded Visa and save; also update
VisaViewController.processUpdate to call visaService.updateVisa(id,
updateVisaDTO, currentUserId) (and remove/ignore dto.id() usage) so the URL path
id is the source of truth.

---

Duplicate comments:
In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`:
- Around line 146-152: When catching IllegalArgumentException in
VisaViewController.updateVisa handling, the code repopulates the model via
prepareApplyModel(...) but fails to re-add the statusInformation attribute so
the feedback banner disappears; modify the IllegalArgumentException catch block
(the branch that calls bindingResult.rejectValue("travelDate", ...),
prepareApplyModel(currentUserId, model), model.addAttribute("isEdit", true),
return "visa/edit-form") to also fetch and add the same statusInformation value
used elsewhere (the attribute prepared for the edit form, e.g., by calling the
same helper or retrieving from visaService) to the model before returning so the
feedback banner is preserved.

In `@src/main/java/org/example/visacasemanagementsystem/visa/entity/Visa.java`:
- Line 96: The Visa.toString method currently appends the raw statusInformation
field which may leak sensitive free-text case notes; update the Visa.toString
implementation (the toString method on class Visa) to stop including the raw
statusInformation value and instead include a safe indicator such as a boolean
presence flag (e.g., statusInformation != null && !statusInformation.isEmpty())
or the character length (e.g., statusInformationLength) or omit it entirely;
locate the concatenation that references statusInformation and replace it with
the chosen safe representation and update any related string labels accordingly.
🪄 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: fa1928e7-9cde-4f87-b0c5-59ddd8a6df3b

📥 Commits

Reviewing files that changed from the base of the PR and between 77e4e58 and d16cec6.

📒 Files selected for processing (6)
  • src/main/java/org/example/visacasemanagementsystem/exception/GlobalExceptionHandler.java
  • src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java
  • src/main/java/org/example/visacasemanagementsystem/visa/entity/Visa.java
  • src/main/java/org/example/visacasemanagementsystem/visa/repository/VisaRepository.java
  • src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java
  • src/main/resources/templates/visa/dashboard.html
✅ Files skipped from review due to trivial changes (2)
  • src/main/java/org/example/visacasemanagementsystem/visa/repository/VisaRepository.java
  • src/main/resources/templates/visa/dashboard.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/example/visacasemanagementsystem/exception/GlobalExceptionHandler.java

Comment on lines +38 to +41
public String showDashboard(@RequestParam Long currentUserId, Model model) {
// Find current user
UserDTO user = userService.findById(currentUserId)
.orElseThrow(() -> new EntityNotFoundException("User not found."));

@coderabbitai coderabbitai Bot Apr 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Spring Security artifacts:\n'
fd -i 'SecurityUser|UserDetailsServiceImpl|SecurityConfig' src/main/java

printf '\nRequest-supplied identity in VisaViewController:\n'
rg -n --type=java '@RequestParam\s+Long\s+currentUserId|findById\(currentUserId\)|findVisasByApplicant\(currentUserId\)|approveVisa\(id,\s*currentUserId\)|requestMoreInformation\(id,\s*currentUserId\)|rejectVisa\(id,\s*currentUserId\)|assignHandler\(id,\s*currentUserId\)' src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java

printf '\nAuthenticated-principal lookups in the web layer:\n'
rg -n --type=java '@AuthenticationPrincipal|SecurityContextHolder|getAuthentication\(' src/main/java

Repository: ithsjava25/visa-case-management-system

Length of output: 1532


🏁 Script executed:

# Check for build configuration and Spring Security dependency
fd -i 'pom\.xml|build\.gradle' src/ . | head -20

# Search for authentication/security-related code that might indicate integration
rg -n 'PasswordEncoder|BCrypt|UserDetailsService|AuthenticationManager|WebSecurityConfigurerAdapter' src/main/java --type=java

# Look for any Spring Security imports
rg -n 'import.*springframework\.security' src/main/java --type=java

# Check if user service has security-related methods
rg -n 'class.*UserService|implements.*UserDetailsService' src/main/java --type=java -A 5

Repository: ithsjava25/visa-case-management-system

Length of output: 781


🏁 Script executed:

# Check pom.xml for Spring Security dependency
cat pom.xml | grep -A 2 -B 2 'security\|spring-security' || echo "No Spring Security found in pom.xml"

# Also verify the actual state of authentication in the application
rg -n 'public class.*Controller' src/main/java/org/example/visacasemanagementsystem/visa/controller/ --type=java -A 3 | head -40

Repository: ithsjava25/visa-case-management-system

Length of output: 1539


🏁 Script executed:

# View the specific lines mentioned in the review to confirm the vulnerability
sed -n '38,41p' src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java

# Check if there's any authentication context usage in this controller
rg -n 'SecurityContextHolder|@AuthenticationPrincipal|getAuthentication|getPrincipal' src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java

Repository: ithsjava25/visa-case-management-system

Length of output: 334


🏁 Script executed:

# Check the processUpdate method and the missing statusInformation regression (around lines 146-152)
sed -n '140,160p' src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java

# Also check if there's a similar pattern in other methods to see the full scope of the issue
rg -n 'model\.addAttribute.*statusInformation|BindingResult|IllegalArgumentException' src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java

Repository: ithsjava25/visa-case-management-system

Length of output: 1422


Stop trusting currentUserId from the request.

Every ownership/admin check in this controller is derived from a query/form parameter. A caller can impersonate another applicant—or an admin—just by supplying an admin id in the request, and VisaService.validateHandler(Long) will authorize the looked-up user. Spring Security is declared as a dependency but not integrated here. Resolve the current user from Spring Security instead and remove currentUserId from the request surface.

Also add statusInformation to the model in the error catch block (lines 148–152). When visaService.updateVisa() throws IllegalArgumentException, the error handler calls prepareApplyModel() and sets isEdit to true but omits statusInformation, causing the edit form template to render with a missing dropdown.

Applies to all handler methods: 38–41, 62–64, 77–80, 101–103, 131–135, 157–166, 174–185, 189–196, 216–218.

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

In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`
around lines 38 - 41, Stop trusting request-supplied currentUserId: remove the
`@RequestParam` Long currentUserId from all affected controller handler methods
(showDashboard, and the methods referenced around lines 62–64, 77–80, 101–103,
131–135, 157–166, 174–185, 189–196, 216–218) and instead resolve the
authenticated user via Spring Security
(SecurityContextHolder.getContext().getAuthentication() or an injected
Principal) to obtain the current user's id/username, then use userService (e.g.,
userService.findById or findByUsername) to load UserDTO and pass that into
visaService.validateHandler(...) and other authorization/ownership checks; also
update method signatures to remove currentUserId from request surfaces.
Additionally, in the catch block that handles IllegalArgumentException from
visaService.updateVisa(), ensure you add model.addAttribute("statusInformation",
...) so the edit form has the missing dropdown (use the same source used
elsewhere for statusInformation or call the method that prepares it before
returning prepareApplyModel(...) with isEdit=true).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This will later be resolved when we implement the missing Spring Security

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@eeebbaandersson, understood — noted for future reviews. Just to flag one distinction: the missing statusInformation attribute in the IllegalArgumentException catch block of processUpdate() (lines 148–152) is independent of Spring Security — it's a pure model-population bug that would cause the edit form's status information banner to render empty regardless of how the user is authenticated. Worth addressing in isolation even before the Spring Security work lands, but I'll leave that to your discretion.


✏️ Learnings added
Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 27
File: src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java:38-41
Timestamp: 2026-04-14T13:13:26.963Z
Learning: In `visa-case-management-system`, `VisaViewController` (`src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`) currently accepts `currentUserId` as a `RequestParam` in all handler methods (showDashboard, showApplyForm, submitApplication, showEditForm, processUpdate, approveVisa, requestMoreInformation, rejectVisa, assignCaseToHandler, viewDetails) instead of resolving the authenticated user from the Spring Security context. This is intentionally deferred until Spring Security is fully wired into the web layer. When implemented, `RequestParam Long currentUserId` should be removed from all these methods and replaced with resolution via `SecurityContextHolder.getContext().getAuthentication()` or `AuthenticationPrincipal`, followed by a `userService` lookup to obtain the `UserDTO`.

Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 27
File: src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java:38-41
Timestamp: 2026-04-14T13:13:26.963Z
Learning: In `visa-case-management-system`, the `processUpdate()` method in `VisaViewController` (`src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`, around lines 148–152) is missing `model.addAttribute("statusInformation", ...)` in the `IllegalArgumentException` catch block. When `visaService.updateVisa()` throws this exception, the edit form (`edit-form.html`) re-renders without `statusInformation`, causing the "Feedback from Handler" banner to display incorrectly. This is a UI/model bug independent of Spring Security and should be fixed by fetching the current visa's `statusInformation` (e.g., via `visaService.findVisaDtoById(id).statusInformation()`) and adding it to the model before returning the edit view.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 27
File: src/main/resources/templates/visa/dashboard.html:125-126
Timestamp: 2026-04-14T12:26:05.436Z
Learning: In the `visa-case-management-system` repository, the "Sign Out" link in `src/main/resources/templates/visa/dashboard.html` (line ~126) points to `/visas/logout`, which is not yet implemented. This is intentionally deferred until Spring Security is fully configured. When implemented, the link should either use Spring Security's default `/logout` endpoint (with CSRF token handling via a POST form) or a dedicated `VisaViewController` method that delegates to `SecurityContextLogoutHandler`.

Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 16
File: src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java:42-43
Timestamp: 2026-04-09T11:13:00.080Z
Learning: In the `visa-case-management-system` repository, the `authorId` field in `CreateCommentDTO` is currently trusted directly in `CommentService.createComment()` (file: `src/main/java/org/example/visacasemanagementsystem/comment/service/CommentService.java`, around line 42). This is a known security gap intentionally deferred until Spring Security is integrated. Once Spring Security is added, the author should be resolved from the authenticated principal (e.g., via `SecurityContextHolder`) rather than from `dto.authorId()`, and the `authorId` field should be removed from `CreateCommentDTO` or restricted to admin use only.

Learnt from: eeebbaandersson
Repo: ithsjava25/visa-case-management-system PR: 27
File: src/main/resources/templates/visa/details.html:237-244
Timestamp: 2026-04-14T12:40:25.740Z
Learning: In `visa-case-management-system`, the comment form in `src/main/resources/templates/visa/details.html` (around lines 237-244) currently submits to `/api/comments` with form fields `visaId`, `authorId`, and `content` (wrong field name). The intended fix is to replace the HTML form with JavaScript/fetch to POST JSON to the existing REST endpoint `/api/comments`. The correct JSON body field for the comment text is `text` (matching `CreateCommentDTO`), not `content`. This is a known deferred issue to be addressed in a future PR.

Learnt from: Martin-E-Karlsson
Repo: ithsjava25/visa-case-management-system PR: 26
File: src/main/java/org/example/visacasemanagementsystem/user/dto/CreateUserDTO.java:11-11
Timestamp: 2026-04-13T17:12:15.296Z
Learning: In the `visa-case-management-system` repository, Spring Security has been integrated (via `SecurityUser` and `UserDetailsServiceImpl` in PR `#26`), but password hashing in `UserService.createUser()` (file: `src/main/java/org/example/visacasemanagementsystem/user/service/UserService.java`, line ~54) still uses plaintext (`user.setPassword(dto.password())`). BCrypt encoding is intentionally deferred to a future pull request. When implemented, a `BCryptPasswordEncoder` `Bean` should be configured and injected into `UserService` via constructor injection, replacing the plaintext assignment with `passwordEncoder.encode(dto.password())`.

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

♻️ Duplicate comments (1)
src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java (1)

146-152: ⚠️ Potential issue | 🟡 Minor

Re-add statusInformation in the exception path too.

The validation-error branch restores the handler feedback banner, but the IllegalArgumentException branch still returns visa/edit-form without repopulating statusInformation, so that banner disappears on service-level validation failures.

🔧 Suggested fix
         } catch (IllegalArgumentException e) {
             bindingResult.rejectValue("travelDate", "error.travelDate", e.getMessage());
             prepareApplyModel(currentUserId, model);
             model.addAttribute("isEdit", true);
+            VisaDTO visa = visaService.findVisaDtoById(id);
+            model.addAttribute("statusInformation", visa.statusInformation());
             return "visa/edit-form";
         }

Based on learnings, the processUpdate() method in VisaViewController is missing model.addAttribute("statusInformation", ...) in the IllegalArgumentException catch block.

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

In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`
around lines 146 - 152, The IllegalArgumentException catch block in
processUpdate() calls bindingResult.rejectValue(...), prepareApplyModel(...),
sets isEdit and returns "visa/edit-form" but forgets to repopulate the
statusInformation model attribute, causing the handler banner to disappear;
update the IllegalArgumentException catch block in
VisaViewController.processUpdate() (the block that catches
IllegalArgumentException from visaService.updateVisa(...)) to add
model.addAttribute("statusInformation", <same value used elsewhere, e.g., the
one set in prepareApplyModel or where statusInformation is built>) before
returning so the view receives the statusInformation attribute on service-level
validation failures.
🤖 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/config/SecurityConfig.java`:
- Around line 13-18: The security config currently permits all requests and
disables CSRF/frame protections (anyRequest().permitAll(), csrf.disable(),
headers.frameOptions(...).disable()), exposing mutation endpoints to
unauthenticated parameter substitution; update the HttpSecurity DSL to require
authentication for application routes (replace anyRequest().permitAll() with
explicit matchers so that endpoints like "/api/**" require authenticated() while
allowing "/h2-console/**" and static resources to permitAll()), re-enable CSRF
but ignore the H2 console only (use csrf().ignoringRequestMatchers(new
AntPathRequestMatcher("/h2-console/**")) or
ignoringAntMatchers("/h2-console/**")), and set frameOptions to sameOrigin()
instead of disable() (headers().frameOptions().sameOrigin()) so the H2 console
still works without opening frames globally.

In
`@src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java`:
- Around line 197-209: The updateVisa() flow currently resets visa status to
SUBMITTED unconditionally; change it to prevent applicant edits from reopening
finalized cases by checking visa.getVisaStatus() (use the VisaStatus enum)
before modifying status: if visa.getVisaStatus() is GRANTED, REJECTED, or
ASSIGNED then either reject the edit (throw
UnauthorizedException/IllegalStateException) or at minimum avoid changing
visa.setVisaStatus(...) and visa.setStatusInformation(null); keep the ownership
check (visa.getApplicant().getId().equals(userId)),
validateTravelDate(dto.travelDate()), and still update editable fields
(visaType, nationality, passportNumber, travelDate) but do not override
finalized statuses.

In
`@src/main/java/org/example/visacasemanagementsystem/VisaCaseManagementSystemApplication.java`:
- Around line 24-39: The current seeding block checks only
userRepository.findByEmail("user@test.com") before creating both the regular
User and the Admin, so if the user exists but the admin (created as
admin.setEmail("user@test.com2")) is missing it will never be created; update
the logic to perform separate existence checks and inserts per email by calling
userRepository.findByEmail("user@test.com") before creating the User and
userRepository.findByEmail("user@test.com2") before creating the Admin (refer to
userRepository.findByEmail(...) and the User/ admin creation blocks) so each
account is seeded independently.
- Around line 20-52: The initData CommandLineRunner currently seeds predictable
accounts on every startup; guard it behind a non-production profile by making
the bean conditional so it only runs in dev/test environments (e.g., annotate
the initData bean or method with `@Profile`("!prod") or use
`@Profile`({"dev","test"}) / `@ConditionalOnProperty`) and/or check the active
Spring Environment inside initData before creating users; update references to
UserRepository and the initData method so seeding occurs only when the active
profile is not production and remove or externalize hard-coded credentials.

---

Duplicate comments:
In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`:
- Around line 146-152: The IllegalArgumentException catch block in
processUpdate() calls bindingResult.rejectValue(...), prepareApplyModel(...),
sets isEdit and returns "visa/edit-form" but forgets to repopulate the
statusInformation model attribute, causing the handler banner to disappear;
update the IllegalArgumentException catch block in
VisaViewController.processUpdate() (the block that catches
IllegalArgumentException from visaService.updateVisa(...)) to add
model.addAttribute("statusInformation", <same value used elsewhere, e.g., the
one set in prepareApplyModel or where statusInformation is built>) before
returning so the view receives the statusInformation attribute on service-level
validation failures.
🪄 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: 3f12bf5b-3e4b-4dbc-bc51-81ced2349b56

📥 Commits

Reviewing files that changed from the base of the PR and between d16cec6 and f17034a.

📒 Files selected for processing (6)
  • src/main/java/org/example/visacasemanagementsystem/VisaCaseManagementSystemApplication.java
  • src/main/java/org/example/visacasemanagementsystem/config/SecurityConfig.java
  • src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java
  • src/main/java/org/example/visacasemanagementsystem/visa/repository/VisaRepository.java
  • src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/example/visacasemanagementsystem/visa/repository/VisaRepository.java

Comment on lines +13 to +18
http
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
)
.csrf(csrf -> csrf.disable())
.headers(headers -> headers.frameOptions(frame -> frame.disable()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine SecurityConfig.java
fd -t f "SecurityConfig.java"

Repository: ithsjava25/visa-case-management-system

Length of output: 159


🏁 Script executed:

# Read the SecurityConfig.java file to verify the exact code
fd -t f "SecurityConfig.java" -x cat -n {}

Repository: ithsjava25/visa-case-management-system

Length of output: 984


🏁 Script executed:

# Examine VisaViewController to confirm currentUserId RequestParam usage
fd -t f "VisaViewController.java" -x head -100 {}

Repository: ithsjava25/visa-case-management-system

Length of output: 4013


🏁 Script executed:

# Search for all handler methods in VisaViewController that use currentUserId
fd -t f "VisaViewController.java" -x rg -A 2 "@PostMapping|@GetMapping|@PutMapping" {}

Repository: ithsjava25/visa-case-management-system

Length of output: 1686


This config exposes every visa mutation to unauthenticated request parameter substitution.

The combination of anyRequest().permitAll() with CSRF disabled and controller methods accepting currentUserId as a request parameter creates an immediate security vulnerability. An attacker can submit POST requests to approve, reject, assign, or apply for visas as any user ID without authentication. The frame options are also disabled globally rather than scoped to the H2 console.

Require authentication for application routes, limit CSRF exceptions to H2 only, and use sameOrigin for frame options:

🛡️ Safer baseline
         http
                 .authorizeHttpRequests(auth -> auth
-                        .anyRequest().permitAll()
+                        .requestMatchers("/h2-console/**", "/error").permitAll()
+                        .anyRequest().authenticated()
                 )
-                .csrf(csrf -> csrf.disable())
-                .headers(headers -> headers.frameOptions(frame -> frame.disable()));
+                .csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**"))
+                .headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()));
         return http.build();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/config/SecurityConfig.java`
around lines 13 - 18, The security config currently permits all requests and
disables CSRF/frame protections (anyRequest().permitAll(), csrf.disable(),
headers.frameOptions(...).disable()), exposing mutation endpoints to
unauthenticated parameter substitution; update the HttpSecurity DSL to require
authentication for application routes (replace anyRequest().permitAll() with
explicit matchers so that endpoints like "/api/**" require authenticated() while
allowing "/h2-console/**" and static resources to permitAll()), re-enable CSRF
but ignore the H2 console only (use csrf().ignoringRequestMatchers(new
AntPathRequestMatcher("/h2-console/**")) or
ignoringAntMatchers("/h2-console/**")), and set frameOptions to sameOrigin()
instead of disable() (headers().frameOptions().sameOrigin()) so the H2 console
still works without opening frames globally.

Comment on lines +20 to +52
@Bean
public CommandLineRunner initData(UserRepository userRepository) {
return args -> {
// Skapa en vanlig användare (Applicant)
if (userRepository.findByEmail("user@test.com").isEmpty()) {
User user = new User();
user.setFullName("USER");
user.setEmail("user@test.com");
user.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
user.setUserAuthorization(UserAuthorization.USER);
userRepository.save(user);
System.out.println("Testanvändare skapad med ID: " + user.getId());

User admin = new User();
admin.setFullName("ADMIN");
admin.setEmail("user@test.com2");
admin.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
admin.setUserAuthorization(UserAuthorization.ADMIN);
userRepository.save(admin);
System.out.println("Test-admin skapad med ID: " + admin.getId());
}

// Skapa en admin (Handler)
if (userRepository.findByEmail("admin@test.com").isEmpty()) {
User sysadmin = new User();
sysadmin.setFullName("SYSTEM ADMIN");
sysadmin.setEmail("admin@test.com");
sysadmin.setPassword("password");
sysadmin.setUserAuthorization(UserAuthorization.SYSADMIN);
userRepository.save(sysadmin);
System.out.println("Test-sysadmin skapad med ID: " + sysadmin.getId());
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard this seed runner behind a non-production profile.

This bean creates known accounts with predictable credentials on every startup. In any shared or production-like environment, that becomes a built-in backdoor rather than test data.

🔒 Safer baseline
+import org.springframework.context.annotation.Profile;
 ...
-    `@Bean`
+    `@Bean`
+    `@Profile`("dev")
     public CommandLineRunner initData(UserRepository userRepository) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Bean
public CommandLineRunner initData(UserRepository userRepository) {
return args -> {
// Skapa en vanlig användare (Applicant)
if (userRepository.findByEmail("user@test.com").isEmpty()) {
User user = new User();
user.setFullName("USER");
user.setEmail("user@test.com");
user.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
user.setUserAuthorization(UserAuthorization.USER);
userRepository.save(user);
System.out.println("Testanvändare skapad med ID: " + user.getId());
User admin = new User();
admin.setFullName("ADMIN");
admin.setEmail("user@test.com2");
admin.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
admin.setUserAuthorization(UserAuthorization.ADMIN);
userRepository.save(admin);
System.out.println("Test-admin skapad med ID: " + admin.getId());
}
// Skapa en admin (Handler)
if (userRepository.findByEmail("admin@test.com").isEmpty()) {
User sysadmin = new User();
sysadmin.setFullName("SYSTEM ADMIN");
sysadmin.setEmail("admin@test.com");
sysadmin.setPassword("password");
sysadmin.setUserAuthorization(UserAuthorization.SYSADMIN);
userRepository.save(sysadmin);
System.out.println("Test-sysadmin skapad med ID: " + sysadmin.getId());
}
};
`@Bean`
`@Profile`("dev")
public CommandLineRunner initData(UserRepository userRepository) {
return args -> {
// Skapa en vanlig användare (Applicant)
if (userRepository.findByEmail("user@test.com").isEmpty()) {
User user = new User();
user.setFullName("USER");
user.setEmail("user@test.com");
user.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
user.setUserAuthorization(UserAuthorization.USER);
userRepository.save(user);
System.out.println("Testanvändare skapad med ID: " + user.getId());
User admin = new User();
admin.setFullName("ADMIN");
admin.setEmail("user@test.com2");
admin.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
admin.setUserAuthorization(UserAuthorization.ADMIN);
userRepository.save(admin);
System.out.println("Test-admin skapad med ID: " + admin.getId());
}
// Skapa en admin (Handler)
if (userRepository.findByEmail("admin@test.com").isEmpty()) {
User sysadmin = new User();
sysadmin.setFullName("SYSTEM ADMIN");
sysadmin.setEmail("admin@test.com");
sysadmin.setPassword("password");
sysadmin.setUserAuthorization(UserAuthorization.SYSADMIN);
userRepository.save(sysadmin);
System.out.println("Test-sysadmin skapad med ID: " + sysadmin.getId());
}
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/VisaCaseManagementSystemApplication.java`
around lines 20 - 52, The initData CommandLineRunner currently seeds predictable
accounts on every startup; guard it behind a non-production profile by making
the bean conditional so it only runs in dev/test environments (e.g., annotate
the initData bean or method with `@Profile`("!prod") or use
`@Profile`({"dev","test"}) / `@ConditionalOnProperty`) and/or check the active
Spring Environment inside initData before creating users; update references to
UserRepository and the initData method so seeding occurs only when the active
profile is not production and remove or externalize hard-coded credentials.

Comment on lines +24 to +39
if (userRepository.findByEmail("user@test.com").isEmpty()) {
User user = new User();
user.setFullName("USER");
user.setEmail("user@test.com");
user.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
user.setUserAuthorization(UserAuthorization.USER);
userRepository.save(user);
System.out.println("Testanvändare skapad med ID: " + user.getId());

User admin = new User();
admin.setFullName("ADMIN");
admin.setEmail("user@test.com2");
admin.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
admin.setUserAuthorization(UserAuthorization.ADMIN);
userRepository.save(admin);
System.out.println("Test-admin skapad med ID: " + admin.getId());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Seed each account behind its own lookup.

Lines 24-39 gate both the applicant and admin inserts on findByEmail("user@test.com"), even though the admin is saved with a different email on Line 35. If the applicant exists but the admin row is missing, the admin will never be recreated.

🧩 Suggested fix
-            if (userRepository.findByEmail("user@test.com").isEmpty()) {
+            if (userRepository.findByEmail("user@test.com").isEmpty()) {
                 User user = new User();
                 user.setFullName("USER");
                 user.setEmail("user@test.com");
                 user.setPassword("password");
                 user.setUserAuthorization(UserAuthorization.USER);
                 userRepository.save(user);
                 System.out.println("Testanvändare skapad med ID: " + user.getId());
+            }
 
+            if (userRepository.findByEmail("user@test.com2").isEmpty()) {
                 User admin = new User();
                 admin.setFullName("ADMIN");
                 admin.setEmail("user@test.com2");
                 admin.setPassword("password");
                 admin.setUserAuthorization(UserAuthorization.ADMIN);
                 userRepository.save(admin);
                 System.out.println("Test-admin skapad med ID: " + admin.getId());
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (userRepository.findByEmail("user@test.com").isEmpty()) {
User user = new User();
user.setFullName("USER");
user.setEmail("user@test.com");
user.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
user.setUserAuthorization(UserAuthorization.USER);
userRepository.save(user);
System.out.println("Testanvändare skapad med ID: " + user.getId());
User admin = new User();
admin.setFullName("ADMIN");
admin.setEmail("user@test.com2");
admin.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
admin.setUserAuthorization(UserAuthorization.ADMIN);
userRepository.save(admin);
System.out.println("Test-admin skapad med ID: " + admin.getId());
if (userRepository.findByEmail("user@test.com").isEmpty()) {
User user = new User();
user.setFullName("USER");
user.setEmail("user@test.com");
user.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
user.setUserAuthorization(UserAuthorization.USER);
userRepository.save(user);
System.out.println("Testanvändare skapad med ID: " + user.getId());
}
if (userRepository.findByEmail("user@test.com2").isEmpty()) {
User admin = new User();
admin.setFullName("ADMIN");
admin.setEmail("user@test.com2");
admin.setPassword("password"); // Notera: Du behöver sätta lösenord om din entitet kräver det
admin.setUserAuthorization(UserAuthorization.ADMIN);
userRepository.save(admin);
System.out.println("Test-admin skapad med ID: " + admin.getId());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/VisaCaseManagementSystemApplication.java`
around lines 24 - 39, The current seeding block checks only
userRepository.findByEmail("user@test.com") before creating both the regular
User and the Admin, so if the user exists but the admin (created as
admin.setEmail("user@test.com2")) is missing it will never be created; update
the logic to perform separate existence checks and inserts per email by calling
userRepository.findByEmail("user@test.com") before creating the User and
userRepository.findByEmail("user@test.com2") before creating the Admin (refer to
userRepository.findByEmail(...) and the User/ admin creation blocks) so each
account is seeded independently.

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

🧹 Nitpick comments (3)
src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java (2)

342-346: Error message is misleading when travel date is null.

The same message "Travel date cannot be in the past." is shown whether the date is null or actually in the past. Consider a clearer message that covers both cases.

✏️ Suggested improvement
 public void validateTravelDate(LocalDate travelDate) {
-    if (travelDate == null || travelDate.isBefore(LocalDate.now())) {
-        throw new IllegalArgumentException("Travel date cannot be in the past.");
+    if (travelDate == null) {
+        throw new IllegalArgumentException("Travel date is required.");
+    }
+    if (travelDate.isBefore(LocalDate.now())) {
+        throw new IllegalArgumentException("Travel date cannot be in the past.");
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java`
around lines 342 - 346, In validateTravelDate(LocalDate travelDate) distinguish
the null case from the past-date case: first check if travelDate == null and
throw an IllegalArgumentException (or NullPointerException if preferred) with a
clear message like "Travel date cannot be null.", then check
travelDate.isBefore(LocalDate.now()) and throw "Travel date cannot be in the
past."; update the thrown messages in the validateTravelDate method so callers
can tell whether the input was missing versus actually earlier than today.

58-70: Consolidate duplicate applicant visa retrieval methods.

findVisasByApplicant() and findVisasByApplicantId() both query visas by applicant ID and sort by updatedAt descending using identical implementation patterns. Consider removing one and updating callers (VisaViewController and UserViewController) to use a single method with a consistent name to reduce maintenance overhead.

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

In
`@src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java`
around lines 58 - 70, There are two duplicate methods in VisaService —
findVisasByApplicant(...) and findVisasByApplicantId(...) — both calling
visaRepository.findByApplicant_Id(..., Sort.by(Sort.Direction.DESC,
"updatedAt")) and mapping via visaMapper::toDTO; pick a single canonical method
name (e.g., keep findVisasByApplicantId(Long applicantId) with the existing
signature and behavior), remove the other duplicate method from VisaService, and
update all call sites (VisaViewController and UserViewController) to call the
retained method; ensure the retained method preserves the
Sort.by(Sort.Direction.DESC, "updatedAt") and List<VisaDTO> return type so
behavior and tests remain unchanged.
src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java (1)

89-95: Same overly broad exception mapping issue in apply flow.

applyForVisa() currently only throws IllegalArgumentException for travel date validation, but if additional validations are added later, they would also be incorrectly mapped to the travelDate field. Consider using a global error or distinct exception types for consistency with the edit flow.

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

In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`
around lines 89 - 95, The catch in the apply flow maps any
IllegalArgumentException from visaService.applyForVisa(createVisaDTO,
currentUserId) to the travelDate field, which is too broad; change the handling
in VisaViewController.apply (the try/catch around applyForVisa) to either catch
a more specific exception type thrown only for travel date validation or catch
IllegalArgumentException and map it to a global error (use
bindingResult.reject("error.global", e.getMessage())) instead of
bindingResult.rejectValue("travelDate", ...); keep the call to
prepareApplyModel(currentUserId, model) and return "visa/apply-form" as before.
🤖 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/visa/controller/VisaViewController.java`:
- Around line 146-156: The catch in VisaViewController around
visaService.updateVisa(id, updateVisaDTO, currentUserId) wrongly maps all
IllegalArgumentException instances to the travelDate field; update the handler
to distinguish error types by either catching specific exceptions thrown by
VisaService (e.g., create and throw a dedicated InvalidStateException for
status/check errors and catch it separately) or by adding
bindingResult.reject("globalError", e.getMessage()) for non-travel-date errors
so only the actual "Travel date cannot be in the past." message is bound to
"travelDate" while other messages (e.g., "Mismatched visa id.", "This
application can no longer be edited.") are reported as global errors; adjust the
catch blocks in VisaViewController accordingly and ensure
prepareApplyModel(...), model.addAttribute("isEdit", true) and the return
"visa/edit-form" logic remain intact.

---

Nitpick comments:
In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`:
- Around line 89-95: The catch in the apply flow maps any
IllegalArgumentException from visaService.applyForVisa(createVisaDTO,
currentUserId) to the travelDate field, which is too broad; change the handling
in VisaViewController.apply (the try/catch around applyForVisa) to either catch
a more specific exception type thrown only for travel date validation or catch
IllegalArgumentException and map it to a global error (use
bindingResult.reject("error.global", e.getMessage())) instead of
bindingResult.rejectValue("travelDate", ...); keep the call to
prepareApplyModel(currentUserId, model) and return "visa/apply-form" as before.

In
`@src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java`:
- Around line 342-346: In validateTravelDate(LocalDate travelDate) distinguish
the null case from the past-date case: first check if travelDate == null and
throw an IllegalArgumentException (or NullPointerException if preferred) with a
clear message like "Travel date cannot be null.", then check
travelDate.isBefore(LocalDate.now()) and throw "Travel date cannot be in the
past."; update the thrown messages in the validateTravelDate method so callers
can tell whether the input was missing versus actually earlier than today.
- Around line 58-70: There are two duplicate methods in VisaService —
findVisasByApplicant(...) and findVisasByApplicantId(...) — both calling
visaRepository.findByApplicant_Id(..., Sort.by(Sort.Direction.DESC,
"updatedAt")) and mapping via visaMapper::toDTO; pick a single canonical method
name (e.g., keep findVisasByApplicantId(Long applicantId) with the existing
signature and behavior), remove the other duplicate method from VisaService, and
update all call sites (VisaViewController and UserViewController) to call the
retained method; ensure the retained method preserves the
Sort.by(Sort.Direction.DESC, "updatedAt") and List<VisaDTO> return type so
behavior and tests remain unchanged.
🪄 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: 44d6085d-7e24-4a1a-b159-a34346c6d7af

📥 Commits

Reviewing files that changed from the base of the PR and between f17034a and b84820b.

📒 Files selected for processing (2)
  • src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java
  • src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java

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

🧹 Nitpick comments (2)
src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java (2)

222-229: Consider making this helper method private.

prepareApplyModel is only used within this controller and doesn't need to be public.

✏️ Suggested fix
-    public void prepareApplyModel(Long currentUserId, Model model) {
+    private void prepareApplyModel(Long currentUserId, Model model) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`
around lines 222 - 229, The helper method prepareApplyModel is declared public
but is only used inside VisaViewController; change its visibility to private to
encapsulate it. Locate the prepareApplyModel method (which calls
userService.findById(...).orElseThrow(...), adds "currentUser" using UserDTO and
sets "visaTypes" via VisaType.values() on the Model) and update its declaration
from public to private, leaving the method body and behavior unchanged.

146-161: Consider using exception types instead of message parsing.

The e.getMessage().contains("date") check is functional but fragile—if the error messages in VisaService change, this routing logic will break silently. A more robust approach would use distinct exception types.

💡 Alternative approach using custom exceptions

Create a TravelDateValidationException in the service layer:

// New exception class
public class TravelDateValidationException extends IllegalArgumentException {
    public TravelDateValidationException(String message) {
        super(message);
    }
}

Then in VisaService.validateTravelDate():

-throw new IllegalArgumentException("Travel date cannot be in the past.");
+throw new TravelDateValidationException("Travel date cannot be in the past.");

And in the controller:

-} catch (IllegalArgumentException e) {
-    if (e.getMessage().contains("date")) {
-        bindingResult.rejectValue("travelDate", "error.travelDate", e.getMessage());
-    } else {
-        bindingResult.reject("globalError",e.getMessage());
-    }
+} catch (TravelDateValidationException e) {
+    bindingResult.rejectValue("travelDate", "error.travelDate", e.getMessage());
+} catch (IllegalArgumentException e) {
+    bindingResult.reject("globalError", e.getMessage());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`
around lines 146 - 161, Replace fragile message-parsing in VisaViewController:
instead of checking e.getMessage().contains("date") after
visaService.updateVisa(id, updateVisaDTO, currentUserId) throws
IllegalArgumentException, introduce a specific custom exception (e.g.,
TravelDateValidationException extends IllegalArgumentException) thrown from the
service (e.g., VisaService.validateTravelDate/ updateVisa) for travel-date
errors, then catch TravelDateValidationException in the controller and call
bindingResult.rejectValue("travelDate", ...) for that catch branch while keeping
a separate catch(IllegalArgumentException e) for other errors that calls
bindingResult.reject("globalError", ...); update imports and remove the
message-contains logic.
🤖 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/visa/service/VisaService.java`:
- Around line 342-350: The null-check in validateTravelDate currently throws an
IllegalArgumentException with the message "Travel date cannot be in the past."
which is misleading; change the null branch to throw an IllegalArgumentException
with a clear message like "Travel date is required" (or similar), while keeping
the existing past-date check and its message for
travelDate.isBefore(LocalDate.now()) so callers can distinguish missing vs past
dates.

---

Nitpick comments:
In
`@src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java`:
- Around line 222-229: The helper method prepareApplyModel is declared public
but is only used inside VisaViewController; change its visibility to private to
encapsulate it. Locate the prepareApplyModel method (which calls
userService.findById(...).orElseThrow(...), adds "currentUser" using UserDTO and
sets "visaTypes" via VisaType.values() on the Model) and update its declaration
from public to private, leaving the method body and behavior unchanged.
- Around line 146-161: Replace fragile message-parsing in VisaViewController:
instead of checking e.getMessage().contains("date") after
visaService.updateVisa(id, updateVisaDTO, currentUserId) throws
IllegalArgumentException, introduce a specific custom exception (e.g.,
TravelDateValidationException extends IllegalArgumentException) thrown from the
service (e.g., VisaService.validateTravelDate/ updateVisa) for travel-date
errors, then catch TravelDateValidationException in the controller and call
bindingResult.rejectValue("travelDate", ...) for that catch branch while keeping
a separate catch(IllegalArgumentException e) for other errors that calls
bindingResult.reject("globalError", ...); update imports and remove the
message-contains logic.
🪄 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: 78c44227-6bb3-499f-92bf-396626ffae1a

📥 Commits

Reviewing files that changed from the base of the PR and between b84820b and 4ffd190.

📒 Files selected for processing (5)
  • src/main/java/org/example/visacasemanagementsystem/visa/controller/VisaViewController.java
  • src/main/java/org/example/visacasemanagementsystem/visa/service/VisaService.java
  • src/main/resources/templates/visa/apply-form.html
  • src/main/resources/templates/visa/edit-form.html
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceTest.java
✅ Files skipped from review due to trivial changes (1)
  • src/main/resources/templates/visa/apply-form.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/resources/templates/visa/edit-form.html
  • src/test/java/org/example/visacasemanagementsystem/visa/VisaServiceTest.java

@eeebbaandersson
eeebbaandersson merged commit 4c9363c into main Apr 14, 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.

Extend Global Exception Handler to catch Internal Server Error Implement VisaViewController and Thymeleaf templates for Case Management

1 participant