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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c6a7937
added some TODO's
Warded120 Jan 6, 2025
0456ecc
avoid duplicates when saving locations and places
Warded120 Jan 6, 2025
c44d8e0
formatted
Warded120 Jan 6, 2025
a20dcdc
created FilterGeocodingApiDto
Warded120 Jan 8, 2025
8b39520
merged dev
Warded120 Jan 8, 2025
59f1dd1
created endpoint to fetch from geocoding api
Warded120 Jan 9, 2025
f8e8527
created service method to fetch places from Google Places API
Warded120 Jan 9, 2025
4467920
added default JSON request body for FilterPlacesApiDto in swagger
Warded120 Jan 10, 2025
6697bbf
fixed endpoint security; added tests
Warded120 Jan 10, 2025
cc8de08
resolved issue
Warded120 Jan 10, 2025
c7c454b
Merge branch 'dev' into 7988-synchronize-places-from-database-with-go…
Warded120 Jan 10, 2025
0617104
resolved issue again
Warded120 Jan 10, 2025
f13c631
Merge remote-tracking branch 'origin/7988-synchronize-places-from-dat…
Warded120 Jan 10, 2025
b6b3502
added tests for coverage
Warded120 Jan 13, 2025
b578eab
fixed issues
Warded120 Jan 13, 2025
3ca148f
fixed issues
Warded120 Jan 13, 2025
79ca715
fixed more issues
Warded120 Jan 13, 2025
db4e065
merged with remote branch]
Warded120 Jan 13, 2025
a64e9b4
last issue fixed
Warded120 Jan 13, 2025
c6b8591
added javaDocs
Warded120 Jan 14, 2025
ff58960
Merge branch 'dev' into 7988-synchronize-places-from-database-with-go…
Warded120 Jan 14, 2025
35a803e
formatted files
Warded120 Jan 14, 2025
f218b6f
Merge remote-tracking branch 'origin/7988-synchronize-places-from-dat…
Warded120 Jan 14, 2025
c65acaf
fixed conflicts with dev
Warded120 Jan 14, 2025
d98477b
change author of a method in PlaceService.java
Warded120 Jan 14, 2025
7ad1897
resolved some potential issues
Warded120 Jan 15, 2025
af7ee78
formatted files
Warded120 Jan 15, 2025
0779b50
Merge remote-tracking branch 'origin/7988-synchronize-places-from-dat…
Warded120 Jan 15, 2025
dfae829
fixed tests
Warded120 Jan 15, 2025
6675532
added more tests for coverage
Warded120 Jan 15, 2025
0995dbc
small test fix for coverage
Warded120 Jan 15, 2025
5bc07fe
Update max rate message in messages.properties
Warded120 Jan 24, 2025
2f22452
resolved issues from comments
Warded120 Jan 25, 2025
f414388
Merge remote-tracking branch 'origin/7988-synchronize-places-from-dat…
Warded120 Jan 25, 2025
d4cd904
Merge branch 'dev' into 7988-synchronize-places-from-database-with-go…
Warded120 Jan 25, 2025
da83268
formatted files
Warded120 Jan 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/main/java/greencity/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
"/place/{placeId}/comments",
"/place/propose",
"/place/save/favorite/",
"/place/filter/api",
USER_CUSTOM_TO_DO_LIST_ITEMS,
USER_TO_DO_LIST,
"/user/{userId}/habit",
Expand Down
37 changes: 35 additions & 2 deletions core/src/main/java/greencity/controller/PlaceController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import greencity.constant.HttpStatuses;
import greencity.dto.PageableDto;
import greencity.dto.favoriteplace.FavoritePlaceDto;
import greencity.dto.filter.FilterPlacesApiDto;
import greencity.dto.filter.FilterPlaceDto;
import greencity.dto.place.PlaceAddDto;
import greencity.dto.place.PlaceInfoDto;
Expand Down Expand Up @@ -255,8 +256,9 @@ public ResponseEntity<PageableDto<AdminPlaceDto>> getPlacesByStatus(
}

