Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

7988 synchronize places from database with google places api #8015

Merged

Conversation

Warded120
Copy link
Contributor

@Warded120 Warded120 commented Jan 10, 2025

Issue Link: 7988

Changes:

  • Duplicates are avoided when adding places from ui to database
  • created google api service method to fetch from API
  • created endpoint to fetch places from places API

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for filtering places using Google Places API.
    • Introduced a new endpoint for filtering places in the application.
    • Enhanced place creation with location existence checks.
    • Added new methods for interacting with Google Places API.
    • Implemented a new exception for handling duplicate places.
    • Added new constants for error messages related to location data.
  • Bug Fixes

    • Corrected grammatical error in validation message.
    • Improved error handling for duplicate place creation.
  • Documentation

    • Updated API documentation for place filtering methods.
    • Added detailed error messages for specific scenarios.
  • Security

    • Updated security configuration to protect new place filtering endpoint.

@Warded120 Warded120 linked an issue Jan 10, 2025 that may be closed by this pull request
Copy link

coderabbitai bot commented Jan 14, 2025

Warning

Rate limit exceeded

@Warded120 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 15 minutes and 43 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between d4cd904 and da83268.

📒 Files selected for processing (1)
  • service/src/test/java/greencity/ModelUtils.java (4 hunks)

Walkthrough

The pull request introduces enhanced place filtering capabilities in the GreenCity application by integrating Google Places API functionality. The changes span multiple components, including security configuration, controllers, services, and data transfer objects. A new endpoint for filtering places from an external API has been added, along with comprehensive error handling and testing infrastructure to support this feature. Additionally, new methods and exception handling mechanisms have been introduced to ensure robust interactions with the Google Places API.

Changes

File Change Summary
core/src/main/java/greencity/config/SecurityConfig.java Added security configuration for new /place/filter/api endpoint
core/src/main/java/greencity/controller/PlaceController.java Added new method getFilteredPlacesFromApi() for API-based place filtering; updated method documentation and parameter annotations
core/src/main/java/greencity/exception/handler/CustomExceptionHandler.java Introduced handler for PlaceAlreadyExistsException
core/src/main/resources/messages.properties Corrected validation message for maximum rate
dao/src/main/java/greencity/repository/LocationRepo.java Added method existsByLatAndLng for checking location existence
service-api/pom.xml Added dependency for google-maps-services library
service-api/src/main/java/greencity/constant/ErrorMessage.java Added constant LOCATION_NOT_FOUND and GEOCODING_RESULT_IS_EMPTY for error messaging
service-api/src/main/java/greencity/dto/filter/FilterPlacesApiDto.java New DTO for capturing Google Places API filter parameters
service-api/src/main/java/greencity/exception/exceptions/PlaceAlreadyExistsException.java Introduced new exception class for handling existing places
service-api/src/main/java/greencity/service/GoogleApiService.java Added methods to interact with Google Places API
service-api/src/main/java/greencity/service/LocationService.java Added method existsByLatAndLng for location existence check
service-api/src/main/java/greencity/service/PlaceService.java Added method getPlacesByFilter for filtering places using API DTO
service-api/src/main/java/greencity/service/PlaceServiceImpl.java Implemented place filtering from external API; enhanced error handling for place creation
service/src/test/java/greencity/controller/PlaceControllerTest.java Added test for getFilteredPlacesFromApi() method
service/src/test/java/greencity/service/GoogleApiServiceTest.java Added tests for Google Places API interactions
service/src/test/java/greencity/service/PlaceServiceImplTest.java Added tests for place filtering and existing location checks

Possibly related PRs

Suggested reviewers

  • KizerovDmitriy
  • holotsvan
  • Cr1stal423

Poem

🌍 Mapping Adventures, API in Hand
Places filtered with precision's command,
Coordinates dance, boundaries expand,
Google's magic weaves its grand design,
Searching realms both near and far online!
🗺️ Code explores each geographic strand!


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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. (Beta)
  • @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.

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.

Copy link

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

🧹 Nitpick comments (14)
service/src/main/java/greencity/service/GoogleApiService.java (2)

72-95: Add parameter validation and consider response pagination.

The implementation looks solid, but consider these improvements:

  1. Add null validation for the userVO parameter
  2. The Places API supports pagination through next_page_token, which might be useful for handling large result sets

