Person Profile

If you click on a Person card anywhere on the website, you will be taken to a Person profile. This API call is the same regardless of whether the person is an actor, director, or both.

We have already implemented the methods that populate the endpoints in the MovieService to get a list of movies that the person has either acted in or directed.

But if you take a look at the PeopleService, you will see two methods that need to be implemented to complete this page:

FindByIdAsync()

The FindByIdAsync() method is currently hardcoded to return data by id from the people.json list.

c#
Neoflix/Services/PeopleService.cs
public async Task<Dictionary<string, object>> FindByIdAsync(string id)
{
    // TODO: Find a user by their ID
    return await Task.FromResult(Fixtures.Pacino);
}

You will update this method to run the following cypher statement in a read transaction.

cypher
Get Person information
MATCH (p:Person {tmdbId: $id})
RETURN p {
  .*,
  actedCount: count { (p)-[:ACTED_IN]->() },
  directedCount: count { (p)-[:DIRECTED]->() }
} AS person

The query will return the properties for the person with the corresponding tmdbId, along with a count of the number of movies that the person has acted in and directed.

Your Task

  • Modify the FindByIdAsync() method to query Neo4j and return details for the requested person.

  • The returned object should include counts of the number of movies that the person has acted in or directed.

  • If not exactly one record is returned, the method will throw a NotFoundError by using the result.single() API call.

  • Remember to auto-close the session and use the ToListAsync() function to convert the object into native C# types.

Click here to reveal the final FindByIdAsync() method.
c#
public async Task<Dictionary<string, object>> FindByIdAsync(string id)
{
    await using var session = _driver.AsyncSession();

    return await session.ExecuteReadAsync(async tx =>
    {
        var query = @"
            MATCH (p:Person {tmdbId: $id})
            RETURN p {
                .*,
                actedCount: size((p)-[:ACTED_IN]->()),
                directedCount: size((p)-[:DIRECTED]->())
            } AS person";

        var cursor = await tx.RunAsync(query, new {id});

        if (!await cursor.FetchAsync())
        {
            throw new NotFoundException($"No person could be found with tmdbId: {id}");
        }

        return cursor.Current["person"].As<Dictionary<string, object>>();
    });
}

GetSimilarPeopleAsync()

The GetSimilarPeopleAsync() method should return a paginated list of similar people based on their second degree connections - either people who have acted in or have directed the same movies as our person.

c#
Neoflix/Services/PeopleService.cs
public async Task<Dictionary<string, object>[]> GetSimilarPeopleAsync(string id, int limit = 6, int skip = 0)
{
    // TODO: Get a list of similar people to the person by their id
    return await Task.FromResult(Fixtures.People.Skip(skip).Take(limit).ToArray());
}

There could be a more clever algorithm for finding similar people by weighting the type of relationship differently, but for now, the following query will find a list of similar people based on the number of relationships in common.

cypher
Get Similar People
MATCH (:Person {tmdbId: $id})-[:ACTED_IN|DIRECTED]->(m)<-[r:ACTED_IN|DIRECTED]-(p)
WITH p, collect(m {.tmdbId, .title, type: type(r)}) AS inCommon
RETURN p {
  .*,
  actedCount: count { (p)-[:ACTED_IN]->() },
  directedCount: count {(p)-[:DIRECTED]->() },
  inCommon: inCommon
} AS person
ORDER BY size(person.inCommon) DESC
SKIP 0
LIMIT 6

Parameters in Neo4j Browser

To run this query in Neo4j Sandbox, you will have to define a parameter in your Neo4j Browser session. To do so, you can use the :param command.

For example, to view Kevin Bacon, you can set the id parameter to "4724":

cypher
:param id: "4724"

Your Task

  • Modify the GetSimilarPeopleAsync() method to query Neo4j and return a list of similar people.

  • The returned objects should include a list of actors and

  • An inCommon property to show how the two people are related (a list of the movies they in common, with their tmbdId, title, and type of relationship).

  • Here we order by the number of entries that two people have in common

  • The pagination information is passed as parameters

  • Remember to auto-close the session and use the ToListAsync() function within a list() function on Result to convert the values to native C# types.

Click here to reveal the final GetSimilarPeopleAsync() method.
c#
public async Task<Dictionary<string, object>[]> GetSimilarPeopleAsync(string id, int limit = 6, int skip = 0)
{
    await using var session = _driver.AsyncSession();

    return await session.ExecuteReadAsync(async tx =>
    {
        var query = @"                
            MATCH (:Person {tmdbId: $id})-[:ACTED_IN|DIRECTED]->(m)<-[r:ACTED_IN|DIRECTED]-(p)
            RETURN p {
                .*,
                actedCount: size((p)-[:ACTED_IN]->()),
                directedCount: size((p)-[:DIRECTED]->()),
                inCommon: collect(m {.tmdbId, .title, type: type(r)})
            } AS person
            ORDER BY size(person.inCommon) DESC
            SKIP $skip
            LIMIT $limit";

        var cursor = await tx.RunAsync(query, new { id, skip, limit });
        var records = await cursor.ToListAsync();
        return records
            .Select(x => x["person"].As<Dictionary<string, object>>())
            .ToArray();
    });
}

Testing

To test that this functionality has been correctly implemented, run the following code in a new terminal session:

sh
Running the test
dotnet test --logger "console;verbosity=detailed" --filter "Neoflix.Challenges._15_PersonProfile"

The test file is located at Neoflix.Challenges/15-PersonProfile.cs.

Are you stuck? Click here for help

If you get stuck, you can see a working solution by checking out the 15-person-profile branch by running:

sh
Check out the 15-person-profile branch
git checkout 15-person-profile

You may have to commit or stash your changes before checking out this branch. You can also click here to expand the Support pane.

Verifying the Test

According to our algorithm, who is the most similar person to Francis Ford Coppola?

As part of the test suite, the final test will log the name of the most common person to Francis Ford Coppola according to our Cypher statement.

Paste the name of the person the box below without quotes or whitespace and click Check Answer.

  • ✓ Frederic Forrest

Hint

You can also find the answer by running the following Cypher statement:

cypher
MATCH (:Person {tmdbId: '1776'})-[:ACTED_IN|DIRECTED]->(m)<-[r:ACTED_IN|DIRECTED]-(p)
WITH p, collect(m) AS inCommon
RETURN p.name
ORDER BY size(inCommon) DESC
LIMIT 1

Copy the answer without any double quotes or whitespace.

Lesson Summary

In this Challenge, you have implemented the final methods in the PeopleService.