/**
* The method which return a list {@code PlaceByBoundsDto} filtered by values
* The method which return a list of {@link PlaceByBoundsDto} filtered by values
* contained in the incoming {@link FilterPlaceDto} object.
* {@link PlaceByBoundsDto} are retrieved from the database
*
* @param filterDto contains all information about the filtering of the list.
* @return a list of {@code PlaceByBoundsDto}
Expand All @@ -278,7 +280,38 @@ public ResponseEntity<PageableDto<AdminPlaceDto>> getPlacesByStatus(
@PostMapping("/filter")
public ResponseEntity<List<PlaceByBoundsDto>> getFilteredPlaces(
@Valid @RequestBody FilterPlaceDto filterDto,
@CurrentUser UserVO userVO) {
@Parameter(hidden = true) @CurrentUser UserVO userVO) {
return ResponseEntity.ok().body(placeService.getPlacesByFilter(filterDto, userVO));
}

/**
* 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 = "400", description = HttpStatuses.BAD_REQUEST,
content = @Content(examples = @ExampleObject(HttpStatuses.BAD_REQUEST))),
@ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED,
content = @Content(examples = @ExampleObject(HttpStatuses.UNAUTHORIZED))),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
})
Warded120 marked this conversation as resolved.
Show resolved Hide resolved
@PostMapping("/filter/api")
public ResponseEntity<List<PlaceByBoundsDto>> getFilteredPlacesFromApi(
@Schema(
description = "Filters for places from API",
name = "FilterPlacesApiDto",
type = "object",
example = FilterPlacesApiDto.defaultJson) @RequestBody FilterPlacesApiDto filterDto,
@CurrentUser @Parameter(hidden = true) UserVO userVO) {
return ResponseEntity.ok().body(placeService.getPlacesByFilter(filterDto, userVO));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import greencity.exception.exceptions.EventDtoValidationException;
import greencity.exception.exceptions.InvalidStatusException;
import greencity.exception.exceptions.InvalidURLException;
import greencity.exception.exceptions.LowRoleLevelException;
import greencity.exception.exceptions.MultipartXSSProcessingException;
import greencity.exception.exceptions.NotCurrentUserException;
import greencity.exception.exceptions.NotDeletedException;
Expand All @@ -20,13 +21,14 @@
import greencity.exception.exceptions.ToDoListItemNotFoundException;
import greencity.exception.exceptions.TagNotFoundException;
import greencity.exception.exceptions.UnsupportedSortException;
import greencity.exception.exceptions.UserBlockedException;
import greencity.exception.exceptions.UserHasNoFriendWithIdException;
import greencity.exception.exceptions.UserHasNoPermissionToAccessException;
import greencity.exception.exceptions.UserHasNoToDoListItemsException;
import greencity.exception.exceptions.UserToDoListItemStatusNotUpdatedException;
import greencity.exception.exceptions.WrongIdException;
import greencity.exception.exceptions.ResourceNotFoundException;
import greencity.exception.exceptions.*;
import greencity.exception.exceptions.WrongIdException;
import greencity.exception.exceptions.PlaceAlreadyExistsException;
import jakarta.validation.ConstraintDeclarationException;
import jakarta.validation.ValidationException;
import lombok.AllArgsConstructor;
Expand Down Expand Up @@ -264,6 +266,14 @@ public final ResponseEntity<Object> handleStatusException(InvalidStatusException
return ResponseEntity.status(HttpStatus.CONFLICT).body(exceptionResponse);
}

@ExceptionHandler(PlaceAlreadyExistsException.class)
public final ResponseEntity<Object> handlePlaceAlreadyExistsException(PlaceAlreadyExistsException ex,
WebRequest request) {
log.warn(ex.getMessage());
ExceptionResponse exceptionResponse = new ExceptionResponse(getErrorAttributes(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exceptionResponse);
}

/**
* Method interceptor for exceptions related to unsuccessful operations such as
* {@link NotDeletedException}, {@link NotUpdatedException},
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ greenCity.validation.min.lng=Has to be greater or equals -180
greenCity.validation.max.lng=Has to be lower or equals 180

greenCity.validation.min.rate=The rate must be at least {value}
greenCity.validation.max.rate=The rate must bigger than {min} and less then {max}
greenCity.validation.max.rate=The rate must be greater than {min} and less than {max}

greenCity.validation.habit.complexity=The habit's complexity must be between 1 and 3
greenCity.validation.invalid.discount.value=Min discount value is {min}, max discount value is {max}
Expand Down
26 changes: 26 additions & 0 deletions core/src/test/java/greencity/ModelUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package greencity;

import com.google.maps.model.LatLng;
import com.google.maps.model.PriceLevel;
import com.google.maps.model.RankBy;
import greencity.dto.PageableAdvancedDto;
import greencity.dto.PageableDetailedDto;
import greencity.dto.PageableDto;
Expand All @@ -24,6 +27,7 @@
import greencity.dto.filter.FilterDistanceDto;
import greencity.dto.filter.FilterEventDto;
import greencity.dto.filter.FilterPlaceDto;
import greencity.dto.filter.FilterPlacesApiDto;
import greencity.dto.friends.UserAsFriendDto;
import greencity.dto.habit.CustomHabitDtoRequest;
import greencity.dto.habit.HabitAssignCustomPropertiesDto;
Expand All @@ -32,7 +36,9 @@
import greencity.dto.habittranslation.HabitTranslationDto;
import greencity.dto.language.LanguageDTO;
import greencity.dto.language.LanguageTranslationDTO;
import greencity.dto.location.LocationDto;
import greencity.dto.location.MapBoundsDto;
import greencity.dto.place.PlaceByBoundsDto;
import greencity.dto.todolistitem.CustomToDoListItemResponseDto;
import greencity.dto.todolistitem.ToDoListItemPostDto;
import greencity.dto.todolistitem.ToDoListItemRequestDto;
Expand Down Expand Up @@ -562,4 +568,24 @@ public static Map<String, String> getUserRoleBody() {
body.put("role", ROLE_ADMIN);
return body;
}

public static FilterPlacesApiDto getFilterPlacesApiDto() {
return FilterPlacesApiDto.builder()
.location(new LatLng(0d, 0d))
.radius(10000)
.keyword("test")
.rankBy(RankBy.PROMINENCE)
.openNow(true)
.minPrice(PriceLevel.FREE)
.maxPrice(PriceLevel.VERY_EXPENSIVE)
.build();
}

public static List<PlaceByBoundsDto> getPlaceByBoundsDto() {
return List.of(PlaceByBoundsDto.builder()
.id(1L)
.name("testx")
.location(new LocationDto())
.build());
}
}
33 changes: 33 additions & 0 deletions core/src/test/java/greencity/controller/PlaceControllerTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package greencity.controller;

import greencity.converters.UserArgumentResolver;
import greencity.dto.filter.FilterPlacesApiDto;
import greencity.dto.place.PlaceAddDto;
import greencity.dto.place.PlaceVO;
import greencity.dto.place.AddPlaceDto;
Expand Down Expand Up @@ -52,6 +53,8 @@
import greencity.service.PlaceService;
import static greencity.ModelUtils.getFilterPlaceDto;
import static greencity.ModelUtils.getUserVO;
import static greencity.ModelUtils.getFilterPlacesApiDto;
import static greencity.ModelUtils.getPlaceByBoundsDto;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
Expand Down Expand Up @@ -342,6 +345,36 @@ void getFilteredPlaces() throws Exception {
verify(placeService).getPlacesByFilter(filterPlaceDto, userVO);
}

@Test
void getFilteredPlacesFromApi() throws Exception {
UserVO userVO = getUserVO();
FilterPlacesApiDto filterDto = getFilterPlacesApiDto();
String json = """
{
"location": {
"lat": 0,
"lng": 0
},
"radius": 10000,
"rankBy": "PROMINENCE",
"keyword": "test",
"minPrice": "0",
"maxPrice": "4",
"openNow": true
}
""";
when(userService.findByEmail(anyString())).thenReturn(userVO);
when(placeService.getPlacesByFilter(filterDto, userVO)).thenReturn(getPlaceByBoundsDto());

this.mockMvc.perform(post(placeLink + "/filter/api")
.content(json)
.principal(principal)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
verify(placeService).getPlacesByFilter(filterDto, userVO);
}

@Test
void filterPlaceBySearchPredicate() throws Exception {
int pageNumber = 5;
Expand Down
23 changes: 23 additions & 0 deletions dao/src/main/java/greencity/repository/LocationRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import greencity.entity.Location;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

/**
Expand All @@ -19,4 +21,25 @@ public interface LocationRepo extends JpaRepository<Location, Long> {
* @author Kateryna Horokh.
*/
Optional<Location> findByLatAndLng(Double lat, Double lng);

