Skip to content

Redis2.0-User-Cat#49

Open
LinusAltemark wants to merge 18 commits into
mainfrom
Redis2.0
Open

Redis2.0-User-Cat#49
LinusAltemark wants to merge 18 commits into
mainfrom
Redis2.0

Conversation

@LinusAltemark

@LinusAltemark LinusAltemark commented Apr 21, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Introduced Redis caching for enhanced performance in user and cat data handling.
    • Added persistent storage volumes for Postgres and Redis in deployment configuration.
    • Added support for partial updates to cat records via new API endpoints.
  • Improvements

    • Refactored controllers and services to use DTOs for improved data encapsulation and type safety.
    • Enhanced validation with explicit constraints and custom error messages on user and cat inputs.
    • Updated application configuration to enable Redis caching across environments.
    • Simplified cat gender handling by replacing enum with string fields.
    • Improved OAuth2 user provisioning and update logic with logging.
    • Streamlined ownership and authentication checks in cat-related controllers.
    • Centralized caching annotations across service and controller layers for user and cat operations.
    • Removed redundant user management endpoints from the user view controller.
    • Enhanced DTO mapping with cache-aware updates and consistent entity conversions.
  • Bug Fixes

    • Fixed validation and type safety issues in user and cat input processing.
  • Documentation

    • Added descriptive Javadoc comments to DTO classes for improved clarity.
  • Chores

    • Updated deployment files to Docker Compose version 3.8 with Redis service added.

Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
# Conflicts:
#	src/main/java/org/example/springboot25/controller/CatRestController.java
#	src/main/java/org/example/springboot25/controller/CatViewController.java
#	src/main/java/org/example/springboot25/dto/CatInputDTO.java
#	src/main/java/org/example/springboot25/entities/Cat.java
#	src/main/java/org/example/springboot25/mapper/CatMapper.java
#	src/main/java/org/example/springboot25/service/CatService.java
@coderabbitai

coderabbitai Bot commented Apr 21, 2025

Copy link
Copy Markdown

"""

Walkthrough

This update introduces Redis caching to the application by modifying configuration files, adding Redis and cache dependencies, and updating the Docker Compose setup to include a Redis service. Major refactoring was performed on controllers and services to use DTOs instead of entities, centralizing mapping logic and validation. Caching annotations were added to user and cat-related service and controller methods to optimize data retrieval and consistency. The Cat and User entities were updated for compatibility with Redis and to simplify field types. The application now leverages Redis as a distributed cache, with all relevant configuration and dependency changes in place.

Changes

File(s) Change Summary
compose.yaml, application.properties, application-dev.properties, application-ci.properties, pom.xml Added Redis service and persistent volumes to Docker Compose; enabled Redis caching in Spring configs; added Redis/cache dependencies in Maven; added CI profile disabling Redis.
src/main/java/org/example/springboot25/Springboot25Application.java Enabled Spring caching and JPA repositories via annotations.
src/main/java/org/example/springboot25/entities/Cat.java, src/main/java/org/example/springboot25/entities/User.java Updated entities for Redis compatibility: made serializable, changed catGender from enum to String, added validation annotations, changed JPA table name for User, added lazy fetching, added setters, and removed redundant annotations.
src/main/java/org/example/springboot25/dto/CatInputDTO.java, src/main/java/org/example/springboot25/dto/UserInputDTO.java, src/main/java/org/example/springboot25/dto/UserOutputDTO.java, src/main/java/org/example/springboot25/dto/UserUpdateDTO.java, src/main/java/org/example/springboot25/dto/CachedUserDTO.java Added validation annotations with custom messages and Javadoc comments; reordered fields and methods for clarity; introduced new CachedUserDTO for Redis caching.
src/main/java/org/example/springboot25/mapper/CatMapper.java, src/main/java/org/example/springboot25/mapper/UserMapper.java Simplified cat gender mapping by removing enum usage; added method to update Cat from input DTO; annotated UserMapper update method with cache update; ensured userId propagation between DTO and entity.
src/main/java/org/example/springboot25/controller/CatGraphQLController.java, src/main/java/org/example/springboot25/controller/CatRestController.java, src/main/java/org/example/springboot25/controller/CatViewController.java Refactored controllers to use DTOs and mappers; delegated mapping and business logic fully to service layer; added partial update mutation/endpoint; removed direct entity exposure; improved ownership and authentication handling.
src/main/java/org/example/springboot25/controller/ReminderViewController.java Changed cat retrieval method to use entity-returning service method for reminders.
src/main/java/org/example/springboot25/controller/UserGraphQLController.java, src/main/java/org/example/springboot25/controller/UserRestController.java Added Spring caching annotations to user-related GraphQL and REST controller methods for caching and cache eviction.
src/main/java/org/example/springboot25/controller/UserViewController.java Removed all user creation, update, and delete endpoints; updated cat list retrieval to use DTOs; made remaining methods public; removed unused imports.
src/main/java/org/example/springboot25/security/CustomOAuth2UserService.java Improved OAuth2 user load logic to update or create users with cache consistency; added logging for user update and provisioning; ensured re-fetching user after update/creation.
src/main/java/org/example/springboot25/service/CatService.java, src/main/java/org/example/springboot25/service/UserService.java Refactored service methods to use DTOs and mappers; added caching annotations for retrieval, update, creation, partial update, and deletion; simplified partial update logic; removed custom property editor; added cache eviction on deletes.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Mapper
    participant Repository
    participant Redis

    Client->>Controller: API Request (e.g., GET /cats)
    Controller->>Service: getAllCats()
    Service->>Redis: Check cache for cats
    alt Cache hit
        Redis-->>Service: Return cached cats
    else Cache miss
        Service->>Repository: Fetch cats from DB
        Repository-->>Service: Return cat entities
        Service->>Mapper: Map entities to DTOs
        Mapper-->>Service: Return DTOs
        Service->>Redis: Store DTOs in cache
    end
    Service-->>Controller: Return DTOs
    Controller-->>Client: Respond with DTOs
Loading

Possibly related PRs

Suggested reviewers

  • malvamalmgren

Poem

🐇
With Redis in the burrow, our cache is now quite fleet,
No more slow paws searching for data to eat!
DTOs hop in, entities hop out,
Controllers are lighter, of that there's no doubt.
Caching and mapping, the service does cheer,
Spring Boot and Redis—our app’s running clear!

~ A code rabbit, hopping with delight.
"""


📜 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 998eb7d and f9bd243.

📒 Files selected for processing (2)
  • pom.xml (3 hunks)
  • src/main/java/org/example/springboot25/dto/CachedUserDTO.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/example/springboot25/dto/CachedUserDTO.java
  • pom.xml
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🔭 Outside diff range comments (4)
src/main/java/org/example/springboot25/service/UserService.java (3)

47-64: ⚠️ Potential issue

Cache key strategy is incomplete – username & email caches may become stale

@Cacheable correctly stores look‑ups by ID, username and email in three independent caches.
However, only the users cache is kept in sync when the record is later mutated (see @CachePut / @CacheEvict further below). As a consequence:

  • After a user changes her e‑mail or username, usersByEmail / usersByUsername will still return the old data.
  • Deleting a user will not evict the old entries, because the key passed to @CacheEvict is the userId while those caches are keyed by the email / username.

Unless a cache‑level TTL is relied upon (which is brittle), these entries will live forever and return wrong data.

Two minimal fixes:

-@CachePut(value = "users", key = "#userId")
+@Caching(put = {
+    @CachePut(value = "users",           key = "#userId"),
+    @CachePut(value = "usersByUsername", key = "#result.userName"),
+    @CachePut(value = "usersByEmail",    key = "#result.userEmail")
+})

and

-@CacheEvict(value = { "users", "usersByUsername", "usersByEmail" }, key = "#userId")
+@Caching(evict = {
+    @CacheEvict(value = "users",           key = "#userId"),
+    @CacheEvict(value = "usersByUsername", key = "#result.userName", beforeInvocation = true),
+    @CacheEvict(value = "usersByEmail",    key = "#result.userEmail", beforeInvocation = true)
+})

