Skip to content

Commit

Permalink
adding recommendations
Browse files Browse the repository at this point in the history
  • Loading branch information
maxdemarzi committed Mar 23, 2017
1 parent 0cd4855 commit d77ce48
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 2 deletions.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,28 @@ A Neo4j Based Twitter Clone Backend

5. Create the Schema:

:POST /v1/schema/create
:POST /v1/schema/create
6. API:

:GET /v1/users/{username}
:POST /v1/users {username:'', password:'', email:'', name:''}
:GET /v1/users/{username}/followers
:GET /v1/users/{username}/following
:POST /v1/users/{username}/follows/{username2}
:GET /v1/users/{username}/posts
:POST /v1/users/{username}/posts {status:''}
:POST /v1/users/{username}/posts/{username2}/{time}
:GET /v1/users/{username}/likes
:POST /v1/users/{username}/likes/{username2}/{time}
:GET /v1/users/{username}/timeline
:GET /v1/users/{username}/recommendations/friends
:GET /v1/users/{username}/recommendations/follows

7. Query Parameters:

limit=25 or any whole number
since=<a number representing a date in linux epoc time>
See https://www.epochconverter.com/


2 changes: 1 addition & 1 deletion src/main/java/com/maxdemarzi/posts/Posts.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public Response createRepost(@PathParam("username") final String username,
Node post = getPost(user2, time);

LocalDateTime dateTime = LocalDateTime.now(utc);

user.createRelationshipTo(post, RelationshipType.withName("REPOSTED_ON_" +
dateTime.format(dateFormatter)));
results = post.getAllProperties();
Expand Down
95 changes: 95 additions & 0 deletions src/main/java/com/maxdemarzi/recommendations/Recommendations.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.maxdemarzi.recommendations;

import com.maxdemarzi.RelationshipTypes;
import org.codehaus.jackson.map.ObjectMapper;
import org.neo4j.graphdb.*;

import javax.ws.rs.*;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.LongAdder;

import static com.maxdemarzi.Properties.EMAIL;
import static com.maxdemarzi.Properties.PASSWORD;
import static com.maxdemarzi.users.Users.findUser;
import static java.util.Collections.reverseOrder;

@Path("/users/{username}/recommendations")
public class Recommendations {

private static final ObjectMapper objectMapper = new ObjectMapper();

@GET
@Path("/friends")
public Response recommendFriends(@PathParam("username") final String username,
@QueryParam("limit") @DefaultValue("25") final Integer limit,
@Context GraphDatabaseService db) throws IOException {
ArrayList<Map<String, Object>> results = new ArrayList<>();
try (Transaction tx = db.beginTx()) {
Node user = findUser(username, db);
HashSet<Long> following = new HashSet<>();
for (Relationship r1: user.getRelationships(Direction.OUTGOING, RelationshipTypes.FOLLOWS)) {
following.add(r1.getEndNode().getId());
}

for (Relationship r1 : user.getRelationships(Direction.INCOMING, RelationshipTypes.FOLLOWS)) {
Node follower = r1.getStartNode();
if (!following.contains(follower.getId())) {
Map<String, Object> properties = r1.getStartNode().getAllProperties();
properties.remove(PASSWORD);
properties.remove(EMAIL);
results.add(properties);
}
}
tx.success();
}
return Response.ok().entity(objectMapper.writeValueAsString(
results.subList(0, Math.min(results.size(), limit))))
.build();
}

@GET
@Path("/follows")
public Response recommendFollows(@PathParam("username") final String username,
@QueryParam("limit") @DefaultValue("25") final Integer limit,
@Context GraphDatabaseService db) throws IOException {
ArrayList<Map<String, Object>> results = new ArrayList<>();
try (Transaction tx = db.beginTx()) {
Node user = findUser(username, db);
HashSet<Node> following = new HashSet<>();
for (Relationship r1: user.getRelationships(Direction.OUTGOING, RelationshipTypes.FOLLOWS)) {
following.add(r1.getEndNode());
}

HashMap<Node, LongAdder> fofs = new HashMap<>();

for (Node user2 : following) {
for (Relationship r1 : user2.getRelationships(Direction.OUTGOING, RelationshipTypes.FOLLOWS)) {
Node fof = r1.getEndNode();
if(fofs.containsKey(fof)) {
fofs.get(fof).increment();
} else {
LongAdder counter = new LongAdder();
counter.increment();
fofs.put(fof, counter);
}
}
}
fofs.remove(user);
ArrayList<Map.Entry<Node, LongAdder>> fofList = new ArrayList<>(fofs.entrySet());
fofList.sort(Comparator.comparing(m -> (Long) m.getValue().longValue(), reverseOrder()));
for (Map.Entry<Node, LongAdder> entry : fofList.subList(0, Math.min(fofList.size(), limit))) {
Map<String, Object> properties = entry.getKey().getAllProperties();
properties.remove(PASSWORD);
properties.remove(EMAIL);
results.add(properties);
}

tx.success();
}
return Response.ok().entity(objectMapper.writeValueAsString(results)).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.maxdemarzi.recommendations;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.neo4j.harness.junit.Neo4jRule;
import org.neo4j.test.server.HTTP;

import java.util.ArrayList;
import java.util.HashMap;

public class RecommendFollowsTest {
@Rule
public Neo4jRule neo4j = new Neo4jRule()
.withFixture(FIXTURE)
.withExtension("/v1", Recommendations.class);

@Test
public void shouldRecommendFollows() {
HTTP.POST(neo4j.httpURI().resolve("/v1/schema/create").toString());

HTTP.Response response = HTTP.GET(neo4j.httpURI().resolve("/v1/users/maxdemarzi/recommendations/follows").toString());
ArrayList<HashMap> actual = response.content();
Assert.assertEquals(expected, actual);
}
private static final String FIXTURE =
"CREATE (max:User {username:'maxdemarzi', " +
"email: 'max@neo4j.com', " +
"name: 'Max De Marzi'," +
"password: 'swordfish'})" +
"CREATE (jexp:User {username:'jexp', " +
"email: 'michael@neo4j.com', " +
"name: 'Michael Hunger'," +
"password: 'tunafish'})" +
"CREATE (laeg:User {username:'laexample', " +
"email: 'luke@neo4j.com', " +
"name: 'Luke Gannon'," +
"password: 'cuddlefish'})" +
"CREATE (stefan:User {username:'darthvader42', " +
"email: 'stefan@neo4j.com', " +
"name: 'Stefan Armbruster'," +
"password: 'catfish'})" +
"CREATE (mark:User {username:'markhneedham', " +
"email: 'mark@neo4j.com', " +
"name: 'Mark Needham'," +
"password: 'jellyfish'})" +

"CREATE (max)-[:FOLLOWS]->(laeg)" +
"CREATE (max)-[:FOLLOWS]->(jexp)" +
"CREATE (laeg)-[:FOLLOWS]->(stefan)" +
"CREATE (laeg)-[:FOLLOWS]->(mark)" +
"CREATE (jexp)-[:FOLLOWS]->(mark)";

private static final ArrayList<HashMap<String, Object>> expected = new ArrayList<HashMap<String, Object>>() {{
add(new HashMap<String, Object>() {{
put("username", "markhneedham");
put("name", "Mark Needham");
}});
add(new HashMap<String, Object>() {{
put("username", "darthvader42");
put("name", "Stefan Armbruster");
}});

}};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.maxdemarzi.recommendations;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.neo4j.harness.junit.Neo4jRule;
import org.neo4j.test.server.HTTP;

import java.util.ArrayList;
import java.util.HashMap;

public class RecommendFriendsTest {
@Rule
public Neo4jRule neo4j = new Neo4jRule()
.withFixture(FIXTURE)
.withExtension("/v1", Recommendations.class);

@Test
public void shouldRecommendFriends() {
HTTP.POST(neo4j.httpURI().resolve("/v1/schema/create").toString());

HTTP.Response response = HTTP.GET(neo4j.httpURI().resolve("/v1/users/maxdemarzi/recommendations/friends").toString());
ArrayList<HashMap> actual = response.content();
Assert.assertEquals(expected, actual);
}
private static final String FIXTURE =
"CREATE (max:User {username:'maxdemarzi', " +
"email: 'max@neo4j.com', " +
"name: 'Max De Marzi'," +
"password: 'swordfish'})" +
"CREATE (jexp:User {username:'jexp', " +
"email: 'michael@neo4j.com', " +
"name: 'Michael Hunger'," +
"password: 'tunafish'})" +
"CREATE (laeg:User {username:'laexample', " +
"email: 'luke@neo4j.com', " +
"name: 'Luke Gannon'," +
"password: 'cuddlefish'})" +
"CREATE (max)-[:FOLLOWS]->(laeg)" +
"CREATE (max)<-[:FOLLOWS]-(jexp)";

private static final ArrayList<HashMap<String, Object>> expected = new ArrayList<HashMap<String, Object>>() {{
add(new HashMap<String, Object>() {{
put("username", "jexp");
put("name", "Michael Hunger");
}});
}};
}

0 comments on commit d77ce48

Please sign in to comment.