Conversation
WalkthroughReplaces a simple Java entrypoint with a Spring Boot application that implements a Pet REST API (JPA entity, repository, service, controller), adds API-key security and Spring Security config, Flyway migration, MySQL docker service, updated Maven parent/pom, IntelliJ module metadata, HTTP examples, and new/updated tests and configs. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant ApiKeyFilter
participant SecurityConfig
participant PetController
participant PetService
participant PetRepository
participant Database
Client->>ApiKeyFilter: POST /api/addPet (X-Api-Key: secret)
ApiKeyFilter->>ApiKeyFilter: validate X-Api-Key
alt invalid or missing
ApiKeyFilter-->>Client: 401 Unauthorized
else valid
ApiKeyFilter->>SecurityConfig: set SecurityContext (ROLE_API)
ApiKeyFilter->>PetController: forward request
PetController->>PetService: addPet(payload)
PetService->>PetRepository: save(pet)
PetRepository->>Database: INSERT pet
Database-->>PetRepository: persisted row with id
PetRepository-->>PetService: saved Pet
PetService-->>PetController: Pet
PetController-->>Client: 200 OK (Pet JSON)
end
sequenceDiagram
participant Client
participant ApiKeyFilter
participant PetController
participant PetService
participant Lock as ReentrantLock
participant PetRepository
participant Database
Client->>ApiKeyFilter: POST /api/feed/{id} (X-Api-Key: secret)
ApiKeyFilter->>PetController: forward (authenticated)
PetController->>PetService: feedPet(id)
PetService->>Lock: lock.acquire()
PetService->>PetRepository: findById(id)
PetRepository->>Database: SELECT * FROM pet WHERE id=?
Database-->>PetRepository: row / empty
alt pet found
PetService->>PetService: hungerLevel += 10
PetService->>PetRepository: save(pet)
PetRepository->>Database: UPDATE pet SET hunger_level=?
Database-->>PetRepository: success
PetService->>Lock: lock.release()
PetService-->>PetController: updated Pet
PetController-->>Client: 200 OK ("Pet fed successfully")
else not found
PetService->>Lock: lock.release()
PetController-->>Client: 404 Not Found
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–75 minutes
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (14)
src/main/resources/application-dev.properties (1)
1-2: Consider relying on Flyway as the single schema source of truth in dev
spring.docker.compose.lifecycle-management=start_onlylooks aligned with your local MySQL container, andddl-auto=updateis convenient in dev. However, since you already have Flyway migrations, you may want to avoidddl-auto=update(or switch tovalidate) even indevto prevent the dev schema from drifting away from what migrations describe. Optional, but it keeps behavior between environments more predictable.JavaTemplate.iml (1)
1-17: IDE metadata is fine, but consider keeping .iml out of VCSThis IntelliJ
.imlfile just configures the JPA facet and has no runtime impact. If your team prefers editor‑agnostic repos, consider adding.imlfiles to.gitignore; otherwise, keeping it is acceptable for a classroom project.docker-compose.yml (1)
1-10: Compose setup is good for local dev; optionally add a volume for persistenceThis MySQL service definition is fine for a throwaway dev DB. If you want DB state to survive container restarts, consider adding a volume mapping, e.g.:
services: mysql: # ... volumes: - mysql-data:/var/lib/mysql volumes: mysql-data:Also, the in‑repo credentials are OK for this exercise repo, but avoid reusing them unchanged in any non‑training environment.
src/main/java/org/example/Exercise2025Application.java (1)
6-12: Boot application entry point looks correct; consider removing the redundant commentThe
@SpringBootApplicationclass andpublic static void mainare set up correctly and will start the app as expected. The comment// Can't run without staticdoesn’t add much for typical Java readers; you can safely drop it to keep the entry point minimal.src/main/java/org/example/PetRepository.java (1)
6-7: CustomremoveByIdis redundant with Spring Data’s built-in delete methodExtending
ListCrudRepository<Pet, Integer>already gives youdeleteById(ID id). YourremoveById(Integer id)method should also work as a derived delete query, but it’s functionally overlapping and adds an extra method name to remember.If you don’t need different semantics, you could drop
removeByIdand use the existingdeleteByIdfromListCrudRepositoryin your service instead, keeping the repository surface smaller.src/main/resources/db/migration/V1__Initial.sql (1)
1-11: Consider adding validation constraints for data integrity.The schema lacks constraints that would enforce business rules at the database level:
hunger_levelandhappinessappear to be percentage values (based on test usage showing "%" in output), but have no CHECK constraints to limit them to valid ranges (e.g., 0-100).agehas no constraint to prevent negative values.nameandspeciesare nullable, which may allow incomplete pet records.Consider applying this enhancement to enforce data integrity:
CREATE TABLE pet ( id INT AUTO_INCREMENT NOT NULL, - name VARCHAR(255) NULL, - age INT NOT NULL, - species VARCHAR(255) NULL, - hunger_level INT NOT NULL, - happiness INT NOT NULL, + name VARCHAR(255) NOT NULL, + age INT NOT NULL CHECK (age >= 0), + species VARCHAR(50) NOT NULL, + hunger_level INT NOT NULL CHECK (hunger_level BETWEEN 0 AND 100), + happiness INT NOT NULL CHECK (happiness BETWEEN 0 AND 100), created_at datetime NULL, CONSTRAINT pk_pet PRIMARY KEY (id) );src/main/java/org/example/config/SecurityConfig.java (2)
34-45: Unused UserDetailsService configuration.The
UserDetailsServicebean defines in-memory users with username/password authentication, but the security configuration only uses API key authentication viaApiKeyFilter. These user definitions are never utilized in the current authentication flow.If username/password authentication is not needed, consider removing this bean to reduce confusion and maintain cleaner code.
47-61: Unused AuthenticationManager bean.The
authenticationManager2bean implements username/password authentication, but it's never invoked since the security chain only usesApiKeyFilterfor authentication. Additionally, the bean nameauthenticationManager2is non-standard.If this authentication mechanism is not required, consider removing this bean. If it's intended for future use, add a comment explaining its purpose.
src/test/java/org/example/PetControllerTest.java (1)
72-76: Incomplete test coverage for feed endpoint.The test suite includes
feedPetUnauthorizedWithoutApiKeyto verify unauthorized access is blocked, but there's no corresponding test for the authorized feed action (similar toplayPetAuthorizedfor the play endpoint).Consider adding a test for the successful feed scenario:
@Test void feedPetAuthorized() throws Exception { String json = """ { "name": "TestPet", "age": 3, "species": "Dog", "hungerLevel": 20, "happiness": 60 } """; MvcResult result = mockMvc.perform(post("/api/addPet") .header("X-Api-Key", API_KEY) .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isOk()) .andReturn(); String responseBody = result.getResponse().getContentAsString(); ObjectMapper mapper = new ObjectMapper(); int createdId = mapper.readTree(responseBody).get("id").asInt(); mockMvc.perform(post("/api/feed/" + createdId) .header("X-Api-Key", API_KEY)) .andExpect(status().isOk()); }src/main/java/org/example/filters/ApiKeyFilter.java (1)
24-38: Consider adding security event logging.The filter silently rejects unauthorized requests without logging. Adding log statements would help with debugging and security monitoring.
Consider adding logging for security events:
+import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + @Component public class ApiKeyFilter extends OncePerRequestFilter { + private static final Logger log = LoggerFactory.getLogger(ApiKeyFilter.class); private static final String API_KEY_HEADER = "X-Api-Key"; private static final String VALID_API_KEY = "secret"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String apiKey = request.getHeader(API_KEY_HEADER); if(VALID_API_KEY.equals(apiKey)) { + log.debug("Valid API key provided for request: {}", request.getRequestURI()); Authentication auth = new UsernamePasswordAuthenticationToken("api_user", null, List.of(new SimpleGrantedAuthority("ROLE_API"))); SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(auth); SecurityContextHolder.setContext(context); filterChain.doFilter(request, response); } else { + log.warn("Unauthorized API access attempt from IP: {} for URI: {}", + request.getRemoteAddr(), request.getRequestURI()); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } } }src/main/java/org/example/PetService.java (1)
10-10: Consider database-level or distributed locking for production.The
ReentrantLockonly protects against concurrent modifications within a single JVM instance. In a distributed deployment or with multiple application instances, this lock won't prevent concurrent updates from different servers.For production deployments, consider:
- Optimistic locking with
@Versionannotation on the Pet entity- Pessimistic locking with JPA's
@Lock(LockModeType.PESSIMISTIC_WRITE)- Database-level locking mechanisms
src/main/java/org/example/PetController.java (1)
23-26: Unnecessary stream conversion in getPets.The code converts
findAll()to a stream and then to a list with.stream().toList(). SincefindAll()already returns anIterable<Pet>, this is redundant.Simplify the return statement:
@GetMapping("pets") public List<Pet> getPets() { - return repository.findAll().stream().toList(); + return (List<Pet>) repository.findAll(); }Or if you prefer type safety and the repository returns
Iterable:@GetMapping("pets") public List<Pet> getPets() { - return repository.findAll().stream().toList(); + return StreamSupport.stream(repository.findAll().spliterator(), false) + .toList(); }src/main/java/org/example/entities/Pet.java (2)
17-28: Missing validation annotations on entity fields.The entity lacks validation constraints that would align with business rules (pets should have names, valid ages, bounded happiness/hunger levels).
Consider adding validation annotations:
+import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + @Entity public class Pet { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; + @NotBlank(message = "Pet name is required") private String name; + @Min(value = 0, message = "Age must be non-negative") private int age; + @NotBlank(message = "Species is required") private String species; + @Min(value = 0, message = "Hunger level must be between 0 and 100") + @Max(value = 100, message = "Hunger level must be between 0 and 100") private int hungerLevel; + @Min(value = 0, message = "Happiness must be between 0 and 100") + @Max(value = 100, message = "Happiness must be between 0 and 100") private int happiness; @CreationTimestamp private LocalDateTime createdAt;
10-97: Consider adding equals, hashCode, and toString methods.JPA entities typically benefit from properly implemented
equals(),hashCode(), andtoString()methods for correct behavior in collections, debugging, and logging.Consider adding these methods, or use Lombok's
@Dataor@EqualsAndHashCode(onlyExplicitlyIncluded = true)with@ToString:@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Pet pet)) return false; return Objects.equals(id, pet.id); } @Override public int hashCode() { return Objects.hashCode(id); } @Override public String toString() { return "Pet{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", species='" + species + '\'' + ", hungerLevel=" + hungerLevel + ", happiness=" + happiness + ", createdAt=" + createdAt + '}'; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
JavaTemplate.iml(1 hunks)docker-compose.yml(1 hunks)pets.http(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/PetController.java(1 hunks)src/main/java/org/example/PetRepository.java(1 hunks)src/main/java/org/example/PetService.java(1 hunks)src/main/java/org/example/config/SecurityConfig.java(1 hunks)src/main/java/org/example/entities/Pet.java(1 hunks)src/main/java/org/example/filters/ApiKeyFilter.java(1 hunks)src/main/resources/application-dev.properties(1 hunks)src/main/resources/application.properties(1 hunks)src/main/resources/db/migration/V1__Initial.sql(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/Exercise2025ApplicationTest.java(1 hunks)src/test/java/org/example/PetControllerTest.java(1 hunks)
💤 Files with no reviewable changes (3)
- src/test/java/org/example/AppTest.java
- src/test/java/org/example/AppIT.java
- src/main/java/org/example/App.java
🧰 Additional context used
🧠 Learnings (4)
📚 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.propertiessrc/main/java/org/example/Exercise2025Application.javasrc/test/java/org/example/Exercise2025ApplicationTest.javapom.xml
📚 Learning: 2025-09-10T14:02:54.365Z
Learnt from: alfredbrannare
Repo: fungover/exercise2025 PR: 66
File: src/test/java/org/example/service/WarehouseTest.java:1-21
Timestamp: 2025-09-10T14:02:54.365Z
Learning: In the fungover/exercise2025 project, static mocking of LocalDateTime.now() works successfully with the existing Mockito setup without requiring mockito-inline dependency.
Applied to files:
src/test/java/org/example/Exercise2025ApplicationTest.java
📚 Learning: 2025-09-19T11:36:59.329Z
Learnt from: LolloGro
Repo: fungover/exercise2025 PR: 106
File: src/main/java/exercise4/repository/InMemoryProductRepository.java:0-0
Timestamp: 2025-09-19T11:36:59.329Z
Learning: In the InMemoryProductRepository class in src/main/java/exercise4/repository/InMemoryProductRepository.java, the updateProduct method implements a versioning system where multiple products with the same ID can exist, but identical product objects are rejected using contains() check. This allows maintaining product history while preventing duplicate identical entries.
Applied to files:
src/main/java/org/example/PetRepository.java
📚 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
🔇 Additional comments (4)
src/main/resources/application.properties (1)
1-6: Datasource and app config look coherent for this exercise setupThese properties line up with your MySQL compose service and Spring Boot setup. Given this is an exercise project, in‑file credentials are fine, but if you ever reuse this in a real environment, move
spring.datasource.username/passwordto environment variables or an external config source instead of committing real secrets.src/test/java/org/example/Exercise2025ApplicationTest.java (1)
6-11: Good minimal Spring Boot context smoke testUsing
@SpringBootTestwith an emptycontextLoads()is a standard way to verify that the application context starts successfully. This is a solid baseline; you can always add more focused tests around specific beans later.src/main/java/org/example/filters/ApiKeyFilter.java (1)
20-21: Hard-coded API key is acceptable for this learning project.The API key is hard-coded as
"secret", which is appropriate for an exercise/learning environment per the project guidelines.Based on learnings, the repository fungover/exercise2025 is an exercise/learning project where hard-coded credentials are acceptable and intentional.
pom.xml (1)
29-31: Java 25 was released as Generally Available on September 16, 2025, with official OpenJDK and Oracle builds available for download. Using Java 25 in the project configuration is valid and supported.
| http | ||
| .securityMatcher("/api/**") | ||
| .csrf(csrf -> csrf.disable()) | ||
| .addFilterAfter(apiKeyFilter, UsernamePasswordAuthenticationFilter.class) |
There was a problem hiding this comment.
Maybe adding API key filter before authFilter would be a good idea here, hers why.
The ApiKeyFilter is added after UsernamePasswordAuthenticationFilter, which means the username and password filter will process the request first. For API key authentication, the custom filter should typically be added before standard authentication filters to avoid unnecessary processing and ensure proper authentication precedence.
example: addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class)
kikoDevv
left a comment
There was a problem hiding this comment.
The project looks great overall in my review. The only feedback I have is for a few small, improvements in SecurityConfig.java but nothing major.
nilsishome
left a comment
There was a problem hiding this comment.
All the tests are working as expected, as well as the different API-routes with coherent HTTP-requests. Great job!
REST Api in springboot
Summary by CodeRabbit
New Features
Tests
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.