(You may also opt for allEntries = true to keep the code shorter.)

Failing to address this will surface as hard‑to‑spot inconsistencies in production.


122-136: 🛠️ Refactor suggestion

@CachePut must update username / email caches as well

Same reasoning as above – when a full update is performed this method can modify both userName and userEmail, but only the users cache entry is refreshed. Use the multi‑cache @Caching notation so that the auxiliary caches stay coherent.


186-193: ⚠️ Potential issue

@CacheEvict uses the wrong key for "usersByUsername" / "usersByEmail"

Because those caches are keyed by the value (username / email) rather than the ID, the eviction here will never hit. Either:

  • compute the relevant keys and evict them explicitly, or
  • simply use allEntries = true on those caches.

Not changing this leads to “ghost” users that appear to exist after deletion.

src/main/java/org/example/springboot25/controller/CatRestController.java (1)

98-109: 🛠️ Refactor suggestion

Partial update lacks bean validation

Same as for the PUT endpoint – add @Valid to ensure incoming data respects min/max/blank constraints. Without it, catAge = -5 sails through.

🧹 Nitpick comments (10)
src/main/resources/application-dev.properties (2)

1-1: Application name defined in multiple locations.

The application name is defined here and also in the main application.properties file. While this works, it's generally better to define such properties in a single location to avoid potential inconsistencies.

Consider removing this duplicate definition since it's already defined in the main properties file.


13-15: Consider using consistent property formats across environments.

In the main application.properties, you're using environment variables with fallbacks, but here you're using hardcoded values. While this works for local development, it creates an inconsistency in configuration styles.

Consider using the same environment variable approach with fallbacks for consistency:

-spring.cache.type=redis
-spring.data.redis.host=localhost
-spring.data.redis.port=6379
+spring.cache.type=redis
+spring.data.redis.host=${SPRING_REDIS_HOST:localhost}
+spring.data.redis.port=${SPRING_REDIS_PORT:6379}
src/main/java/org/example/springboot25/mapper/UserMapper.java (1)

13-13: Consider caching consistency across mapping methods

The @CachePut annotation is only applied to the toUser(UserUpdateDTO) method but not to other mapping methods like toUserUpdateDTO or updateUserFromDto. This could potentially lead to cache inconsistencies when these other methods are used.

Consider adding appropriate cache annotations to these methods to ensure consistent cache state, particularly for updateUserFromDto which handles partial updates:

    public UserUpdateDTO toUserUpdateDTO(User user) {
        if (user == null) throw new IllegalArgumentException("User cannot be null");

        UserUpdateDTO dto = new UserUpdateDTO();
        dto.setUserId(user.getUserId());
        dto.setUserFullName(user.getUserFullName());
        dto.setUserName(user.getUserName());
        dto.setUserEmail(user.getUserEmail());
        dto.setUserLocation(user.getUserLocation());
        dto.setUserRole(user.getUserRole());
        dto.setUserAuthProvider(user.getUserAuthProvider());
        return dto;
    }

