diff --git a/README.md b/README.md
index fe8aeaaa..b705275b 100644
--- a/README.md
+++ b/README.md
@@ -60,9 +60,9 @@ gzcat < gse.sql.gz | mysql -u gseadmin -pLugano2020 gse
### Server
-Before attempting to run the server, I advise you generate your own GitHub personal access token (PAT).
-Said token should include the `repo` scope, in order for it to effectively crawl the GitHub API.
-While the token is not mandatory, the impact its presence has on the mining speed can not be understated.
+Before attempting to run the server, you must generate your own GitHub personal access token (PAT).
+GHS relies on the GraphQL API, which is inaccessible without authentication.
+The token must include the `repo` scope, in order for it to access the information present in the GitHub API.
Once that is done, you can run the server locally using Maven:
diff --git a/pom.xml b/pom.xml
index 6237856d..583de70a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -115,6 +115,14 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-graphql
+
org.springframework.boot
spring-boot-starter-aop
diff --git a/src/main/java/usi/si/seart/config/GraphQlConfig.java b/src/main/java/usi/si/seart/config/GraphQlConfig.java
new file mode 100644
index 00000000..661185c9
--- /dev/null
+++ b/src/main/java/usi/si/seart/config/GraphQlConfig.java
@@ -0,0 +1,57 @@
+package usi.si.seart.config;
+
+import lombok.AllArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.graphql.client.GraphQlClient;
+import org.springframework.graphql.client.HttpGraphQlClient;
+import org.springframework.web.reactive.function.client.ClientRequest;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.ExchangeFunction;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+import usi.si.seart.github.Endpoint;
+import usi.si.seart.github.GitHubTokenManager;
+
+@Configuration
+@AllArgsConstructor(onConstructor_ = @Autowired)
+public class GraphQlConfig {
+
+ GitHubTokenManager gitHubTokenManager;
+
+ @Bean
+ public GraphQlClient graphQlClient() {
+ return HttpGraphQlClient.create(webClient());
+ }
+
+ @Bean
+ public WebClient webClient() {
+ return WebClient.builder()
+ .baseUrl(Endpoint.GRAPH_QL.toString())
+ .defaultHeader("X-GitHub-Api-Version", "2022-11-28")
+ .filter(exchangeFilterFunction())
+ .build();
+ }
+
+ @Bean
+ public ExchangeFilterFunction exchangeFilterFunction() {
+ return new ExchangeFilterFunction() {
+
+ @NotNull
+ @Override
+ public Mono filter(@NotNull ClientRequest original, @NotNull ExchangeFunction next) {
+ ClientRequest modified = ClientRequest.from(original)
+ .headers(headers -> {
+ String token = gitHubTokenManager.getCurrentToken();
+ if (token != null)
+ headers.setBearerAuth(token);
+ })
+ .build();
+ return next.exchange(modified);
+ }
+ };
+ }
+}
diff --git a/src/main/java/usi/si/seart/converter/JsonObjectToErrorResponseConverter.java b/src/main/java/usi/si/seart/converter/JsonObjectToErrorResponseConverter.java
index 561bc4da..7afcc905 100644
--- a/src/main/java/usi/si/seart/converter/JsonObjectToErrorResponseConverter.java
+++ b/src/main/java/usi/si/seart/converter/JsonObjectToErrorResponseConverter.java
@@ -4,19 +4,19 @@
import com.google.gson.JsonObject;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
-import usi.si.seart.github.ErrorResponse;
+import usi.si.seart.github.RestErrorResponse;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Optional;
import java.util.stream.StreamSupport;
-public class JsonObjectToErrorResponseConverter implements Converter {
+public class JsonObjectToErrorResponseConverter implements Converter {
@Override
@NonNull
- public ErrorResponse convert(@NonNull JsonObject source) {
- ErrorResponse.ErrorResponseBuilder builder = ErrorResponse.builder();
+ public RestErrorResponse convert(@NonNull JsonObject source) {
+ RestErrorResponse.RestErrorResponseBuilder builder = RestErrorResponse.builder();
builder.message(source.get("message").getAsString());
@@ -48,7 +48,7 @@ public ErrorResponse convert(@NonNull JsonObject source) {
String codeName = Optional.ofNullable(object.get("code"))
.map(JsonElement::getAsString)
.orElse(null);
- return new ErrorResponse.Error(resource, field, codeName, message);
+ return new RestErrorResponse.Error(resource, field, codeName, message);
}).forEach(builder::error);
return builder.build();
diff --git a/src/main/java/usi/si/seart/converter/JsonObjectToGraphQlErrorResponse.java b/src/main/java/usi/si/seart/converter/JsonObjectToGraphQlErrorResponse.java
new file mode 100644
index 00000000..dc2eb5e0
--- /dev/null
+++ b/src/main/java/usi/si/seart/converter/JsonObjectToGraphQlErrorResponse.java
@@ -0,0 +1,66 @@
+package usi.si.seart.converter;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import graphql.ErrorClassification;
+import graphql.language.SourceLocation;
+import lombok.AllArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+import usi.si.seart.github.GraphQlErrorResponse;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+@Component
+@AllArgsConstructor(onConstructor_ = @Autowired)
+public class JsonObjectToGraphQlErrorResponse implements Converter {
+
+ Gson gson;
+ JsonObjectToSourceLocationConverter sourceLocationConverter;
+ StringToGraphQlErrorResponseErrorTypeConverter errorTypeConverter;
+
+ @Override
+ @NotNull
+ public GraphQlErrorResponse convert(@NotNull JsonObject source) {
+ GraphQlErrorResponse.GraphQlErrorResponseBuilder builder = GraphQlErrorResponse.builder();
+
+ String message = source.getAsJsonPrimitive("message").getAsString();
+ builder.message(message);
+ ErrorClassification errorType = errorTypeConverter.convert(message);
+ builder.errorType(errorType);
+
+ if (source.has("path")) {
+ JsonArray array = source.getAsJsonArray("path");
+ Type type = new TypeToken>() { }.getType();
+ List