Skip to content

MagnusTerak/exercise8#224

Open
MagnusTerak wants to merge 15 commits intomainfrom
MagnusTerak/exercise8
Open

MagnusTerak/exercise8#224
MagnusTerak wants to merge 15 commits intomainfrom
MagnusTerak/exercise8

Conversation

@MagnusTerak
Copy link
Copy Markdown

@MagnusTerak MagnusTerak commented Nov 30, 2025

REST Api in springboot

Summary by CodeRabbit

  • New Features

    • Pet management REST API with feed/play actions
    • API-key authentication for all API endpoints
    • MySQL service via Docker Compose with automated DB migrations
    • Project migrated to Spring Boot application runtime
  • Tests

    • End-to-end and security tests covering authorized/unauthorized flows
  • Documentation

    • HTTP request examples for all Pet API operations

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 30, 2025

Walkthrough

Replaces 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

Cohort / File(s) Summary
Project configuration & containers
JavaTemplate.iml, pom.xml, docker-compose.yml
Adds IntelliJ module metadata; replaces POM with Spring Boot 3.5.7 parent and reorganized properties/dependencies/plugins (Spring Boot starters, Flyway, MySQL, security, etc.); adds a docker-compose service for MySQL with env vars and port mapping.
Application entrypoint
src/main/java/org/example/Exercise2025Application.java, src/main/java/org/example/App.java
Removes legacy App.java; adds Spring Boot entry class Exercise2025Application with main.
Domain & persistence
src/main/java/org/example/entities/Pet.java, src/main/resources/db/migration/V1__Initial.sql, src/main/java/org/example/PetRepository.java
Adds JPA entity Pet (id, name, age, species, hungerLevel, happiness, createdAt); adds Flyway V1 migration creating pet table; introduces PetRepository extending ListCrudRepository with removeById.
Business logic
src/main/java/org/example/PetService.java
Adds PetService with repository dependency, add/get/remove methods, and thread-safe state mutations (feedPet, playPet) guarded by a ReentrantLock.
REST API
src/main/java/org/example/PetController.java
Adds PetController exposing endpoints under /api for list, create, get-by-id, delete, feed, and play; returns 404 for missing pets and textual statuses for feed/play.
Security & filter
src/main/java/org/example/config/SecurityConfig.java, src/main/java/org/example/filters/ApiKeyFilter.java
Adds ApiKeyFilter (OncePerRequestFilter) validating X-Api-Key and setting ROLE_API authentication; adds SecurityConfig wiring the filter chain, in-memory users, AuthenticationManager, and PasswordEncoder.
Configuration
src/main/resources/application.properties, src/main/resources/application-dev.properties
Adds application name, datasource URL/credentials, virtual threads flag; dev properties set Docker Compose lifecycle management and spring.jpa.hibernate.ddl-auto=update.
HTTP examples
pets.http
Adds HTTP request examples demonstrating API usage (GET/POST/DELETE, feed, play) with X-Api-Key and appropriate headers/bodies.
Database migration
src/main/resources/db/migration/V1__Initial.sql
Adds initial Flyway migration creating the pet table with columns and primary key.
Tests
src/test/java/org/example/Exercise2025ApplicationTest.java, src/test/java/org/example/PetControllerTest.java, deleted: src/test/java/org/example/AppIT.java, src/test/java/org/example/AppTest.java
Removes legacy placeholder tests; adds Exercise2025ApplicationTest (context loads) and PetControllerTest (MockMvc tests for auth, CRUD, feed/play flows and error cases).

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–75 minutes

  • Inspect pom.xml changes for dependency/plugin compatibility with Spring Boot 3.5.7 and the simplified build lifecycle.
  • Verify SecurityConfig and ApiKeyFilter integration/order in the filter chain and AuthenticationManager correctness.
  • Review PetService locking: ensure lock is always released (exception safety) and that repository interactions are consistent.
  • Confirm JPA entity mappings (field vs column names) align with Flyway migration (e.g., hunger_level naming) and DB types.
  • Validate tests (MockMvc) setup and whether Flyway/test datasource config will run correctly in CI/local runs.
✨ 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 MagnusTerak/exercise8

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f2ddba and 2854a8f.

📒 Files selected for processing (2)
  • pets.http (1 hunks)
  • src/main/java/org/example/entities/Pet.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • pets.http
  • src/main/java/org/example/entities/Pet.java

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: 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_only looks aligned with your local MySQL container, and ddl-auto=update is convenient in dev. However, since you already have Flyway migrations, you may want to avoid ddl-auto=update (or switch to validate) even in dev to 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 VCS

This IntelliJ .iml file just configures the JPA facet and has no runtime impact. If your team prefers editor‑agnostic repos, consider adding .iml files 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 persistence

This 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 comment

The @SpringBootApplication class and public static void main are set up correctly and will start the app as expected. The comment // Can't run without static doesn’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: Custom removeById is redundant with Spring Data’s built-in delete method

Extending ListCrudRepository<Pet, Integer> already gives you deleteById(ID id). Your removeById(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 removeById and use the existing deleteById from ListCrudRepository in 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_level and happiness appear 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).
  • age has no constraint to prevent negative values.
  • name and species are 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 UserDetailsService bean defines in-memory users with username/password authentication, but the security configuration only uses API key authentication via ApiKeyFilter. 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 authenticationManager2 bean implements username/password authentication, but it's never invoked since the security chain only uses ApiKeyFilter for authentication. Additionally, the bean name authenticationManager2 is 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 feedPetUnauthorizedWithoutApiKey to verify unauthorized access is blocked, but there's no corresponding test for the authorized feed action (similar to playPetAuthorized for 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 ReentrantLock only 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 @Version annotation 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(). Since findAll() already returns an Iterable<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(), and toString() methods for correct behavior in collections, debugging, and logging.

Consider adding these methods, or use Lombok's @Data or @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

📥 Commits

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

📒 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.properties
  • src/main/java/org/example/Exercise2025Application.java
  • src/test/java/org/example/Exercise2025ApplicationTest.java
  • pom.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 setup

These 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/password to 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 test

Using @SpringBootTest with an empty contextLoads() 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.

Comment thread pets.http
Comment thread src/main/java/org/example/entities/Pet.java Outdated
Comment thread src/main/java/org/example/PetController.java
Comment thread src/main/java/org/example/PetController.java
Comment thread src/main/java/org/example/PetController.java
Comment thread src/main/java/org/example/PetService.java
Comment thread src/main/java/org/example/PetService.java
http
.securityMatcher("/api/**")
.csrf(csrf -> csrf.disable())
.addFilterAfter(apiKeyFilter, UsernamePasswordAuthenticationFilter.class)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown

@kikoDevv kikoDevv left a comment

Choose a reason for hiding this comment

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

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.

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.

All the tests are working as expected, as well as the different API-routes with coherent HTTP-requests. Great job!

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