Resotrack: Exploring the Resonate API with Django-Neomodel on Aura


Resotrack (demo, code) uses Neo4j to suggest popular, relevant tracks from Resonate’s music catalog

Resonate is a relatively young, (almost all) open source music streaming platform with a catalog featuring mostly but not exclusively ambient and electronic music, as categorized by Tags at the album level.

Photo by Lee Campbell on Unsplash

While many users enjoy exploring in random mode, listeners interested in learning more about the seemingly endless subgenres within Resonate would benefit from having a simple recommendation tool to find the most popular tracks for each tag.

In an attempt to provide a solution to this discovery issue, Resotrack (demo, code) uses Neo4j to suggest popular, relevant tracks for each tag in Resonate’s music catalog.

Resonate – the ethical music streaming co-op

Resotrack also happens to be built using Django-Neomodel and deployed on Aura Free and Heroku. This example will present a brief overview of the project and provide simple examples of apoc.periodic.commit and importing JSON Data from a REST API into Neo4j using apoc.load.json.

The Resotrack Data Model

Resotrack’s data model
Resotrack’s base data model

While the creators of the tracks can add their tracks to a variety of types of TrackGroups (lp, eps, etc.) and add Tags to these TrackGroups, non-artists can add tracks to TrackGroups of type playlist, and have limited (or no) tagging capabilities.

Resotrack data model, with tags
Resotrack’s data model, with tags

As Resotrack users search and interact with the data, the system calculates a “Top Track” for each tag and presents the “Top Track” for a particular tag to the user.

Using Resotrack

In the example below, the user has searched for techno and is presented with the top track for tags containing the string techno. In the image below, since ambienttechno has techno as a substring, we can see that Daibutsu is the TOP_TRACK for the Tag ambientechno.

Resotrack UI
Resotrack’s Frontend

In the Django admin, the superuser can view the project’s R(esonate) Users, Tags, Track groups, and Tracks.

Resotrack’s Admin

Connecting the Django-Neomodel App to Aura Free

If you haven’t already, clone the repo to your local machine:

git clone git@github.com:whatSocks/reso-tag-charts.git

GitHub – whatSocks/reso-tag-charts: Example Movie app for Neo4j and Django

Create or log in to your Aura account

Neo4j Aura – Fully Managed Cloud Solution

Tap Create a database then select Aura Free.

Follow the prompts to create the database and take note of the username (neo4j) and password.

Log in to the neo4j browser and verify the database exists with the “Open” button.

In your terminal, navigate to the project’s root directory and create the database environment variable:

export NEO4J_BOLT_URL=neo4j+s://neo4j:password@host-or-ip:port

Run migrations and create your superuser (for the admin, this is using an SQLite database)

./manage.py migrate
./manage.py createsuperuser

Run the server:

python manage.py runserver

Now you should be able to access https://localhost:8000 and view the empty app.

Load the Data

An empty database is no fun. We can use apoc.load.json (Strava example) and apoc.periodic.commit to load the data from the Resonate API.

Importing JSON Data from a REST API into Neo4j – Developer Guides

You can easily create a Listener account on Resonate and create your own playlists before you start. While not required, accounts are free and are helpful in understanding the lay of the data.

Create Constraints

CREATE CONSTRAINT ON (a:Ruser) ASSERT a.uuid IS UNIQUE;
CREATE CONSTRAINT ON (a:TrackGroup) ASSERT a.uuid IS UNIQUE;
CREATE CONSTRAINT ON (a:Track) ASSERT a.uuid IS UNIQUE;

Add the first page of Playlists (a type of TrackGroup)

WITH 'https://api.resonate.coop/v2/' AS uri
CALL apoc.load.json(uri + ‘trackgroups?type=playlist’) // in this example, grabbing listener-generated playlists
YIELD value
UNWIND value[“data”] as data
MERGE (u:RUser {uuid:toString(data['user']['id'])})
MERGE (t:TrackGroup {uuid:toString(data['id'])})
MERGE (u)-[:OWNS]->(t)
SET t.title = data['title']
SET t.type = data['type']
SET t.slug = data['slug']
SET t.tracks_imported = false

Add more TrackGroups

WITH 'https://api.resonate.coop/v2/' AS uri
CALL apoc.load.json(uri + ‘trackgroups’) // in this example, grabbing listener-generated playlists
YIELD value
UNWIND value[“data”] as data
MERGE (u:RUser {uuid:toString(data['user']['id'])})
MERGE (t:TrackGroup {uuid:toString(data['id'])})
MERGE (u)-[:OWNS]->(t)
SET t.title = data['title']
SET t.type = data['type”]
SET t.slug = data['slug]
SET t.tracks_imported = false

Add Tracks

CALL apoc.periodic.commit(
"MATCH (tg:TrackGroup)
WHERE NOT tg.tracks_imported
SET tg.tracks_imported = true
WITH tg limit $limit
WITH 'https://api.resonate.coop/v2/' AS uri, tg.uuid as tg_id
CALL apoc.load.json(uri + 'trackgroups/' + tg_id )
YIELD value
UNWIND value['data']['items'] as items
MERGE (u:RUser {uuid:toString(items['track']['creator_id'])})
MERGE (track:Track {uuid:toString(items['track']['id'])})
MERGE (t)-[:HAS_TRACK]->(track)
MERGE (track)<-[:CREATED]-(u)
SET track.title = items['track']['title']
SET track.tags_imported = false
RETURN count(*)",

{limit:10});

Add Tags

CALL apoc.periodic.commit(
"
MATCH (u:RUser)-[:CREATED]->(track:Track)
WHERE not u.uuid in ['7212','4315',’4414'] // bad data
AND NOT track.tags_imported
SET track.tags_imported = true
WITH u as artist, u.uuid as user_id, count(DISTINCT track) as tracks,'https://api.resonate.coop/v2/' as uri
ORDER BY tracks desc
LIMIT $limit
CALL apoc.load.json(uri + 'artists/' + user_id + '/releases') // grabbing all
YIELD value
UNWIND value['data'] as data
UNWIND data['tags'] as tags
MERGE (t:TrackGroup {uuid:toString(data['id'])})
MERGE (user:RUser {uuid:toString(user_id)})-[:OWNS]->(t)
MERGE (tag:Tag {name:toLower(tags)})
MERGE (tag)<-[:HAS_TAG]-(t)
SET tag.uuid=apoc.create.uuid()
SET t.title = data['title']
SET t.type = data['type']
RETURN count(*)
",
{limit:10});

Now you should be able to explore the data locally, with your data safe and sound in Aura.

Learn More


Resotrack: Exploring the Resonate API with Django-Neomodel on Aura was originally published in Neo4j Developer Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.