Now that our users can high five and low five each other, we want to show the other person those high fives and low fives. Well…do we really want to show the low fives? I’m not sure. A few years ago we talked about how to store the people who “swiped left” on a user (aka the “asholes” of Tinder). In this case, the user is not rejecting a person forever, they are just putting down one of their posts. If it’s two people who are competing for dates, then maybe the low five has a negative intent, but it would make the person who wrote the post feel they are doing something right. If the low five was from a potential mate, it could be a case of “negging” ( which is stupid and you should never do that to people), it could be in jest if it was from someone they already had a conversation with, it could just have negative intent or maybe a clumsy tap on the wrong button. We don’t really know.
How would you feel if someone high fived one of your posts, and low fived another? What about someone giving you a low five every day? I guess we have the blocking capability to deal with abusive behavior. Would people block anyone who gives them a low five? I don’t know… I can’t know until we have users on the site and analyze their behavior. Let’s opt to be permissive for now, except I don’t think we want to show fives from blocked users, but we will still count them. We start off our method with a few parameters:
@GET public Response getFives(@PathParam("username") final String username, @QueryParam("limit") @DefaultValue("25") final Integer limit, @QueryParam("since") final String since, @Context GraphDatabaseService db) throws IOException { ArrayList<Map<String, Object>> results = new ArrayList<>(); ZonedDateTime latest; if (since == null) { latest = ZonedDateTime.now(utc); } else { latest = ZonedDateTime.parse(since); }
Next we find the user, get their properties and find out who they have blocked.
Node user = Users.findUser(username, db); Map userProperties = user.getAllProperties(); HashSet<Node> blocked = new HashSet<>(); for (Relationship r1 : user.getRelationships(Direction.OUTGOING, RelationshipTypes.BLOCKS)) { blocked.add(r1.getEndNode()); }
We want to show them only the high fives they received in the last 5 days because those are the only ones they will be able to use to send messages. ZonedDateTime make this easy to get by getting now, then the beginning of the day and then subtracting 5 days.
ZoneId zoneId = ZoneId.of((String) user.getProperty(TIMEZONE)); ZonedDateTime startOfFiveDays = ZonedDateTime.now(zoneId).with(LocalTime.MIN).minusDays(5);
A post older than 5 days could have generated a new high five, so we have to check them all. This means that the query will get slower as people post things… well the one individual user we are looking at posts things. That’s part of the secret to Neo4j, it doesn’t matter how many people are in our database, it doesn’t matter how many total posts they all have, all that matters to the performance of our query is how many posts this one user has posted. I’d estimate even the most narcissistic people who post pictures of themselves non-stop would only ever post a few thousand pictures. Since we can traverse 2-4 Million relationships per second per core, it won’t be a problem.
ArrayList<RelationshipType> types = new ArrayList<>(); for (RelationshipType t : user.getRelationshipTypes()) { if (t.name().startsWith("POSTED_ON")) { types.add(t); } }
We will traverse all the “dated” relationship types that start with POSTED_ON, get the post, check to see if it has any incoming HIGH_FIVED relationships that are within our window and if so add them to our result set.
for (Relationship r1 : user.getRelationships(types.toArray(new RelationshipType[0]))) { Node post = r1.getEndNode(); Map<String, Object> result = post.getAllProperties(); for (Relationship r : post.getRelationships(RelationshipTypes.HIGH_FIVED, Direction.INCOMING)) { ZonedDateTime when = (ZonedDateTime) r.getProperty(TIME); if (when.isAfter(startOfFiveDays) && when.isBefore(latest)) { // Add post to results
But we aren’t just returning the posts, we are returning who high fived our post and when they did that. So we need to add bare minimum user data and the time the high five relationship was created to our post.
Node user2 = r.getStartNode(); if (!blocked.contains(user2)) { Map<String, Object> user2Properties = user2.getAllProperties(); result.put(ID, post.getId()); result.put(TIME, when); result.put(USERNAME, username); result.put(USERNAME2, user2Properties.get(USERNAME)); result.put(NAME, userProperties.get(NAME)); result.put(NAME2, user2Properties.get(NAME)); result.put(HASH, userProperties.get(HASH)); result.put(HASH2, user2Properties.get(HASH));
Before we return we need to sort our results by when they were given those high fives and make sure we don’t go over our limit.
results.sort(timedComparator); return Response.ok().entity(objectMapper.writeValueAsString( results.subList(0, Math.min(results.size(), limit)))) .build();
Now to integrate this into the front end, we need to add it to our API:
@GET("users/{username}/high_fives") Call<List<Post>> getHighFives(@Path("username") String username);
But our Post model will need a few more properties to capture the additional information we sent. We are adding the name, username and hash of the user who high fived the post:
@Data public class Post { private Long id; private String status; private String name; ... private String name2; private String username2; private String hash2;
With that in there, we can call our api to get the results, but we will need some new views to show off this information.
get("/high_fives", req -> { CommonProfile profile = require(CommonProfile.class); String username = profile.getUsername(); User authenticated = App.getUserProfile(username); Response<List<Post>> response = App.api.getHighFives(username).execute(); List<Post> posts; if (response.isSuccessful()) { posts = response.body(); } else { throw new Err(Status.BAD_REQUEST); } return views.fives.template(authenticated, posts, "high"); });
The fives template is very similar to our home template, but each individual post has to be shown with the person who high fived it. The view is modeled after the notifications tab on Twitter except we don’t really want to bundle users together. Our user needs to know when the high five will expire, so they will be prompted into action and message the user who gave them the high five. This is what it looks like:
The low five functionality is identical, so I won’t show it here but you can see it on the source code.
You can see that we have a button for messaging and another for blocking just in case. The blocking one works just like the block on the timeline, but we don’t have the functionality for messaging yet. We should probably work on that next.