Skip to content

Commit

Permalink
adding mentions
Browse files Browse the repository at this point in the history
  • Loading branch information
maxdemarzi committed Apr 2, 2017
1 parent 2325cfd commit c597f5e
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ A Neo4j Based Twitter Clone Backend
:POST /v1/users/{username}/posts/{username2}/{time}
:GET /v1/users/{username}/likes
:POST /v1/users/{username}/likes/{username2}/{time}
:GET /v1/users/{username}/mentions
:GET /v1/users/{username}/timeline
:GET /v1/users/{username}/recommendations/friends
:GET /v1/users/{username}/recommendations/follows
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/maxdemarzi/likes/Likes.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public Response getLikes(@PathParam("username") final String username,
properties.put(LIKEDTIME, time);
properties.put(USERNAME, author.getProperty(USERNAME));
properties.put(NAME, author.getProperty(NAME));
properties.put(HASH, author.getProperty(HASH));
properties.put(LIKES, post.getDegree(RelationshipTypes.LIKES));
properties.put(REPOSTS, post.getDegree() - 1 - post.getDegree(RelationshipTypes.LIKES));

Expand Down
98 changes: 98 additions & 0 deletions src/main/java/com/maxdemarzi/mentions/Mentions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.maxdemarzi.mentions;

import com.maxdemarzi.Labels;
import com.maxdemarzi.RelationshipTypes;
import com.maxdemarzi.users.Users;
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.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.maxdemarzi.Properties.*;
import static com.maxdemarzi.Time.*;
import static com.maxdemarzi.posts.Posts.getAuthor;
import static java.util.Collections.reverseOrder;

@Path("/users/{username}/mentions")
public class Mentions {

private static final Pattern mentionsPattern = Pattern.compile("@(\\S+)");

private static final ObjectMapper objectMapper = new ObjectMapper();

@GET
public Response getMentions(@PathParam("username") final String username,
@QueryParam("limit") @DefaultValue("25") final Integer limit,
@QueryParam("since") final Long since,
@Context GraphDatabaseService db) throws IOException {
ArrayList<Map<String, Object>> results = new ArrayList<>();
LocalDateTime dateTime;
if (since == null) {
dateTime = LocalDateTime.now(utc);
} else {
dateTime = LocalDateTime.ofEpochSecond(since, 0, ZoneOffset.UTC);
}
Long latest = dateTime.toEpochSecond(ZoneOffset.UTC);

try (Transaction tx = db.beginTx()) {
Node user = Users.findUser(username, db);
int count = 0;
while (count < limit && (dateTime.isAfter(earliest))) {
RelationshipType relType = RelationshipType.withName("MENTIONED_ON_" +
dateTime.format(dateFormatter));

for (Relationship r1 : user.getRelationships(Direction.INCOMING, relType)) {
Node post = r1.getStartNode();
Map<String, Object> result = post.getAllProperties();
Long time = (Long)r1.getProperty("time");
if(time < latest) {
Node author = getAuthor(post, time);
result.put(TIME, time);
result.put(USERNAME, author.getProperty(USERNAME));
result.put(NAME, author.getProperty(NAME));
result.put(HASH, author.getProperty(HASH));
result.put(LIKES, post.getDegree(RelationshipTypes.LIKES));
result.put(REPOSTS, post.getDegree(Direction.INCOMING)
- 1 // for the Posted Relationship Type
- post.getDegree(RelationshipTypes.LIKES)
- post.getDegree(RelationshipTypes.REPLIED_TO));

results.add(result);
count++;
}
}
dateTime = dateTime.minusDays(1);
}
tx.success();
}

results.sort(Comparator.comparing(m -> (Long) m.get(TIME), reverseOrder()));

return Response.ok().entity(objectMapper.writeValueAsString(results)).build();
}

public static void createMentions(Node post, HashMap<String, Object> input, LocalDateTime dateTime, GraphDatabaseService db) {
Matcher mat = mentionsPattern.matcher(((String)input.get("status")).toLowerCase());
while (mat.find()) {
String username = mat.group(1);
Node user = db.findNode(Labels.User, USERNAME, username);
if (user != null) {
Relationship r1 = post.createRelationshipTo(user, RelationshipType.withName("MENTIONED_ON_" +
dateTime.format(dateFormatter)));
r1.setProperty(TIME, dateTime.toEpochSecond(ZoneOffset.UTC));
}
}
}
}
6 changes: 3 additions & 3 deletions src/main/java/com/maxdemarzi/posts/Posts.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.maxdemarzi.Labels;
import com.maxdemarzi.RelationshipTypes;
import com.maxdemarzi.mentions.Mentions;
import com.maxdemarzi.tags.Tags;
import com.maxdemarzi.users.Users;
import org.codehaus.jackson.map.ObjectMapper;
Expand All @@ -20,9 +21,7 @@
import java.util.Map;

import static com.maxdemarzi.Properties.*;
import static com.maxdemarzi.Time.dateFormatter;
import static com.maxdemarzi.Time.earliest;
import static com.maxdemarzi.Time.utc;
import static com.maxdemarzi.Time.*;
import static com.maxdemarzi.users.Users.getPost;
import static java.util.Collections.reverseOrder;

Expand Down Expand Up @@ -111,6 +110,7 @@ private Node createPost(@Context GraphDatabaseService db, HashMap input, Node us
dateTime.format(dateFormatter)));
r1.setProperty(TIME, dateTime.toEpochSecond(ZoneOffset.UTC));
Tags.createTags(post, input, dateTime, db);
Mentions.createMentions(post, input, dateTime, db);
return post;
}

