Skip to content

Commit

Permalink
refactor: extract repository for meal plan
Browse files Browse the repository at this point in the history
  • Loading branch information
jmesserli committed Oct 31, 2023
1 parent 098697e commit bd174de
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 70 deletions.
11 changes: 3 additions & 8 deletions src/main/java/nu/peg/svmeal/application/MealController.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
package nu.peg.svmeal.application;

import lombok.RequiredArgsConstructor;
import nu.peg.svmeal.domain.exceptions.UnknownRestaurantException;
import nu.peg.svmeal.domain.model.AvailabilityDto;
import nu.peg.svmeal.domain.model.MealPlanDto;
import nu.peg.svmeal.domain.model.MealPlansDto;
import nu.peg.svmeal.domain.model.RestaurantDto;
import nu.peg.svmeal.domain.service.MealService;
import nu.peg.svmeal.domain.service.RestaurantService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin
@RestController
@RequestMapping("/api/restaurant")
@RequiredArgsConstructor
public class MealController {
private final MealService mealService;
private final RestaurantService restaurantService;

@Autowired
public MealController(MealService mealService, RestaurantService restaurantService) {
this.mealService = mealService;
this.restaurantService = restaurantService;
}

@GetMapping("/{restaurant}/meal")
public MealPlansDto getRestaurantMeal(@PathVariable("restaurant") String restaurantString) {
return mealService.getMealPlans(findRestaurant(restaurantString));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
package nu.peg.svmeal.application;

import java.util.List;
import lombok.RequiredArgsConstructor;
import nu.peg.svmeal.domain.model.RestaurantDto;
import nu.peg.svmeal.domain.service.RestaurantService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin
@RestController
@RequestMapping("/api/restaurant")
@RequiredArgsConstructor
public class RestaurantController {
private final RestaurantService restaurantService;

@Autowired
public RestaurantController(RestaurantService restaurantService) {
this.restaurantService = restaurantService;
}

@GetMapping("")
public List<RestaurantDto> getRestaurants() {
return restaurantService.getRestaurantDtos();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nu.peg.svmeal.domain.repository;

import nu.peg.svmeal.domain.model.MealPlansDto;
import nu.peg.svmeal.domain.model.RestaurantDto;

public interface MealPlanRepository {
MealPlansDto getMealPlans(RestaurantDto restaurant);
}
56 changes: 8 additions & 48 deletions src/main/java/nu/peg/svmeal/domain/service/MealService.java
Original file line number Diff line number Diff line change
@@ -1,40 +1,24 @@
package nu.peg.svmeal.domain.service;

import static nu.peg.svmeal.infrastructure.config.CacheNames.MEAL_PLAN;
import static nu.peg.svmeal.infrastructure.config.CircuitBreakers.SV_MENU;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import nu.peg.svmeal.domain.exceptions.ExternalException;
import nu.peg.svmeal.domain.exceptions.MealPlanParsingException;
import nu.peg.svmeal.domain.model.AvailabilityDto;
import nu.peg.svmeal.domain.model.MealPlanDto;
import nu.peg.svmeal.domain.model.MealPlansDto;
import nu.peg.svmeal.domain.model.RestaurantDto;
import nu.peg.svmeal.infrastructure.converter.DocumentMealPlanParser;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import nu.peg.svmeal.domain.repository.MealPlanRepository;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Slf4j
@Service
@RequiredArgsConstructor
public class MealService {
private static final String NO_MEALPLAN_AVAILABLE_ERROR = "No meal plan available for this date";

private final DocumentMealPlanParser docParser;
private final RestTemplate restTemplate;
private static final String NO_MEAL_PLAN_FOR_DAY = "No meal plan available for this day";

@Autowired
public MealService(DocumentMealPlanParser docParser, RestTemplate restTemplate) {
this.docParser = docParser;
this.restTemplate = restTemplate;
}
private final MealPlanRepository repository;

/**
* Checks if a meal plan is available for the given dayOffset and restaurant
Expand Down Expand Up @@ -66,43 +50,19 @@ public AvailabilityDto getAvailability(int dayOffset, RestaurantDto restaurant)
* @return The scraped {@link MealPlanDto}
*/
public MealPlanDto getMealPlan(int dayOffset, RestaurantDto restaurant) {
final MealPlansDto mealPlans = getMealPlans(restaurant);
final var mealPlans = repository.getMealPlans(restaurant);

final LocalDate offsetDate =
mealPlans.getPlans().keySet().stream()
.sorted()
.skip(dayOffset)
.findFirst()
.orElseThrow(() -> new MealPlanParsingException(NO_MEALPLAN_AVAILABLE_ERROR));
.orElseThrow(() -> new MealPlanParsingException(NO_MEAL_PLAN_FOR_DAY));

return mealPlans.getPlans().get(offsetDate);
}

/**
* Scrapes all available meal plans from an SV-Group website and parses them into a {@link
* MealPlansDto}
*
* @param restaurant Which restaurant website to scrape the meal plan from
* @return The scraped {@link MealPlanDto}
*/
@Cacheable(MEAL_PLAN)
@CircuitBreaker(name = SV_MENU)
public MealPlansDto getMealPlans(RestaurantDto restaurant) {
log.debug("Scraping meal plan for restaurant {}", restaurant);

ResponseEntity<String> response = restTemplate.getForEntity(restaurant.getLink(), String.class);

if (response.getStatusCode() != HttpStatus.OK) {
throw new ExternalException("Error while fetching meal plan");
}

Document document = Jsoup.parse(response.getBody());
MealPlansDto dto = docParser.convert(document);

if (dto == null) {
throw new MealPlanParsingException(NO_MEALPLAN_AVAILABLE_ERROR);
}

return dto;
return repository.getMealPlans(restaurant);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package nu.peg.svmeal.domain.service;

import static nu.peg.svmeal.infrastructure.config.CacheNames.RESTAURANT_DTOS;

import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import nu.peg.svmeal.domain.model.RestaurantDto;
import nu.peg.svmeal.domain.repository.RestaurantRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -17,7 +14,6 @@ public class RestaurantService {

private final RestaurantRepository repository;

@Cacheable(RESTAURANT_DTOS)
public List<RestaurantDto> getRestaurantDtos() {
return repository.getAllRestaurants();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package nu.peg.svmeal.infrastructure.repository;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import nu.peg.svmeal.domain.exceptions.ExternalException;
import nu.peg.svmeal.domain.exceptions.MealPlanParsingException;
import nu.peg.svmeal.domain.model.MealPlansDto;
import nu.peg.svmeal.domain.model.RestaurantDto;
import nu.peg.svmeal.domain.repository.MealPlanRepository;
import nu.peg.svmeal.infrastructure.converter.DocumentMealPlanParser;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import static nu.peg.svmeal.infrastructure.config.CacheNames.MEAL_PLAN;
import static nu.peg.svmeal.infrastructure.config.CircuitBreakers.SV_MENU;

@Component
@Slf4j
@RequiredArgsConstructor
public class SvMealPlanRepository implements MealPlanRepository {
private static final String NO_MEALPLAN_AVAILABLE_ERROR = "No meal plan available";

private final RestTemplate restTemplate;
private final DocumentMealPlanParser docParser;

@Override
@Cacheable(MEAL_PLAN)
@CircuitBreaker(name = SV_MENU)
public MealPlansDto getMealPlans(RestaurantDto restaurant) {
log.debug("Scraping meal plan for restaurant {}", restaurant);

ResponseEntity<String> response = restTemplate.getForEntity(restaurant.getLink(), String.class);

if (response.getStatusCode() != HttpStatus.OK) {
throw new ExternalException("Error while fetching meal plan");
}

Document document = Jsoup.parse(response.getBody());
MealPlansDto dto = docParser.convert(document);

if (dto == null) {
throw new MealPlanParsingException(NO_MEALPLAN_AVAILABLE_ERROR);
}

return dto;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nu.peg.svmeal.infrastructure.repository;

import static nu.peg.svmeal.infrastructure.config.CacheNames.RESTAURANT_DTOS;
import static nu.peg.svmeal.infrastructure.config.CircuitBreakers.SV_SEARCH;

import com.fasterxml.jackson.core.JsonProcessingException;
Expand All @@ -19,6 +20,7 @@
import nu.peg.svmeal.infrastructure.model.svsearch.RestaurantSearchResponseCallbackDto;
import nu.peg.svmeal.infrastructure.model.svsearch.RestaurantSearchResponseDto;
import nu.peg.svmeal.infrastructure.util.HttpUtil;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.convert.ConversionService;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
Expand All @@ -38,6 +40,7 @@ public class SvRestaurantRepository implements RestaurantRepository {
private final ConversionService converter;

@Override
@Cacheable(RESTAURANT_DTOS)
@CircuitBreaker(name = SV_SEARCH)
public List<RestaurantDto> getAllRestaurants() {
log.info("Fetching restaurant list");
Expand Down
9 changes: 7 additions & 2 deletions src/test/java/nu/peg/svmeal/SvmealApiApplicationTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.junit.jupiter.api.Assertions.assertTrue;

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class SvmealApiApplicationTests {
class SvmealApiApplicationTests {

@Test
public void contextLoads() {}
void contextLoads() {
// sonar
assertTrue(true);
}
}
1 change: 0 additions & 1 deletion src/test/java/nu/peg/svmeal/util/DateUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Stream;

import nu.peg.svmeal.infrastructure.util.DateUtil;
import org.junit.jupiter.api.Test;

Expand Down

0 comments on commit bd174de

Please sign in to comment.