diff --git a/.env b/.env new file mode 100644 index 0000000..db820b4 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +NEO4J_ADDR=localhost \ No newline at end of file diff --git a/.github/workflows/app-test.yml b/.github/workflows/app-test.yml new file mode 100644 index 0000000..20851fd --- /dev/null +++ b/.github/workflows/app-test.yml @@ -0,0 +1,29 @@ +name: app-test + +on: + push: + branches: + - develop + - master + pull_request: + +defaults: + run: + working-directory: . + +jobs: + apptests: + name: Tests + runs-on: self-hosted + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 16 + uses: actions/setup-java@v1 + with: + java-version: 16 + - name: Clean db before tests + run: delete_all_nodes + - name: Run mvn test + run: mvn test + - name: Clean db after tests + run: delete_all_nodes \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a12293e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/target/ +.settings +.classpath +.factorypath +.project +.idea \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..09c8e92 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM maven:3.6.3-openjdk-16 + +WORKDIR /root/.m2/repository +COPY . ./ +RUN mvn verify clean --fail-never +RUN mvn compile; sleep 15 +ENTRYPOINT [ "mvn","exec:java" ] +EXPOSE 8000 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5c83b8f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3.7" +services: + assignment: + container_name: a1 + build: + context: ./ + dockerfile: Dockerfile + depends_on: + - neo4j + ports: + - '8080:8080' + environment: + - NEO4J_ADDR=neo4j + - HTTP_PROXY="http://cms-proxy.utsc.utoronto.ca:8118/" + - HTTPS_PROXY="http://cms-proxy.utsc.utoronto.ca:8118/" + - NO_PROXY=".utsc.utoronto.ca,.utoronto.ca,127.0.0.0/8" + neo4j: + container_name: neo4j + #platform: linux/amd64 + image: neo4j:latest + environment: + - NEO4J_AUTH=neo4j/123456 + ports: + - '7687:7687' diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4816e4e --- /dev/null +++ b/pom.xml @@ -0,0 +1,90 @@ + + 4.0.0 + ca.utoronto.utm.mcs + a1 + jar + 1.0-SNAPSHOT + a1 + http://maven.apache.org + + + org.junit.jupiter + junit-jupiter + 5.7.2 + test + + + com.google.dagger + dagger + 2.35.1 + + + org.neo4j.driver + neo4j-java-driver + 4.3.4 + + + org.json + json + 20090211 + + + io.github.cdimascio + dotenv-java + 2.2.0 + + + + UTF-8 + 16 + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M4 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 16 + + + com.google.dagger + dagger-compiler + 2.35.1 + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.0.0 + + + + java + + + + + ca.utoronto.utm.mcs.App + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + --enable-preview + + + + + diff --git a/src/main/java/ca/utoronto/utm/mcs/App.java b/src/main/java/ca/utoronto/utm/mcs/App.java new file mode 100644 index 0000000..b29c0b8 --- /dev/null +++ b/src/main/java/ca/utoronto/utm/mcs/App.java @@ -0,0 +1,26 @@ +package ca.utoronto.utm.mcs; + +import io.github.cdimascio.dotenv.Dotenv; +import java.io.IOException; + +public class App +{ + static int port = 8080; + + public static void main(String[] args) throws IOException + { + ReqHandlerComponent handlerComponent = DaggerReqHandlerComponent.create(); + ServerComponent serverComponent = DaggerServerComponent.create(); + Server server = serverComponent.buildServer(); + server.createContext("/api/v1/", handlerComponent.buildHandler()); + server.startServer(); + + // TODO Create Your Server Context Here, There Should Only Be One Context + System.out.printf("Server started on port %d\n", port); + + // This code is used to get the neo4j address, you must use this so that we can mark :) + Dotenv dotenv = Dotenv.load(); + String addr = dotenv.get("NEO4J_ADDR"); + System.out.println(addr); + } +} diff --git a/src/main/java/ca/utoronto/utm/mcs/Neo4jDAO.java b/src/main/java/ca/utoronto/utm/mcs/Neo4jDAO.java new file mode 100644 index 0000000..65812da --- /dev/null +++ b/src/main/java/ca/utoronto/utm/mcs/Neo4jDAO.java @@ -0,0 +1,228 @@ +package ca.utoronto.utm.mcs; + +import org.json.*; +import org.neo4j.driver.*; +import org.neo4j.driver.Record; + +import java.util.List; + +import javax.inject.Inject; + +// All your database transactions or queries should +// go in this class +public class Neo4jDAO { + private final Session session; + private final Driver driver; + + private final String kevinBid = "nm0000102"; + + @Inject + public Neo4jDAO(Driver driver) { + this.driver = driver; + this.session = this.driver.session(); + + } + + public int addActor(String Name, String id) { + String query; + query = "MATCH (a:actor { id: \"%s\"}) RETURN a.id"; + query = String.format(query, id); + Result result = this.session.run(query); + if(result.hasNext()){ + return 400; + } + query = "CREATE (a:actor {Name: \"%s\", id: \"%s\"})"; + query = String.format(query, Name, id); + this.session.run(query); + return 200; + } + + public int addMovie(String Name, String id) { + String query; + query = "MATCH (m:movie { id: \"%s\"}) RETURN m.id"; + query = String.format(query, id); + Result result = this.session.run(query); + if(result.hasNext()){ + return 400; + } + query = "CREATE (m:movie {Name: \"%s\", id: \"%s\"})"; + query = String.format(query, Name, id); + this.session.run(query); + return 200; + } + + public int addRelationship(String actorId, String movieId) { + String query; + query = "MATCH (a:actor { id: \"%s\"}) RETURN a.id"; + query = String.format(query, actorId); + Result result = this.session.run(query); + if(!result.hasNext()){ + return 404; + } + + query = "MATCH (m:movie { id: \"%s\"}) RETURN m.id"; + query = String.format(query, movieId); + result = this.session.run(query); + if(!result.hasNext()){ + return 404; + } + + query = "MATCH (a:actor {id: \"%s\"})-[:ACTED_IN]->(m:movie {id: \"%s\"}) RETURN a.id"; + query = String.format(query, actorId, movieId); + result = this.session.run(query); + if(result.hasNext()){ + return 400; + } + + query = "MATCH (a:actor {id: \"%s\"}), (m:movie {id: \"%s\"}) CREATE (a)-[r:ACTED_IN]->(m)"; + query = String.format(query, actorId, movieId); + this.session.run(query); + return 200; + } + + public String getActor(String id) throws JSONException { + JSONObject response = new JSONObject(); + String query; + query = "MATCH (a:actor { id: \"%s\"}) RETURN a.id"; + query = String.format(query, id); + + Result result = this.session.run(query); + if(!result.hasNext()){ + return "404"; + } + List resultValues = result.list(); + response.put("id", id); + response.put("Name", resultValues.get(0).get("a.Name").asString()); + + query = "MATCH (a:actor { id: \"%s\"})-[r:ACTED_IN]->(m:movie) RETURN m.id";//maybe change Movie to lowercase + query = String.format(query, id); + result = this.session.run(query); + resultValues = result.list(); + JSONArray movies = new JSONArray(); + resultValues.forEach((record)->{movies.put(record.get("m.id").asString());}); + //movies.put(record.get("m.id").asString()); + response.put("movies", movies); + return response.toString(); + } + + public String getMovie(String movieId) throws JSONException { + JSONObject response = new JSONObject(); + String query; + query = "MATCH (m:movie { id: \"%s\"}) RETURN m.Name"; + query = String.format(query, movieId); + + Result result = this.session.run(query); + if(!result.hasNext()){ + return "404"; + } + List resultValues = result.list(); + response.put("movieId", movieId); + response.put("name", resultValues.get(0).get("m.Name").asString()); + + query = "MATCH (a:actor)-[r:ACTED_IN]->(m:movie { id: \"%s\"}) RETURN a.id"; + query = String.format(query, movieId); + result = this.session.run(query); + resultValues = result.list(); + JSONArray actors = new JSONArray(); + resultValues.forEach((record)->{actors.put(record.get("a.id").asString());}); + response.put("actors", actors); + return response.toString(); + } + public String hasRelationship(String actorId, String movieId) throws JSONException { + JSONObject response = new JSONObject(); + String query; + + query = "MATCH (m:movie { id: \"%s\"}) RETURN m.Name"; + query = String.format(query, movieId); + Result result = this.session.run(query); + if(!result.hasNext()){ + return "404"; + } + + query = "MATCH (a:actor { id: \"%s\"}) RETURN a.Name"; + query = String.format(query, actorId); + result = this.session.run(query); + if(!result.hasNext()){ + return "404"; + } + + response.put("movieId", movieId); + response.put("actorId", actorId); + + query = "MATCH (a:actor { id: \"%s\"})-[r:ACTED_IN]->(m:movie { id: \"%s\"}) RETURN r"; + query = String.format(query, actorId,movieId); + result = this.session.run(query); + if(!result.hasNext()){ + response.put("hasRelationship", false); + }else{ + response.put("hasRelationship", true); + } + return response.toString(); + } + + public String computeBaconNumber(String actorId) throws JSONException{ + JSONObject response = new JSONObject(); + String query; + int baconNumber = 0; + + if(actorId.equals(kevinBid)){ + response.put("baconNumber", 0); + return response.toString(); + } + query = "MATCH (a:actor {id: \"%s\"}), (KevinB:actor {id: \"%s\"} ), p = shortestPath((a)-[:ACTED_IN*]-(KevinB)) RETURN nodes(p)"; + query = String.format(query, actorId, kevinBid); + + Result result = this.session.run(query); + if(!result.hasNext()){ + return "404"; + } + Record record = result.next(); + for(int i=0; i sendRequest(String endpoint, String method, String reqBody) throws IOException, InterruptedException { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(API_URL + endpoint)) + .method(method, HttpRequest.BodyPublishers.ofString(reqBody)) + .build(); + + return client.send(request, HttpResponse.BodyHandlers.ofString()); + } + + + @Test + public void exampleTest() { + assertTrue(true); + } + + @Test + public void addActorPass() throws JSONException, IOException, InterruptedException { + JSONObject confirmReq = new JSONObject() + .put("name", "TestActor") + .put("actorId", "123123"); + HttpResponse confirmRes = sendRequest("/api/v1/addActor", "PUT", confirmReq.toString()); + sendRequest("/api/v1/deleteActor", "DELETE", confirmReq.toString()); + assertEquals(HttpURLConnection.HTTP_OK, confirmRes.statusCode()); + } + + @Test + public void addActorFail() throws JSONException, IOException, InterruptedException { + JSONObject confirmReq = new JSONObject() + .put("name", "TestActor"); + HttpResponse confirmRes = sendRequest("/api/v1/addActor", "PUT", confirmReq.toString()); + assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, confirmRes.statusCode()); + } + + @Test + public void addMoviePass() throws JSONException, IOException, InterruptedException { + + JSONObject confirmReq = new JSONObject() + .put("name", "TestMovie") + .put("movieId", "12345678901"); + HttpResponse confirmRes = sendRequest("/api/v1/addMovie", "PUT", confirmReq.toString()); + sendRequest("/api/v1/deleteMovie", "DELETE", confirmReq.toString()); + assertEquals(HttpURLConnection.HTTP_OK, confirmRes.statusCode()); + } + + @Test + public void addMovieFail() throws JSONException, IOException, InterruptedException { + JSONObject confirmReq = new JSONObject() + .put("name", "TestMovie"); + HttpResponse confirmRes = sendRequest("/api/v1/addMovie", "PUT", confirmReq.toString()); + assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, confirmRes.statusCode()); + } + + @Test + public void addRelationshipPass() throws JSONException, IOException, InterruptedException { + JSONObject confirmReq = new JSONObject() + .put("name", "TestActor") + .put("actorId", "123456789011"); + HttpResponse confirmRes = sendRequest("/api/v1/addActor", "PUT", confirmReq.toString()); + confirmReq = new JSONObject() + .put("name", "TestMovie") + .put("movieId", "123456789012"); + confirmRes = sendRequest("/api/v1/addMovie", "PUT", confirmReq.toString()); + confirmReq = new JSONObject() + .put("actorId", "123456789011") + .put("movieId", "123456789012"); + confirmRes = sendRequest("/api/v1/addRelationship", "PUT", confirmReq.toString()); + sendRequest("/api/v1/deleteActor", "DELETE", confirmReq.toString()); + sendRequest("/api/v1/deleteMovie", "DELETE", confirmReq.toString()); + assertEquals(HttpURLConnection.HTTP_OK, confirmRes.statusCode()); + } + + @Test + public void addRelationshipFail() throws JSONException, IOException, InterruptedException { + JSONObject confirmReq = new JSONObject() + .put("movieId", "123123"); + HttpResponse confirmRes = sendRequest("/api/v1/addRelationship", "PUT", confirmReq.toString()); + assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, confirmRes.statusCode()); + } + + @Test + public void getActorPass() throws JSONException, IOException, InterruptedException { + JSONObject setupReq = new JSONObject() + .put("name", "TestActor") + .put("actorId", "12345678901"); + sendRequest("/api/v1/addActor", "PUT", setupReq.toString()); + + JSONObject confirmReq = new JSONObject() + .put("actorId", "12345678901"); + HttpResponse confirmRes = sendRequest("/api/v1/getActor", "GET", confirmReq.toString()); + + sendRequest("/api/v1/deleteActor", "DELETE", setupReq.toString()); + assertEquals(HttpURLConnection.HTTP_OK, confirmRes.statusCode()); + } + + @Test + public void getActorFail() throws JSONException, IOException, InterruptedException { + JSONObject setupReq = new JSONObject() + .put("name", "TestActor") + .put("actorId", "12345678901"); + sendRequest("/api/v1/addActor", "PUT", setupReq.toString()); + + JSONObject confirmReq = new JSONObject() + .put("name", "TestActor"); + HttpResponse confirmRes = sendRequest("/api/v1/getActor", "GET", confirmReq.toString()); + + sendRequest("/api/v1/deleteActor", "DELETE", setupReq.toString()); + assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, confirmRes.statusCode()); + } + + @Test + public void getMoviePass() throws JSONException, IOException, InterruptedException { + JSONObject setupReq = new JSONObject() + .put("name", "TestMovie") + .put("movieId", "12345678901"); + sendRequest("/api/v1/addMovie", "PUT", setupReq.toString()); + + JSONObject confirmReq = new JSONObject() + .put("movieId", "12345678901"); + HttpResponse confirmRes = sendRequest("/api/v1/getMovie", "GET", confirmReq.toString()); + + sendRequest("/api/v1/deleteMovie", "DELETE", confirmReq.toString()); + assertEquals(HttpURLConnection.HTTP_OK, confirmRes.statusCode()); + } + + @Test + public void getMovieFail() throws JSONException, IOException, InterruptedException { + JSONObject confirmReq = new JSONObject() + .put("name", "TestMovie"); + HttpResponse confirmRes = sendRequest("/api/v1/getMovie", "GET", confirmReq.toString()); + + assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, confirmRes.statusCode()); + } + + @Test + public void hasRelationshipPass() throws JSONException, IOException, InterruptedException { + JSONObject setupReq = new JSONObject() + .put("name", "TestMovie") + .put("movieId", "12345678901"); + sendRequest("/api/v1/addMovie", "PUT", setupReq.toString()); + + setupReq = new JSONObject() + .put("name", "TestActor") + .put("actorId", "12345678902"); + sendRequest("/api/v1/addActor", "PUT", setupReq.toString()); + + setupReq = new JSONObject() + .put("movieId", "12345678901") + .put("actorId", "12345678902"); + sendRequest("/api/v1/addRelationship", "PUT", setupReq.toString()); + + JSONObject confirmReq = new JSONObject() + .put("movieId", "12345678901") + .put("actorId", "12345678902"); + HttpResponse confirmRes = sendRequest("/api/v1/hasRelationship", "GET", confirmReq.toString()); + + sendRequest("/api/v1/deleteMovie", "DELETE", confirmReq.toString()); + sendRequest("/api/v1/deleteActor", "DELETE", confirmReq.toString()); + assertEquals(HttpURLConnection.HTTP_OK, confirmRes.statusCode()); + } + + @Test + public void hasRelationshipFail() throws JSONException, IOException, InterruptedException { + JSONObject setupReq1 = new JSONObject() + .put("name", "TestMovie") + .put("movieId", "12345678901"); + sendRequest("/api/v1/addMovie", "PUT", setupReq1.toString()); + + JSONObject setupReq2 = new JSONObject() + .put("name", "TestActor") + .put("actorId", "12345678902"); + sendRequest("/api/v1/addActor", "PUT", setupReq2.toString()); + + JSONObject setupReq3 = new JSONObject() + .put("movieId", "12345678901") + .put("actorId", "12345678902"); + sendRequest("/api/v1/addRelationship", "PUT", setupReq3.toString()); + + JSONObject confirmReq = new JSONObject() + .put("movieId", "12345678901"); + HttpResponse confirmRes = sendRequest("/api/v1/hasRelationship", "GET", confirmReq.toString()); + + sendRequest("/api/v1/deleteMovie", "DELETE", setupReq1.toString()); + sendRequest("/api/v1/deleteActor", "DELETE", setupReq2.toString()); + assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, confirmRes.statusCode()); + } + + @Test + public void computeBaconNumberPass() throws JSONException, IOException, InterruptedException { + boolean deleteKB = false; + JSONObject setupReq1 = new JSONObject() + .put("name", "Kevin Bacon") + .put("actorId", "nm0000102"); + HttpResponse setupRes = sendRequest("/api/v1/addActor", "PUT", setupReq1.toString()); + + if(setupRes.statusCode() == 200){ + deleteKB = true; + } + + JSONObject setupReq2 = new JSONObject() + .put("name", "TestActor") + .put("actorId", "12345678902"); + sendRequest("/api/v1/addActor", "PUT", setupReq2.toString()); + + JSONObject setupReq3 = new JSONObject() + .put("name", "TestMovie") + .put("movieId", "12345678901"); + sendRequest("/api/v1/addMovie", "PUT", setupReq3.toString()); + + JSONObject confirmReq = new JSONObject() + .put("actorId", "12345678902") + .put("movieId", "12345678901"); + sendRequest("/api/v1/addRelationship", "PUT", confirmReq.toString()); + + confirmReq = new JSONObject() + .put("actorId", "nm0000102") + .put("movieId", "12345678901"); + sendRequest("/api/v1/addRelationship", "PUT", confirmReq.toString()); + + confirmReq = new JSONObject() + .put("actorId", "12345678902"); + HttpResponse confirmRes = sendRequest("/api/v1/computeBaconNumber", "GET", confirmReq.toString()); + + if(deleteKB){ + sendRequest("/api/v1/deleteActor", "DELETE", setupReq1.toString()); + } + sendRequest("/api/v1/deleteActor", "DELETE", setupReq2.toString()); + sendRequest("/api/v1/deleteMovie", "DELETE", setupReq3.toString()); + + assertEquals(HttpURLConnection.HTTP_OK, confirmRes.statusCode()); + } + + @Test + public void computeBaconNumberFail() throws JSONException, IOException, InterruptedException { + JSONObject confirmReq = new JSONObject() + .put("name", "Kevin Bacon"); + HttpResponse confirmRes = sendRequest("/api/v1/computeBaconNumber", "GET", confirmReq.toString()); + + assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, confirmRes.statusCode()); + } + + @Test + public void computeBaconPathPass() throws JSONException, IOException, InterruptedException { + boolean deleteKB = false; + JSONObject setupReq1 = new JSONObject() + .put("name", "Kevin Bacon") + .put("actorId", "nm0000102"); + HttpResponse setupRes = sendRequest("/api/v1/addActor", "PUT", setupReq1.toString()); + + if(setupRes.statusCode() == 200){ + deleteKB = true; + } + + JSONObject setupReq2 = new JSONObject() + .put("name", "TestActor") + .put("actorId", "12345678902"); + sendRequest("/api/v1/addActor", "PUT", setupReq2.toString()); + + JSONObject setupReq3 = new JSONObject() + .put("name", "TestMovie") + .put("movieId", "12345678901"); + sendRequest("/api/v1/addMovie", "PUT", setupReq3.toString()); + + JSONObject confirmReq = new JSONObject() + .put("actorId", "12345678902") + .put("movieId", "12345678901"); + sendRequest("/api/v1/addRelationship", "PUT", confirmReq.toString()); + + confirmReq = new JSONObject() + .put("actorId", "nm0000102") + .put("movieId", "12345678901"); + sendRequest("/api/v1/addRelationship", "PUT", confirmReq.toString()); + + confirmReq = new JSONObject() + .put("actorId", "12345678902"); + HttpResponse confirmRes = sendRequest("/api/v1/computeBaconPath", "GET", confirmReq.toString()); + + if(deleteKB){ + sendRequest("/api/v1/deleteActor", "DELETE", setupReq1.toString()); + } + sendRequest("/api/v1/deleteActor", "DELETE", setupReq2.toString()); + sendRequest("/api/v1/deleteMovie", "DELETE", setupReq3.toString()); + + assertEquals(HttpURLConnection.HTTP_OK, confirmRes.statusCode()); + } + + @Test + public void computeBaconPathFail() throws JSONException, IOException, InterruptedException { + JSONObject confirmReq = new JSONObject() + .put("name", "Kevin Bacon"); + HttpResponse confirmRes = sendRequest("/api/v1/computeBaconPath", "GET", confirmReq.toString()); + + assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, confirmRes.statusCode()); + } +}