Expand Down
5 changes: 5 additions & 0 deletions src/test/java/com/maxdemarzi/likes/GetLikesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,17 @@ public void shouldGetLikesSince() {
"CREATE (max:User {username:'maxdemarzi', " +
"email: 'max@neo4j.com', " +
"name: 'Max De Marzi'," +
"hash: '0bd90aeb51d5982062f4f303a62df935'," +
"password: 'swordfish'})" +
"CREATE (jexp:User {username:'jexp', " +
"email: 'michael@neo4j.com', " +
"hash: '0bd90aeb51d5982062f4f303a62df935'," +
"name: 'Michael Hunger'," +
"password: 'tunafish'})" +
"CREATE (laeg:User {username:'laexample', " +
"email: 'luke@neo4j.com', " +
"name: 'Luke Gannon'," +
"hash: '0bd90aeb51d5982062f4f303a62df935'," +
"password: 'cuddlefish'})" +
"CREATE (post1:Post {status:'Hello World!', " +
"time: 1490140299})" +
Expand All @@ -71,6 +74,7 @@ public void shouldGetLikesSince() {
add(new HashMap<String, Object>() {{
put("username", "laexample");
put("name", "Luke Gannon");
put("hash", "0bd90aeb51d5982062f4f303a62df935");
put("status", "How are you!");
put("time", 1490208700);
put("liked_time", 1490209400);
Expand All @@ -80,6 +84,7 @@ public void shouldGetLikesSince() {
add(new HashMap<String, Object>() {{
put("username", "jexp");
put("name", "Michael Hunger");
put("hash", "0bd90aeb51d5982062f4f303a62df935");
put("status", "Hello World!");
put("time", 1490140299);
put("liked_time", 1490209300);
Expand Down
73 changes: 73 additions & 0 deletions src/test/java/com/maxdemarzi/mentions/CreateMentionsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.maxdemarzi.mentions;

import com.maxdemarzi.posts.Posts;
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;

import static com.maxdemarzi.Properties.*;
import static java.lang.Thread.sleep;

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

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

HTTP.Response response = HTTP.POST(neo4j.httpURI().resolve("/v1/users/maxdemarzi/posts").toString(), input);
HashMap actual = response.content();
Assert.assertEquals(expected.get(STATUS), actual.get(STATUS));
Assert.assertTrue(actual.containsKey(TIME));
sleep(1000); // Needed due to artifact in testing
response = HTTP.GET(neo4j.httpURI().resolve("/v1/users/jexp/mentions").toString());
ArrayList<HashMap> actual2 = response.content();
expected2.get(0).put(TIME, actual2.get(0).get(TIME));
Assert.assertEquals(expected2, actual2);

Assert.assertEquals("maxdemarzi", actual2.get(0).get(USERNAME));
Assert.assertEquals("Max De Marzi", actual2.get(0).get(NAME));
Assert.assertEquals("Hello @jexp", actual2.get(0).get(STATUS));
}

private static final String FIXTURE =
"CREATE (max:User {username:'maxdemarzi', " +
"email: 'maxdemarzi@hotmail.com', " +
"name: 'Max De Marzi'," +
"hash: '0bd90aeb51d5982062f4f303a62df935'," +
"password: 'swordfish'})" +
"CREATE (jexp:User {username:'jexp', " +
"email: 'michael@neo4j.com', " +
"name: 'Michael Hunger'," +
"hash: '0bd90aeb51d5982062f4f303a62df935'," +
"password: 'tunafish'})";

private static final HashMap input = new HashMap<String, Object>() {{
put(STATUS, "Hello @jexp");
}};

private static final HashMap expected = new HashMap<String, Object>() {{
put(STATUS, "Hello @jexp");
}};

private static final ArrayList<HashMap<String, Object>> expected2 = new ArrayList<HashMap<String, Object>>() {
{
add(new HashMap<String, Object>() {{
put("username", "maxdemarzi");
put("name", "Max De Marzi");
put("hash", "0bd90aeb51d5982062f4f303a62df935");
put("status", "Hello @jexp");
put("likes", 0);
put("reposts", 0);
}});
}};
}
94 changes: 94 additions & 0 deletions src/test/java/com/maxdemarzi/mentions/GetMentionsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.maxdemarzi.mentions;

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 GetMentionsTest {
@Rule
public Neo4jRule neo4j = new Neo4jRule()
.withFixture(FIXTURE)
.withExtension("/v1", Mentions.class);

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

HTTP.Response response = HTTP.GET(neo4j.httpURI().resolve("/v1/users/jexp/mentions").toString());
ArrayList<HashMap> actual = response.content();
Assert.assertEquals(expected, actual);
}

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

HTTP.Response response = HTTP.GET(neo4j.httpURI().resolve("/v1/users/jexp/mentions?limit=1").toString());
ArrayList<HashMap> actual = response.content();
Assert.assertTrue(actual.size() == 1);
Assert.assertEquals(expected.get(0), actual.get(0));
}

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

HTTP.Response response = HTTP.GET(neo4j.httpURI().resolve("/v1/users/jexp/mentions?since=1490208600").toString());
ArrayList<HashMap> actual = response.content();
Assert.assertTrue(actual.size() == 1);
Assert.assertEquals(expected.get(1), actual.get(0));
}

private static final String FIXTURE =
"CREATE (max:User {username:'maxdemarzi', " +
"email: 'max@neo4j.com', " +
"name: 'Max De Marzi'," +
"hash: '0bd90aeb51d5982062f4f303a62df935'," +
"password: 'swordfish'})" +
"CREATE (jexp:User {username:'jexp', " +
"email: 'michael@neo4j.com', " +
"name: 'Michael Hunger'," +
"hash: '0bd90aeb51d5982062f4f303a62df935'," +
"password: 'tunafish'})" +
"CREATE (laeg:User {username:'laexample', " +
"email: 'luke@neo4j.com', " +
"name: 'Luke Gannon'," +
"hash: '0bd90aeb51d5982062f4f303a62df935'," +
"password: 'cuddlefish'})" +
"CREATE (post1:Post {status:'Hello @jexp', " +
"time: 1490140299})" +
"CREATE (post2:Post {status:'Hi @jexp', " +
"time: 1490208700})" +
"CREATE (max)-[:POSTED_ON_2017_03_21 {time: 1490140299}]->(post1)" +
"CREATE (laeg)-[:POSTED_ON_2017_03_22 {time: 1490208700}]->(post2)" +
"CREATE(post1)-[:MENTIONED_ON_2017_03_21 {time: 1490140299}]->(jexp)" +
"CREATE(post2)-[:MENTIONED_ON_2017_03_22 {time: 1490208700}]->(jexp)" +
"CREATE (laeg)-[:REPOSTED_ON_2017_03_22 {time: 1490208800}]->(post1)" +
"CREATE (max)-[:LIKES {time: 1490208800 }]->(post2)";

private static final ArrayList<HashMap<String, Object>> expected = new ArrayList<HashMap<String, Object>>() {{
add(new HashMap<String, Object>() {{
put("username", "laexample");
put("name", "Luke Gannon");
put("hash", "0bd90aeb51d5982062f4f303a62df935");
put("status", "Hi @jexp");
put("time", 1490208700);
put("likes", 1);
put("reposts", 0);
}});
add(new HashMap<String, Object>() {{
put("username", "maxdemarzi");
put("name", "Max De Marzi");
put("hash", "0bd90aeb51d5982062f4f303a62df935");
put("status", "Hello @jexp");
put("time", 1490140299);
put("likes", 0);
put("reposts", 1);
}});
}};
}

0 comments on commit c597f5e

Please sign in to comment.