Skip to content

viktornoskire/exercise8#220

Open
viktornoskire wants to merge 12 commits intomainfrom
viktornoskire/exercise8
Open

viktornoskire/exercise8#220
viktornoskire wants to merge 12 commits intomainfrom
viktornoskire/exercise8

Conversation

@viktornoskire
Copy link
Copy Markdown

@viktornoskire viktornoskire commented Nov 26, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Hospital management system for tracking patients and admissions
    • Secure user authentication with role-based access control
    • Patient registration and admission management interface
    • Admin dashboard for managing patient records and admission details
  • Infrastructure

    • Application framework modernization with database persistence
    • Automated database migrations and schema management
    • Docker containerization support for deployment

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 26, 2025

Walkthrough

The pull request establishes a Spring Boot 3.5.7 application from scratch, replacing a basic Java project. Introduces MySQL database persistence, Spring Security with dev/prod profiles, web controllers with Thymeleaf templates, JPA entities (Patient/Admission), repositories, DTOs, and corresponding integration tests. Includes Docker Compose and SQL schema migration.

Changes

Cohort / File(s) Summary
Project & Build Configuration
pom.xml, docker-compose.yml
Migrated to Spring Boot 3.5.7 parent POM; updated project metadata (artifactId: exercise2025, version: 0.0.1-SNAPSHOT); replaced legacy plugins with native-maven-plugin and spring-boot-maven-plugin; added Spring Boot ecosystem dependencies (web, JPA, Thymeleaf, Security, validation, testcontainers, MySQL driver). Added docker-compose with MySQL 9.5.0 service on port 3306.
Spring Boot Application Entry Point
src/main/java/org/example/Exercise2025Application.java
Added @SpringBootApplication entry point with SpringApplication.run() main method.
Security Configuration
src/main/java/org/example/config/SecurityConfigDev.java, src/main/java/org/example/config/SecurityConfigProd.java
Added dev profile config with custom login page, CSRF disabled, URL-based authorization, and in-memory admin user (username: admin, password: password). Added prod profile config with HTTP Basic auth, requiring authentication for /api/\*, denying all other requests. Both expose PasswordEncoder (BCrypt).
Data Layer: Entities
src/main/java/org/example/entity/Patient.java, src/main/java/org/example/entity/Admission.java
Added JPA entities: Patient (pat_id PK, firstName/lastName/address/dateOfBirth/ssn, all with @NotNull except ssn); Admission (admission_id PK, dateIn/dateOut/diagnosis/department, many-to-one relationship to Patient).
Data Layer: Repositories
src/main/java/org/example/repository/AdmissionRepository.java, src/main/java/org/example/repository/PatientRepository.java
Added ListCrudRepository interfaces: AdmissionRepository with @Query methods getAdmissionById() and getAdmissionsByPatId(); PatientRepository with @Query method findBySsn().
Data Layer: Initialization & Config
src/main/java/org/example/config/DataInitializer.java
Added ApplicationRunner component (dev/default profiles) injecting both repositories; initializes three Patient and three Admission records on startup if --force-init flag provided or repositories empty.
Controllers
src/main/java/org/example/controller/WebController.java, src/main/java/org/example/controller/PatientsController.java, src/main/java/org/example/controller/AdmissionsController.java
Added three controllers with endpoints: WebController (/, /login, /logout); PatientsController (@PreAuthorize ADMIN, GET /patients, /patients/new, POST /api/patients/new, GET /api/patients/{id}); AdmissionsController (@PreAuthorize ADMIN, GET /admissions-menu, /admissions, /admissions/new, POST /api/admissions/new, /api/admissions/{id}/writeOut, GET /api/admissions/{pat_id}).
Data Transfer Objects (DTOs)
src/main/java/org/example/dto/PatientDTO.java, src/main/java/org/example/dto/AdmissionDTO.java, src/main/java/org/example/dto/NewAdmissionDTO.java, src/main/java/org/example/dto/PatientAdmissionRequest.java
Added four records: PatientDTO (firstName, lastName, address, dob), AdmissionDTO (admId, patId, dateIn, dateOut, diagnosis, department), NewAdmissionDTO (patSsn, diagnosis, department), PatientAdmissionRequest (patient, admission).
Application Configuration
src/main/resources/application.yml, src/main/resources/application-dev.yml
Added main application config: app name (exercise2025), virtual threads enabled, MySQL connection string, JPA validation DDL mode, dev profile activation. Added dev profile config: Docker Compose lifecycle, SQL/Hibernate formatting.
Database Schema & Migrations
src/main/resources/db/migration/V1__.sql
Added Flyway migration creating patient (pat_id PK, firstName/lastName/address/dateOfBirth/ssn) and admission (admission_id PK, dateIn/dateOut/diagnosis/department/pat_id FK) tables with constraints.
Test Data & Seed
src/test/resources/org/example/patients.sql
Added test seed data: three Patient rows and three Admission rows with established pat_id relationships.
HTML Templates
src/main/resources/templates/index.html, src/main/resources/templates/login.html, src/main/resources/templates/logout.html, src/main/resources/templates/error.html
src/main/resources/templates/patients/{patientsMenu,patients,newPatient}.html
src/main/resources/templates/admissions/{admissionMenu,admissions,newAdmission}.html
Added Thymeleaf templates: home page with navigation, login/logout forms, error page, patient management UI (menu, list, form), admission management UI (menu, list, form with SSN lookup and write-out button).
Static Assets
src/main/resources/static/style.css
Added global CSS: Corbel serif font, bold weight, 300px div container, h1 centered in blue, block display for anchors/labels.
Tests
src/test/java/org/example/ControllerTest.java, src/test/java/org/example/AdmissionRepositoryTest.java
Added integration tests: ControllerTest (@WebMvcTest with mocked repositories) verifying admission/patient endpoints with auth checks; AdmissionRepositoryTest (@DataJpaTest) verifying getAdmissionsByPatId() query correctness. Removed obsolete unit tests AppTest.java and AppIT.java.
Cleanup
(removed) src/main/java/org/example/App.java
Deleted legacy App class with simple "Hello There!" main entry point.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Browser/Client
    participant Controller as Spring Controller
    participant Security as SecurityConfig
    participant UserSvc as UserDetailsService
    participant Repository as Repository Layer
    participant Database as MySQL Database

    rect rgb(200, 220, 240)
    note over Client,Database: Authentication Flow (Dev Profile)
    Client->>Controller: GET /login
    Controller->>Client: Return login.html template
    Client->>Controller: POST /login (username: admin, password: password)
    Controller->>Security: Validate credentials via SecurityFilterChain
    Security->>UserSvc: Load user details (userDetailsService bean)
    UserSvc->>Security: Return in-memory admin user + role ADMIN
    Security->>Security: BCrypt password encoder validates password
    Security->>Client: Authenticated session created (redirect to /)
    end

    rect rgb(220, 240, 200)
    note over Client,Database: Authorized Request Flow
    Client->>Controller: GET /admissions (with admin session)
    Controller->>Security: `@PreAuthorize` checks role ADMIN
    Security->>Client: Authorization granted
    Controller->>Repository: admissionRepository.findAll()
    Repository->>Database: SELECT * FROM admission
    Database->>Repository: Return Admission rows (with patient references)
    Repository->>Controller: Return List<Admission>
    Controller->>Controller: Map Admission to AdmissionDTO
    Controller->>Client: Render admissions.html with DTOs
    end

    rect rgb(240, 230, 200)
    note over Client,Database: Unauthorized Request Flow
    Client->>Controller: GET /api/patients/new (without ADMIN role)
    Controller->>Security: `@PreAuthorize`("hasRole('ADMIN')") check fails
    Security->>Client: HTTP 403 Forbidden (access denied)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Security configurations (SecurityConfigDev.java, SecurityConfigProd.java): Verify dual-profile configs, CSRF/HTTP Basic settings, authorization rules, and in-memory user setup align with security requirements
  • Entity relationships (Patient.java, Admission.java, repositories): Confirm JPA mappings, foreign key constraints, lazy-loading behavior, and query correctness (especially getAdmissionsByPatId JPQL)
  • Controller authorization (AdmissionsController, PatientsController): Verify @PreAuthorize decorators are correctly applied to all endpoints requiring ADMIN role
  • Template form bindings (newPatient.html, newAdmission.html): Validate Thymeleaf th:field bindings match DTO/entity field names and POST endpoint expectations
  • Database schema migration (V1__.sql): Ensure primary keys, foreign key constraints, and NOT NULL constraints align with entity JPA annotations
  • Test coverage (ControllerTest, AdmissionRepositoryTest): Verify mock setup, authentication context (@WithMockUser), and assertions cover both success and failure paths
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch viktornoskire/exercise8

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Nitpick comments (32)
.gitignore (1)

1-3: Reconsider ignoring docker-compose.yml

Ignoring docker-compose.yml means each developer may run with a different, unversioned Docker setup; consider committing a default or docker-compose.example.yml and keeping only the local override ignored instead.

src/main/resources/static/style.css (1)

6-9: Global 300px width on all <div>s is very restrictive

div { width: 300px; margin: 0 auto; } constrains every container in the app to a narrow column and will look cramped on wider screens; consider moving this rule to a specific class (e.g. .form-container) and applying it only where needed.

src/main/resources/templates/error.html (1)

1-16: Error template message is 403-specific but file name is generic

Since error.html is usually rendered for many error statuses, always saying “You are not authorized to access this resource” may be confusing on 404/500 etc.; consider either making the message generic or adding status-specific views like 403.html for authorization failures.

src/main/resources/db/migration/V1__.sql (1)

1-24: Tighten nullability on admission.pat_id if every admission must have a patient

Currently pat_id is nullable even though there’s an FK to patient(pat_id); if the domain doesn’t allow “orphan” admissions, consider declaring pat_id BIGINT NOT NULL to enforce that invariant at the database level.

src/main/java/org/example/config/JavaMigrationsFlywayCustomizer.java (1)

1-15: Remove or implement the fully commented-out Flyway customizer

The whole JavaMigrationsFlywayCustomizer class is wrapped in a block comment, so it never compiles or runs but still clutters the config package; either delete this file for now or uncomment and implement it once you actually need custom Flyway configuration.

pom.xml (1)

12-31: Verify java.version and clean up placeholder POM metadata

Setting <java.version>25</java.version> assumes your build and runtime JDK are 25; if the exercise environment (or CI) is still on 21, you’ll want to align this property to avoid compiler/bytecode-level issues. Also consider either filling in or removing the empty <url>, <licenses>, <developers>, and <scm> sections so the POM reflects the project more accurately.

src/main/resources/templates/index.html (1)

