Skip to content

Commit

Permalink
Adds documentation and polish (#1557)
Browse files Browse the repository at this point in the history
* feat : upgrade to spring boot 3.4.0

* feat : remove lombok

* feat : polish Code

* adds java doc

* adds code review comments

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* adds swagger documentation

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* add documentation

* feat : convert to nonblocking io

* feat : handle exception

* fix : issue with loading all data

* implement code review comments

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
rajadilipkolli and coderabbitai[bot] authored Dec 5, 2024
1 parent 7ce45a3 commit e8245e9
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 108 deletions.
10 changes: 10 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@
"projectName": "multidatasource-multitenancy",
"args": "",
"envFile": "${workspaceFolder}/.env"
},
{
"type": "java",
"name": "Spring Boot-TestMongoESApplication<boot-mongodb-elasticsearch>",
"request": "launch",
"cwd": "${workspaceFolder}",
"mainClass": "com.example.mongoes.TestMongoESApplication",
"projectName": "boot-mongodb-elasticsearch",
"args": "",
"envFile": "${workspaceFolder}/.env"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.mongoes.config;

import com.example.mongoes.response.GenericMessage;
import com.example.mongoes.web.exception.DuplicateRestaurantException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(DuplicateRestaurantException.class)
public ResponseEntity<GenericMessage> handleDuplicateRestaurantException(
DuplicateRestaurantException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(new GenericMessage(ex.getMessage()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@
import java.util.Date;

public class DateUtility {

/**
* Converts a {@link Date} to {@link LocalDateTime} using the system default timezone.
*
* @param dateToConvert the date to convert, can be null
* @return the converted {@link LocalDateTime} or null if input is null
*/
public static LocalDateTime convertToLocalDateViaInstant(Date dateToConvert) {
if (dateToConvert == null) {
return null;
}
return dateToConvert.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.springframework.data.elasticsearch.core.SearchPage;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down Expand Up @@ -77,18 +79,24 @@ public Mono<ResponseEntity<Restaurant>> addNotesToRestaurant(
}

@PostMapping
public ResponseEntity<GenericMessage> createRestaurant(
@RequestBody RestaurantRequest restaurantRequest) {
return ResponseEntity.created(
URI.create(
String.format(
"/restaurant/%s",
this.restaurantService
.createRestaurant(restaurantRequest)
.map(Restaurant::getName))))
.body(
new GenericMessage(
"restaurant with name %s created"
.formatted(restaurantRequest.name())));
public Mono<ResponseEntity<GenericMessage>> createRestaurant(
@RequestBody @Valid RestaurantRequest restaurantRequest) {
return this.restaurantService
.createRestaurant(restaurantRequest)
.map(
restaurant ->
ResponseEntity.created(
URI.create(
String.format(
"/api/restaurant/name/%s",
URLEncoder.encode(
restaurantRequest.name(),
StandardCharsets.UTF_8))))
.body(
new GenericMessage(
"restaurant with name %s created"
.formatted(
restaurantRequest
.name()))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
import com.example.mongoes.response.ResultData;
import com.example.mongoes.web.service.SearchService;
import io.micrometer.core.annotation.Timed;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import java.util.List;
import org.springframework.data.elasticsearch.core.SearchPage;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -145,12 +154,43 @@ public Mono<ResponseEntity<AggregationSearchResponse>> aggregateSearch(
.map(ResponseEntity::ok);
}

@Operation(
summary = "Search restaurants within range",
description = "Find restaurants within specified distance from given coordinates",
responses = {
@ApiResponse(
responseCode = "200",
description = "Successfully retrieved restaurants",
content = @Content(schema = @Schema(implementation = ResultData.class))),
@ApiResponse(responseCode = "400", description = "Invalid parameters provided")
})
@GetMapping("/search/restaurant/withInRange")
public Flux<ResultData> searchRestaurantsWithInRange(
@RequestParam Double lat,
@RequestParam Double lon,
@RequestParam Double distance,
@RequestParam(defaultValue = "km", required = false) String unit) {
@Parameter(
description = "Latitude coordinate (between -90 and 90)",
example = "40.7128")
@RequestParam
@Min(-90)
@Max(90)
Double lat,
@Parameter(
description = "Longitude coordinate (between -180 and 180)",
example = "-74.0060")
@RequestParam
@Min(-180)
@Max(180)
Double lon,
@Parameter(description = "Distance from coordinates (must be positive)")
@RequestParam
@Positive
Double distance,
@Parameter(
description = "Unit of distance",
example = "km",
schema = @Schema(allowableValues = {"km", "mi"}))
@RequestParam(defaultValue = "km", required = false)
@Pattern(regexp = "^(km|mi)$", message = "Unit must be either 'km' or 'mi'")
String unit) {
return this.searchService.searchRestaurantsWithInRange(lat, lon, distance, unit);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.mongoes.web.exception;

public class DuplicateRestaurantException extends RuntimeException {

public DuplicateRestaurantException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.example.mongoes.web.service;

import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregation;
import org.springframework.stereotype.Service;

/**
* Processes Elasticsearch aggregations and transforms them into a structured map format. Supports
* 'terms' and 'dateRange' aggregation types.
*
* <p>Example output format: { "termAggregation": {"term1": 10, "term2": 20},
* "dateRangeAggregation": {"2023-01-01 - 2023-12-31": 100} }
*/
@Service
class AggregationProcessor {

private static final Logger log = LoggerFactory.getLogger(AggregationProcessor.class);

/**
* Processes Elasticsearch aggregations and returns a structured map of results.
*
* @param aggregationMap Map of aggregation key to ElasticsearchAggregation
* @return Map of aggregation key to counts, where counts is a map of bucket key to document
* count
* @throws IllegalArgumentException if aggregationMap is null
*/
public Map<String, Map<String, Long>> processAggregations(
Map<String, ElasticsearchAggregation> aggregationMap) {
if (aggregationMap == null) {
throw new IllegalArgumentException("aggregationMap must not be null");
}
Map<String, Map<String, Long>> resultMap = new HashMap<>();
aggregationMap.forEach(
(String aggregateKey, ElasticsearchAggregation aggregation) -> {
Map<String, Long> countMap = new HashMap<>();
Aggregate aggregate = aggregation.aggregation().getAggregate();
processAggregate(aggregate, countMap);
resultMap.put(aggregateKey, countMap);
});
return resultMap;
}

private void processAggregate(Aggregate aggregate, Map<String, Long> countMap) {
if (aggregate.isSterms()) {
processTermsAggregate(aggregate, countMap);
} else if (aggregate.isDateRange()) {
processDateRangeAggregate(aggregate, countMap);
} else {
log.debug(
"Unsupported aggregation type encountered: {}",
aggregate.getClass().getSimpleName());
}
}

private void processTermsAggregate(Aggregate aggregate, Map<String, Long> countMap) {
aggregate
.sterms()
.buckets()
.array()
.forEach(bucket -> countMap.put(bucket.key().stringValue(), bucket.docCount()));
}

private void processDateRangeAggregate(Aggregate aggregate, Map<String, Long> countMap) {
aggregate.dateRange().buckets().array().stream()
.filter(bucket -> bucket.docCount() != 0)
.forEach(
bucket ->
countMap.put(
bucket.fromAsString() + " - " + bucket.toAsString(),
bucket.docCount()));
}
}
Loading

0 comments on commit e8245e9

Please sign in to comment.