+   @CachePut(value = "userDtoCache", key = "#dto.userId")
    public void updateUserFromDto(UserUpdateDTO dto, User user) {
        if (dto == null || user == null) throw new IllegalArgumentException("DTO or User cannot be null");

Also applies to: 69-81, 97-110

compose.yaml (1)

16-22: Good Redis integration, but consider adding authentication.

The Redis service is properly configured with ports and persistent storage. However, Redis is running without authentication, which could be a security concern in production environments.

Consider adding Redis authentication for better security:

  redis:
    image: 'redis:latest'
    container_name: 'redis-cache'
+   command: redis-server --requirepass ${REDIS_PASSWORD}
    ports:
      - '6379:6379'
    volumes:
      - redis-data:/data

Then update the app environment variables:

      - SPRING_REDIS_HOST=redis
      - SPRING_REDIS_PORT=6379
+     - SPRING_REDIS_PASSWORD=${REDIS_PASSWORD}
src/main/java/org/example/springboot25/entities/User.java (1)

23-24: Dual ID annotation approach could use documentation.

The approach of using both JPA and Redis ID annotations is correct for dual persistence, but could benefit from an explanatory comment.

    @jakarta.persistence.Id
-   @Id // Redis ID
+   @Id // Redis ID - dual annotation required for both JPA and Redis persistence
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userId;
src/main/java/org/example/springboot25/mapper/CatMapper.java (1)

73-80: Consider refactoring to reduce code duplication.

The new updateCatFromInputDTO method contains logic that's very similar to the toCat method, which could lead to maintenance issues if the mapping logic changes.

Consider refactoring to reduce duplication by extracting the common mapping logic:

-public void updateCatFromInputDTO(CatInputDTO dto, Cat cat) {
-    cat.setCatName(dto.getCatName().trim());
-    cat.setCatProfilePicture(dto.getCatProfilePicture() != null ? dto.getCatProfilePicture().trim() : null);
-    cat.setCatBreed(dto.getCatBreed().trim());
-    cat.setCatGender(dto.getCatGender().trim());
-    cat.setCatAge(dto.getCatAge());
-    cat.setCatPersonality(dto.getCatPersonality() != null ? dto.getCatPersonality().trim() : null);
+// Extract common mapping logic to a private method
+private void mapCatFieldsFromInputDTO(CatInputDTO dto, Cat cat) {
+    cat.setCatName(dto.getCatName().trim());
+    cat.setCatProfilePicture(dto.getCatProfilePicture() != null ? dto.getCatProfilePicture().trim() : null);
+    cat.setCatBreed(dto.getCatBreed().trim());
+    cat.setCatGender(dto.getCatGender().trim());
+    cat.setCatAge(dto.getCatAge());
+    cat.setCatPersonality(dto.getCatPersonality() != null ? dto.getCatPersonality().trim() : null);
+}
+
+public Cat toCat(CatInputDTO dto) {
+    Cat cat = new Cat();
+    mapCatFieldsFromInputDTO(dto, cat);
+
+    User user = userRepository.findById(dto.getUserId())
+            .orElseThrow(() -> new NotFoundException("User with ID " + dto.getUserId() + " not found"));
+
+    cat.setUserCatOwner(user);
+    return cat;
+}
+
+public void updateCatFromInputDTO(CatInputDTO dto, Cat cat) {
+    mapCatFieldsFromInputDTO(dto, cat);
}
src/main/java/org/example/springboot25/controller/CatGraphQLController.java (1)

37-40: Inconsistent layering – controller performs mapping only in this method

All other queries/mutations delegate completely to CatService, which already returns CatOutputDTO.
Here, however, the controller pulls an Optional<Cat> and performs the mapping itself. This breaks the single‑responsibility rule and surprises future maintainers.

Consider adding Optional<CatOutputDTO> getCatByIdDTO(...) (or changing the existing service signature) so the controller never touches entities.

src/main/java/org/example/springboot25/controller/CatRestController.java (1)

3-8: Unused imports & missing validation

HttpStatus and Map are no longer used after the refactor. More importantly, the new DTO parameters are not annotated with @Valid, so the JSR‑303 constraints defined in CatInputDTO / CatUpdateDTO are silently ignored.

-import org.springframework.http.HttpStatus;
-import java.util.Map;
+import jakarta.validation.Valid;

and mark the method parameters accordingly (@Valid @RequestBody CatInputDTO ...).

src/main/java/org/example/springboot25/controller/UserViewController.java (1)

17-20: Remove unused caching imports

@CacheEvict, @CachePut, and @Cacheable are no longer referenced in this controller. Keeping unused imports adds noise, triggers IDE warnings, and may mislead future readers into thinking the controller does its own caching.

-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.CachePut;
-import org.springframework.cache.annotation.Cacheable;
src/main/java/org/example/springboot25/controller/CatViewController.java (1)

62-77: Duplicate manual mapping – delegate to CatMapper

Manually copying every field from CatOutputDTO to CatInputDTO is error‑prone and violates DRY. Use your mapper:

-var inputDto = new CatInputDTO();
-// field‑by‑field copying…
-catService.createCat(inputDto);
+catService.createCat(catMapper.toInputDTO(catDTO, user.getUserId()));

Add a mapper helper:

CatInputDTO toInputDTO(CatOutputDTO dto, Long userId);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 574f1da and 470e5f2.

📒 Files selected for processing (23)
  • compose.yaml (1 hunks)
  • pom.xml (1 hunks)
  • src/main/java/org/example/springboot25/Springboot25Application.java (1 hunks)
  • src/main/java/org/example/springboot25/controller/CatGraphQLController.java (1 hunks)
  • src/main/java/org/example/springboot25/controller/CatRestController.java (5 hunks)
  • src/main/java/org/example/springboot25/controller/CatViewController.java (3 hunks)
  • src/main/java/org/example/springboot25/controller/ReminderViewController.java (4 hunks)
  • src/main/java/org/example/springboot25/controller/UserGraphQLController.java (3 hunks)
  • src/main/java/org/example/springboot25/controller/UserRestController.java (6 hunks)
  • src/main/java/org/example/springboot25/controller/UserViewController.java (4 hunks)
  • src/main/java/org/example/springboot25/dto/CatInputDTO.java (2 hunks)
  • src/main/java/org/example/springboot25/dto/UserInputDTO.java (1 hunks)
  • src/main/java/org/example/springboot25/dto/UserOutputDTO.java (1 hunks)
  • src/main/java/org/example/springboot25/dto/UserUpdateDTO.java (1 hunks)
  • src/main/java/org/example/springboot25/entities/Cat.java (4 hunks)
  • src/main/java/org/example/springboot25/entities/User.java (3 hunks)
  • src/main/java/org/example/springboot25/mapper/CatMapper.java (1 hunks)
  • src/main/java/org/example/springboot25/mapper/UserMapper.java (2 hunks)
  • src/main/java/org/example/springboot25/security/CustomOAuth2UserService.java (1 hunks)
  • src/main/java/org/example/springboot25/service/CatService.java (1 hunks)
  • src/main/java/org/example/springboot25/service/UserService.java (6 hunks)
  • src/main/resources/application-dev.properties (2 hunks)
  • src/main/resources/application.properties (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/main/java/org/example/springboot25/mapper/UserMapper.java (1)
Learnt from: ehottinen
PR: storkforge/catinder#32
File: src/main/java/org/example/springboot25/dto/UserUpdateDTO.java:7-12
Timestamp: 2025-04-14T15:50:31.116Z
Learning: In the DTO-based architecture, validation for UserUpdateDTO fields (e.g., email format) is handled inside UserMapper.updateUserFromDto() method rather than using validation annotations directly on the DTO. The service layer only checks for user existence and delegates field validation to the mapper.
🧬 Code Graph Analysis (4)
src/main/java/org/example/springboot25/controller/UserRestController.java (1)
src/main/java/org/example/springboot25/controller/AdminController.java (1)
  • PreAuthorize (19-158)
src/main/java/org/example/springboot25/security/CustomOAuth2UserService.java (1)
src/main/java/org/example/springboot25/utility/RandomUsernameGenerator.java (1)
  • RandomUsernameGenerator (5-9)
src/main/java/org/example/springboot25/controller/CatRestController.java (4)
src/main/java/org/example/springboot25/dto/CatInputDTO.java (1)
  • CatInputDTO (7-84)
src/main/java/org/example/springboot25/dto/CatOutputDTO.java (1)
  • CatOutputDTO (3-76)
src/main/java/org/example/springboot25/dto/CatUpdateDTO.java (1)
  • CatUpdateDTO (3-59)
src/main/java/org/example/springboot25/exceptions/NotFoundException.java (1)
  • NotFoundException (3-7)
src/main/java/org/example/springboot25/controller/CatViewController.java (2)
src/main/java/org/example/springboot25/dto/CatOutputDTO.java (1)
  • CatOutputDTO (3-76)
src/main/java/org/example/springboot25/exceptions/NotFoundException.java (1)
  • NotFoundException (3-7)
🔇 Additional comments (40)
src/main/java/org/example/springboot25/dto/UserOutputDTO.java (1)

5-7: Good documentation addition!

The added Javadoc comment clearly describes the purpose of this DTO - for exposing user details without sensitive information. This improves code clarity and follows best practices for API documentation.

pom.xml (1)

71-78: Redis and cache dependencies correctly added.

The addition of Spring Boot Redis and Cache starter dependencies is appropriate for implementing Redis-based caching. These dependencies will provide the necessary infrastructure for the Redis caching configuration referenced in the application properties.

src/main/resources/application.properties (1)

13-18: Well-configured Redis settings with environment variables.

The Redis configuration is properly set up with:

  1. Explicit cache type setting
  2. Environment variables with sensible fallbacks
  3. Clear comments explaining the purpose

This approach enables flexible deployment across different environments while maintaining local development simplicity.

src/main/java/org/example/springboot25/controller/ReminderViewController.java (1)

77-77:

Details

✅ Verification successful

Method updates aligned with DTO refactoring.

The changes from getAllCatsByUser to getAllCatsByUserAsEntity reflect a broader refactoring to separate DTO and entity returns in the service layer. This controller needs entity objects, so the change is appropriate.

Also applies to: 97-97, 115-115, 128-128


🏁 Script executed:

#!/bin/bash
# Verify the implementation of the new method in CatService
rg -A 5 "getAllCatsByUserAsEntity" --type java

Length of output: 3469


Approved: Controller correctly uses the new entity-returning service method

Verified that getAllCatsByUserAsEntity(User) is implemented in CatService.java (returns catRepository.findAllByUserCatOwner(user)) and returns the required Cat entities. The controller’s calls to this method are appropriate.

Locations updated in ReminderViewController.java:

  • Line 77
  • Line 97
  • Line 115
  • Line 128

No further action required.

src/main/java/org/example/springboot25/dto/UserUpdateDTO.java (2)

5-7: Great addition of class-level Javadoc

The added documentation clearly describes the purpose of this DTO, which improves code maintainability and helps other developers understand the intended use case.


10-10: LGTM: Improved field organization

Moving the userId field and its accessors to the top of the class makes sense since this is the primary key field used for cache operations. This reorganization aligns well with the caching strategy implemented throughout the application.

Also applies to: 20-26

src/main/java/org/example/springboot25/Springboot25Application.java (1)

5-6: Well-implemented caching and repository configuration

The addition of @EnableCaching properly activates Spring's caching infrastructure, which is essential for the Redis caching implementation. The @EnableJpaRepositories annotation with explicit package configuration is a good practice that improves startup performance by limiting the scanning scope.

These annotations ensure that:

  1. Caching annotations like @Cacheable, @CachePut, and @CacheEvict will work properly throughout the application
  2. JPA repositories are explicitly configured with a targeted package scan

Also applies to: 11-12

src/main/java/org/example/springboot25/mapper/UserMapper.java (3)

7-7: Appropriate cache update implementation

Adding the @CachePut annotation ensures that when a user is updated via this mapper, the cache is also updated with the latest data. This is a key component of maintaining cache consistency.

Also applies to: 13-13


15-15: Error message improvement

The updated error message explicitly mentions UserUpdateDTO, which improves clarity for debugging.


18-18: Consistent ID handling for caching

Explicitly setting the userId in both entity and DTO mapping methods ensures proper identity preservation, which is crucial for caching operations that rely on the userId as a key.

Also applies to: 73-73

src/main/java/org/example/springboot25/dto/CatInputDTO.java (2)

3-3: Appropriate import for validation

The added import for jakarta.validation.constraints.Min is correctly included to support the new minimum value validation on the catAge field.


14-14: Good validation constraints for cat data

The added validation constraints improve data integrity:

  • @NotBlank on catBreed and catGender ensures these required string fields aren't empty
  • @NotNull and @Min(0) on catAge prevent null values and negative ages

These constraints align well with the broader DTO refactoring for cat-related operations and the change from enum to string for catGender.

Also applies to: 17-17, 20-21

src/main/java/org/example/springboot25/dto/UserInputDTO.java (2)

7-9: Good addition of class-level documentation.

The Javadoc clearly describes the purpose of this DTO class, which improves code readability and helps developers understand its intended use.


12-29: Well-implemented custom validation messages.

Adding descriptive validation messages to all constraints is a great improvement. These custom messages will provide clearer feedback to clients when validation fails, making the API more user-friendly.

src/main/java/org/example/springboot25/security/CustomOAuth2UserService.java (2)

47-54: Good improvement to conditional user update logic.

The selective update approach that only modifies the user when the name has changed is an optimization that will reduce unnecessary database operations and cache updates.


73-74: Good practice: Re-fetching user after changes.

Re-fetching the user after update/creation ensures the OAuth2User is created with the most current data, which is especially important with caching in place.

compose.yaml (2)

26-27: Proper service dependencies and environment configuration.

The app service correctly depends on Redis and has the necessary environment variables to connect to it. This ensures services start in the right order and the app can locate the Redis service.

Also applies to: 34-35


39-41: Good implementation of persistent volumes.

Defining named volumes for both PostgreSQL and Redis ensures data persistence across container restarts and updates, which is essential for production environments.

src/main/java/org/example/springboot25/entities/User.java (1)

68-70: Nice code organization improvement.

Adding section dividers improves code readability, especially in large entity classes.

src/main/java/org/example/springboot25/controller/UserGraphQLController.java (5)

8-10: Adding caching dependencies to support Redis caching strategy.

The addition of these Spring caching annotations imports is appropriate for implementing the Redis caching strategy in the GraphQL controller.


28-28: Properly caching the list of all users.

The @Cacheable(value = "users") annotation will improve performance by caching the results of getAllUsers() in Redis.


34-34: Implementing user-specific caching with appropriate key.

The @Cacheable(value = "users", key = "#userId") annotation correctly uses the user ID as the cache key.


40-40: Implementing username-based caching with appropriate key.

The @Cacheable(value = "usersByUsername", key = "#userName") annotation appropriately caches user data by username.


51-51: Ensuring cache consistency on user updates.

The @CachePut annotation ensures the cache is updated when a user is modified through the API.

src/main/java/org/example/springboot25/entities/Cat.java (6)

8-8: Adding Serializable support for Redis caching.

Implementing Serializable is necessary for Redis caching to properly serialize and deserialize entity objects.


13-13: Implementing Serializable for Redis cache compatibility.

The Cat entity now implements Serializable, which is required for objects stored in Redis cache.


24-24: Adding validation and simplifying the gender field type.

The @notblank annotations add proper validation for required fields, and changing the gender field from an enum to String simplifies serialization for caching purposes.

Also applies to: 27-28


36-36: Using lazy loading for improved performance.

Changing to FetchType.LAZY for the owner relationship improves performance by avoiding unnecessary loading of related entities when the Cat entity is fetched from the database.


53-55: Adding setter for the catId field.

Adding a setter for the primary key field helps with entity mapping operations, particularly during cache operations.


81-83: Updated getter/setter to use String instead of enum type.

The method signatures for getCatGender() and setCatGender() now correctly use String instead of the previous enum type, consistent with the field type change.

Also applies to: 85-87

src/main/java/org/example/springboot25/mapper/CatMapper.java (3)

21-35: Improved toCat mapping with consistent string handling.

The updated mapper method properly trims string values and handles null values appropriately. The handling of user lookup is also improved.


37-56: Enhanced update method with proper null checks and trimming.

The updated method properly checks for null values before updating each field and consistently trims string values.


58-71: Simplified DTO mapping with direct assignments.

The toDTO method is now cleaner with direct field assignments and proper null handling for the user relationship.

src/main/java/org/example/springboot25/controller/UserRestController.java (2)

10-12: Adding caching dependencies to support Redis caching strategy.

The addition of Spring caching annotations imports supports the Redis caching implementation in the REST controller.


48-48: Caching all users list to improve API performance.

The @Cacheable annotation on getAllUsers() will significantly improve performance for this potentially expensive operation.

src/main/java/org/example/springboot25/service/UserService.java (1)

138-184: Partial update can silently leave stale cache entries

The map‑based updateUser variant also writes only to the "users" cache, even though the supplied map may contain "userName" or "userEmail". Please mirror the fix suggested for the DTO‑based update.

src/main/java/org/example/springboot25/controller/CatGraphQLController.java (1)

48-55: Method naming mismatch between GraphQL update & service

updateCat (full) receives a CatInputDTO, while partialUpdateCat uses CatUpdateDTO. That is good.
But in REST (see CatRestController) the full update also forwards an Input DTO, after overriding the userId. Make sure the service layer validates ownership for GraphQL as well, otherwise a malicious caller could change the owner field inside the input.

src/main/java/org/example/springboot25/controller/CatRestController.java (2)

38-45: isNotOwnerOrAdmin returns true when userCatOwner is null – is this intended?

A null owner currently blocks access for everyone (including ADMIN), because the helper instantly returns true. If unowned cats are a valid use‑case, consider:

if (cat.getUserCatOwner() == null) {
    return !isAdmin;   // allow admins, block others
}

Clarify the rule or document the current behaviour.


82-93: ⚠️ Potential issue

Updating a cat unintentionally transfers ownership

catInputDTO.setUserId(currentUser.getUserId()); assigns the authenticated user as the new owner on every full update. If an ADMIN edits somebody else’s cat, the animal will suddenly belong to the admin account.

Move this line to the creation endpoint only, or augment the service to ignore the field on updates unless explicitly allowed.

-        catInputDTO.setUserId(currentUser.getUserId());
+        // Keep the original owner; do not overwrite automatically
⛔ Skipped due to learnings
Learnt from: ehottinen
PR: storkforge/catinder#16
File: src/main/java/org/example/springboot25/service/CatService.java:45-56
Timestamp: 2025-04-03T07:22:36.756Z
Learning: In the CatService class, the userCatOwner field is intentionally not updated in the updateCat method to keep it immutable after creation and prevent accidental ownership switches.
src/main/java/org/example/springboot25/service/CatService.java (1)

85-92: @CachePut on partial update can cache stale data

partialUpdateCat updates the cache before the transaction commits.
If the transaction rolls back, Redis will hold a value that was never persisted.

Add @TransactionalEventListener–based cache population (after commit) or move the @CachePut call to a separate, @Transactional(propagation = REQUIRES_NEW, afterCommit) method.

Comment on lines 58 to +68
User user = new User();
user.setUserEmail(email);

String fullName = oAuth2User.getAttribute("name");
user.setUserFullName(fullName != null ? fullName : "");
user.setUserName(RandomUsernameGenerator.getRandomUsername());

String providerName = userRequest.getClientRegistration().getRegistrationId();
user.setUserAuthProvider(providerName);
user.setUserRole(UserRole.BASIC);
userService.addUser(user);

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

Consider setting userLocation for new users.

When creating new users, all fields with @notblank constraints should be initialized to avoid validation issues. While most required fields are set, userLocation is missing an initial value.

 user.setUserEmail(email);

 String fullName = oAuth2User.getAttribute("name");
 user.setUserFullName(fullName != null ? fullName : "");
 user.setUserName(RandomUsernameGenerator.getRandomUsername());
+user.setUserLocation("Unknown"); // Set default location to satisfy validation

 String providerName = userRequest.getClientRegistration().getRegistrationId();
 user.setUserAuthProvider(providerName);
 user.setUserRole(UserRole.BASIC);
📝 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
User user = new User();
user.setUserEmail(email);
String fullName = oAuth2User.getAttribute("name");
user.setUserFullName(fullName != null ? fullName : "");
user.setUserName(RandomUsernameGenerator.getRandomUsername());
String providerName = userRequest.getClientRegistration().getRegistrationId();
user.setUserAuthProvider(providerName);
user.setUserRole(UserRole.BASIC);
userService.addUser(user);
User user = new User();
user.setUserEmail(email);
String fullName = oAuth2User.getAttribute("name");
user.setUserFullName(fullName != null ? fullName : "");
user.setUserName(RandomUsernameGenerator.getRandomUsername());
user.setUserLocation("Unknown"); // Set default location to satisfy validation
String providerName = userRequest.getClientRegistration().getRegistrationId();
user.setUserAuthProvider(providerName);
user.setUserRole(UserRole.BASIC);

Comment thread src/main/java/org/example/springboot25/entities/User.java Outdated
Comment thread src/main/java/org/example/springboot25/controller/UserGraphQLController.java Outdated
Comment thread src/main/java/org/example/springboot25/controller/UserRestController.java Outdated
Comment thread src/main/java/org/example/springboot25/controller/UserRestController.java Outdated
Comment thread src/main/java/org/example/springboot25/service/CatService.java Outdated
Comment thread src/main/java/org/example/springboot25/service/CatService.java Outdated
Comment thread src/main/java/org/example/springboot25/controller/CatViewController.java Outdated
Comment on lines 22 to 35

public Cat toCat(CatInputDTO catInputDTO) {
public Cat toCat(CatInputDTO dto) {
Cat cat = new Cat();
cat.setCatName(catInputDTO.getCatName().trim());
cat.setCatProfilePicture(catInputDTO.getCatProfilePicture() != null ?
catInputDTO.getCatProfilePicture().trim() : null);
cat.setCatBreed(catInputDTO.getCatBreed().trim());
if (catInputDTO.getCatGender() != null) {
cat.setCatGender(CatGender.valueOf(catInputDTO.getCatGender().trim().toUpperCase()));
} else {
cat.setCatGender(null); // or handle as needed
}
cat.setCatAge(catInputDTO.getCatAge());
cat.setCatPersonality(catInputDTO.getCatPersonality() != null ?
catInputDTO.getCatPersonality().trim() : null);
cat.setCatName(dto.getCatName().trim());
cat.setCatProfilePicture(dto.getCatProfilePicture() != null ? dto.getCatProfilePicture().trim() : null);
cat.setCatBreed(dto.getCatBreed().trim());
cat.setCatGender(dto.getCatGender().trim());
cat.setCatAge(dto.getCatAge());
cat.setCatPersonality(dto.getCatPersonality() != null ? dto.getCatPersonality().trim() : null);

User user = userRepository.findById(catInputDTO.getUserId())
.orElseThrow(() -> new NotFoundException("User with ID " + catInputDTO.getUserId() + " not found"));
User user = userRepository.findById(dto.getUserId())
.orElseThrow(() -> new NotFoundException("User with ID " + dto.getUserId() + " not found"));

cat.setUserCatOwner(user);
return cat;
}

@ehottinen ehottinen Apr 22, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Du har två versioner av samma metod ihopklistrade:
cat.setCatGender(CatGender.valueOf(catInputDTO.getCatGender().trim().toUpperCase())); och
cat.setCatGender(dto.getCatGender().trim());

Att använda både catInputDTO och dto i samma metod gör det rörigt.
Du försöker dessutom sätta en String till ett fält som förväntar sig en CatGender enum – vilket leder till ett kompileringsfel.
Det ser ut som att du råkade merga metoden utan att rensa dubbletter.

Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
# Conflicts:
#	src/main/java/org/example/springboot25/controller/CatGraphQLController.java
#	src/main/java/org/example/springboot25/controller/CatRestController.java
#	src/main/java/org/example/springboot25/controller/CatViewController.java
#	src/main/java/org/example/springboot25/controller/ReminderViewController.java
#	src/main/java/org/example/springboot25/controller/UserViewController.java
#	src/main/java/org/example/springboot25/dto/UserInputDTO.java
#	src/main/java/org/example/springboot25/dto/UserUpdateDTO.java
#	src/main/java/org/example/springboot25/entities/User.java
#	src/main/java/org/example/springboot25/mapper/CatMapper.java
#	src/main/java/org/example/springboot25/service/CatService.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (5)
src/main/java/org/example/springboot25/service/CatService.java (2)

42-48: Consider marking this read‑only query as @Transactional(readOnly = true)

getAllCats() performs a pure read yet participates in the default read‑write transaction inherited from the class‑level @Transactional.
Declaring the method (or annotating the class with @Transactional(readOnly = true) and overriding only mutators) avoids accidental flushes and gives the persistence provider a hint for optimisations.


69-73: Minor: expose isAdmin logic through Spring Security’s hasRole expression

Instead of manual authority scanning, consider a method‑level security annotation:

@PreAuthorize("hasRole('ADMIN') or #currentUser.userId == principal.userId")
public List<Cat> getCatsVisibleTo(Authentication auth, User currentUser) { … }

It removes boiler‑plate and pushes the concern to Spring Security.

src/main/java/org/example/springboot25/controller/CatViewController.java (3)

38-46: Remove the unused Authentication parameter

authentication is injected but never used.
Cleaning it up simplifies the method signature:

-public String listCats(Model model,
-                       @AuthenticationPrincipal OAuth2User principal,
-                       Authentication authentication) {
+public String listCats(Model model,
+                       @AuthenticationPrincipal OAuth2User principal) {

46-48: Handle the “user record not found” edge case

userService.findUserByEmail(...) may throw NotFoundException, which will bubble up as a 500 error.
Return an error view or redirect the user gracefully.

try {
    User me = userService.findUserByEmail(email);
} catch (NotFoundException ex) {
    redirectAttributes.addFlashAttribute("error", ex.getMessage());
    return "redirect:/";
}

52-57: Service should return a DTO, not the controller doing the mapping

CatService#getCatById returns an entity which the controller immediately converts.
Move the conversion into the service to keep presentation logic out of controllers and to ensure the cached value is already a DTO.

-Optional<Cat> getCatById(Long id)
+Optional<CatOutputDTO> getCatById(Long id)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5250d65 and b028c77.

📒 Files selected for processing (7)
  • src/main/java/org/example/springboot25/controller/CatRestController.java (1 hunks)
  • src/main/java/org/example/springboot25/controller/CatViewController.java (2 hunks)
  • src/main/java/org/example/springboot25/controller/UserGraphQLController.java (3 hunks)
  • src/main/java/org/example/springboot25/controller/UserRestController.java (6 hunks)
  • src/main/java/org/example/springboot25/controller/UserViewController.java (9 hunks)
  • src/main/java/org/example/springboot25/dto/UserOutputDTO.java (1 hunks)
  • src/main/java/org/example/springboot25/service/CatService.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/org/example/springboot25/dto/UserOutputDTO.java
  • src/main/java/org/example/springboot25/controller/UserGraphQLController.java
  • src/main/java/org/example/springboot25/controller/UserRestController.java
  • src/main/java/org/example/springboot25/controller/CatRestController.java
🔇 Additional comments (2)
src/main/java/org/example/springboot25/controller/UserViewController.java (1)

56-60: Good move switching to the primitive cache key

Using catService.getAllCatsByUserId(userId) avoids the serialisation pitfalls of passing the full User entity.
No further remarks here.

src/main/java/org/example/springboot25/controller/CatViewController.java (1)

122-130: HTML forms cannot issue DELETE – confirm front‑end support

Browsers only support GET and POST.
Unless you rely on JavaScript or Spring’s hidden _method field, this endpoint will never be hit.
If you intend a plain <form> post, keep it as @PostMapping("/delete").

Comment thread src/main/java/org/example/springboot25/service/CatService.java
Comment on lines +80 to +90
@Caching(evict = {
@CacheEvict(value = "cats", allEntries = true),
@CacheEvict(value = "cat", allEntries = true)
})
public CatOutputDTO createCat(CatInputDTO dto) {
if (!userRepository.existsById(dto.getUserId())) {
throw new NotFoundException("User not found with id " + dto.getUserId());
}
log.info("Deleting cat with id: {}", catId);
catRepository.deleteById(catId);
}

public void deleteCat(Long catId) {
deleteCatById(catId);
}

// ========================
// Externa metoder (DTO)
// ========================

public List<CatOutputDTO> getAllCats() {
return catRepository.findAll().stream().map(catMapper::toDto).toList();
}

public List<CatOutputDTO> getAllCatsByUser(User user) {
return catRepository.findAllByUserCatOwner(user).stream().map(catMapper::toDto).toList();
}

public List<CatOutputDTO> getCatsByName(String name) {
return catRepository.findByCatNameContainingIgnoreCase(name).stream().map(catMapper::toDto).toList();
}

public CatOutputDTO getCatDtoById(Long id) {
return catMapper.toDto(findCatById(id));
}

public CatOutputDTO addCat(CatInputDTO dto) {
log.info("Creating new cat: {}", dto.getCatName());
Cat cat = catMapper.toCat(dto);
return catMapper.toDto(saveCat(cat));
return catMapper.toDTO(catRepository.save(cat));

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

allEntries = true nukes the entire cache – evict only what you modified

Every new cat invalidates every cached list and individual cat.
You can keep unrelated cache lines warm and reduce Redis traffic by targeting specific keys:

-@Caching(evict = {
-    @CacheEvict(value = "cats", allEntries = true),
-    @CacheEvict(value = "cat", allEntries = true)
-})
+@Caching(evict = {
+    @CacheEvict(value = "cats", key = "#dto.userId"), // user‑specific list
+    @CacheEvict(value = "cat", key = "#result.catId") // just the created cat
+})

(The same applies to updateCat, partialUpdateCat, and deleteCat.)

📝 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
@Caching(evict = {
@CacheEvict(value = "cats", allEntries = true),
@CacheEvict(value = "cat", allEntries = true)
})
public CatOutputDTO createCat(CatInputDTO dto) {
if (!userRepository.existsById(dto.getUserId())) {
throw new NotFoundException("User not found with id " + dto.getUserId());
}
log.info("Deleting cat with id: {}", catId);
catRepository.deleteById(catId);
}
public void deleteCat(Long catId) {
deleteCatById(catId);
}
// ========================
// Externa metoder (DTO)
// ========================
public List<CatOutputDTO> getAllCats() {
return catRepository.findAll().stream().map(catMapper::toDto).toList();
}
public List<CatOutputDTO> getAllCatsByUser(User user) {
return catRepository.findAllByUserCatOwner(user).stream().map(catMapper::toDto).toList();
}
public List<CatOutputDTO> getCatsByName(String name) {
return catRepository.findByCatNameContainingIgnoreCase(name).stream().map(catMapper::toDto).toList();
}
public CatOutputDTO getCatDtoById(Long id) {
return catMapper.toDto(findCatById(id));
}
public CatOutputDTO addCat(CatInputDTO dto) {
log.info("Creating new cat: {}", dto.getCatName());
Cat cat = catMapper.toCat(dto);
return catMapper.toDto(saveCat(cat));
return catMapper.toDTO(catRepository.save(cat));
@Caching(evict = {
@CacheEvict(value = "cats", key = "#dto.userId"), // user‑specific list
@CacheEvict(value = "cat", key = "#result.catId") // just the created cat
})
public CatOutputDTO createCat(CatInputDTO dto) {
if (!userRepository.existsById(dto.getUserId())) {
throw new NotFoundException("User not found with id " + dto.getUserId());
}
Cat cat = catMapper.toCat(dto);
return catMapper.toDTO(catRepository.save(cat));
}

Comment thread src/main/java/org/example/springboot25/service/CatService.java
Comment thread src/main/java/org/example/springboot25/controller/CatViewController.java Outdated
Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
# Conflicts:
#	pom.xml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
src/main/java/org/example/springboot25/service/CatService.java (3)

75-78: ⚠️ Potential issue

Avoid caching Optional wrappers – cache the entity itself

@Cacheable will store the wrapper (Optional<Cat>) in Redis. The default Jackson‑based serializer tends to mis-handle Optional, leading to deserialisation errors or silent cache misses.

-@Cacheable(value = "cat", key = "#catId")
-public Optional<Cat> getCatById(Long catId) {
-    return catRepository.findById(catId);
-}
+@Cacheable(value = "cat", key = "#catId")
+public Cat getCatById(Long catId) {
+    return catRepository.findById(catId)
+            .orElseThrow(() -> new NotFoundException("Cat not found with id " + catId));
+}

Callers that need an Optional can wrap the result on their side.


50-58: 🛠️ Refactor suggestion

Stop constructing a dummy User just to query by FK

Creating a detached User instance only to hit the repository is brittle and can trigger extra selects when the proxy is initialised.
Prefer a repository method that filters directly on the foreign‑key column:

-User user = new User();
-user.setUserId(userId);
-return catRepository.findAllByUserCatOwner(user)
+return catRepository.findAllByUserCatOwnerUserId(userId)
         .stream()
         .map(catMapper::toDTO)
         .collect(Collectors.toList());

Add List<Cat> findAllByUserCatOwnerUserId(Long userId); to CatRepository.


97-105: 🛠️ Refactor suggestion

allEntries = true nukes the entire cache

Every new cat invalidates every cached list, even from other users, defeating the purpose of caching.
Evict only the affected user’s list and consider putting the freshly‑created cat into its individual cache entry:

-@CacheEvict(value = "cats", allEntries = true)
+@Caching(evict = {
+    @CacheEvict(value = "cats", key = "#dto.userId")
+}, put = {
+    @CachePut(value = "cat", key = "#result.catId")
+})

(The same adjustment applies to updateCat, partialUpdateCat, and deleteCat.)

src/main/java/org/example/springboot25/controller/CatViewController.java (1)

103-116: 🛠️ Refactor suggestion

Still binding the form to an output DTO – switch to CatInputDTO

Using CatOutputDTO as the form‑backing object forces this manual, error‑prone copy block and skips Bean‑Validation rules declared on CatInputDTO.

-@PostMapping("/{catId}")
-public String updateCat(@PathVariable Long catId, @ModelAttribute("cat") CatOutputDTO catDTO) {
+@PostMapping("/{catId}")
+public String updateCat(@PathVariable Long catId,
+                        @Valid @ModelAttribute("cat") CatInputDTO catDTO,
+                        BindingResult errors) {
+    if (errors.hasErrors()) {
+        return "cat/existing-edit-cat-form";
+    }
     try {
-        Long currentOwnerId = catService.getOwnerIdOfCat(catId);
-        CatInputDTO inputDto = new CatInputDTO();
-        inputDto.setCatName(catDTO.getCatName());
-
-        inputDto.setUserId(currentOwnerId); // protect ownership
-
-        catService.updateCat(catId, inputDto);
+        Long currentOwnerId = catService.getOwnerIdOfCat(catId);
+        catDTO.setUserId(currentOwnerId);       // protect ownership
+        catService.updateCat(catId, catDTO);
     } catch (Exception e) {
         throw new RuntimeException("Update failed for cat with id " + catId, e);
     }
     return "redirect:/cats";
 }

This removes the redundant object copy and restores validation.

🧹 Nitpick comments (1)
src/main/java/org/example/springboot25/controller/CatViewController.java (1)

70-88: Handle userService.findUserByEmail failures gracefully

If the OAuth2 user exists in the Identity Provider but not yet in your local DB, findUserByEmail will throw NotFoundException, causing a 500 error.
Consider on‑the‑fly user provisioning or redirecting the user to a signup page instead of blowing up:

try {
    User user = userService.findUserByEmail(email);
    catDTO.setUserId(user.getUserId());
    catService.createCat(catDTO);
} catch (NotFoundException e) {
    return "redirect:/signup";
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b028c77 and 2e231ec.

📒 Files selected for processing (5)
  • pom.xml (2 hunks)
  • src/main/java/org/example/springboot25/controller/CatViewController.java (1 hunks)
  • src/main/java/org/example/springboot25/dto/CachedUserDTO.java (1 hunks)
  • src/main/java/org/example/springboot25/service/CatService.java (1 hunks)
  • src/main/java/org/example/springboot25/service/UserService.java (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/org/example/springboot25/dto/CachedUserDTO.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • pom.xml
  • src/main/java/org/example/springboot25/service/UserService.java

Comment thread src/main/java/org/example/springboot25/service/CatService.java
Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
@malvamalmgren
malvamalmgren self-requested a review April 22, 2025 14:39
Signed-off-by: LinusAltemark <Linalt0905@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
src/main/java/org/example/springboot25/controller/CatGraphQLController.java (1)

55-63: ⚠️ Potential issue

Swallowing NotFoundException hides useful error information from GraphQL clients

Returning false on any NotFoundException means the client receives no error details and cannot distinguish “not found” from other failures (e.g., database outage).
GraphQL already conveys exceptions nicely; letting the exception propagate results in a proper errors array in the response.

Suggested fix:

-    @MutationMapping
-    public boolean deleteCat(@Argument Long catId) {
-        try {
-            catService.deleteCat(catId);
-            return true;
-        } catch (NotFoundException e) {
-            return false;
-        }
-    }
+    @MutationMapping
+    public Boolean deleteCat(@Argument Long catId) {
+        catService.deleteCat(catId);   // will throw NotFoundException automatically
+        return true;
+    }

If you still prefer a boolean indicator, consider returning a custom payload containing an ok field and optional error details, or log the exception so it’s not silently discarded.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e31e1f1 and 77b3440.

📒 Files selected for processing (7)
  • src/main/java/org/example/springboot25/controller/CatGraphQLController.java (2 hunks)
  • src/main/java/org/example/springboot25/controller/CatRestController.java (1 hunks)
  • src/main/java/org/example/springboot25/controller/CatViewController.java (1 hunks)
  • src/main/java/org/example/springboot25/controller/ReminderViewController.java (3 hunks)
  • src/main/java/org/example/springboot25/entities/User.java (3 hunks)
  • src/main/java/org/example/springboot25/repository/CatRepository.java (1 hunks)
  • src/main/java/org/example/springboot25/service/CatService.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/main/java/org/example/springboot25/controller/ReminderViewController.java
  • src/main/java/org/example/springboot25/entities/User.java
  • src/main/java/org/example/springboot25/controller/CatRestController.java
  • src/main/java/org/example/springboot25/service/CatService.java
  • src/main/java/org/example/springboot25/controller/CatViewController.java
🔇 Additional comments (3)
src/main/java/org/example/springboot25/repository/CatRepository.java (1)

10-10: Good addition that aligns with architectural changes.

The new method follows Spring Data JPA naming conventions correctly and enables direct filtering of cats by user ID. This change supports the broader refactoring effort to use DTOs instead of entities and optimizes data retrieval by allowing cats to be queried by ID rather than requiring the complete User entity. This is more efficient when only the ID is needed for lookups.

src/main/java/org/example/springboot25/controller/CatGraphQLController.java (2)

45-48: Check DTO choice for full updates

updateCat now expects CatInputDTO, not CatUpdateDTO.
CatInputDTO usually represents object creation and may require all fields.
• For a PUT‑style update you normally need an “all fields required” DTO, so this change might be intentional – just ensure:

  1. CatInputDTO validation rules match update semantics (non‑null for mandatory fields).
  2. CatService#updateCat signature was updated accordingly; otherwise compilation will fail.

Please confirm the service layer has been adapted.


50-53: Good addition of partialUpdateCat mutation

Introducing a distinct mutation for PATCH semantics avoids over‑loading the full update endpoint and keeps validation rules clear.

Comment on lines 22 to 28
private final CatService catService;
private final UserService userService;
private final CatMapper catMapper;

public CatGraphQLController(CatService catService, UserService userService) {
public CatGraphQLController(CatService catService, CatMapper catMapper) {
this.catService = catService;
this.userService = userService;
this.catMapper = catMapper;
}

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

Constructor injects CatMapper but its use is limited to a single call – consider pushing mapping to the service layer

CatMapper is injected and only referenced once in getCatById(). In contrast, getAllCats() relies on the service to return CatOutputDTO objects directly.
This split responsibility makes it unclear which layer owns the entity‑to‑DTO conversion and can easily lead to inconsistencies or duplicated work.

Recommendation

  1. Move the catMapper.toDTO(...) call into CatService#getCatById so all controller methods consume the same DTO contract.
  2. Remove the CatMapper dependency from this controller if no other mapping remains.

This keeps the controller thin, centralises conversion logic, and avoids unnecessary constructor parameters.

Comment on lines 30 to 38
@QueryMapping
@PreAuthorize("isAuthenticated()")
public List<CatOutputDTO> getAllCats(Authentication auth) {
User currentUser = userService.findUserByUserName(auth.getName());
return catService.getAllCatsByUser(currentUser);
public List<CatOutputDTO> getAllCats() {
return catService.getAllCats();
}

@QueryMapping
@PreAuthorize("isAuthenticated()")
public CatOutputDTO getCatById(@Argument Long catId, Authentication auth) {
CatOutputDTO cat = catService.getCatDtoById(catId);
User currentUser = userService.findUserByUserName(auth.getName());

if (!cat.getUserId().equals(currentUser.getUserId()) &&
!currentUser.getUserRole().name().equals("ADMIN")) {
throw new AccessDeniedException("You can only view your own cats.");
}

return cat;
public CatOutputDTO getCatById(@Argument Long catId) {
return catMapper.toDTO(catService.getCatById(catId));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Inconsistent DTO handling between getAllCats and getCatById

getAllCats() trusts the service to return List<CatOutputDTO> while getCatById() manually maps the service result.
If CatService#getCatById ever changes its return type (e.g., to CatOutputDTO) the controller mapping will silently double‑map or fail.

-    public CatOutputDTO getCatById(@Argument Long catId) {
-        return catMapper.toDTO(catService.getCatById(catId));
+    public CatOutputDTO getCatById(@Argument Long catId) {
+        return catService.getCatById(catId);   // let service return DTO
     }

Ensure CatService consistently exposes DTOs (or entities) for all queries and update tests accordingly.


🏁 Script executed:

#!/bin/bash
# Locate CatService definition and method signatures
rg -n "interface CatService"
rg -n "class CatService"

# Find service method implementations or declarations
rg -n "getAllCats" 
rg -n "getCatById"

Length of output: 4379


🏁 Script executed:

#!/bin/bash
file=src/main/java/org/example/springboot25/service/CatService.java

echo "=== getAllCats implementation ==="
rg -n -C5 'public List<CatOutputDTO> getAllCats' "$file"

echo ""
echo "=== getCatById implementation ==="
rg -n -C5 'public Cat getCatById' "$file"

Length of output: 1903


Standardize CatService return types for consistency

The service currently returns DTOs for getAllCats() but returns an entity for getCatById(), forcing the GraphQL controller to map manually. This inconsistency can lead to double mapping or silent failures if signatures change.

Please update the service and controller as follows:

• In src/main/java/org/example/springboot25/service/CatService.java
– Change the signature of getCatById to return CatOutputDTO
– Map the entity inside the service using catMapper.toDTO(...)
• Example diff:
diff - @Cacheable(value = "cat", key = "#catId") - public Cat getCatById(Long catId) { - Cat cat = catRepository.findById(catId) - .orElseThrow(() -> new NotFoundException("Cat not found with id " + catId)); - return cat; - } + @Cacheable(value = "cat", key = "#catId") + public CatOutputDTO getCatById(Long catId) { + Cat cat = catRepository.findById(catId) + .orElseThrow(() -> new NotFoundException("Cat not found with id " + catId)); + return catMapper.toDTO(cat); + }

• In src/main/java/org/example/springboot25/controller/CatGraphQLController.java
– Remove the manual mapping and simply return the DTO from the service
• Example diff:
diff - public CatOutputDTO getCatById(@Argument Long catId) { - return catMapper.toDTO(catService.getCatById(catId)); - } + public CatOutputDTO getCatById(@Argument Long catId) { + return catService.getCatById(catId); + }

• Update any affected tests (e.g., controller and service tests) and other callers of getCatById to expect CatOutputDTO.
• Verify that all controllers and GraphQL schema definitions remain aligned with the new DTO return type.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since we removed lombok, we need to make getters and setters manually

Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
@malvamalmgren

Copy link
Copy Markdown
Collaborator

I think the build is failing because of a lack of constructors for cached user dto and possibly user? I'm looking into it

Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
import java.util.List;

@Entity
@Table(name = "app_user")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why the renaming, have you fixed this elsewhere?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/main/java/org/example/springboot25/dto/CachedUserDTO.java (1)

1-16: Add validation annotations for field constraints

Consider adding validation annotations (such as from Jakarta Validation) to ensure data integrity, especially for fields like email.

Example implementation:

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

// Then on your fields:
@NotNull
private Long userId;

@NotBlank
private String userName;

@NotBlank
@Email
private String userEmail;

// Add appropriate constraints for other fields
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e33c73 and c923311.

📒 Files selected for processing (4)
  • src/main/java/org/example/springboot25/dto/CachedUserDTO.java (1 hunks)
  • src/main/java/org/example/springboot25/entities/User.java (3 hunks)
  • src/main/resources/application-dev.properties (2 hunks)
  • src/main/resources/application.properties (0 hunks)
💤 Files with no reviewable changes (1)
  • src/main/resources/application.properties
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/resources/application-dev.properties
  • src/main/java/org/example/springboot25/entities/User.java

Comment on lines +7 to +16
public class CachedUserDTO implements Serializable {

private static final long serialVersionUID = 1L;

private Long userId;
private String userName;
private String userEmail;
private String userLocation;
private String userRole;
}

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

Missing constructors and accessor methods

The class lacks both a default constructor and a parameterized constructor, which are needed for proper serialization/deserialization with Redis. Additionally, since Lombok has been removed, you need to manually implement getters and setters for all fields.

Add the following constructors and methods:

// Default constructor
public CachedUserDTO() {
}

// Parameterized constructor
public CachedUserDTO(Long userId, String userName, String userEmail, String userLocation, String userRole) {
    this.userId = userId;
    this.userName = userName;
    this.userEmail = userEmail;
    this.userLocation = userLocation;
    this.userRole = userRole;
}

// Getters and Setters
public Long getUserId() {
    return userId;
}

public void setUserId(Long userId) {
    this.userId = userId;
}

public String getUserName() {
    return userName;
}

public void setUserName(String userName) {
    this.userName = userName;
}

public String getUserEmail() {
    return userEmail;
}

public void setUserEmail(String userEmail) {
    this.userEmail = userEmail;
}

public String getUserLocation() {
    return userLocation;
}

public void setUserLocation(String userLocation) {
    this.userLocation = userLocation;
}

public String getUserRole() {
    return userRole;
}

public void setUserRole(String userRole) {
    this.userRole = userRole;
}

// equals, hashCode and toString methods would also be needed

Comment on lines +7 to +10
public class CachedUserDTO implements Serializable {

private static final long serialVersionUID = 1L;

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

Consider adding equals, hashCode, and toString methods

For proper object comparison and debugging, implement these methods manually since Lombok is no longer available.

Here's an implementation example for equals and hashCode:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    CachedUserDTO that = (CachedUserDTO) o;
    return Objects.equals(userId, that.userId) &&
           Objects.equals(userName, that.userName) &&
           Objects.equals(userEmail, that.userEmail) &&
           Objects.equals(userLocation, that.userLocation) &&
           Objects.equals(userRole, that.userRole);
}

@Override
public int hashCode() {
    return Objects.hash(userId, userName, userEmail, userLocation, userRole);
}

@Override
public String toString() {
    return "CachedUserDTO{" +
           "userId=" + userId +
           ", userName='" + userName + '\'' +
           ", userEmail='" + userEmail + '\'' +
           ", userLocation='" + userLocation + '\'' +
           ", userRole='" + userRole + '\'' +
           '}';
}

Don't forget to add the import: import java.util.Objects;

Comment on lines +3 to +6
import lombok.Data;
import java.io.Serializable;

@Data

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

Lombok usage conflicts with project configuration

Based on the previous review comment, Lombok has been removed from the project, but this class still uses the @Data annotation from Lombok. This is likely contributing to the build failure mentioned in the PR comments.

Remove the Lombok dependency and implement the required methods manually:

-import lombok.Data;
 import java.io.Serializable;

-@Data
 public class CachedUserDTO implements Serializable {
📝 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
import lombok.Data;
import java.io.Serializable;
@Data
// src/main/java/org/example/springboot25/dto/CachedUserDTO.java
import java.io.Serializable;
public class CachedUserDTO implements Serializable {
// TODO: implement getters, setters, equals, hashCode, toString
// …
}

Signed-off-by: LinusAltemark <Linalt0905@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/main/java/org/example/springboot25/dto/CachedUserDTO.java (2)

6-54: Missing equals, hashCode, and toString methods

For proper object comparison and debugging, implement equals, hashCode, and toString methods. This is especially important for cached objects where identity and equality checks are common.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    CachedUserDTO that = (CachedUserDTO) o;
    return Objects.equals(userId, that.userId) &&
           Objects.equals(userName, that.userName) &&
           Objects.equals(userEmail, that.userEmail) &&
           Objects.equals(userLocation, that.userLocation) &&
           Objects.equals(userRole, that.userRole);
}

@Override
public int hashCode() {
    return Objects.hash(userId, userName, userEmail, userLocation, userRole);
}

@Override
public String toString() {
    return "CachedUserDTO{" +
           "userId=" + userId +
           ", userName='" + userName + '\'' +
           ", userEmail='" + userEmail + '\'' +
           ", userLocation='" + userLocation + '\'' +
           ", userRole='" + userRole + '\'' +
           '}';
}

Don't forget to add the import: import java.util.Objects;


7-14: ⚠️ Potential issue

Missing constructors needed for proper Redis serialization

The class lacks both a default constructor and a parameterized constructor, which are needed for proper serialization/deserialization with Redis. This is likely contributing to the build failure mentioned in the PR comments.

Add the following constructors:

// Default constructor
public CachedUserDTO() {
}

// Parameterized constructor
public CachedUserDTO(Long userId, String userName, String userEmail, String userLocation, String userRole) {
    this.userId = userId;
    this.userName = userName;
    this.userEmail = userEmail;
    this.userLocation = userLocation;
    this.userRole = userRole;
}
🧹 Nitpick comments (1)
src/main/java/org/example/springboot25/dto/CachedUserDTO.java (1)

7-7: Consider adding serialVersionUID for versioning

When implementing Serializable, it's recommended to declare a serialVersionUID field for better version control of serialized objects. This helps maintain compatibility between different versions of the class.

public class CachedUserDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // rest of the class
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c923311 and 998eb7d.

📒 Files selected for processing (1)
  • src/main/java/org/example/springboot25/dto/CachedUserDTO.java (1 hunks)

Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
Signed-off-by: LinusAltemark <Linalt0905@gmail.com>
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.

3 participants