/**
* Method checks if {@code Location} with such {@code lat} and {@code lng}
* exist. Only first 4 decimal places of {@code lat} and {@code lng} are taken
* into account
*
* @param lat latitude of point of the map
* @param lng longitude of point of the map
* @return {@code true} if {@code Location} with such coordinates exist, or else
* - {@code false}
* @author Hrenevych Ivan.
*/
@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)
boolean existsByLatAndLng(@Param("lat") double lat, @Param("lng") double lng);
}
6 changes: 6 additions & 0 deletions service-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,11 @@
<version>1.18.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.maps</groupId>
<artifactId>google-maps-services</artifactId>
<version>2.2.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class ErrorMessage {
"Subscriber with this email address and subscription type is exists.";
public static final String UBSCRIPTION_BY_TOKEN_NOT_FOUND = "Subscriber with this token not found.";
public static final String LOCATION_NOT_FOUND_BY_ID = "The location does not exist by this id: ";
public static final String LOCATION_NOT_FOUND = "Location must be provided either in filterDto or userVO";
public static final String HABIT_HAS_BEEN_ALREADY_ENROLLED = "You can enroll habit only once a day";
public static final String HABIT_ALREADY_ACQUIRED = "You have already acquired habit with id: ";
public static final String HABIT_IS_NOT_ENROLLED_ON_CURRENT_DATE = "Habit is not enrolled on ";
Expand Down Expand Up @@ -127,6 +128,7 @@ public class ErrorMessage {
public static final String COMMENT_PROPERTY_TYPE_NOT_FOUND = "For type comment not found this property :";
public static final String CANNOT_REPLY_THE_REPLY = "You can't reply on reply";
public static final String NOT_A_CURRENT_USER = "You can't perform actions with the data of other user";
public static final String PLACE_ALREADY_EXISTS = "Place with lat: %.4f and lng: %.4f already exists";
public static final String FAVORITE_PLACE_ALREADY_EXISTS =
"Favorite place already exist for this placeId: %d and user with email: %s";
public static final String FAVORITE_PLACE_NOT_FOUND = "The favorite place does not exist ";
Expand Down Expand Up @@ -227,6 +229,7 @@ public class ErrorMessage {
public static final String GIT_REPOSITORY_NOT_INITIALIZED =
"Git repository not initialized. Commit info is unavailable.";
public static final String FAILED_TO_FETCH_COMMIT_INFO = "Failed to fetch commit info due to I/O error: ";
public static final String GEOCODING_RESULT_IS_EMPTY = "No geocoding results found for given location";
public static final String MAX_PAGE_SIZE_EXCEPTION = "Page size must be less than or equal to 100";
public static final String INVALID_VALUE_EXCEPTION = "Invalid value for %s: must be an integer";
public static final String NEGATIVE_VALUE_EXCEPTION = "%s must be a positive number";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package greencity.dto.filter;

import com.google.maps.model.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FilterPlacesApiDto {
private LatLng location;
private int radius;
private RankBy rankBy;
private String keyword;
private PriceLevel minPrice;
private PriceLevel maxPrice;
private String name;
private boolean openNow;
private PlaceType type;

public static final String defaultJson = """
{
"location": {
"lat": 50.4500,
"lng": 30.5234
},
"radius": 0,
"rankBy": "PROMINENCE",
"keyword": "string",
"minPrice": "0",
"maxPrice": "4",
"name": "string",
"openNow": true,
"type": "restaurant"
}
""";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package greencity.exception.exceptions;

import lombok.experimental.StandardException;

@StandardException
public class PlaceAlreadyExistsException extends RuntimeException {
}
13 changes: 13 additions & 0 deletions service-api/src/main/java/greencity/service/LocationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,17 @@ public interface LocationService {
* @author Kateryna Horokh.
*/
Optional<LocationVO> findByLatAndLng(Double lat, Double lng);

/**
* Method checks if {@code Location} with such {@code lat} and {@code lng}
* exist. Only first 4 decimal places of {@code lat} and {@code lng} are taken
* into account
*
* @param lat latitude of point of the map
* @param lng longitude of point of the map
* @return {@code true} if {@code Location} with such coordinates exist, or else
* - {@code false}
* @author Hrenevych Ivan.
*/
boolean existsByLatAndLng(Double lat, Double lng);
}
Loading
Loading