diff --git a/oracle-of-bacon-backend/.gitignore b/oracle-of-bacon-backend/.gitignore index 7f396a4..e2cc8f5 100644 --- a/oracle-of-bacon-backend/.gitignore +++ b/oracle-of-bacon-backend/.gitignore @@ -5,6 +5,10 @@ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 .idea/ +*.iml +.classpath +.project +.settings # User-specific stuff: .idea/workspace.xml @@ -90,3 +94,4 @@ gradle-app.setting # gradle/wrapper/gradle-wrapper.properties # End of https://www.gitignore.io/api/gradle,java,intellij +/bin/ \ No newline at end of file diff --git a/oracle-of-bacon-backend/build.gradle b/oracle-of-bacon-backend/build.gradle index 421d9ff..f833850 100644 --- a/oracle-of-bacon-backend/build.gradle +++ b/oracle-of-bacon-backend/build.gradle @@ -4,6 +4,11 @@ apply plugin: 'application' mainClassName = "com.serli.oracle.of.bacon.Application" +task loadElastic(type: JavaExec) { + main = 'com.serli.oracle.of.bacon.loader.elasticsearch.CompletionLoader' + args '/Users/asus/Téléchargements/imdb-data/actors.csv' + classpath = sourceSets.main.runtimeClasspath +} repositories { jcenter() } diff --git a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/api/APIEndPoint.java b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/api/APIEndPoint.java index c2a35df..ba1ffbe 100644 --- a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/api/APIEndPoint.java +++ b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/api/APIEndPoint.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class APIEndPoint { private final Neo4JRepository neo4JRepository; @@ -22,64 +23,24 @@ public APIEndPoint() { } @Get("bacon-to?actor=:actorName") - // TODO change return type + // change return type public String getConnectionsToKevinBacon(String actorName) { - return "[\n" + - "{\n" + - "\"data\": {\n" + - "\"id\": 85449,\n" + - "\"type\": \"Actor\",\n" + - "\"value\": \"Bacon, Kevin (I)\"\n" + - "}\n" + - "},\n" + - "{\n" + - "\"data\": {\n" + - "\"id\": 2278636,\n" + - "\"type\": \"Movie\",\n" + - "\"value\": \"Mystic River (2003)\"\n" + - "}\n" + - "},\n" + - "{\n" + - "\"data\": {\n" + - "\"id\": 1394181,\n" + - "\"type\": \"Actor\",\n" + - "\"value\": \"Robbins, Tim (I)\"\n" + - "}\n" + - "},\n" + - "{\n" + - "\"data\": {\n" + - "\"id\": 579848,\n" + - "\"source\": 85449,\n" + - "\"target\": 2278636,\n" + - "\"value\": \"PLAYED_IN\"\n" + - "}\n" + - "},\n" + - "{\n" + - "\"data\": {\n" + - "\"id\": 9985692,\n" + - "\"source\": 1394181,\n" + - "\"target\": 2278636,\n" + - "\"value\": \"PLAYED_IN\"\n" + - "}\n" + - "}\n" + - "]"; + redisRepository.saveSearch(actorName); + List elementsGraphe = neo4JRepository.getConnectionsToKevinBacon(actorName); + String elements = elementsGraphe.stream().map(element -> element.toString()).collect(Collectors.joining(", ")); + + return "[" + elements + "]"; + + } @Get("suggest?q=:searchQuery") public List getActorSuggestion(String searchQuery) throws IOException { - return Arrays.asList("Niro, Chel", - "Senanayake, Niro", - "Niro, Juan Carlos", - "de la Rua, Niro", - "Niro, Simão"); + return elasticSearchRepository.getActorsSuggests(searchQuery); } @Get("last-searches") public List last10Searches() { - return Arrays.asList("Peckinpah, Sam", - "Robbins, Tim (I)", - "Freeman, Morgan (I)", - "De Niro, Robert", - "Pacino, Al (I)"); + return redisRepository.getLastTenSearches(); } } diff --git a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/loader/elasticsearch/CompletionLoader.java b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/loader/elasticsearch/CompletionLoader.java index f533919..7899e07 100644 --- a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/loader/elasticsearch/CompletionLoader.java +++ b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/loader/elasticsearch/CompletionLoader.java @@ -5,34 +5,87 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.concurrent.atomic.AtomicInteger; - import org.elasticsearch.client.RestHighLevelClient; - import com.serli.oracle.of.bacon.repository.ElasticSearchRepository; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.common.xcontent.XContentType; public class CompletionLoader { private static AtomicInteger COUNT = new AtomicInteger(0); + private static int successCount = 0; + private static int BULK_SIZE = 100000; + private static RestHighLevelClient client = null; + private static BulkRequest request = null; - public static void main(String[] args) throws IOException, InterruptedException { - RestHighLevelClient client = ElasticSearchRepository.createClient(); - - if (args.length != 1) { - System.err.println("Expecting 1 arguments, actual : " + args.length); - System.err.println("Usage : completion-loader "); - System.exit(-1); + private static void doRequest() { + try { + BulkResponse bulkResponse = client.bulk(request); + successCount += request.numberOfActions(); + System.out.println(successCount + " actors inserted"); + if(bulkResponse.hasFailures()) { + System.out.println("Something went wrong"); + } + } catch (Exception e) { + System.out.println("Everything went wrong"); + } finally { + request = new BulkRequest(); } + } + public static void main(String[] args) throws IOException, InterruptedException { + if (args.length != 1) { + System.err.println("Expecting 1 arguments, actual : " + args.length); + System.err.println("Usage : completion-loader "); + System.exit(-1); + } + client = ElasticSearchRepository.createClient(); + request = new BulkRequest(); String inputFilePath = args[0]; try (BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(inputFilePath))) { bufferedReader .lines() .forEach(line -> { - // TODO + // Ajout de elasticsearch + if(count.get() == 0) { + count.getAndIncrement(); + return; + } + String[] input = line.split(","); + StringBuilder suggestionArray = new StringBuilder("["); + suggestionArray.append("\"") + .append(line.replace("\"", "")) + .append("\""); + + if (input.length == 2) { + suggestionArray.append(", \"") + .append(input[1].replace("\"", "")) + .append(", ") + .append(input[0].replace("\"", "")) + .append("\""); + } + + suggestionArray.append("]"); + + + String jsonString = "{ \"name_suggest\": {\"input\": " + suggestionArray.toString() + " }, \"name\": \""+ line.replace("\"", "") + "\"}"; + request.add( + new IndexRequest("actors") + .id(Integer.toString(count.getAndIncrement())) + .type("_doc") + .source(jsonString, XContentType.JSON) + ); + if(count.get() % BULK_SIZE == 0) { + doRequest(); + } }); } - System.out.println("Inserted total of " + COUNT.get() + " actors"); + doRequest(); + + System.out.println("Inserted total of " + successCount + " actors"); client.close(); } diff --git a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/ElasticSearchRepository.java b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/ElasticSearchRepository.java index 045c7a7..5a2df86 100644 --- a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/ElasticSearchRepository.java +++ b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/ElasticSearchRepository.java @@ -1,15 +1,26 @@ package com.serli.oracle.of.bacon.repository; - -import java.io.IOException; -import java.util.List; - import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.suggest.Suggest; +import org.elasticsearch.search.suggest.SuggestBuilder; +import org.elasticsearch.search.suggest.SuggestBuilders; +import org.elasticsearch.search.suggest.SuggestionBuilder; +import org.elasticsearch.search.suggest.completion.CompletionSuggestion; +import org.elasticsearch.search.suggest.term.TermSuggestion; public class ElasticSearchRepository { - - private final RestHighLevelClient client; + private static final String FIELD_NAME = "name"; + private static final String ACTORS_COMPLETION = "actors_completion"; + private static final String SUGGESTION_FIELD = "name_suggest"; + private static final String INDEX = "actors"; + private final RestHighLevelClient client; public ElasticSearchRepository() { client = createClient(); @@ -25,7 +36,31 @@ public static RestHighLevelClient createClient() { } public List getActorsSuggests(String searchQuery) throws IOException { - // TODO - return null; + + SearchRequest searchRequest = new SearchRequest(INDEX); + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + SuggestionBuilder completionSuggestionBuilder = + SuggestBuilders.completionSuggestion(SUGGESTION_FIELD).text(searchQuery).size(10); + SuggestBuilder suggestBuilder = new SuggestBuilder(); + suggestBuilder.addSuggestion(ACTORS_COMPLETION, completionSuggestionBuilder); + searchSourceBuilder.suggest(suggestBuilder); + + searchRequest.source(searchSourceBuilder); + + SearchResponse searchResponse = client.search(searchRequest); + + // Récupération des suggestions + Suggest suggest = searchResponse.getSuggest(); + CompletionSuggestion completionSuggestion = suggest.getSuggestion(ACTORS_COMPLETION); + + List suggestions = new LinkedList<>(); + for (CompletionSuggestion.Entry entry : completionSuggestion.getEntries()) { + for (CompletionSuggestion.Entry.Option option : entry) { + suggestions.add((String) option.getHit().getSourceAsMap().get(FIELD_NAME)); + } + } + + return suggestions; } } diff --git a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/Neo4JRepository.java b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/Neo4JRepository.java index e5490f4..b874539 100644 --- a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/Neo4JRepository.java +++ b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/Neo4JRepository.java @@ -9,20 +9,68 @@ import org.neo4j.driver.Session; import org.neo4j.driver.types.Node; import org.neo4j.driver.types.Relationship; +import org.neo4j.driver.internal.value.PathValue; +import org.neo4j.driver.v1.Record; +import org.neo4j.driver.v1.StatementResult; +import org.neo4j.driver.v1.Transaction; +import org.neo4j.driver.v1.Value; +import org.neo4j.driver.v1.types.Node; +import org.neo4j.driver.v1.types.Path; +import org.neo4j.driver.v1.types.Relationship; +import static org.neo4j.driver.v1.Values.parameters; + +import java.util.LinkedList; public class Neo4JRepository { private final Driver driver; public Neo4JRepository() { - this.driver = GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "password")); + this.driver = GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "motdepasse")); } - public List> getConnectionsToKevinBacon(String actorName) { - Session session = driver.session(); + private String getQuery() { + StringBuilder strBuilder = new StringBuilder(); + strBuilder.append("MATCH p = shortestPath((Kevin:Actor {name: \"Bacon, Kevin (I)\"})-[:PLAYED_IN*]-(Other:Actor {name: {nomAutreActeur}})) ") + .append("RETURN p") + ; + return strBuilder.toString(); + } - // TODO - return null; - } + public List getConnectionsToKevinBacon(String actorName) { + Session session = driver.session(); + // Implémentation de l'oracle de Bacon + List graphe = new LinkedList<>(); + StatementResult resultats = null; + try (Transaction transaction = session.beginTransaction()) + { + resultats = transaction.run(getQuery(), parameters("nomAutreActeur", actorName)); + transaction.success(); + } + + while (resultats.hasNext()) { + Record entree = resultats.next(); + List valeurs = entree.values(); + + + for (Value valeur : valeurs) { + Path chemin = valeur.asPath(); + + chemin.nodes().forEach(node -> { + String type = node.labels().iterator().next(); + + String clePropriete = "Actor".equals(type) ? "name" : "title"; + graphe.add(new GraphNode(node.id(), node.get(clePropriete).asString(), type)); + }); + + chemin.relationships().forEach(relationship -> { + graphe.add(new GraphEdge(relationship.id(), relationship.startNodeId(), relationship.endNodeId(), relationship.type())); + }); + } + } + + return graphe; + } + private GraphEdge mapRelationShipToNodeEdge(Relationship relationship) { return new GraphEdge(relationship.id(), relationship.startNodeId(), relationship.endNodeId(), relationship.type()); @@ -85,5 +133,17 @@ public GraphEdge(long id, long source, long target, String value) { this.target = target; this.value = value; } + @Override + public String toString() { + StringBuilder strBuilder = new StringBuilder(); + strBuilder.append("{ \"data\" : { ") + .append(" \"id\": " ).append(this.id ).append(", " ) + .append(" \"source\": " ).append(this.source).append(", " ) + .append(" \"target\": " ).append(this.target).append(", " ) + .append(" \"value\": \"").append(this.value ).append("\"") + .append("}}") + ; + return strBuilder.toString(); + } } } diff --git a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/RedisRepository.java b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/RedisRepository.java index 38f86c9..c98b787 100644 --- a/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/RedisRepository.java +++ b/oracle-of-bacon-backend/src/main/java/com/serli/oracle/of/bacon/repository/RedisRepository.java @@ -4,15 +4,23 @@ import redis.clients.jedis.Jedis; +import java.util.Arrays; + public class RedisRepository { - private final Jedis jedis; + private static final int HISTORY_MAX_LENGTH = 9; + + private final Jedis jedis; + private final static String KEY = "SearchHistory"; public RedisRepository() { this.jedis = new Jedis("localhost"); } - + public void saveSearch(String query) { + jedis.lpush(KEY, query); + jedis.ltrim(KEY, 0, HISTORY_MAX_LENGTH); + } public List getLastTenSearches() { - // TODO - return null; + String[] last10 = jedis.lrange(KEY, 0, -1).toArray(new String[0]); + return Arrays.asList(last10); } } diff --git a/oracle-of-bacon-frontend/package-lock.json b/oracle-of-bacon-frontend/package-lock.json index b75aea5..2f69e98 100644 --- a/oracle-of-bacon-frontend/package-lock.json +++ b/oracle-of-bacon-frontend/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "frontend", "version": "0.0.0", "dependencies": { "cytoscape": "^3.18.1", @@ -607,9 +608,6 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.39.1.tgz", "integrity": "sha512-9rfr0Z6j+vE+eayfNVFr1KZ+k+jiUl2+0e4quZafy1x6SFCjzFspfRSO2ZZQeWeX9noeDTUDgg6eCENiEPFvQg==", "dev": true, - "dependencies": { - "fsevents": "~2.3.1" - }, "bin": { "rollup": "dist/bin/rollup" }, @@ -692,7 +690,6 @@ "dev": true, "dependencies": { "esbuild": "^0.8.47", - "fsevents": "~2.3.1", "postcss": "^8.2.1", "resolve": "^1.19.0", "rollup": "^2.38.5"