Skip to content

Commit

Permalink
#244 Migrate coffee API calls to GraphQL
Browse files Browse the repository at this point in the history
  • Loading branch information
conorheffron committed Jan 18, 2025
1 parent 1cfef2c commit 94860f3
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 2 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/CoffeeHome.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function CoffeeHome() {
const [coffeeItems, setCoffeeItems] = useState([]);

useEffect(() => {
axios.get('/coffees')
axios.get('/coffees-graph-ql')
.then(response => setCoffeeItems(response.data))
.catch(error => console.error('Error fetching coffee details:', error));
}, []);
Expand Down
31 changes: 31 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<frontend-maven-plugin.version>1.15.1</frontend-maven-plugin.version>
<node.version>v20.16.0</node.version>
<npm.version>10.8.1</npm.version>
<kotlin.version>1.5.0</kotlin.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -70,6 +71,36 @@
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<!-- Spring Boot Starter GraphQL -->
<!-- <dependency>-->
<!-- <groupId>com.graphql-java-kickstart</groupId>-->
<!-- <artifactId>graphql-spring-boot-starter</artifactId>-->
<!-- <version>11.1.0</version>-->
<!-- </dependency>-->
<!-- GraphQL Client -->
<!-- <dependency>-->
<!-- <groupId>com.graphql-java-kickstart</groupId>-->
<!-- <artifactId>graphql-java-client</artifactId>-->
<!-- <version>1.0.0</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>15.0.0</version>
</dependency>

<!-- testing facilities -->
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter-test</artifactId>
<version>15.0.0</version>
<scope>test</scope>
</dependency>
<!-- Jackson for JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Jakarta -->
<dependency>
<groupId>jakarta.servlet</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package net.ironoc.portfolio.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import net.ironoc.portfolio.service.GraphQLClientService;
import net.ironoc.portfolio.logger.AbstractLogger;
import net.ironoc.portfolio.domain.CoffeeDomain;
import net.ironoc.portfolio.service.Coffees;
Expand All @@ -13,7 +15,10 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@RestController
public class CoffeeController extends AbstractLogger {
Expand All @@ -22,10 +27,13 @@ public class CoffeeController extends AbstractLogger {

private final CoffeesCache coffeesCache;

private final GraphQLClientService graphQLClientService;

@Autowired
public CoffeeController(Coffees coffeesService, CoffeesCache coffeesCache) {
public CoffeeController(Coffees coffeesService, CoffeesCache coffeesCache, GraphQLClientService graphQLClientService) {
this.coffeesService = coffeesService;
this.coffeesCache = coffeesCache;
this.graphQLClientService = graphQLClientService;
}

@Operation(summary = "Get Hot/Iced Coffee Details",
Expand All @@ -44,4 +52,20 @@ public ResponseEntity<List<CoffeeDomain>> getCoffeeDetails() {
return ResponseEntity.ok(coffeeDomain);
}
}

@GetMapping(value = {"/coffees-graph-ql"}, produces= MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Map<String, Object>>> getCoffeeDetailsGraphQl() {
try {
Map<String, Object> response = graphQLClientService.fetchCoffeeDetails();
List<Map<String, Object>> hot = graphQLClientService.getAllHotCoffees(response);
List<Map<String, Object>> ice = graphQLClientService.getAllIcedCoffees(response);
List<Map<String, Object>> mergedCoffees = new ArrayList<>();
mergedCoffees.addAll(hot);
mergedCoffees.addAll(ice);
return ResponseEntity.ok(mergedCoffees);
} catch (JsonProcessingException e) {
error("Unexpected exception occurred loading GraphQL query, msg={}", e.getMessage());
}
return ResponseEntity.ok(Collections.emptyList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package net.ironoc.portfolio.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.ironoc.portfolio.logger.AbstractLogger;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class GraphQLClientService extends AbstractLogger {

private final ObjectMapper objectMapper;

private final ResourceLoader resourceLoader;

private final RestTemplate restTemplate;

private static final String GRAPHQL_URL = "https://api.sampleapis.com/coffee/graphql";// TODO move to yml

@Autowired
public GraphQLClientService(RestTemplateBuilder restTemplateBuilder,
ObjectMapper objectMapper, ResourceLoader resourceLoader) {
this.restTemplate = restTemplateBuilder.build();
this.objectMapper = objectMapper;
this.resourceLoader = resourceLoader;
}

public Map<String, Object> fetchCoffeeDetails() throws JsonProcessingException {
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");

String query = this.loadQuery("query.graphql");
if (!StringUtils.isBlank(query)) {
String requestPayload = "{ \"query\": \"" + query.replace("\"", "\\\"").replace("\n", " ") + "\" }";
HttpEntity<String> request = new HttpEntity<>(requestPayload, headers);
ResponseEntity<String> response = restTemplate.exchange(GRAPHQL_URL, HttpMethod.POST, request, String.class);
return objectMapper.readValue(response.getBody(), Map.class);
} else {
return new HashMap<>();
}
}

private String loadQuery(String fileName) {
Resource resource = resourceLoader.getResource("classpath:graphql" + File.separator + fileName);
try {
return new String(Files.readAllBytes(Paths.get(resource.getURI())));
} catch (IOException e) {
error("Unexpected exception occurred loading GraphQL query, msg={}", e.getMessage());
}
return null;
}

public List<Map<String, Object>> getAllIcedCoffees(Map<String, Object> response) {
return (List<Map<String, Object>>) ((Map<String, Object>) response.get("data")).get("allIceds");
}

public List<Map<String, Object>> getAllHotCoffees(Map<String, Object> response) {
return (List<Map<String, Object>>) ((Map<String, Object>) response.get("data")).get("allHots");
}
}
14 changes: 14 additions & 0 deletions src/main/resources/graphql/query.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
allIceds {
id
ingredients
image
title
},
allHots {
id
ingredients
image
title
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.ironoc.portfolio.controller;

import net.ironoc.portfolio.domain.CoffeeDomain;
import net.ironoc.portfolio.service.GraphQLClientService;
import net.ironoc.portfolio.service.CoffeesCache;
import net.ironoc.portfolio.service.CoffeesService;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -42,6 +43,9 @@ public class CoffeeControllerIntegrationTest extends ControllerIntegrationTest {
@MockitoBean
private CoffeesCache coffeesCacheMock;

@MockitoBean
private GraphQLClientService graphQLClientServiceMock;

private MockMvc mockMvc;

@BeforeEach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import net.ironoc.portfolio.dto.RepositoryDetailDto;
import net.ironoc.portfolio.dto.RepositoryIssueDto;
import net.ironoc.portfolio.service.GraphQLClientService;
import net.ironoc.portfolio.service.GitDetailsService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -46,6 +47,9 @@ public class GitProjectsControllerIntegrationTest extends ControllerIntegrationT
@MockitoBean
private GitDetailsService gitDetailsServiceMock;

@MockitoBean
private GraphQLClientService graphQLClientServiceMock;

@InjectMocks
private GitProjectsController gitProjectsController;// controller under test

Expand Down

0 comments on commit 94860f3

Please sign in to comment.