2-2: Remove unused namespace declaration.

The sec namespace is declared but never used in this template.

-<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
+<html lang="en" xmlns:th="http://www.thymeleaf.org">
src/main/resources/templates/login.html (1)

5-5: Use consistent path format for stylesheet reference.

The CSS reference lacks a leading slash, which could cause path resolution issues. Use a Thymeleaf URL expression or add a leading slash for consistency with other templates.

-    <link rel="stylesheet" href="login.css">
+    <link rel="stylesheet" th:href="@{/login.css}">
src/main/java/org/example/dto/NewAdmissionDTO.java (1)

4-5: Consider adding validation annotations.

As a DTO that accepts user input for creating admissions, this record should include validation annotations (e.g., @NotBlank, @Pattern for SSN format) to ensure data integrity and provide meaningful error messages.

 package org.example.dto;

+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;

-public record NewAdmissionDTO(String patSsn, String diagnosis, String department) {
+public record NewAdmissionDTO(
+    @NotBlank(message = "Patient SSN is required")
+    @Pattern(regexp = "\\d{8}-\\d{4}", message = "Invalid SSN format")
+    String patSsn,
+    @NotBlank(message = "Diagnosis is required")
+    String diagnosis,
+    @NotBlank(message = "Department is required")
+    String department
+) {
src/main/resources/templates/patients/patients.html (1)

12-16: Use semantic HTML structure for patient names.

Using <p> tags with inline display to create horizontal layout is semantically incorrect. Consider using <span> elements or proper CSS layout techniques.

     <ul th:each="patient : ${patients}">
         <li>
-            <p th:text="${patient.firstName()}" style="display: inline"></p>
-            <p style="display: inline"></p>
-            <p th:text="${patient.lastName()}" style="display: inline"></p>
+            <span th:text="${patient.firstName()}"></span>
+            <span th:text="' ' + ${patient.lastName()}"></span>
         </li>
     </ul>
src/main/java/org/example/dto/PatientDTO.java (1)

5-6: Use explicit field name instead of abbreviation.

The field name dob is abbreviated. Consider using dateOfBirth for better code readability and maintainability.

-public record PatientDTO(String firstName, String lastName, String address, LocalDate dob) {
+public record PatientDTO(String firstName, String lastName, String address, LocalDate dateOfBirth) {
src/main/resources/templates/patients/newPatient.html (1)

9-17: Add CSRF token (if enabled) and label the submit action

If Spring Security CSRF protection is enabled, this POST will need a CSRF token. Also, giving the submit control an explicit label improves clarity.

Suggested change:

-    <form method="post" th:action="@{/api/patients/new}" th:object="${patient}">
+    <form method="post" th:action="@{/api/patients/new}" th:object="${patient}">
@@
-        <label for="ssn">Social Security Number:</label><input type="text" id="ssn" th:field="*{ssn}">
-        <input type="submit">
+        <label for="ssn">Social Security Number:</label><input type="text" id="ssn" th:field="*{ssn}">
+        <!-- Only needed if CSRF is enabled in Spring Security -->
+        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
+        <input type="submit" value="Create patient">

Please verify how CSRF is configured; if it is disabled for these endpoints, the hidden field can be omitted.

src/main/java/org/example/Exercise2025Application.java (1)

6-11: Spring Boot application entry point looks good (scanBasePackages optional)

The main class is a standard Spring Boot bootstrap and should work as expected. Since this class is already in org.example, the explicit scanBasePackages = "org.example" is redundant but not harmful—feel free to drop it if you prefer the default component scan.

src/main/java/org/example/controller/WebController.java (1)

7-23: MVC mappings are fine; consider removing unused Model parameters and align /logout with security config

The three GET handlers cleanly route to index, login, and logout views. The Model parameter isn’t used in any of them, so it can be removed to reduce noise. Also, ensure your Spring Security configuration’s logout handling (usually POST /logout by default) is consistent with serving a GET /logout view.

Example cleanup:

-    @GetMapping("/")
-    public String index(Model model) {
+    @GetMapping("/")
+    public String index() {
         return "index";
     }
@@
-    @GetMapping("/login")
-    public String login(Model model) {
+    @GetMapping("/login")
+    public String login() {
         return "login";
     }
@@
-    @GetMapping("/logout")
-    public String logout(Model model) {
+    @GetMapping("/logout")
+    public String logout() {
         return "logout";
     }

Please double‑check that your security configuration and templates work together as intended for the logout flow.

src/main/java/org/example/repository/AdmissionRepository.java (2)

11-14: Redundant query method.

getAllAdmissions() duplicates the inherited findAll() method from ListCrudRepository. Consider removing this custom query and using the inherited method directly.


16-19: Consider using derived query methods or inherited findById.

getAdmissionById replicates functionality already provided by ListCrudRepository.findById(Long id). If custom return type (non-Optional) is intentional, consider using Spring Data's derived query: Admission findByAdmission_id(Long id) — though note this returns null when not found rather than Optional.

src/main/java/org/example/repository/PatientRepository.java (3)

7-12: Simplify type references and consider using inherited method.

findPatientById replicates findById() from ListCrudRepository. Also, java.lang.Long can be simplified to Long.

-public interface PatientRepository extends ListCrudRepository<Patient, java.lang.Long> {
+public interface PatientRepository extends ListCrudRepository<Patient, Long> {
 
-	@Query("""
-					select pat from Patient pat where  pat.pat_id = :id
-					""")
-	public Patient findPatientById(java.lang.Long id);

If you need a non-Optional return, use derived query naming: Patient findByPatId(Long id).


14-17: Fragile name matching with CONCAT.

The CONCAT-based comparison is sensitive to exact spacing and case. Consider matching first and last name separately for robustness:

 	@Query("""
-					select pat from Patient pat where concat(pat.firstName, ' ', pat.lastName) = concat(:first, ' ', :last)
+					select pat from Patient pat where pat.firstName = :first and pat.lastName = :last
 				""")
 	public Patient findByFirstAndLastName(String first, String last);

Or use Spring Data's derived query: Patient findByFirstNameAndLastName(String firstName, String lastName).


19-22: Consider using derived query method.

Spring Data JPA can auto-generate this query from the method name. The explicit @Query is unnecessary.

-	@Query("""
-				select pat from Patient pat where pat.ssn = :ssn
-				""")
-	public Patient findBySsn(String ssn);
+	Patient findBySsn(String ssn);
src/test/java/org/example/ControllerTest.java (1)

40-52: Consider adding response body assertions.

The test verifies status but doesn't assert the response content. For the /admissions endpoint serving a Thymeleaf view, consider verifying the model attribute or view name.

 		mockMvc.perform(get("/admissions"))
-						.andExpect(status().isOk());
+						.andExpect(status().isOk())
+						.andExpect(view().name("admissions/admissions"))
+						.andExpect(model().attributeExists("admissions"));

You'll need to import:

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
src/main/java/org/example/controller/AdmissionsController.java (2)

21-24: Misleading method name.

displayPatientMenu handles the admissions menu, not patients. Rename for clarity.

 	@GetMapping("/admissions-menu")
-	public String displayPatientMenu(Model model) {
+	public String displayAdmissionsMenu(Model model) {
 		return "admissions/admissionMenu";
 	}

16-17: Prefer constructor injection over field injection.

Field injection with @Autowired makes testing harder and hides dependencies. Constructor injection is the recommended approach in Spring.

 @Controller
 @PreAuthorize("hasRole('ADMIN')")
 public class AdmissionsController {
 
-	@Autowired
-	AdmissionRepository admissionRepository;
+	private final AdmissionRepository admissionRepository;
+
+	public AdmissionsController(AdmissionRepository admissionRepository) {
+		this.admissionRepository = admissionRepository;
+	}

This pattern is already used in HospitalController (see lines 17-21 in that file).

src/main/java/org/example/entity/Admission.java (4)

11-11: Consider Java naming conventions for the ID field.

admission_id uses snake_case which is unconventional in Java. Consider admissionId with an explicit @Column(name = "admission_id") annotation to maintain database column mapping.

 	@Id
 	@GeneratedValue(strategy = GenerationType.IDENTITY)
-	private java.lang.Long admission_id;
+	@Column(name = "admission_id")
+	private Long admissionId;

This would require updating the getter and JPQL queries accordingly.


19-21: Make the patient field private.

The patient field lacks an access modifier, making it package-private. For proper encapsulation, it should be private.

 	@ManyToOne
 	@JoinColumn(name = "pat_id")
-	Patient patient;
+	private Patient patient;

25-30: LocalDateTime.now() in constructor reduces testability.

Calling LocalDateTime.now() directly makes unit testing time-dependent behavior difficult. Consider accepting a Clock or the date as a constructor parameter for testability.

For improved testability:

public Admission(Patient patient, String diagnosis, String department, LocalDateTime dateIn) {
    this.patient = patient;
    this.diagnosis = diagnosis;
    this.department = department;
    this.dateIn = dateIn;
}

Then callers can pass LocalDateTime.now() in production and fixed values in tests.


59-61: Unconventional setter signature.

setDateOut() with no parameter is unexpected for a setter. Consider either renaming to markDischarged() or writeOut() to reflect the action, or accepting a LocalDateTime parameter for standard setter behavior.

-	public void setDateOut() {
-		this.dateOut = LocalDateTime.now();
+	public void markDischarged() {
+		this.dateOut = LocalDateTime.now();
 	}
+
+	public void setDateOut(LocalDateTime dateOut) {
+		this.dateOut = dateOut;
+	}
src/main/java/org/example/config/SecurityConfig.java (2)

51-63: Avoid putting the encoded password into the Authentication credentials

The custom AuthenticationManager currently sets the principal to username and the credentials to the stored (encoded) password. Best practice is to use the UserDetails as principal and set credentials to null after verification so secrets are not kept around in the SecurityContext.

-        UserDetails user = userDetailsService.loadUserByUsername(username);
-
-        if (passwordEncoder.matches(rawPassword, user.getPassword())) {
-            return new UsernamePasswordAuthenticationToken(username, user.getPassword(), user.getAuthorities());
-        }
+        UserDetails user = userDetailsService.loadUserByUsername(username);
+
+        if (passwordEncoder.matches(rawPassword, user.getPassword())) {
+            // Principal is the full UserDetails; credentials cleared after successful auth
+            return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
+        }
         throw new BadCredentialsException("Bad credentials");

You can also drop the unused AuthenticationManager authenticationManager parameter from the web bean method to reduce noise.


41-48: Hard‑coded in‑memory admin user should be clearly dev‑only

Defining an admin / password user in InMemoryUserDetailsManager is fine for an exercise or dev profile, but it’s unsafe in any shared or production‑like environment. At minimum, restrict this bean to a dev profile or externalize the username/password into configuration.

Example:

-@Configuration
-public class SecurityConfig {
+@Configuration
+@Profile({"dev", "default"})
+public class SecurityConfig {

Or create a separate dev‑only config for the in‑memory user and rely on a real user store elsewhere.

src/main/java/org/example/controller/PatientsController.java (1)

12-18: Prefer constructor injection and private final repository field

The controller logic looks fine, but the field injection with package‑private visibility is non‑idiomatic compared to your other controllers.

Consider aligning with HospitalController by using constructor injection and making the dependency private final:

-@Controller
-@PreAuthorize("hasRole('ADMIN')")
-public class PatientsController {
-
-    @Autowired
-    PatientRepository patientRepository;
+@Controller
+@PreAuthorize("hasRole('ADMIN')")
+public class PatientsController {
+
+    private final PatientRepository patientRepository;
+
+    public PatientsController(PatientRepository patientRepository) {
+        this.patientRepository = patientRepository;
+    }

This improves testability and avoids reflection‑based field injection.

src/main/java/org/example/config/DataInitializer.java (1)

16-43: Seed behavior tied to dev and default profiles—confirm this matches your environments

The initializer correctly guards against reseeding by checking admissionRepository.count() and supports a --force-init flag. However, annotating it with @Profile({"dev", "default"}) means it will also run whenever no profile is explicitly set (Spring’s default profile).

If you intend the sample data strictly for local development, consider narrowing to just dev (and maybe test) or ensuring production runs with a distinct profile:

-@Profile({"dev", "default"})
+@Profile({"dev"})
 public class DataInitializer implements ApplicationRunner {

Alternatively, standardize on a dedicated seeding profile (e.g., local) to avoid surprises.

src/main/java/org/example/entity/Patient.java (1)

31-37: Constructor parameter order is surprising and easy to misuse

The Patient constructor takes (LocalDate dateOfBirth, String address, String lastName, String firstName, String ssn), which is an unusual order compared to how you construct DTOs and use the fields elsewhere (usually firstName, lastName, address, dob).

To improve readability and reduce the chance of mixing arguments in future call sites, consider either:

  • Reordering the parameters to (String firstName, String lastName, String address, LocalDate dateOfBirth, String ssn), updating the initializer accordingly, or
  • Replacing this multi‑arg constructor with a builder/static factory and keeping the no‑arg constructor for JPA only.

Not urgent given the limited usage, but it will make the API clearer as the model grows.

src/main/java/org/example/controller/HospitalController.java (1)

66-77: Minor cleanup: simplify null‑check and remove redundant else

The handleNew method is functionally correct, but the else is redundant because the if branch throws.

You can simplify slightly:

-    public String handleNew(@ModelAttribute NewAdmissionDTO newAdm) {
-
-        Patient pat;
-        pat = patientRepository.findBySsn(newAdm.patSsn());
-        if (pat == null) {
-            throw new IllegalArgumentException("Patient with SSN " + newAdm.patSsn() + " not found");
-        } else {
-            admissionRepository.save(new Admission(pat, newAdm.diagnosis(), newAdm.department()));
-        }
+    public String handleNew(@ModelAttribute NewAdmissionDTO newAdm) {
+        Patient pat = patientRepository.findBySsn(newAdm.patSsn());
+        if (pat == null) {
+            throw new IllegalArgumentException("Patient with SSN " + newAdm.patSsn() + " not found");
+        }
+        admissionRepository.save(new Admission(pat, newAdm.diagnosis(), newAdm.department()));

This keeps the control flow straightforward and avoids an unnecessary branch.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8618163 and e07c96e.

📒 Files selected for processing (37)
  • .gitignore (1 hunks)
  • pom.xml (1 hunks)
  • src/main/java/org/example/App.java (0 hunks)
  • src/main/java/org/example/Exercise2025Application.java (1 hunks)
  • src/main/java/org/example/config/DataInitializer.java (1 hunks)
  • src/main/java/org/example/config/JavaMigrationsFlywayCustomizer.java (1 hunks)
  • src/main/java/org/example/config/SecurityConfig.java (1 hunks)
  • src/main/java/org/example/controller/AdmissionsController.java (1 hunks)
  • src/main/java/org/example/controller/HospitalController.java (1 hunks)
  • src/main/java/org/example/controller/PatientsController.java (1 hunks)
  • src/main/java/org/example/controller/WebController.java (1 hunks)
  • src/main/java/org/example/dto/AdmissionDTO.java (1 hunks)
  • src/main/java/org/example/dto/Handler.java (1 hunks)
  • src/main/java/org/example/dto/NewAdmissionDTO.java (1 hunks)
  • src/main/java/org/example/dto/PatientDTO.java (1 hunks)
  • src/main/java/org/example/entity/Admission.java (1 hunks)
  • src/main/java/org/example/entity/Patient.java (1 hunks)
  • src/main/java/org/example/repository/AdmissionRepository.java (1 hunks)
  • src/main/java/org/example/repository/PatientRepository.java (1 hunks)
  • src/main/resources/application-dev.yml (1 hunks)
  • src/main/resources/application.yml (1 hunks)
  • src/main/resources/db/migration/V1__.sql (1 hunks)
  • src/main/resources/static/style.css (1 hunks)
  • src/main/resources/templates/admissions/admissionMenu.html (1 hunks)
  • src/main/resources/templates/admissions/admissions.html (1 hunks)
  • src/main/resources/templates/admissions/newAdmission.html (1 hunks)
  • src/main/resources/templates/error.html (1 hunks)
  • src/main/resources/templates/index.html (1 hunks)
  • src/main/resources/templates/login.html (1 hunks)
  • src/main/resources/templates/logout.html (1 hunks)
  • src/main/resources/templates/patients/newPatient.html (1 hunks)
  • src/main/resources/templates/patients/patients.html (1 hunks)
  • src/main/resources/templates/patients/patientsMenu.html (1 hunks)
  • src/test/java/org/example/AppIT.java (0 hunks)
  • src/test/java/org/example/AppTest.java (0 hunks)
  • src/test/java/org/example/ControllerTest.java (1 hunks)
  • src/test/resources/org/example/patients.sql (1 hunks)
💤 Files with no reviewable changes (3)
  • src/test/java/org/example/AppTest.java
  • src/main/java/org/example/App.java
  • src/test/java/org/example/AppIT.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-16T13:56:11.263Z
Learnt from: jennymakki
Repo: fungover/exercise2025 PR: 204
File: src/main/java/org/example/config/SecurityConfig.java:19-24
Timestamp: 2025-11-16T13:56:11.263Z
Learning: The repository fungover/exercise2025 is an exercise/learning project where hard-coded credentials and other shortcuts for ease of testing are acceptable and intentional.

Applied to files:

  • src/main/resources/application.yml
  • pom.xml
📚 Learning: 2025-11-21T16:16:54.793Z
Learnt from: LolloGro
Repo: fungover/exercise2025 PR: 211
File: src/main/resources/templates/listOfBooks.html:11-12
Timestamp: 2025-11-21T16:16:54.793Z
Learning: In the repository fungover/exercise2025, the Author DTO uses NotEmpty validation on firstName and lastName fields, and BookDto uses NotNull validation on the author field. This validation architecture ensures that books always have complete author information, making null-safety checks in Thymeleaf templates unnecessary.

Applied to files:

  • pom.xml
🧬 Code graph analysis (5)
src/main/java/org/example/controller/WebController.java (3)
src/main/java/org/example/controller/AdmissionsController.java (1)
  • Controller (12-39)
src/main/java/org/example/controller/HospitalController.java (1)
  • Controller (15-95)
src/main/java/org/example/controller/PatientsController.java (1)
  • Controller (12-40)
src/main/java/org/example/entity/Admission.java (1)
src/main/java/org/example/entity/Patient.java (1)
  • Entity (11-85)
src/main/java/org/example/controller/AdmissionsController.java (3)
src/main/java/org/example/controller/HospitalController.java (1)
  • Controller (15-95)
src/main/java/org/example/controller/PatientsController.java (1)
  • Controller (12-40)
src/main/java/org/example/controller/WebController.java (1)
  • Controller (7-25)
src/main/java/org/example/entity/Patient.java (1)
src/main/java/org/example/entity/Admission.java (1)
  • Entity (7-70)
src/main/java/org/example/controller/HospitalController.java (3)
src/main/java/org/example/controller/AdmissionsController.java (1)
  • Controller (12-39)
src/main/java/org/example/controller/PatientsController.java (1)
  • Controller (12-40)
src/main/java/org/example/controller/WebController.java (1)
  • Controller (7-25)
🔇 Additional comments (5)
src/main/resources/application-dev.yml (1)

1-9: Dev JPA and docker-compose settings look appropriate

Limiting SQL logging and docker-compose lifecycle management to the application-dev.yml profile is a good fit for local debugging; no changes needed here.

pom.xml (1)

32-115: Spring Boot, Flyway, MySQL, and Testcontainers dependency setup looks coherent

The combination of Spring Boot starters, Flyway (flyway-core + flyway-mysql), MySQL runtime driver, docker-compose support, and Testcontainers modules is consistent with a modern Boot 3.x app; just make sure the chosen Spring Boot version and plugin setup build and run cleanly in your current environment (IDE + CI).

src/main/java/org/example/dto/AdmissionDTO.java (1)

1-6: AdmissionDTO record definition looks appropriate

The DTO fields and types match the controller mappings and view usage; using a record here is concise and suitable.

src/main/resources/templates/admissions/admissions.html (1)

11-24: The review comment incorrectly identifies a non-existent syntax error

The th:style attribute on line 15 (displayed as line 11-24 in the review) DOES contain the closing } in the expression. The actual code is:

th:style="${admission.dateOut == null}? 'display: block;' : 'display: none;'"

This is valid Thymeleaf ternary syntax. The closing brace properly terminates the ${...} expression before the ? operator. The proposed diff to add a space before ? is unnecessary because there is no missing brace to fix.

The optional refactor suggestion to use th:if instead of inline styles remains a valid stylistic recommendation but should not be framed as a syntax error fix.

Likely an incorrect or invalid review comment.

src/main/java/org/example/controller/HospitalController.java (1)

27-32: I'll verify the review comment by examining the HospitalController file to confirm the current state of the code and the validity of the concerns raised.
<function_calls>


#!/bin/bash

Locate the HospitalController file

find . -type f -name "HospitalController.java"


</function_calls>
<function_calls>


#!/bin/bash

Read the HospitalController file with line numbers

cat -n src/main/java/org/example/controller/HospitalController.java


</function_calls>

Comment on lines +30 to +37
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/").permitAll()
.requestMatchers("**.css").permitAll()
.requestMatchers("/login").permitAll()
.requestMatchers("/api/**").authenticated()
.requestMatchers("/logout").authenticated()
.requestMatchers("/api/admissions/new").hasRole("ADMIN")
.anyRequest().authenticated());
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
# Locate and read SecurityConfig.java
fd -t f "SecurityConfig.java" | head -5

Repository: fungover/exercise2025


<shell_result>
Repository: fungover/exercise2025
Exit code: 0

stdout:

src/main/java/org/example/config/SecurityConfig.java

</shell_result>


Script executed:

#!/bin/bash
# Read SecurityConfig.java to verify the matcher ordering
cat -n src/main/java/org/example/config/SecurityConfig.java

Repository: fungover/exercise2025


<shell_result>
Repository: fungover/exercise2025
Exit code: 0

stdout:

     1	package org.example.config;
     2	
     3	import org.springframework.context.annotation.Bean;
     4	import org.springframework.context.annotation.Configuration;
     5	import org.springframework.security.authentication.AuthenticationManager;
     6	import org.springframework.security.authentication.BadCredentialsException;
     7	import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
     8	import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
     9	import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    10	import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
    11	import org.springframework.security.core.userdetails.User;
    12	import org.springframework.security.core.userdetails.UserDetails;
    13	import org.springframework.security.core.userdetails.UserDetailsService;
    14	import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    15	import org.springframework.security.crypto.password.PasswordEncoder;
    16	import org.springframework.security.provisioning.InMemoryUserDetailsManager;
    17	import org.springframework.security.web.SecurityFilterChain;
    18	
    19	@Configuration
    20	@EnableMethodSecurity
    21	public class SecurityConfig {
    22	
    23		@Bean
    24		public SecurityFilterChain web(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
    25			http
    26							.formLogin(formLogin -> formLogin.defaultSuccessUrl("/", true).loginPage("/login"))
    27							.logout(logout -> {
    28								logout.logoutSuccessUrl("/");
    29							})
    30							.authorizeHttpRequests((authorize) -> authorize
    31											.requestMatchers("/").permitAll()
    32											.requestMatchers("**.css").permitAll()
    33											.requestMatchers("/login").permitAll()
    34											.requestMatchers("/api/**").authenticated()
    35											.requestMatchers("/logout").authenticated()
    36											.requestMatchers("/api/admissions/new").hasRole("ADMIN")
    37											.anyRequest().authenticated());
    38			return http.build();
    39		}
    40	
    41		@Bean
    42		public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
    43			UserDetails admin = User.withUsername("admin")
    44							.password(passwordEncoder.encode("password"))
    45							.roles("ADMIN", "DEV", "USER")
    46							.build();
    47			return new InMemoryUserDetailsManager(admin);
    48		}
    49	
    50		@Bean
    51		public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
    52			return authentication -> {
    53				String username = authentication.getName();
    54				String rawPassword = authentication.getCredentials().toString();
    55	
    56				UserDetails user = userDetailsService.loadUserByUsername(username);
    57	
    58				if (passwordEncoder.matches(rawPassword, user.getPassword())) {
    59					return new UsernamePasswordAuthenticationToken(username, user.getPassword(), user.getAuthorities());
    60				}
    61				throw new BadCredentialsException("Bad credentials");
    62			};
    63		}
    64	
    65		@Bean
    66		public PasswordEncoder passwordEncoder() {
    67			return new BCryptPasswordEncoder();
    68		}
    69	
    70	}

</shell_result>


Script executed:

#!/bin/bash
# Search for Spring Security documentation or comments about matcher ordering
rg -i "authorizeHttpRequests|requestMatchers" -A 5 -B 5 --type java | head -100

Repository: fungover/exercise2025


<shell_result>
Repository: fungover/exercise2025
Exit code: 0

stdout:

src/main/java/org/example/config/SecurityConfig.java-		http
src/main/java/org/example/config/SecurityConfig.java-						.formLogin(formLogin -> formLogin.defaultSuccessUrl("/", true).loginPage("/login"))
src/main/java/org/example/config/SecurityConfig.java-						.logout(logout -> {
src/main/java/org/example/config/SecurityConfig.java-							logout.logoutSuccessUrl("/");
src/main/java/org/example/config/SecurityConfig.java-						})
src/main/java/org/example/config/SecurityConfig.java:						.authorizeHttpRequests((authorize) -> authorize
src/main/java/org/example/config/SecurityConfig.java:										.requestMatchers("/").permitAll()
src/main/java/org/example/config/SecurityConfig.java:										.requestMatchers("**.css").permitAll()
src/main/java/org/example/config/SecurityConfig.java:										.requestMatchers("/login").permitAll()
src/main/java/org/example/config/SecurityConfig.java:										.requestMatchers("/api/**").authenticated()
src/main/java/org/example/config/SecurityConfig.java:										.requestMatchers("/logout").authenticated()
src/main/java/org/example/config/SecurityConfig.java:										.requestMatchers("/api/admissions/new").hasRole("ADMIN")
src/main/java/org/example/config/SecurityConfig.java-										.anyRequest().authenticated());
src/main/java/org/example/config/SecurityConfig.java-		return http.build();
src/main/java/org/example/config/SecurityConfig.java-	}
src/main/java/org/example/config/SecurityConfig.java-
src/main/java/org/example/config/SecurityConfig.java-	@Bean

</shell_result>


Script executed:

#!/bin/bash
# Verify Spring Security AntPath pattern matching behavior
# Check if there are any tests or documentation about this
find . -type f -name "*.java" -o -name "*.md" -o -name "*.txt" | xargs rg -l "AntPath|matcher.*order" 2>/dev/null | head -10

Repository: fungover/exercise2025


<shell_result>
Repository: fungover/exercise2025
Exit code: 0

</shell_result>


Based on my verification of the code and Spring Security's behavior:

The matcher ordering bug is confirmed—/api/admissions/new is not restricted to ADMIN users

In Spring Security's authorizeHttpRequests(), matchers are evaluated in order and the first match applies. Currently, /api/** on line 34 matches /api/admissions/new before the specific hasRole("ADMIN") rule on line 36 is reached, allowing any authenticated user to access this endpoint.

The proposed fix is correct: move the specific /api/admissions/new matcher before the generic /api/** matcher. The CSS pattern adjustment from "**.css" to "/**/*.css" is also valid—more explicit and idiomatic for AntPath patterns, though both technically function.

-            .authorizeHttpRequests((authorize) -> authorize
-                            .requestMatchers("/").permitAll()
-                            .requestMatchers("**.css").permitAll()
-                            .requestMatchers("/login").permitAll()
-                            .requestMatchers("/api/**").authenticated()
-                            .requestMatchers("/logout").authenticated()
-                            .requestMatchers("/api/admissions/new").hasRole("ADMIN")
-                            .anyRequest().authenticated());
+            .authorizeHttpRequests((authorize) -> authorize
+                            .requestMatchers("/").permitAll()
+                            .requestMatchers("/**/*.css").permitAll()
+                            .requestMatchers("/login").permitAll()
+                            .requestMatchers("/api/admissions/new").hasRole("ADMIN")
+                            .requestMatchers("/api/**").authenticated()
+                            .requestMatchers("/logout").authenticated()
+                            .anyRequest().authenticated());
🤖 Prompt for AI Agents
In src/main/java/org/example/config/SecurityConfig.java around lines 30 to 37,
the authorization matchers are ordered so the generic "/api/**" rule precedes
the specific "/api/admissions/new" rule and thus the specific ADMIN restriction
is never applied; also the CSS pattern should be more explicit. Fix by moving
the .requestMatchers("/api/admissions/new").hasRole("ADMIN") line to appear
before .requestMatchers("/api/**").authenticated() and change the CSS matcher
from "**.css" to "/**/*.css" so the specific rules are evaluated before the
generic one and the CSS pattern uses the idiomatic AntPath form.

Comment on lines +5 to +6
public record Handler(Patient patient, NewAdmissionDTO admission) {
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Rename to a more descriptive name.

The name "Handler" is misleading and doesn't convey the purpose of this DTO. Consider a more descriptive name like PatientAdmissionRequest or NewAdmissionWithPatient that clearly indicates this record bundles a patient with admission data.

-public record Handler(Patient patient, NewAdmissionDTO admission) {
+public record PatientAdmissionRequest(Patient patient, NewAdmissionDTO admission) {
📝 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
public record Handler(Patient patient, NewAdmissionDTO admission) {
}
public record PatientAdmissionRequest(Patient patient, NewAdmissionDTO admission) {
}
🤖 Prompt for AI Agents
In src/main/java/org/example/dto/Handler.java around lines 5-6, the record name
"Handler" is non-descriptive; rename the record to a clearer name such as
PatientAdmissionRequest (or NewAdmissionWithPatient) and update the file name to
match (PatientAdmissionRequest.java), then update all references/imports/usages
across the codebase (controllers, services, tests, JSON serialization keys if
applicable) to the new type name so compilation and runtime serialization
continue to work.

Comment thread src/main/java/org/example/repository/AdmissionRepository.java
Comment thread src/main/resources/application.yml Outdated
Comment thread src/main/resources/templates/admissions/admissionMenu.html Outdated
Comment thread src/main/resources/templates/logout.html Outdated
Comment thread src/main/resources/templates/patients/patients.html Outdated
Comment on lines +10 to +14
<h1>Patients Menu</h1>
<a href="/patients" th:text="@{all patients}"></a>
<a href="/patients/new" th:text="@{new patient}"></a>
<a href="/patients/find" th:text="@{find patient}"></a>
<a href="/patients/delete" th:text="@{delete patient}"></a>
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

# Find and examine the patientsMenu.html file
find . -name "patientsMenu.html" -type f 2>/dev/null | head -20

Repository: fungover/exercise2025


<shell_result>
Repository: fungover/exercise2025
Exit code: 0

stdout:

./src/main/resources/templates/patients/patientsMenu.html

</shell_result>


Script executed:

#!/bin/bash

# Read the patientsMenu.html file
cat -n ./src/main/resources/templates/patients/patientsMenu.html

echo "---"

# Search for similar templates (admissionMenu mentioned in scratchpad)
find . -name "*admissionMenu.html" -type f 2>/dev/null

Repository: fungover/exercise2025


<shell_result>
Repository: fungover/exercise2025
Exit code: 0

stdout:

     1	<!DOCTYPE html>
     2	<html lang="en" xmlns:th="http://www.thymeleaf.org">
     3	<head>
     4	    <meta charset="UTF-8">
     5	    <title>Patients Menu</title>
     6	    <link rel="stylesheet" href="/style.css">
     7	</head>
     8	<body>
     9	<div>
    10	    <h1>Patients Menu</h1>
    11	    <a href="/patients" th:text="@{all patients}"></a>
    12	    <a href="/patients/new" th:text="@{new patient}"></a>
    13	    <a href="/patients/find" th:text="@{find patient}"></a>
    14	    <a href="/patients/delete" th:text="@{delete patient}"></a>
    15	</div>
    16	</body>
    17	</html>---
./src/main/resources/templates/admissions/admissionMenu.html

</shell_result>


Script executed:

#!/bin/bash

# Read admissionMenu.html to compare patterns
cat -n ./src/main/resources/templates/admissions/admissionMenu.html

echo "---"

# Search for other th:text usage patterns in templates
rg -n "th:text" ./src/main/resources/templates/ -A1 -B1 | head -60

Repository: fungover/exercise2025


<shell_result>
Repository: fungover/exercise2025
Exit code: 0

stdout:

     1	<!DOCTYPE html>
     2	<html lang="en" xmlns:th="http://www.thymeleaf.org">
     3	<head>
     4	    <meta charset="UTF-8">
     5	    <title>Admission Menu</title>
     6	    <link rel="stylesheet" href="/style.css">
     7	</head>
     8	<body>
     9	<div>
    10	    <h1>Admission Menu</h1>
    11	    <a href="/admissions" th:text="@{all admissions}" ></a>
    12	    <a href="/admissions/new" th:text="@{new admission}"></a>
    13	    <a href="/admissions/find" th:text="@{find admission}"></a>
    14	    <a href="/admissions/delete" th:text="@{delete admission}"></a>
    15	</div>
    16	</body>
    17	</html>---
./src/main/resources/templates/patients/patientsMenu.html-10-    <h1>Patients Menu</h1>
./src/main/resources/templates/patients/patientsMenu.html:11:    <a href="/patients" th:text="@{all patients}"></a>
./src/main/resources/templates/patients/patientsMenu.html:12:    <a href="/patients/new" th:text="@{new patient}"></a>
./src/main/resources/templates/patients/patientsMenu.html:13:    <a href="/patients/find" th:text="@{find patient}"></a>
./src/main/resources/templates/patients/patientsMenu.html:14:    <a href="/patients/delete" th:text="@{delete patient}"></a>
./src/main/resources/templates/patients/patientsMenu.html-15-</div>
--
./src/main/resources/templates/patients/patients.html-12-        <li>
./src/main/resources/templates/patients/patients.html:13:            <p th:text="${patient.firstName()}" style="display: inline"></p>
./src/main/resources/templates/patients/patients.html-14-            <p style="display: inline"></p>
./src/main/resources/templates/patients/patients.html:15:            <p th:text="${patient.lastName()}" style="display: inline"></p>
./src/main/resources/templates/patients/patients.html-16-        </li>
--
./src/main/resources/templates/index.html-12-    </h1>
./src/main/resources/templates/index.html:13:    <a href="/patients-menu" th:text="patients"></a>
./src/main/resources/templates/index.html:14:    <a href="/admissions-menu" th:text="admissions"></a>
./src/main/resources/templates/index.html-15-</div>
--
./src/main/resources/templates/admissions/admissions.html-13-            <ul style="list-style-type:disc; margin-top:2em;">
./src/main/resources/templates/admissions/admissions.html:14:                <li><p th:text="${'Admission ID:' + admission.admId}"></p>
./src/main/resources/templates/admissions/admissions.html-15-                    <form th:style="${admission.dateOut == null}? 'display: block;' : 'display: none;'"
--
./src/main/resources/templates/admissions/admissions.html-19-                </li>
./src/main/resources/templates/admissions/admissions.html:20:                <li><p th:text="${'Patient ID: ' + admission.patId}"></p></li>
./src/main/resources/templates/admissions/admissions.html:21:                <li><p th:text="${'Date In: ' + admission.dateIn}"></p></li>
./src/main/resources/templates/admissions/admissions.html:22:                <li><p th:text="${'Date Out: ' + (admission.dateOut == null? '-' : admission.dateOut)}"></p></li>
./src/main/resources/templates/admissions/admissions.html:23:                <li><p th:text="${'Diagnosis: ' + admission.diagnosis}"></p></li>
./src/main/resources/templates/admissions/admissions.html:24:                <li><p th:text="${'Department: ' + admission.department}"></p></li>
./src/main/resources/templates/admissions/admissions.html-25-            </ul>
--
./src/main/resources/templates/admissions/admissionMenu.html-10-    <h1>Admission Menu</h1>
./src/main/resources/templates/admissions/admissionMenu.html:11:    <a href="/admissions" th:text="@{all admissions}" ></a>
./src/main/resources/templates/admissions/admissionMenu.html:12:    <a href="/admissions/new" th:text="@{new admission}"></a>
./src/main/resources/templates/admissions/admissionMenu.html:13:    <a href="/admissions/find" th:text="@{find admission}"></a>
./src/main/resources/templates/admissions/admissionMenu.html:14:    <a href="/admissions/delete" th:text="@{delete admission}"></a>
./src/main/resources/templates/admissions/admissionMenu.html-15-</div>

</shell_result>


Script executed:

#!/bin/bash

# Search for any test files or error indicators related to templates
find . -name "*Test*.java" -o -name "*test*.java" | xargs grep -l "patientsMenu\|template" 2>/dev/null | head -5

echo "---"

# Check if there are any template tests or integration tests
find . -name "*.java" -type f | xargs grep -l "patientsMenu" 2>/dev/null

echo "---"

# Look for any documentation on Thymeleaf syntax in the repo
find . -name "*.md" -o -name "*.txt" | xargs grep -i "thymeleaf\|th:text" 2>/dev/null | head -10

Repository: fungover/exercise2025


<shell_result>
Repository: fungover/exercise2025
Exit code: 0

stdout:

---
./src/main/java/org/example/controller/PatientsController.java
---

</shell_result>


Script executed:

#!/bin/bash

# Check PatientsController to see how patientsMenu is rendered
cat ./src/main/java/org/example/controller/PatientsController.java

Repository: fungover/exercise2025


<shell_result>
Repository: fungover/exercise2025
Exit code: 0

stdout:

package org.example.controller;

import org.example.dto.PatientDTO;
import org.example.entity.Patient;
import org.example.repository.PatientRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
@PreAuthorize("hasRole('ADMIN')")
public class PatientsController {

	@Autowired
	PatientRepository patientRepository;


	// ---- PATIENTS ----

	@GetMapping("/patients-menu")
	public String patientsMenu(Model model) {
		return "patients/patientsMenu";
	}

	@GetMapping("/patients")
	public String displayPatients(Model model) {
		model.addAttribute("patients", patientRepository.findAll().stream()
						.map(pat -> new PatientDTO(pat.getFirstName(), pat.getLastName(), pat.getAddress(), pat.getDateOfBirth()))
						.toList());
		return "patients/patients";
	}

	@GetMapping("/patients/new")
	public String newPatient(Model model) {
		model.addAttribute("patient", new Patient());
		return "patients/newPatient";
	}
}

</shell_result>


Based on my verification, the original review comment is correct and well-founded. Here's what I confirmed:

  1. File exists and matches: The patientsMenu.html file at lines 10-14 contains exactly the code shown
  2. Identical pattern in admissionMenu.html: Both menu templates have the same issue (th:text="@{all admissions}", th:text="@{all patients}", etc.)
  3. Actual Thymeleaf syntax violation:
    • @{...} is Thymeleaf's Link Expression syntax, designed for URL generation
    • th:text expects text content: plain strings, ${expressions}, #{messages}, or [[...]]
    • Using @{all patients} attempts to parse "all patients" as a URL expression with spaces, which is invalid
  4. Template is actively used: The controller has a /patients-menu route that renders this template, so the error would occur at runtime

The fix suggested in the original review is correct.


Fix invalid th:text usage in patients menu links

The th:text="@{all patients}" syntax misuses Thymeleaf's URL expression (@{...}) for link text. The @{...} syntax is for URL generation, not text content, and spaces make it syntactically invalid. This will break template rendering.

Replace with plain text:

-    <h1>Patients Menu</h1>
-    <a href="/patients" th:text="@{all patients}"></a>
-    <a href="/patients/new" th:text="@{new patient}"></a>
-    <a href="/patients/find" th:text="@{find patient}"></a>
-    <a href="/patients/delete" th:text="@{delete patient}"></a>
+    <h1>Patients Menu</h1>
+    <a href="/patients">All patients</a>
+    <a href="/patients/new">New patient</a>
+    <a href="/patients/find">Find patient</a>
+    <a href="/patients/delete">Delete patient</a>

Alternatively, for i18n support, use th:text="#{patients.all}" etc. with message bundle keys.

Note: The same issue exists in src/main/resources/templates/admissions/admissionMenu.html.

📝 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
<h1>Patients Menu</h1>
<a href="/patients" th:text="@{all patients}"></a>
<a href="/patients/new" th:text="@{new patient}"></a>
<a href="/patients/find" th:text="@{find patient}"></a>
<a href="/patients/delete" th:text="@{delete patient}"></a>
<h1>Patients Menu</h1>
<a href="/patients">All patients</a>
<a href="/patients/new">New patient</a>
<a href="/patients/find">Find patient</a>
<a href="/patients/delete">Delete patient</a>
🤖 Prompt for AI Agents
In src/main/resources/templates/patients/patientsMenu.html around lines 10-14,
the links use invalid Thymeleaf link expressions in th:text (e.g. th:text="@{all
patients}") which is a URL expression and breaks on spaces; change each th:text
to either plain literal text (e.g. th:text="'All Patients'") or to i18n message
lookups (e.g. th:text="#{patients.all}"), and apply the same fix to
src/main/resources/templates/admissions/admissionMenu.html where identical
misuse occurs.

Comment thread src/test/java/org/example/ControllerTest.java
Comment thread src/test/resources/org/example/patients.sql Outdated
@fungover fungover deleted a comment from coderabbitai Bot Dec 13, 2025
@Jorlindstrom Jorlindstrom self-requested a review December 17, 2025 16:43
Copy link
Copy Markdown

@Jorlindstrom Jorlindstrom left a comment

Choose a reason for hiding this comment

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

After reviewing the submitted code and project structure, I have identified several issues and areas for improvement that need attention before approval, along with some very positive points.

The project is well-structured packages: Entities, DTOs, controllers, repositories, and config are clearly separated.

You use modern Spring Boot features: @Profile for dev-only data initialization and method-level security with @PreAuthorize and. they are are well done.

You are mapping entities to DTOs for API responses which shows good separation of concerns.

The use of BCryptPasswordEncoder for security is correct.
Automatically populating sample data for dev profile is thoughtful and will help testing.

The project supports both API endpoints and web UI, which is a solid architectural choice.

Areas for Improvement

1. Project Startup Issues
The application fails to start due to missing docker-compose.yml.

This prevents database initialization and must be fixed.

2. Entity Layer
Patient entity uses getId() for pat_id. Consider renaming for consistency (getPatId()).

Admission automatically sets dateIn to LocalDateTime.now() and dateOut in setDateOut(). Consider allowing manual values for flexibility/testing.

@manytoone relationship in Admission defaults to EAGER fetch. Consider LAZY to improve performance.

3. Repository Layer
AdmissionRepository.getAdmissionsByPatId has invalid JPQL.

PatientRepository.findByFirstAndLastName concatenates strings, which is brittle and inefficient. Prefer using unique fields like SSN.

4. Controller Layer
Some duplicated logic between AdmissionsController and HospitalController.
Methods like writeOut silently ignore missing admissions; better error handling is recommended.
/patients endpoints are partially duplicated; consider clearer separation.

5. Security
Hardcoded in-memory admin with password "password" is insecure. Consider differentiating dev vs. prod security.

6. Data Initialization
Hardcoded sample data is acceptable for dev profile, but ensure it does not run in production.

7. DTOs
NewAdmissionDTO is defined twice; remove duplicates.

8. Testing
Only ControllerTest exists. There are no repository or service layer tests.

Conclusion:
The project demonstrates good architectural thinking and solid use of Spring Boot features. With fixes to startup issues, JPQL queries, and test coverage, this project will be robust, maintainable, and production-ready.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (9)
src/main/java/org/example/config/SecurityConfigDev.java (1)

31-31: Use a more idiomatic pattern for CSS resources.

The pattern "/**.css" is non-standard in Spring Security configurations. Consider using "/**/*.css" to match CSS files at any depth, or "/css/**" if CSS files are in a specific directory.

🔎 Apply this diff to use a standard pattern:
-			.requestMatchers("/", "/login", "/**.css").permitAll()
+			.requestMatchers("/", "/login", "/**/*.css").permitAll()
src/main/java/org/example/dto/PatientAdmissionRequest.java (1)

3-5: Consider using PatientDTO instead of the entity.

The DTO embeds a JPA entity (Patient) directly. This couples the request payload to the persistence layer, which can leak internal details and complicate serialization. Consider using PatientDTO instead to maintain clean layer separation.

pom.xml (1)

16-28: Remove or populate empty metadata sections.

The license, developers, and SCM sections contain empty placeholder elements. Either populate them with actual project metadata or remove them to reduce noise.

docker-compose.yml (1)

1-10: Consider adding a volume for data persistence.

The MySQL container has no volume configured. Data will be lost when the container is removed. For development convenience, consider adding a volume:

 services:
   mysql:
     image: 'mysql:9.5.0'
     environment:
       - 'MYSQL_DATABASE=hospital'
       - 'MYSQL_PASSWORD=secret'
       - 'MYSQL_ROOT_PASSWORD=admin'
       - 'MYSQL_USER=user'
     ports:
       - '3306:3306'
+    volumes:
+      - mysql_data:/var/lib/mysql
+
+volumes:
+  mysql_data:
src/main/java/org/example/config/SecurityConfigProd.java (1)

21-21: Clarify CSRF configuration intent.

The empty lambda csrf -> { } leaves CSRF enabled with defaults, which is correct for production. However, for clarity, consider using Customizer.withDefaults() or adding a comment explaining the intent:

-                .csrf(csrf -> { })
+                .csrf(Customizer.withDefaults()) // CSRF enabled with defaults
src/main/java/org/example/entity/Admission.java (2)

14-14: Remove redundant null initialization.

The explicit = null initialization is unnecessary since reference fields default to null in Java.

🔎 Apply this diff to simplify the field declaration:
-	private LocalDateTime dateOut = null;
+	private LocalDateTime dateOut;

19-21: Consider making the patient field private for consistency.

The patient field has package-private visibility while other fields are explicitly private. For consistency and better encapsulation, consider making it private.

🔎 Apply this diff to improve encapsulation:
 	@ManyToOne(fetch = FetchType.LAZY)
 	@JoinColumn(name = "pat_id")
-	Patient patient;
+	private Patient patient;
src/main/java/org/example/controller/AdmissionsController.java (1)

24-27: Prefer constructor injection over field injection.

Constructor injection improves testability and makes dependencies explicit and immutable.

🔎 Apply this diff to use constructor injection:
-	@Autowired
-	AdmissionRepository admissionRepository;
-	@Autowired
-	PatientRepository patientRepository;
+	private final AdmissionRepository admissionRepository;
+	private final PatientRepository patientRepository;
+
+	public AdmissionsController(AdmissionRepository admissionRepository, PatientRepository patientRepository) {
+		this.admissionRepository = admissionRepository;
+		this.patientRepository = patientRepository;
+	}
src/main/java/org/example/controller/PatientsController.java (1)

19-20: Prefer constructor injection over field injection.

Constructor injection improves testability, makes dependencies explicit, and enables final fields for immutability.

🔎 Apply this diff to use constructor injection:
-	@Autowired
-	PatientRepository patientRepository;
+	private final PatientRepository patientRepository;
+
+	public PatientsController(PatientRepository patientRepository) {
+		this.patientRepository = patientRepository;
+	}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e07c96e and a1a48e3.

📒 Files selected for processing (20)
  • docker-compose.yml (1 hunks)
  • pom.xml (1 hunks)
  • src/main/java/org/example/config/DataInitializer.java (1 hunks)
  • src/main/java/org/example/config/SecurityConfigDev.java (1 hunks)
  • src/main/java/org/example/config/SecurityConfigProd.java (1 hunks)
  • src/main/java/org/example/controller/AdmissionsController.java (1 hunks)
  • src/main/java/org/example/controller/PatientsController.java (1 hunks)
  • src/main/java/org/example/dto/PatientAdmissionRequest.java (1 hunks)
  • src/main/java/org/example/entity/Admission.java (1 hunks)
  • src/main/java/org/example/entity/Patient.java (1 hunks)
  • src/main/java/org/example/repository/AdmissionRepository.java (1 hunks)
  • src/main/java/org/example/repository/PatientRepository.java (1 hunks)
  • src/main/resources/application.yml (1 hunks)
  • src/main/resources/templates/admissions/admissionMenu.html (1 hunks)
  • src/main/resources/templates/logout.html (1 hunks)
  • src/main/resources/templates/patients/patients.html (1 hunks)
  • src/main/resources/templates/patients/patientsMenu.html (1 hunks)
  • src/test/java/org/example/AdmissionRepositoryTest.java (1 hunks)
  • src/test/java/org/example/ControllerTest.java (1 hunks)
  • src/test/resources/org/example/patients.sql (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/main/resources/templates/admissions/admissionMenu.html
  • src/main/resources/templates/logout.html
  • src/main/java/org/example/config/DataInitializer.java
  • src/main/java/org/example/repository/PatientRepository.java
  • src/main/resources/application.yml
  • src/test/java/org/example/ControllerTest.java
  • src/main/java/org/example/entity/Patient.java
  • src/main/resources/templates/patients/patients.html
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-16T13:56:11.263Z
Learnt from: jennymakki
Repo: fungover/exercise2025 PR: 204
File: src/main/java/org/example/config/SecurityConfig.java:19-24
Timestamp: 2025-11-16T13:56:11.263Z
Learning: The repository fungover/exercise2025 is an exercise/learning project where hard-coded credentials and other shortcuts for ease of testing are acceptable and intentional.

Applied to files:

  • pom.xml
📚 Learning: 2025-11-21T16:16:54.793Z
Learnt from: LolloGro
Repo: fungover/exercise2025 PR: 211
File: src/main/resources/templates/listOfBooks.html:11-12
Timestamp: 2025-11-21T16:16:54.793Z
Learning: In the repository fungover/exercise2025, the Author DTO uses NotEmpty validation on firstName and lastName fields, and BookDto uses NotNull validation on the author field. This validation architecture ensures that books always have complete author information, making null-safety checks in Thymeleaf templates unnecessary.

Applied to files:

  • pom.xml
🧬 Code graph analysis (5)
src/main/java/org/example/config/SecurityConfigDev.java (1)
src/main/java/org/example/config/SecurityConfigProd.java (1)
  • Configuration (13-35)
src/main/java/org/example/controller/PatientsController.java (2)
src/main/java/org/example/controller/AdmissionsController.java (1)
  • Controller (20-78)
src/main/java/org/example/controller/WebController.java (1)
  • Controller (7-25)
src/main/java/org/example/entity/Admission.java (1)
src/main/java/org/example/entity/Patient.java (1)
  • Entity (11-85)
src/main/java/org/example/config/SecurityConfigProd.java (1)
src/main/java/org/example/config/SecurityConfigDev.java (1)
  • Configuration (16-54)
src/main/java/org/example/controller/AdmissionsController.java (2)
src/main/java/org/example/controller/PatientsController.java (1)
  • Controller (15-59)
src/main/java/org/example/controller/WebController.java (1)
  • Controller (7-25)
🔇 Additional comments (11)
src/main/java/org/example/config/SecurityConfigDev.java (1)

16-19: LGTM!

The class annotations are correctly configured for a dev profile security setup.

src/test/resources/org/example/patients.sql (1)

1-9: LGTM!

The test data is well-structured. The SSN values now correctly match the date_of_birth fields, addressing the previous review feedback.

src/main/resources/templates/patients/patientsMenu.html (1)

1-17: LGTM!

The template now uses plain text for link labels, resolving the previous Thymeleaf syntax issue with th:text="@{...}".

src/test/java/org/example/AdmissionRepositoryTest.java (1)

27-66: LGTM!

The test is well-structured: it sets up isolated data, exercises the repository method, and verifies both the result count and that all returned admissions belong to the expected patient.

src/main/java/org/example/repository/AdmissionRepository.java (2)

16-20: Verify JPQL field naming matches entity.

Similar to the above method, verify that admission.patient.pat_id matches the actual entity field names. If the entity uses camelCase (e.g., patId), the query should be admission.patient.patId.

The previous review's concern about the missing WHERE clause has been addressed.


11-14: Entity field naming violates Java conventions—use camelCase for fields, not snake_case.

The Admission entity defines admission_id as a field name, which violates standard Java naming conventions. Entity field names should use camelCase (e.g., admissionId). Rename admission_id to admissionId and update the JPQL query accordingly to use admission.admissionId. Similarly, verify that the Patient entity uses patId instead of pat_id for its field name, then update the second query to reference admission.patient.patId.

Additionally, the custom getAdmissionById() method can be replaced with the inherited findById(Long id) from ListCrudRepository, or if a direct return type is required, the custom method should unwrap the Optional from findById().

Likely an incorrect or invalid review comment.

docker-compose.yml (1)

3-3: MySQL 9.5.0 image is available on Docker Hub. No action required.

pom.xml (2)

5-10: No action needed—Spring Boot 3.5.7 is a stable GA release.

Spring Boot 3.5.7 has been released and is now available from Maven Central. 3.5.7 marks the latest stable release (GA) in the 3.x series, and the release delivers 69 bug fixes, alongside multiple enhancements to documentation and dependency management.


30-30: Java 25 is now the stable LTS release and appropriate for production use.

Java 25 was released on September 16, 2025 and is the latest Long-Term Support (LTS) version. Oracle provides JDK 25 under a free use license for all users since September 2025. This is a valid and current choice for the project.

src/main/java/org/example/entity/Admission.java (1)

32-69: LGTM! Good use of selective mutability.

The entity exposes setters only for fields that should be updatable post-creation (dateOut, diagnosis, department), while keeping admission_id, dateIn, and patient immutable after construction. This is a sound design choice.

src/main/java/org/example/controller/PatientsController.java (1)

30-36: LGTM! Clean implementation without lazy-loading concerns.

The mapping to PatientDTO only accesses direct Patient fields without triggering any lazy-loaded relationships, so there are no N+1 query issues here.

@Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::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 | 🟠 Major

CSRF protection should remain enabled, even in dev.

Disabling CSRF creates security risks and can lead to issues being discovered only in production. Consider keeping CSRF enabled to ensure proper testing and avoid security vulnerabilities in development environments that may be exposed.

🔎 Apply this diff to enable CSRF protection:
-		.csrf(AbstractHttpConfigurer::disable)
+		.csrf(csrf -> { })
📝 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
.csrf(AbstractHttpConfigurer::disable)
.csrf(csrf -> { })
🤖 Prompt for AI Agents
In src/main/java/org/example/config/SecurityConfigDev.java around line 24, the
configuration calls .csrf(AbstractHttpConfigurer::disable) which disables CSRF
in dev; remove that disable call (or revert to default .csrf() behavior) so CSRF
remains enabled, and if necessary add a CsrfTokenRepository or ensure your dev
controllers/pages accept/emit CSRF tokens to allow local testing without
bypassing protection.

Comment on lines +40 to +48
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
return new InMemoryUserDetailsManager(
User.withUsername("admin")
.password(encoder.encode("password"))
.roles("ADMIN", "DEV", "USER")
.build()
);
}
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

Hardcoded credentials with weak password pose a security risk.

Even in dev environments, using "admin"/"password" credentials is dangerous if the environment becomes exposed (e.g., staging, demo, or misconfigured deployment). Consider using a stronger password or externalizing credentials via environment variables.

🔎 Apply this diff to use a stronger password:
 	public UserDetailsService userDetailsService(PasswordEncoder encoder) {
 		return new InMemoryUserDetailsManager(
 				User.withUsername("admin")
-						.password(encoder.encode("password"))
+						.password(encoder.encode("Dev@dmin2025!"))
 						.roles("ADMIN", "DEV", "USER")
 						.build()
 		);
 	}

Alternatively, externalize via environment variable:

 	public UserDetailsService userDetailsService(PasswordEncoder encoder) {
+		String devPassword = System.getenv().getOrDefault("DEV_ADMIN_PASSWORD", "ChangeMe123!");
 		return new InMemoryUserDetailsManager(
 				User.withUsername("admin")
-						.password(encoder.encode("password"))
+						.password(encoder.encode(devPassword))
 						.roles("ADMIN", "DEV", "USER")
 						.build()
 		);
 	}
📝 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 UserDetailsService userDetailsService(PasswordEncoder encoder) {
return new InMemoryUserDetailsManager(
User.withUsername("admin")
.password(encoder.encode("password"))
.roles("ADMIN", "DEV", "USER")
.build()
);
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
return new InMemoryUserDetailsManager(
User.withUsername("admin")
.password(encoder.encode("Dev@dmin2025!"))
.roles("ADMIN", "DEV", "USER")
.build()
);
}
Suggested change
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
return new InMemoryUserDetailsManager(
User.withUsername("admin")
.password(encoder.encode("password"))
.roles("ADMIN", "DEV", "USER")
.build()
);
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
String devPassword = System.getenv().getOrDefault("DEV_ADMIN_PASSWORD", "ChangeMe123!");
return new InMemoryUserDetailsManager(
User.withUsername("admin")
.password(encoder.encode(devPassword))
.roles("ADMIN", "DEV", "USER")
.build()
);
}

Comment on lines +19 to +29
public SecurityFilterChain prodSecurity(HttpSecurity http) throws Exception {
http
.csrf(csrf -> { })
.httpBasic(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**").authenticated()
.anyRequest().denyAll()
);

return http.build();
}
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

Missing UserDetailsService in production profile.

Unlike SecurityConfigDev, this configuration lacks a UserDetailsService bean. With HTTP Basic authentication enabled, the application needs a user source to authenticate against. Without it, all authentication attempts will fail.

Consider adding a production-appropriate user store (database-backed, LDAP, etc.) or document that this is expected to be provided externally.

🤖 Prompt for AI Agents
In src/main/java/org/example/config/SecurityConfigProd.java around lines 19 to
29, the production SecurityFilterChain enables HTTP Basic but does not provide a
UserDetailsService (or other authentication user store), so authentication will
fail; add and wire a production-appropriate UserDetailsService bean (for example
a JdbcUserDetailsManager wired to your DataSource, or a custom
UserDetailsService that loads users from the production user store) and ensure
it is available to the AuthenticationManager used by this SecurityFilterChain
(inject it into the config or register it as a @Bean), or alternatively document
clearly that a UserDetailsService/LDAP provider must be supplied externally in
production. Ensure the bean is active only in the production profile or guarded
appropriately.

Comment on lines +36 to +42
@GetMapping("/admissions")
public String displayAdmissions(Model model) {
model.addAttribute("admissions", admissionRepository.findAll().stream()
.map(adm -> new AdmissionDTO(adm.getAdmission_id(), adm.getPatient().getPatId(), adm.getDateIn(), adm.getDateOut(), adm.getDiagnosis(), adm.getDepartment()))
.toList());
return "admissions/admissions";
}
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

Critical N+1 query problem detected.

Line 39 accesses adm.getPatient().getPatId() on lazily-fetched Patient entities within a stream over all admissions. This triggers one SELECT query per admission, resulting in N+1 database queries where N is the number of admissions.

Solution: Use a JOIN FETCH query in the repository to eagerly load the patient relationship.

🔎 Add this method to AdmissionRepository:
@Query("SELECT a FROM Admission a JOIN FETCH a.patient")
List<Admission> findAllWithPatient();

Then update this controller method:

 	@GetMapping("/admissions")
 	public String displayAdmissions(Model model) {
-		model.addAttribute("admissions", admissionRepository.findAll().stream()
+		model.addAttribute("admissions", admissionRepository.findAllWithPatient().stream()
 						.map(adm -> new AdmissionDTO(adm.getAdmission_id(), adm.getPatient().getPatId(), adm.getDateIn(), adm.getDateOut(), adm.getDiagnosis(), adm.getDepartment()))
 						.toList());
 		return "admissions/admissions";
 	}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/org/example/controller/AdmissionsController.java around lines
36 to 42 there is an N+1 query: the stream calls adm.getPatient().getPatId() on
lazily-loaded Patient entities causing one query per admission; replace the call
to admissionRepository.findAll() with a new repository method that eagerly
fetches patients (add to AdmissionRepository a method using @Query("SELECT a
FROM Admission a JOIN FETCH a.patient") List<Admission> findAllWithPatient();
and import the necessary annotation), then update this controller to call
admissionRepository.findAllWithPatient() so Patient data is loaded in one query
before mapping to AdmissionDTO.

Comment on lines +52 to +62
@PostMapping("/api/admissions/new")
public String handleNew(@ModelAttribute NewAdmissionDTO newAdm) {

var pat = patientRepository.findBySsn(newAdm.patSsn());
if (pat == null) {
throw new IllegalArgumentException("Patient with SSN " + newAdm.patSsn() + " not found");
} else {
admissionRepository.save(new Admission(pat, newAdm.diagnosis(), newAdm.department(), LocalDateTime.now()));
}
return "redirect:/admissions";
}
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

Improve error handling for better HTTP semantics.

Throwing IllegalArgumentException for a missing patient (line 57) results in a generic 500 error. Web APIs should return appropriate HTTP status codes like 404 for not found scenarios. Additionally, there's no validation on the incoming DTO fields.

🔎 Suggested improvements:
 	@PostMapping("/api/admissions/new")
 	public String handleNew(@ModelAttribute NewAdmissionDTO newAdm) {
-
+		// Add validation here if needed
 		var pat = patientRepository.findBySsn(newAdm.patSsn());
 		if (pat == null) {
-			throw new IllegalArgumentException("Patient with SSN " + newAdm.patSsn() + " not found");
-		} else {
-			admissionRepository.save(new Admission(pat, newAdm.diagnosis(), newAdm.department(), LocalDateTime.now()));
+			// Consider using RedirectAttributes to pass error message
+			return "redirect:/admissions/new?error=patient_not_found";
 		}
+		admissionRepository.save(new Admission(pat, newAdm.diagnosis(), newAdm.department(), LocalDateTime.now()));
 		return "redirect:/admissions";
 	}

Alternatively, for REST API endpoints, use ResponseEntity with proper status codes.

📝 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
@PostMapping("/api/admissions/new")
public String handleNew(@ModelAttribute NewAdmissionDTO newAdm) {
var pat = patientRepository.findBySsn(newAdm.patSsn());
if (pat == null) {
throw new IllegalArgumentException("Patient with SSN " + newAdm.patSsn() + " not found");
} else {
admissionRepository.save(new Admission(pat, newAdm.diagnosis(), newAdm.department(), LocalDateTime.now()));
}
return "redirect:/admissions";
}
@PostMapping("/api/admissions/new")
public String handleNew(@ModelAttribute NewAdmissionDTO newAdm) {
// Add validation here if needed
var pat = patientRepository.findBySsn(newAdm.patSsn());
if (pat == null) {
// Consider using RedirectAttributes to pass error message
return "redirect:/admissions/new?error=patient_not_found";
}
admissionRepository.save(new Admission(pat, newAdm.diagnosis(), newAdm.department(), LocalDateTime.now()));
return "redirect:/admissions";
}
🤖 Prompt for AI Agents
In src/main/java/org/example/controller/AdmissionsController.java around lines
52 to 62, the controller throws IllegalArgumentException when a patient is
missing and does not validate the incoming DTO; change the behavior to return
proper HTTP semantics and validate input: accept the DTO as a @Valid parameter
and add a BindingResult to check for validation errors (return the form view or
a 400 response when invalid), and when the patient lookup returns null either
throw a ResponseStatusException(HttpStatus.NOT_FOUND, "...") or return a
ResponseEntity.notFound() to produce a 404 instead of a 500; ensure successful
saves still redirect to /admissions.

Comment on lines +64 to +70
@PostMapping("/api/admissions/{id}/writeOut")
public String writeOut(@PathVariable java.lang.Long id) {
Admission admission = admissionRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Admission not found"));
admission.setDateOut(LocalDateTime.now());
admissionRepository.save(admission);
return "redirect:/admissions";
}
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

Improve error handling with proper HTTP status codes.

Similar to the previous endpoint, throwing IllegalArgumentException (line 66) for a missing admission results in a 500 error instead of a more appropriate 404.

🔎 Consider this improvement:
 	@PostMapping("/api/admissions/{id}/writeOut")
 	public String writeOut(@PathVariable java.lang.Long id) {
-		Admission admission = admissionRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Admission not found"));
+		Admission admission = admissionRepository.findById(id)
+			.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Admission not found"));
 		admission.setDateOut(LocalDateTime.now());
 		admissionRepository.save(admission);
 		return "redirect:/admissions";
 	}

Note: This requires importing org.springframework.web.server.ResponseStatusException and org.springframework.http.HttpStatus.

📝 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
@PostMapping("/api/admissions/{id}/writeOut")
public String writeOut(@PathVariable java.lang.Long id) {
Admission admission = admissionRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Admission not found"));
admission.setDateOut(LocalDateTime.now());
admissionRepository.save(admission);
return "redirect:/admissions";
}
@PostMapping("/api/admissions/{id}/writeOut")
public String writeOut(@PathVariable java.lang.Long id) {
Admission admission = admissionRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Admission not found"));
admission.setDateOut(LocalDateTime.now());
admissionRepository.save(admission);
return "redirect:/admissions";
}
🤖 Prompt for AI Agents
In src/main/java/org/example/controller/AdmissionsController.java around lines
64 to 70, replace the IllegalArgumentException thrown when an admission is not
found with a ResponseStatusException carrying HttpStatus.NOT_FOUND so the
endpoint returns 404 instead of 500; add imports for
org.springframework.web.server.ResponseStatusException and
org.springframework.http.HttpStatus, change the orElseThrow to throw new
ResponseStatusException(HttpStatus.NOT_FOUND, "Admission not found"), and keep
the rest of the method (setting dateOut, saving, redirect) unchanged.

Comment on lines +72 to +77
@GetMapping("/api/admissions/{pat_id}")
public List<AdmissionDTO> findAllPatientsByPatId(@PathVariable java.lang.Long pat_id) {
return admissionRepository.getAdmissionsByPatId(pat_id).stream()
.map(adm -> new AdmissionDTO(adm.getAdmission_id(), adm.getPatient().getPatId(), adm.getDateIn(), adm.getDateOut(), adm.getDiagnosis(), adm.getDepartment()))
.toList();
}
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

Two critical issues: Missing @responsebody and N+1 query problem.

  1. Missing @responsebody: This endpoint returns List<AdmissionDTO> but lacks @ResponseBody annotation, so Spring won't serialize it to JSON. It will attempt to resolve it as a view name, causing errors.

  2. N+1 Query: Line 75 accesses adm.getPatient().getPatId() on lazily-fetched entities, triggering N+1 queries.

🔎 Apply these fixes:
 	@GetMapping("/api/admissions/{pat_id}")
+	@ResponseBody
 	public List<AdmissionDTO> findAllPatientsByPatId(@PathVariable java.lang.Long pat_id) {
-		return admissionRepository.getAdmissionsByPatId(pat_id).stream()
+		return admissionRepository.findByPatIdWithPatient(pat_id).stream()
 				.map(adm -> new AdmissionDTO(adm.getAdmission_id(), adm.getPatient().getPatId(), adm.getDateIn(), adm.getDateOut(), adm.getDiagnosis(), adm.getDepartment()))
 				.toList();
 	}

And add to AdmissionRepository:

@Query("SELECT a FROM Admission a JOIN FETCH a.patient WHERE a.patient.pat_id = :patId")
List<Admission> findByPatIdWithPatient(@Param("patId") Long patId);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/org/example/controller/AdmissionsController.java around lines
72 to 77, the endpoint is missing @ResponseBody (so Spring may treat the return
as a view) and it triggers an N+1 query by accessing adm.getPatient().getPatId()
on lazily-loaded patients; annotate the handler with @ResponseBody (or make the
controller a @RestController) and change the repository call to a new method
that fetches patients with the admissions using a JOIN FETCH (e.g., add a
repository method like findByPatIdWithPatient that uses "SELECT a FROM Admission
a JOIN FETCH a.patient WHERE a.patient.patId = :patId"), then call that method
here and map the results to AdmissionDTO as before so no extra queries occur.

Comment on lines +44 to +48
@PostMapping("/api/patients/new")
public String newPatient(@ModelAttribute Patient pat) {
patientRepository.save(pat);
return "redirect:/patients";
}
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

Add validation for the patient entity before persisting.

The endpoint saves the patient without validating required fields. Although the Patient entity has @NotNull annotations, validation isn't triggered automatically without @Valid on the parameter.

🔎 Apply this diff to enable validation:
+import jakarta.validation.Valid;
+
 	@PostMapping("/api/patients/new")
-	public String newPatient(@ModelAttribute Patient pat) {
+	public String newPatient(@Valid @ModelAttribute Patient pat, BindingResult bindingResult) {
+		if (bindingResult.hasErrors()) {
+			return "patients/newPatient";
+		}
 		patientRepository.save(pat);
 		return "redirect:/patients";
 	}

Also import org.springframework.validation.BindingResult.

📝 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
@PostMapping("/api/patients/new")
public String newPatient(@ModelAttribute Patient pat) {
patientRepository.save(pat);
return "redirect:/patients";
}
import jakarta.validation.Valid;
import org.springframework.validation.BindingResult;
@PostMapping("/api/patients/new")
public String newPatient(@Valid @ModelAttribute Patient pat, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "patients/newPatient";
}
patientRepository.save(pat);
return "redirect:/patients";
}
🤖 Prompt for AI Agents
In src/main/java/org/example/controller/PatientsController.java around lines 44
to 48, the controller saves a Patient without triggering bean validation; add
javax.validation.Valid to the @ModelAttribute parameter and add a
org.springframework.validation.BindingResult parameter immediately after it,
then check bindingResult.hasErrors() and return the patient form view if there
are validation errors; only call patientRepository.save(pat) and redirect when
there are no errors. Ensure you import
org.springframework.validation.BindingResult.

Comment on lines +50 to +58
@GetMapping("/api/patients/{id}")
public PatientDTO findPatientById(@PathVariable java.lang.Long id) {
var pat = patientRepository.findById(id);
if (pat.isEmpty()) {
throw new IllegalArgumentException("Patient with id " + id + " not found");
}
Patient foundPat = pat.get();
return new PatientDTO(foundPat.getFirstName(), foundPat.getLastName(), foundPat.getAddress(), foundPat.getDateOfBirth());
}
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

Two critical issues: Missing @responsebody and poor error handling.

  1. Missing @responsebody: This endpoint returns PatientDTO but lacks @ResponseBody annotation. Spring will attempt to resolve it as a view name instead of serializing to JSON.

  2. Improper error handling: Throwing IllegalArgumentException (line 54) results in a generic 500 error instead of a proper 404 response.

🔎 Apply these fixes:
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.server.ResponseStatusException;
+import org.springframework.http.HttpStatus;
+
 	@GetMapping("/api/patients/{id}")
+	@ResponseBody
 	public PatientDTO findPatientById(@PathVariable java.lang.Long id) {
-		var pat = patientRepository.findById(id);
-		if (pat.isEmpty()) {
-			throw new IllegalArgumentException("Patient with id " + id + " not found");
-		}
-		Patient foundPat = pat.get();
+		Patient foundPat = patientRepository.findById(id)
+			.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Patient with id " + id + " not found"));
 		return new PatientDTO(foundPat.getFirstName(), foundPat.getLastName(), foundPat.getAddress(), foundPat.getDateOfBirth());
 	}
📝 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
@GetMapping("/api/patients/{id}")
public PatientDTO findPatientById(@PathVariable java.lang.Long id) {
var pat = patientRepository.findById(id);
if (pat.isEmpty()) {
throw new IllegalArgumentException("Patient with id " + id + " not found");
}
Patient foundPat = pat.get();
return new PatientDTO(foundPat.getFirstName(), foundPat.getLastName(), foundPat.getAddress(), foundPat.getDateOfBirth());
}
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;
@GetMapping("/api/patients/{id}")
@ResponseBody
public PatientDTO findPatientById(@PathVariable java.lang.Long id) {
Patient foundPat = patientRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Patient with id " + id + " not found"));
return new PatientDTO(foundPat.getFirstName(), foundPat.getLastName(), foundPat.getAddress(), foundPat.getDateOfBirth());
}
🤖 Prompt for AI Agents
In src/main/java/org/example/controller/PatientsController.java around lines 50
to 58, the controller method is missing @ResponseBody and throws
IllegalArgumentException which yields a 500; update the method to be a REST
response by adding the @ResponseBody annotation (or ensure the class is
@RestController) and replace the IllegalArgumentException with a
ResponseStatusException(HttpStatus.NOT_FOUND, "Patient with id " + id + " not
found") (and add the necessary import), so the endpoint returns JSON and a
proper 404 when the patient is absent.

Copy link
Copy Markdown

@nilsishome nilsishome left a comment

Choose a reason for hiding this comment

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

It's a really impressive project with an implemented website to boot!

The only thing I can find that hasn't been requested by CodeRabbit or JorgenLindstrom, is to maybe change PatientAdmissionRequest to PatientAdmissionRequestDTO for consistency.

You've done an excellent job nonetheless, great work! 🎉

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.

4 participants