Consider adding parameter validation:

 public List<PlacesSearchResult> getResultFromPlacesApi(FilterPlacesApiDto filterDto, UserVO userVO) {
+    if (userVO == null) {
+        throw new IllegalArgumentException("UserVO cannot be null");
+    }
+    if (filterDto == null) {
+        throw new IllegalArgumentException("FilterPlacesApiDto cannot be null");
+    }
     List<PlacesSearchResult> placesResults = new ArrayList<>();

105-133: Well-structured filter application logic!

The implementation is clean and maintainable. Each filter is properly checked before application.

Consider using the builder pattern to make the request construction more fluent:

private static NearbySearchRequest applyFiltersToRequest(FilterPlacesApiDto filterDto, NearbySearchRequest request) {
    return Optional.of(request)
        .map(req -> filterDto.getKeyword() != null ? req.keyword(filterDto.getKeyword()) : req)
        .map(req -> filterDto.getType() != null ? req.type(filterDto.getType()) : req)
        // ... other filters
        .get();
}
service/src/test/java/greencity/service/GoogleApiServiceTest.java (2)

116-144: Consider enhancing assertion coverage

While the test effectively verifies the size of combined results, consider adding assertions for:

  • The content/order of the results
  • Individual locale responses
  • Specific address components

This would provide more comprehensive validation of the geocoding functionality.

Example assertion additions:

// Verify content of results
assertEquals(ModelUtils.getGeocodingResultUk()[0].formattedAddress, 
    actual.get(0).formattedAddress);
assertEquals(ModelUtils.getGeocodingResultEn()[0].formattedAddress, 
    actual.get(ModelUtils.getGeocodingResultUk().length).formattedAddress);

146-172: Enhance error handling verification

The test confirms that exceptions don't propagate, but consider verifying:

  • The returned result (should it be empty list?)
  • Logging of the failed attempts
  • Error messages or specific exception types

Example enhancement:

List<GeocodingResult> results = googleApiService.getResultFromGeoCode(searchRequest);
assertTrue(results.isEmpty(), "Should return empty list when both requests fail");
// Consider adding verification for error logging if applicable
service-api/src/main/java/greencity/exception/exceptions/PlaceAlreadyExistsException.java (1)

1-7: Well-structured exception class!

The exception class is appropriately designed as an unchecked exception and uses Lombok's @StandardException for clean code. Consider adding Javadoc to document the specific scenarios when this exception is thrown.

 @StandardException
+/**
+ * Exception thrown when attempting to add a place that already exists in the database,
+ * typically when the coordinates match an existing place within a certain precision.
+ */
 public class PlaceAlreadyExistsException extends RuntimeException {
service-api/src/main/java/greencity/dto/filter/FilterPlacesApiDto.java (1)

13-23: Consider adding field validations

The DTO fields could benefit from validation annotations to ensure data integrity:

  • radius should have a minimum value (typically > 0)
  • location should not be null when performing location-based searches
  • minPrice and maxPrice should maintain a valid range relationship
 public class FilterPlacesApiDto {
+    @NotNull(message = "Location is required for place search")
     private LatLng location;
+    @Min(value = 1, message = "Radius must be greater than 0")
     private int radius;
     private RankBy rankBy;
     private String keyword;
     private PriceLevel minPrice;
     private PriceLevel maxPrice;
     private String name;
     private boolean openNow;
     private PlaceType type;
dao/src/main/java/greencity/repository/LocationRepo.java (1)

34-42: Well-implemented coordinate comparison with precision handling!

The native SQL query effectively handles coordinate comparison with 4 decimal places precision. A few considerations:

  1. The ROUND + CAST combination might affect index usage
  2. 4 decimal places ≈ 11m precision at the equator, which seems reasonable for place deduplication

Consider adding an index on (lat, lng) if not already present, to optimize these existence checks:

CREATE INDEX idx_locations_lat_lng ON locations (lat, lng);
service-api/src/main/java/greencity/service/LocationService.java (1)

61-70: Consider using primitive types for required parameters

The method is well-documented and aligns with the repository implementation. However, since coordinates are essential for location checking, consider using primitive double instead of Double to indicate that null values are not acceptable.

-    boolean existsByLatAndLng(Double lat, Double lng);
+    boolean existsByLatAndLng(double lat, double lng);
service/src/main/java/greencity/service/LocationServiceImpl.java (1)

122-125: Clean implementation, consider adding parameter validation.

The method effectively delegates to the repository layer. However, consider adding null checks and coordinate range validation.

 @Override
 public boolean existsByLatAndLng(Double lat, Double lng) {
+    if (lat == null || lng == null) {
+        throw new IllegalArgumentException("Coordinates cannot be null");
+    }
+    if (lat < -90 || lat > 90 || lng < -180 || lng > 180) {
+        throw new IllegalArgumentException("Invalid coordinates range");
+    }
     return locationRepo.existsByLatAndLng(lat, lng);
 }
service-api/src/main/java/greencity/constant/ErrorMessage.java (1)

130-130: Clear error message format, consider adding Javadoc.

The error message format is well-structured with proper precision for coordinates.

Consider adding Javadoc to document the format parameters:

/**
 * Error message for duplicate place coordinates.
 * Format parameters:
 * 1. %f - latitude (4 decimal places)
 * 2. %f - longitude (4 decimal places)
 */
public static final String PLACE_ALREADY_EXISTS = "Place with lat: %.4f and lng: %.4f already exists";
core/src/test/java/greencity/controller/PlaceControllerTest.java (1)

435-463: Good test coverage, consider adding error scenarios.

The test effectively verifies the happy path. Consider adding tests for:

  • Invalid coordinates
  • Missing required parameters
  • API error responses

Example test case for invalid coordinates:

@Test
void getFilteredPlacesFromApiWithInvalidCoordinates() throws Exception {
    String json = """
        {
          "location": {
            "lat": 91,  // Invalid latitude
            "lng": 181  // Invalid longitude
          },
          "radius": 10000
        }
        """;
    
    this.mockMvc.perform(post(placeLink + "/filter/api")
        .content(json)
        .principal(principal)
        .contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isBadRequest());
}
core/src/test/java/greencity/ModelUtils.java (1)

580-586: Consider initializing LocationDto fields

The empty LocationDto constructor might not provide meaningful test data. Consider initializing it with test coordinates and address information for more realistic test scenarios.

-            .location(new LocationDto())
+            .location(LocationDto.builder()
+                .id(1L)
+                .lat(50.4501)
+                .lng(30.5234)
+                .address("Test Address")
+                .build())
service/src/test/java/greencity/service/PlaceServiceImplTest.java (1)

657-674: Consider adding error case tests for API integration.

The happy path test is well implemented. Consider adding tests for:

  1. API timeout scenarios
  2. Invalid response formats
  3. Network failures
service/src/test/java/greencity/ModelUtils.java (1)

3332-3342: Consider adding more test data variations.

The FilterPlacesApiDto builder provides good basic test data. Consider adding factory methods for:

  1. Edge cases (maximum radius, empty keyword)
  2. Invalid combinations (incompatible rankBy and radius)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c769ab and c6b8591.

📒 Files selected for processing (19)
  • core/src/main/java/greencity/config/SecurityConfig.java (1 hunks)
  • core/src/main/java/greencity/controller/PlaceController.java (3 hunks)
  • core/src/main/java/greencity/exception/handler/CustomExceptionHandler.java (2 hunks)
  • core/src/main/resources/messages.properties (1 hunks)
  • core/src/test/java/greencity/ModelUtils.java (4 hunks)
  • core/src/test/java/greencity/controller/PlaceControllerTest.java (3 hunks)
  • dao/src/main/java/greencity/repository/LocationRepo.java (2 hunks)
  • service-api/pom.xml (1 hunks)
  • service-api/src/main/java/greencity/constant/ErrorMessage.java (1 hunks)
  • service-api/src/main/java/greencity/dto/filter/FilterPlacesApiDto.java (1 hunks)
  • service-api/src/main/java/greencity/exception/exceptions/PlaceAlreadyExistsException.java (1 hunks)
  • service-api/src/main/java/greencity/service/LocationService.java (1 hunks)
  • service-api/src/main/java/greencity/service/PlaceService.java (3 hunks)
  • service/src/main/java/greencity/service/GoogleApiService.java (3 hunks)
  • service/src/main/java/greencity/service/LocationServiceImpl.java (1 hunks)
  • service/src/main/java/greencity/service/PlaceServiceImpl.java (5 hunks)
  • service/src/test/java/greencity/ModelUtils.java (5 hunks)
  • service/src/test/java/greencity/service/GoogleApiServiceTest.java (3 hunks)
  • service/src/test/java/greencity/service/PlaceServiceImplTest.java (6 hunks)
✅ Files skipped from review due to trivial changes (1)
  • core/src/main/resources/messages.properties
🔇 Additional comments (15)
service/src/main/java/greencity/service/GoogleApiService.java (2)

5-18: Clean and well-organized import additions!

The new imports are properly organized and specifically target the required Google Places API functionality.


Line range hint 35-35: Verify thread safety of GeoApiContext usage.

The context field is shared across multiple methods. Let's verify its thread safety for concurrent API calls.

service/src/test/java/greencity/service/GoogleApiServiceTest.java (2)

6-13: Well-organized imports!

The new imports for Places API integration are properly organized and include all necessary classes for the enhanced functionality.


174-220: Excellent test coverage with room for enhancement

The test thoroughly covers the Places API integration, but consider these improvements:

  1. Reduce mock setup duplication:
private NearbySearchRequest setupMockRequest(NearbySearchRequest request, 
    FilterPlacesApiDto filterDto, String language) {
    return when(request.radius(filterDto.getRadius())).thenReturn(request)
        .language(language).thenReturn(request)
        // ... other common setups
        .name(filterDto.getName()).thenReturn(request);
}
  1. Add edge case tests:
  • Empty/null filter parameters
  • Maximum/minimum values for radius
  • Invalid location coordinates
  • Empty results handling
  1. Verify response transformation:
// Add assertions for specific fields
assertEquals(expected.get(0).name, actual.get(0).name);
assertEquals(expected.get(0).geometry, actual.get(0).geometry);
service-api/src/main/java/greencity/dto/filter/FilterPlacesApiDto.java (1)

24-39: Review default values in defaultJson

The default values need attention:

  • Latitude and longitude of (0,0) is in the Atlantic Ocean
  • A radius of 0 is invalid for place searches
  • Consider documenting the purpose of these default values or adjusting them to more meaningful defaults

Could you clarify if these default values are intended for documentation/example purposes or actual default behavior?

service-api/src/main/java/greencity/service/PlaceService.java (1)

192-202: Well-documented API integration method.

The method signature and documentation are clear and consistent with the existing codebase patterns.

core/src/test/java/greencity/ModelUtils.java (1)

568-578: Well-structured test utility method!

The method provides a comprehensive test DTO with appropriate test data ranges and follows the builder pattern effectively.

core/src/main/java/greencity/controller/PlaceController.java (2)

259-261: Documentation improvements enhance clarity!

The updated documentation clearly distinguishes between places retrieved from the database versus the Google Places API, which helps API consumers understand the data source.


283-285: Clean API documentation with hidden implementation details!

Good use of @parameter(hidden = true) to keep the OpenAPI documentation clean while maintaining the required authentication context.

service/src/test/java/greencity/service/PlaceServiceImplTest.java (1)

788-811: Well-structured test for duplicate location validation!

The test effectively verifies the business logic for preventing duplicate locations. Good use of mocking and verification steps.

service-api/pom.xml (1)

111-116: Verify Google Maps Services dependency version for security.

The dependency is correctly configured, but please verify:

  1. If version 2.2.0 is the latest stable release
  2. Known security vulnerabilities in this version

Run this script to check for newer versions and security advisories:

✅ Verification successful

Google Maps Services dependency is up-to-date and secure

The version 2.2.0 is the latest stable release, and no security vulnerabilities were found in the GitHub Security Advisory Database. The dependency is properly configured.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check Maven Central for latest version
curl -s "https://search.maven.org/solrsearch/select?q=g:com.google.maps+AND+a:google-maps-services&rows=1&wt=json" | jq -r '.response.docs[0].latestVersion'

# Check for security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: MAVEN, package: "com.google.maps:google-maps-services") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 536

core/src/main/java/greencity/config/SecurityConfig.java (1)

331-331: LGTM! Security configuration is properly set for the new endpoint.

The new endpoint /place/filter/api is correctly configured with appropriate role-based access control, requiring authenticated users with USER, ADMIN, MODERATOR, or UBS_EMPLOYEE roles.

core/src/main/java/greencity/exception/handler/CustomExceptionHandler.java (2)

31-32: LGTM! Import statements are properly organized.

The imports for WrongIdException and PlaceAlreadyExistsException are correctly added.


218-224: LGTM! Exception handler is well implemented.

The handler for PlaceAlreadyExistsException follows the established pattern:

  • Proper logging of the exception message
  • Consistent error response structure
  • Appropriate HTTP status code (BAD_REQUEST)
service/src/main/java/greencity/service/PlaceServiceImpl.java (1)

581-585: LGTM! Proper duplicate check implementation.

The implementation correctly:

  • Extracts latitude and longitude from geocoding results
  • Checks for existing places at the same coordinates
  • Throws a descriptive exception with formatted message

Comment on lines 141 to 144
private com.google.maps.model.LatLng getLocationFromUserVO(UserVO userVO) {
return new com.google.maps.model.LatLng(userVO.getUserLocationDto().getLatitude(),
userVO.getUserLocationDto().getLongitude());
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null safety checks for location conversion.

The method needs protection against null UserLocationDto to prevent NullPointerException.

Add null validation:

 private com.google.maps.model.LatLng getLocationFromUserVO(UserVO userVO) {
+    if (userVO.getUserLocationDto() == null) {
+        throw new IllegalArgumentException("User location data is not available");
+    }
     return new com.google.maps.model.LatLng(userVO.getUserLocationDto().getLatitude(),
         userVO.getUserLocationDto().getLongitude());
 }
📝 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
private com.google.maps.model.LatLng getLocationFromUserVO(UserVO userVO) {
return new com.google.maps.model.LatLng(userVO.getUserLocationDto().getLatitude(),
userVO.getUserLocationDto().getLongitude());
}
private com.google.maps.model.LatLng getLocationFromUserVO(UserVO userVO) {
if (userVO.getUserLocationDto() == null) {
throw new IllegalArgumentException("User location data is not available");
}
return new com.google.maps.model.LatLng(userVO.getUserLocationDto().getLatitude(),
userVO.getUserLocationDto().getLongitude());
}

Comment on lines 287 to 317
/**
* The method which return a list of {@link PlaceByBoundsDto} filtered by values
* contained in the incoming {@link FilterPlacesApiDto} object.
* {@link PlaceByBoundsDto} are retrieved from Google Places API
*
* @param filterDto contains all information about the filtering of the list.
* @return a list of {@code PlaceByBoundsDto}
*/
@Operation(summary = "Return a list places from Google Geocoding API filtered by values contained "
+ "in the incoming FilterPlaceDto object")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK,
content = @Content(schema = @Schema(example = FilterPlacesApiDto.defaultJson))),
@ApiResponse(responseCode = "303", description = HttpStatuses.SEE_OTHER,
content = @Content(examples = @ExampleObject(HttpStatuses.SEE_OTHER))),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST,
content = @Content(examples = @ExampleObject(HttpStatuses.BAD_REQUEST))),
@ApiResponse(responseCode = "403", description = HttpStatuses.FORBIDDEN,
content = @Content(examples = @ExampleObject(HttpStatuses.FORBIDDEN))),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
})
@PostMapping("/filter/api")
public ResponseEntity<List<PlaceByBoundsDto>> getFilteredPlacesFromApi(
@Schema(
description = "Filters for places from API",
name = "FilterPlacesApiDto",
type = "object",
example = FilterPlacesApiDto.defaultJson) @Valid @RequestBody FilterPlacesApiDto filterDto,
@CurrentUser @Parameter(hidden = true) UserVO userVO) {
return ResponseEntity.ok().body(placeService.getPlacesByFilter(filterDto, userVO));
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Consider implementing robust rate limiting and error handling for Google Places API

The current implementation makes parallel requests for multiple locales without rate limiting protection. To prevent API quota exhaustion and ensure service reliability:

  1. Implement a rate limiter (e.g., token bucket algorithm) to control requests per second
  2. Add specific error handling for quota exceeded scenarios:
try {
    // API call
} catch (ApiException e) {
    if (e.getMessage().contains("OVER_QUERY_LIMIT")) {
        // Handle rate limit exceeded
    }
}
  1. Consider caching frequently requested results to reduce API calls
🔗 Analysis chain

Verify rate limiting for Google Places API integration.

The endpoint is well-documented with appropriate response codes. However, consider:

  1. Implementing rate limiting to prevent API quota exhaustion
  2. Adding error handling for Google Places API failures

Run this script to check for rate limit handling:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for rate limit handling in the codebase
rg -A 5 "RateLimitExceededException|GoogleApiException" 

Length of output: 57


Script:

#!/bin/bash
# Search for Google Places API related code and configuration
echo "=== Searching for Google Places API related files ==="
fd -t f -e java -e properties -e yml | xargs rg -l "google.*places|places.*api"

echo -e "\n=== Checking service implementation ==="
ast-grep --pattern 'class $_ implements $_ {
  $$$
  getPlacesByFilter($_, $_) {
    $$$
  }
  $$$
}'

Length of output: 397


Script:

#!/bin/bash
# Search with broader patterns
echo "=== Searching for Google API related code ==="
rg -l "google.*api|googleapi|places.*service" --type java

echo -e "\n=== Checking for configuration properties ==="
rg -l "google\." --type properties --type yml

echo -e "\n=== Looking for PlaceService implementation ==="
rg -A 10 "class.*PlaceService.*implements" --type java

Length of output: 4298


Script:

#!/bin/bash
# Check the implementation and configuration
echo "=== Checking PlaceService implementation ==="
rg -A 20 "getPlacesByFilter.*FilterPlacesApiDto" service/src/main/java/greencity/service/PlaceServiceImpl.java

echo -e "\n=== Checking application properties ==="
cat core/src/main/resources/application.properties | grep -A 5 "google"

Length of output: 1718


Script:

#!/bin/bash
# Check Google API Service implementation
echo "=== Checking Google API Service implementation ==="
rg -A 10 "class.*GoogleApiService" --type java

echo "=== Checking for Google API related exception handling ==="
rg -A 5 "getResultFromPlacesApi|GoogleApiService" --type java

Length of output: 11814

Comment on lines +458 to +468
@Override
public List<PlaceByBoundsDto> getPlacesByFilter(FilterPlacesApiDto filterDto, UserVO userVO) {
List<PlacesSearchResult> fromPlacesApi = googleApiService.getResultFromPlacesApi(filterDto, userVO);
return fromPlacesApi.stream()
.map(el -> new PlaceByBoundsDto(
System.currentTimeMillis(),
el.name,
new LocationDto(System.currentTimeMillis(), el.geometry.location.lat, el.geometry.location.lng,
el.vicinity)))
.toList();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using a proper ID generation strategy.

Using System.currentTimeMillis() for IDs is not recommended as it:

  • Can lead to duplicate IDs if multiple places are created in the same millisecond
  • Makes the IDs predictable and potentially vulnerable to enumeration attacks

Consider using:

  • UUID.randomUUID() for globally unique identifiers
  • Database auto-generated sequence for sequential IDs
  • A dedicated ID generation service for distributed systems
-                System.currentTimeMillis(),
+                UUID.randomUUID().toString(),
-                new LocationDto(System.currentTimeMillis(), el.geometry.location.lat, el.geometry.location.lng,
+                new LocationDto(UUID.randomUUID().toString(), el.geometry.location.lat, el.geometry.location.lng,

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

…abase-with-google-places-api' into 7988-synchronize-places-from-database-with-google-places-api
Copy link

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

🧹 Nitpick comments (1)
dao/src/main/java/greencity/repository/LocationRepo.java (1)

36-44: Consider performance optimization for coordinate comparison.

The implementation is correct, but there are a few suggestions to consider:

  1. The current query performs rounding for every row in the table. Consider creating computed/generated columns for rounded coordinates to improve query performance.
  2. The 4 decimal places precision gives approximately 11 meters accuracy at the equator. Consider documenting this business decision in the comments.

Here's an example of how to create computed columns:

ALTER TABLE locations
ADD COLUMN lat_rounded NUMERIC GENERATED ALWAYS AS (ROUND(CAST(lat AS numeric), 4)) STORED,
ADD COLUMN lng_rounded NUMERIC GENERATED ALWAYS AS (ROUND(CAST(lng AS numeric), 4)) STORED;

CREATE INDEX idx_location_coords ON locations(lat_rounded, lng_rounded);

Then the query could be simplified to:

-    @Query(value = """
-         SELECT EXISTS (
-             SELECT 1
-             FROM locations l
-             WHERE ROUND(CAST(l.lat AS numeric), 4) = ROUND(CAST(:lat AS numeric), 4)
-               AND ROUND(CAST(l.lng AS numeric), 4) = ROUND(CAST(:lng AS numeric), 4)
-         )
-        """, nativeQuery = true)
+    @Query(value = """
+         SELECT EXISTS (
+             SELECT 1
+             FROM locations l
+             WHERE l.lat_rounded = ROUND(CAST(:lat AS numeric), 4)
+               AND l.lng_rounded = ROUND(CAST(:lng AS numeric), 4)
+         )
+        """, nativeQuery = true)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff58960 and f218b6f.

📒 Files selected for processing (2)
  • dao/src/main/java/greencity/repository/LocationRepo.java (2 hunks)
  • service-api/src/main/java/greencity/service/LocationService.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • service-api/src/main/java/greencity/service/LocationService.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (3)
dao/src/main/java/greencity/repository/LocationRepo.java (3)

6-7: LGTM! Clean import organization.

The new imports are properly organized and specifically imported rather than using wildcard imports.


25-35: Well-documented method with clear purpose!

The Javadoc is comprehensive and clearly explains the method's behavior, including the important detail about 4 decimal places precision.


36-44: Verify existing indexes on the locations table.

Let's check if there are any existing indexes that could affect query performance.

Copy link

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

🧹 Nitpick comments (2)
service/src/test/java/greencity/service/PlaceServiceImplTest.java (2)

657-674: Good test coverage, consider adding edge cases.

The test effectively verifies the happy path for Google Places API integration. Consider adding test cases for:

  • Empty API results
  • API error responses
  • Rate limit scenarios
 @Test
 void getPlacesByFilterFromApiTest() {
     FilterPlacesApiDto filterDto = getFilterPlacesApiDto();
 
     when(googleApiService.getResultFromPlacesApi(filterDto, userVO)).thenReturn(getPlacesSearchResultEn());
 
     var result = placeService.getPlacesByFilter(filterDto, userVO);
     List<PlaceByBoundsDto> updatedResult = result.stream().map(el -> {
         el.setId(1L);
         el.getLocation().setId(1L);
         return el;
     }).toList();
     List<PlaceByBoundsDto> expectedResult = getPlaceByBoundsDtoFromApi();
 
     Assertions.assertArrayEquals(updatedResult.toArray(), expectedResult.toArray());
 
     verify(googleApiService).getResultFromPlacesApi(filterDto, userVO);
+    
+    // Test empty results
+    when(googleApiService.getResultFromPlacesApi(filterDto, userVO)).thenReturn(Collections.emptyList());
+    assertTrue(placeService.getPlacesByFilter(filterDto, userVO).isEmpty());
+    
+    // Test API error
+    when(googleApiService.getResultFromPlacesApi(filterDto, userVO)).thenThrow(new RuntimeException("API Error"));
+    assertThrows(RuntimeException.class, () -> placeService.getPlacesByFilter(filterDto, userVO));
 }

787-809: Consider a more descriptive test name.

The test effectively verifies duplicate location handling. However, the test name could be more descriptive to better indicate the scenario being tested.

-void addPlaceFromUiSaveAlreadyExistingLocation() {
+void addPlaceFromUi_WhenLocationAlreadyExists_ThrowsException() {
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0779b50 and dfae829.

📒 Files selected for processing (1)
  • service/src/test/java/greencity/service/PlaceServiceImplTest.java (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (1)
service/src/test/java/greencity/service/PlaceServiceImplTest.java (1)

10-10: LGTM! Well-organized imports.

The new imports are properly organized and directly support the new test functionality.

Also applies to: 41-41, 68-70

Copy link

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

🧹 Nitpick comments (2)
service/src/test/java/greencity/service/GoogleApiServiceTest.java (2)

117-173: Consider enhancing test method clarity and maintainability.

The geocoding test methods are well-structured, but could be improved:

  1. Consider making test method names more descriptive:
-void getResultsFromGeocodeTest()
+void getResultFromGeoCode_WhenBothLocalesSucceed_ReturnsCombinedResults()

-void getResultsFromGeocodeThrowsTest()
+void getResultFromGeoCode_WhenBothLocalesFail_ReturnsEmptyList()
  1. Extract repeated test data as constants:
private static final String TEST_SEARCH_REQUEST = "testSearchRequest";
private static final Locale LOCALE_UK = Locale.forLanguageTag("uk");
private static final Locale LOCALE_EN = Locale.forLanguageTag("en");

175-309: Consider adding tests for additional edge cases.

While the current test coverage is good, consider adding tests for these scenarios:

  1. Empty results from both locales
  2. Mixed results (success from one locale, empty from another)
  3. Invalid filter parameters (negative radius, invalid price range)
  4. Rate limit exceeded scenarios

Example for empty results test:

@Test
void getResultFromPlacesApi_WhenBothLocalesReturnEmpty_ReturnsEmptyList() {
    // Setup similar to existing tests but return empty results
    when(requestUk.await()).thenReturn(new PlacesSearchResult[0]);
    when(requestEn.await()).thenReturn(new PlacesSearchResult[0]);
    // Assert empty result
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfae829 and 6675532.

📒 Files selected for processing (1)
  • service/src/test/java/greencity/service/GoogleApiServiceTest.java (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (1)
service/src/test/java/greencity/service/GoogleApiServiceTest.java (1)

6-7: Well-structured imports and class setup!

The new imports are properly organized and align well with the added functionality for Places API integration.

Also applies to: 10-11, 13-13, 15-15, 17-17, 26-26

Comment on lines 175 to 309

List<PlacesSearchResult> actual = googleApiService.getResultFromPlacesApi(filterDto, userVO);

List<PlacesSearchResult> expected = List.of(ModelUtils.getPlacesSearchResultUk().getFirst(),
ModelUtils.getPlacesSearchResultEn().getFirst());

assertEquals(actual.size(), expected.size());

verify(requestUk, times(1)).await();
verify(requestEn, times(1)).await();
}
}

@Test
void getResultFromPlacesApiNullLocationTest() throws IOException, InterruptedException, ApiException {
FilterPlacesApiDto filterDto = ModelUtils.getFilterPlacesApiDto();
filterDto.setLocation(null);
UserVO userVO = ModelUtils.getUserVO();
userVO.getUserLocationDto().setLatitude(null);

Locale localeUk = Locale.forLanguageTag("uk");
Locale localeEn = Locale.forLanguageTag("en");
try (MockedStatic<PlacesApi> placesApiMockedStatic = mockStatic(PlacesApi.class)) {
NearbySearchRequest requestUk = mock(NearbySearchRequest.class);
NearbySearchRequest requestEn = mock(NearbySearchRequest.class);

when(PlacesApi.nearbySearchQuery(context, filterDto.getLocation())).thenReturn(requestUk, requestEn);

when(requestUk.radius(filterDto.getRadius())).thenReturn(requestUk);
when(requestUk.language(localeUk.getLanguage())).thenReturn(requestUk);
when(requestUk.keyword(filterDto.getKeyword())).thenReturn(requestUk);
when(requestUk.type(filterDto.getType())).thenReturn(requestUk);
when(requestUk.rankby(filterDto.getRankBy())).thenReturn(requestUk);
when(requestUk.minPrice(filterDto.getMinPrice())).thenReturn(requestUk);
when(requestUk.maxPrice(filterDto.getMaxPrice())).thenReturn(requestUk);
when(requestUk.openNow(filterDto.isOpenNow())).thenReturn(requestUk);
when(requestUk.name(filterDto.getName())).thenReturn(requestUk);

when(requestEn.radius(filterDto.getRadius())).thenReturn(requestEn);
when(requestEn.language(localeEn.getLanguage())).thenReturn(requestEn);
when(requestEn.keyword(filterDto.getKeyword())).thenReturn(requestEn);
when(requestEn.type(filterDto.getType())).thenReturn(requestEn);
when(requestEn.rankby(filterDto.getRankBy())).thenReturn(requestEn);
when(requestEn.minPrice(filterDto.getMinPrice())).thenReturn(requestEn);
when(requestEn.maxPrice(filterDto.getMaxPrice())).thenReturn(requestEn);
when(requestEn.openNow(filterDto.isOpenNow())).thenReturn(requestEn);
when(requestEn.name(filterDto.getName())).thenReturn(requestEn);

when(requestUk.await()).thenReturn(ModelUtils.getPlacesSearchResponseUk());
when(requestEn.await()).thenReturn(ModelUtils.getPlacesSearchResponseEn());

assertThrows(NotFoundException.class, () -> googleApiService.getResultFromPlacesApi(filterDto, userVO));

verify(requestUk, times(0)).await();
verify(requestEn, times(0)).await();
}
}

@Test
void getResultFromPlacesApiThrowsApiExceptionTest() throws IOException, InterruptedException, ApiException {
FilterPlacesApiDto filterDto = ModelUtils.getFilterPlacesApiDto();
UserVO userVO = ModelUtils.getUserVO();

Locale localeUk = Locale.forLanguageTag("uk");
Locale localeEn = Locale.forLanguageTag("en");
try (MockedStatic<PlacesApi> placesApiMockedStatic = mockStatic(PlacesApi.class)) {
NearbySearchRequest requestUk = mock(NearbySearchRequest.class);
NearbySearchRequest requestEn = mock(NearbySearchRequest.class);

when(PlacesApi.nearbySearchQuery(context, filterDto.getLocation())).thenReturn(requestUk, requestEn);

when(requestUk.radius(filterDto.getRadius())).thenReturn(requestUk);
when(requestUk.language(localeUk.getLanguage())).thenReturn(requestUk);
when(requestUk.keyword(filterDto.getKeyword())).thenReturn(requestUk);
when(requestUk.type(filterDto.getType())).thenReturn(requestUk);
when(requestUk.rankby(filterDto.getRankBy())).thenReturn(requestUk);
when(requestUk.minPrice(filterDto.getMinPrice())).thenReturn(requestUk);
when(requestUk.maxPrice(filterDto.getMaxPrice())).thenReturn(requestUk);
when(requestUk.openNow(filterDto.isOpenNow())).thenReturn(requestUk);
when(requestUk.name(filterDto.getName())).thenReturn(requestUk);

when(requestEn.radius(filterDto.getRadius())).thenReturn(requestEn);
when(requestEn.language(localeEn.getLanguage())).thenReturn(requestEn);
when(requestEn.keyword(filterDto.getKeyword())).thenReturn(requestEn);
when(requestEn.type(filterDto.getType())).thenReturn(requestEn);
when(requestEn.rankby(filterDto.getRankBy())).thenReturn(requestEn);
when(requestEn.minPrice(filterDto.getMinPrice())).thenReturn(requestEn);
when(requestEn.maxPrice(filterDto.getMaxPrice())).thenReturn(requestEn);
when(requestEn.openNow(filterDto.isOpenNow())).thenReturn(requestEn);
when(requestEn.name(filterDto.getName())).thenReturn(requestEn);

when(requestUk.await()).thenReturn(ModelUtils.getPlacesSearchResponseUk());
when(requestEn.await()).thenThrow(ApiException.class);

assertDoesNotThrow(() -> googleApiService.getResultFromPlacesApi(filterDto, userVO));

verify(requestUk, times(1)).await();
verify(requestEn, times(1)).await();
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reduce code duplication in Places API test methods.

The Places API test methods contain significant duplication in mock setup. Consider extracting common setup code:

private NearbySearchRequest setupMockRequest(NearbySearchRequest request, FilterPlacesApiDto filterDto, Locale locale) {
    when(request.radius(filterDto.getRadius())).thenReturn(request);
    when(request.language(locale.getLanguage())).thenReturn(request);
    when(request.keyword(filterDto.getKeyword())).thenReturn(request);
    when(request.type(filterDto.getType())).thenReturn(request);
    when(request.rankby(filterDto.getRankBy())).thenReturn(request);
    when(request.minPrice(filterDto.getMinPrice())).thenReturn(request);
    when(request.maxPrice(filterDto.getMaxPrice())).thenReturn(request);
    when(request.openNow(filterDto.isOpenNow())).thenReturn(request);
    when(request.name(filterDto.getName())).thenReturn(request);
    return request;
}

This would simplify your test methods significantly:

@Test
void getResultFromPlacesApiTest() throws IOException, InterruptedException, ApiException {
    FilterPlacesApiDto filterDto = ModelUtils.getFilterPlacesApiDto();
    UserVO userVO = ModelUtils.getUserVO();
    
    try (MockedStatic<PlacesApi> placesApiMockedStatic = mockStatic(PlacesApi.class)) {
        NearbySearchRequest requestUk = setupMockRequest(
            mock(NearbySearchRequest.class), filterDto, LOCALE_UK);
        NearbySearchRequest requestEn = setupMockRequest(
            mock(NearbySearchRequest.class), filterDto, LOCALE_EN);
        // ... rest of the test
    }
}

Copy link

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

♻️ Duplicate comments (1)
service/src/test/java/greencity/service/GoogleApiServiceTest.java (1)

175-221: 🛠️ Refactor suggestion

Reduce code duplication in Places API test methods.

The Places API test methods contain significant duplication in mock setup for both Ukrainian and English locales. This makes the tests harder to maintain and more prone to errors.

Extract the common setup code into helper methods as suggested in the previous review:

private NearbySearchRequest setupMockRequest(NearbySearchRequest request, FilterPlacesApiDto filterDto, Locale locale) {
    when(request.radius(filterDto.getRadius())).thenReturn(request);
    when(request.language(locale.getLanguage())).thenReturn(request);
    when(request.keyword(filterDto.getKeyword())).thenReturn(request);
    when(request.type(filterDto.getType())).thenReturn(request);
    when(request.rankby(filterDto.getRankBy())).thenReturn(request);
    when(request.minPrice(filterDto.getMinPrice())).thenReturn(request);
    when(request.maxPrice(filterDto.getMaxPrice())).thenReturn(request);
    when(request.openNow(filterDto.isOpenNow())).thenReturn(request);
    when(request.name(filterDto.getName())).thenReturn(request);
    return request;
}

This would significantly simplify your test methods:

@Test
void getResultFromPlacesApiTest() throws IOException, InterruptedException, ApiException {
    FilterPlacesApiDto filterDto = ModelUtils.getFilterPlacesApiDto();
    UserVO userVO = ModelUtils.getUserVO();
    
    try (MockedStatic<PlacesApi> placesApiMockedStatic = mockStatic(PlacesApi.class)) {
        NearbySearchRequest requestUk = setupMockRequest(
            mock(NearbySearchRequest.class), filterDto, Locale.forLanguageTag("uk"));
        NearbySearchRequest requestEn = setupMockRequest(
            mock(NearbySearchRequest.class), filterDto, Locale.forLanguageTag("en"));
        
        when(PlacesApi.nearbySearchQuery(context, filterDto.getLocation()))
            .thenReturn(requestUk, requestEn);
        when(requestUk.await()).thenReturn(ModelUtils.getPlacesSearchResponseUk());
        when(requestEn.await()).thenReturn(ModelUtils.getPlacesSearchResponseEn());
        
        List<PlacesSearchResult> actual = googleApiService.getResultFromPlacesApi(filterDto, userVO);
        assertEquals(2, actual.size());
        verify(requestUk, times(1)).await();
        verify(requestEn, times(1)).await();
    }
}

Also applies to: 223-266, 268-309

🧹 Nitpick comments (1)
service/src/test/java/greencity/service/GoogleApiServiceTest.java (1)

117-145: Test method looks good but could benefit from descriptive test name.

The test logic for getResultsFromGeocodeTest is well-structured and covers both Ukrainian and English locales. However, the test name could be more descriptive of what it's actually testing.

Consider renaming to getResultsFromGeocode_WhenBothLocalesSucceed_ReturnsCombinedResults to better describe the test scenario and expected outcome.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6675532 and 0995dbc.

📒 Files selected for processing (1)
  • service/src/test/java/greencity/service/GoogleApiServiceTest.java (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (3)
service/src/test/java/greencity/service/GoogleApiServiceTest.java (3)

147-173: Test method effectively verifies error handling.

The getResultsFromGeocodeThrowsTest properly verifies that the service gracefully handles API exceptions for both locales.


223-266: Good edge case testing for null location.

The test getResultFromPlacesApiNullLocationTest properly verifies that a NotFoundException is thrown when location is null. The verification of zero calls to await() is a nice touch.


268-309: Good error handling test for API exception.

The test getResultFromPlacesApiThrowsApiExceptionTest effectively verifies that the service gracefully handles API exceptions and continues processing.

Warded120 and others added 3 commits January 24, 2025 17:12
Copy link

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

♻️ Duplicate comments (1)
service/src/test/java/greencity/service/GoogleApiServiceTest.java (1)

177-223: 🛠️ Refactor suggestion

Reduce code duplication in Places API test setup.

There's significant duplication in the mock setup for the Places API requests between Ukrainian and English locales.

Extract the common setup code into a helper method:

private NearbySearchRequest setupMockRequest(NearbySearchRequest request, FilterPlacesApiDto filterDto, Locale locale) {
    when(request.radius(filterDto.getRadius())).thenReturn(request);
    when(request.language(locale.getLanguage())).thenReturn(request);
    when(request.keyword(filterDto.getKeyword())).thenReturn(request);
    when(request.type(filterDto.getType())).thenReturn(request);
    when(request.rankby(filterDto.getRankBy())).thenReturn(request);
    when(request.minPrice(filterDto.getMinPrice())).thenReturn(request);
    when(request.maxPrice(filterDto.getMaxPrice())).thenReturn(request);
    when(request.openNow(filterDto.isOpenNow())).thenReturn(request);
    when(request.name(filterDto.getName())).thenReturn(request);
    return request;
}

This would simplify the test to:

@Test
void getResultFromPlacesApiTest() throws IOException, InterruptedException, ApiException {
    FilterPlacesApiDto filterDto = ModelUtils.getFilterPlacesApiDto();
    UserVO userVO = ModelUtils.getUserVO();

    try (MockedStatic<PlacesApi> placesApiMockedStatic = mockStatic(PlacesApi.class)) {
        NearbySearchRequest requestUk = setupMockRequest(
            mock(NearbySearchRequest.class), filterDto, Locale.forLanguageTag("uk"));
        NearbySearchRequest requestEn = setupMockRequest(
            mock(NearbySearchRequest.class), filterDto, Locale.forLanguageTag("en"));

        when(PlacesApi.nearbySearchQuery(context, filterDto.getLocation()))
            .thenReturn(requestUk, requestEn);
        when(requestUk.await()).thenReturn(ModelUtils.getPlacesSearchResponseUk());
        when(requestEn.await()).thenReturn(ModelUtils.getPlacesSearchResponseEn());

        List<PlacesSearchResult> actual = googleApiService.getResultFromPlacesApi(filterDto, userVO);
        List<PlacesSearchResult> expected = List.of(
            ModelUtils.getPlacesSearchResultUk().getFirst(),
            ModelUtils.getPlacesSearchResultEn().getFirst()
        );

        assertEquals(actual.size(), expected.size());
        verify(requestUk, times(1)).await();
        verify(requestEn, times(1)).await();
    }
}
🧹 Nitpick comments (6)
service/src/test/java/greencity/service/GoogleApiServiceTest.java (1)

215-216: Consider using a more descriptive assertion message.

Add a descriptive message to the assertion to make test failures more informative.

-            assertEquals(actual.size(), expected.size());
+            assertEquals(actual.size(), expected.size(), 
+                "Expected combined results from both locales");
core/src/test/java/greencity/ModelUtils.java (2)

572-582: Add documentation for the test scenario.

The method provides comprehensive test data, but adding a brief comment explaining the test scenario would improve maintainability.

+    /**
+     * Creates a FilterPlacesApiDto with default test values for Google Places API filtering.
+     * Includes location (0,0), 10km radius, test keyword, prominence ranking,
+     * and full price range (FREE to VERY_EXPENSIVE).
+     *
+     * @return FilterPlacesApiDto configured for testing
+     */
     public static FilterPlacesApiDto getFilterPlacesApiDto() {

584-590: Enhance test data quality.

The current implementation uses an empty LocationDto, which might not provide sufficient coverage for testing scenarios.

Consider enhancing the test data:

     public static List<PlaceByBoundsDto> getPlaceByBoundsDto() {
         return List.of(PlaceByBoundsDto.builder()
             .id(1L)
             .name("testx")
-            .location(new LocationDto())
+            .location(LocationDto.builder()
+                .lat(50.4501)
+                .lng(30.5234)
+                .address("1 Test Street")
+                .build())
             .build());
     }
service/src/test/java/greencity/ModelUtils.java (3)

546-550: Consider using meaningful test coordinates.

The hardcoded coordinates (1d, 1d) could be replaced with more realistic test values to better represent real-world scenarios and edge cases.

-                    .latitude(1d)
-                    .longitude(1d)
+                    .latitude(50.4501)  // Kyiv coordinates
+                    .longitude(30.5234)

3349-3363: Enhance test data realism in place bounds DTOs.

The test location data could be more realistic and comprehensive.

     public static List<PlaceByBoundsDto> getPlaceByBoundsDtoFromApi() {
         return List.of(PlaceByBoundsDto.builder()
             .id(1L)
-            .name("testName")
+            .name("Forum Lviv")
             .location(new LocationDto(1L, 
-                1d, 
-                1d, 
-                "testVicinity"
+                49.8419, 
+                24.0315, 
+                "Forum Lviv, 7B Pid Dubom Street"
             ))
             .build());
     }

3365-3406: Extract localized test data into constants.

The hardcoded strings for names and addresses in both English and Ukrainian could be moved to constants for better maintainability.

+    private static final String TEST_PLACE_NAME_UK = "Форум Львів";
+    private static final String TEST_PLACE_VICINITY_UK = "вул. Під Дубом, 7Б";
+    private static final String TEST_PLACE_NAME_EN = "Forum Lviv";
+    private static final String TEST_PLACE_VICINITY_EN = "7B Pid Dubom Street";

     public static List<PlacesSearchResult> getPlacesSearchResultUk() {
         PlacesSearchResult placesSearchResult = new PlacesSearchResult();
-        placesSearchResult.name = "тестІмя";
-        placesSearchResult.vicinity = "тестАдреса";
+        placesSearchResult.name = TEST_PLACE_NAME_UK;
+        placesSearchResult.vicinity = TEST_PLACE_VICINITY_UK;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bc07fe and f414388.

📒 Files selected for processing (5)
  • core/src/main/java/greencity/controller/PlaceController.java (3 hunks)
  • core/src/test/java/greencity/ModelUtils.java (4 hunks)
  • service-api/src/main/java/greencity/dto/filter/FilterPlacesApiDto.java (1 hunks)
  • service/src/test/java/greencity/ModelUtils.java (4 hunks)
  • service/src/test/java/greencity/service/GoogleApiServiceTest.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • service-api/src/main/java/greencity/dto/filter/FilterPlacesApiDto.java
  • core/src/main/java/greencity/controller/PlaceController.java
🔇 Additional comments (6)
service/src/test/java/greencity/service/GoogleApiServiceTest.java (4)

6-17: LGTM! Well-organized imports.

The new imports are properly organized and necessary for the Places API functionality.


119-147: LGTM! Well-structured geocoding test.

The test effectively verifies the geocoding functionality for both Ukrainian and English locales, with proper mocking and assertions.


225-268: LGTM! Comprehensive null location handling test.

The test properly verifies that a NotFoundException is thrown when location is null, and ensures no API calls are made.


270-311: LGTM! Robust error handling test.

The test effectively verifies that the service gracefully handles API exceptions and continues processing.

Note: This test would also benefit from the helper method suggested earlier to reduce code duplication.

core/src/test/java/greencity/ModelUtils.java (1)

3-5: Well-structured imports!

Good job using specific imports instead of wildcards, making the dependencies clear and maintainable.

Also applies to: 30-30, 39-39, 41-41

service/src/test/java/greencity/ModelUtils.java (1)

3337-3347: LGTM! Comprehensive test data for filter parameters.

The test data covers all essential filter parameters including location, radius, keyword, ranking, price levels, and open status.

Copy link

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

🔭 Outside diff range comments (1)
service/src/test/java/greencity/ModelUtils.java (1)

Line range hint 1-3409: Fix formatting issues before merging.

The CI pipeline indicates formatting issues. Please run the following command to fix the formatting:

mvn -f service net.revelc.code.formatter:formatter-maven-plugin:2.23.0:format
🧰 Tools
🪛 GitHub Actions: CI/CD GreenCity

[error] File has not been previously formatted. Please format file using mvn -f service net.revelc.code.formatter:formatter-maven-plugin:2.23.0:format and commit before running validation!

♻️ Duplicate comments (2)
service/src/main/java/greencity/service/PlaceServiceImpl.java (2)

438-448: ⚠️ Potential issue

Consider using UUID for ID generation instead of timestamps.

Using System.currentTimeMillis() for IDs can lead to:

  • Duplicate IDs if multiple places are created simultaneously
  • Predictable IDs that could be vulnerable to enumeration attacks

Apply this diff to use UUID instead:

-                System.currentTimeMillis(),
+                UUID.randomUUID().toString(),
-                new LocationDto(System.currentTimeMillis(), el.geometry.location.lat, el.geometry.location.lng,
+                new LocationDto(UUID.randomUUID().toString(), el.geometry.location.lat, el.geometry.location.lng,

438-448: ⚠️ Potential issue

Add error handling for Google Places API calls.

The external API call could fail due to network issues, rate limiting, or API errors. Consider adding proper exception handling.

Apply this diff to add error handling:

    @Override
    public List<PlaceByBoundsDto> getPlacesByFilter(FilterPlacesApiDto filterDto, UserVO userVO) {
+        try {
            List<PlacesSearchResult> fromPlacesApi = googleApiService.getResultFromPlacesApi(filterDto, userVO);
            return fromPlacesApi.stream()
                .map(el -> new PlaceByBoundsDto(
                    UUID.randomUUID().toString(),
                    el.name,
                    new LocationDto(UUID.randomUUID().toString(), el.geometry.location.lat, el.geometry.location.lng,
                        el.vicinity)))
                .toList();
+        } catch (Exception e) {
+            throw new ExternalServiceException("Failed to fetch places from Google API", e);
+        }
    }
🧹 Nitpick comments (1)
service/src/test/java/greencity/ModelUtils.java (1)

548-552: Consider using more realistic coordinates for test data.

While hardcoded coordinates work for testing, using realistic coordinates (e.g., actual city coordinates) would make the test data more meaningful and help catch potential edge cases.

-                    .latitude(1d)
-                    .longitude(1d)
+                    .latitude(50.4501)  // Kyiv coordinates
+                    .longitude(30.5234)
🧰 Tools
🪛 GitHub Actions: CI/CD GreenCity

[error] File has not been previously formatted. Please format file using mvn -f service net.revelc.code.formatter:formatter-maven-plugin:2.23.0:format and commit before running validation!

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f414388 and d4cd904.

📒 Files selected for processing (11)
  • core/src/main/java/greencity/config/SecurityConfig.java (1 hunks)
  • core/src/main/java/greencity/exception/handler/CustomExceptionHandler.java (3 hunks)
  • core/src/main/resources/messages.properties (1 hunks)
  • core/src/test/java/greencity/controller/PlaceControllerTest.java (3 hunks)
  • service-api/pom.xml (1 hunks)
  • service-api/src/main/java/greencity/constant/ErrorMessage.java (3 hunks)
  • service-api/src/main/java/greencity/service/PlaceService.java (4 hunks)
  • service/src/main/java/greencity/service/LocationServiceImpl.java (1 hunks)
  • service/src/main/java/greencity/service/PlaceServiceImpl.java (4 hunks)
  • service/src/test/java/greencity/ModelUtils.java (4 hunks)
  • service/src/test/java/greencity/service/PlaceServiceImplTest.java (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • service/src/main/java/greencity/service/LocationServiceImpl.java
  • core/src/main/java/greencity/config/SecurityConfig.java
  • service-api/pom.xml
  • service-api/src/main/java/greencity/constant/ErrorMessage.java
  • core/src/main/resources/messages.properties
  • core/src/test/java/greencity/controller/PlaceControllerTest.java
  • core/src/main/java/greencity/exception/handler/CustomExceptionHandler.java
  • service-api/src/main/java/greencity/service/PlaceService.java
🧰 Additional context used
🪛 GitHub Actions: CI/CD GreenCity
service/src/test/java/greencity/ModelUtils.java

[error] File has not been previously formatted. Please format file using mvn -f service net.revelc.code.formatter:formatter-maven-plugin:2.23.0:format and commit before running validation!

🔇 Additional comments (4)
service/src/test/java/greencity/ModelUtils.java (1)

3340-3408: Well-structured test data implementation for Google Places API!

The implementation provides comprehensive test data with proper multilingual support and follows the Google Places API response format. The separation of English and Ukrainian test data facilitates thorough testing of localization features.

🧰 Tools
🪛 GitHub Actions: CI/CD GreenCity

[error] File has not been previously formatted. Please format file using mvn -f service net.revelc.code.formatter:formatter-maven-plugin:2.23.0:format and commit before running validation!

service/src/main/java/greencity/service/PlaceServiceImpl.java (1)

559-568: Well-implemented duplicate place validation!

The implementation correctly:

  • Validates geocoding results
  • Checks for existing locations using coordinates
  • Uses appropriate exception handling
service/src/test/java/greencity/service/PlaceServiceImplTest.java (2)

674-691: Well-structured test for Google Places API integration!

The test thoroughly verifies:

  • Correct API call execution
  • Result transformation
  • Response mapping

804-826: Comprehensive test coverage for duplicate place validation!

The test effectively:

  • Verifies the duplicate check logic
  • Ensures proper exception handling
  • Confirms that save is not called for duplicates

@Warded120 Warded120 requested a review from holotsvan January 25, 2025 12:32
@Warded120 Warded120 merged commit 4235c32 into dev Jan 27, 2025
4 checks passed
@Warded120 Warded120 deleted the 7988-synchronize-places-from-database-with-google-places-api branch January 27, 2025 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: On Review
Development

Successfully merging this pull request may close these issues.

Synchronize places from database with google places API
6 participants