GSoC 2022: Personal recommendation of a track

Personal Information

Name: Shatabarto Bhattacharya

IRC nick: riksucks

Email: rik61072@gmail.com

Github: hrik2001

Time Zone: UTC+05:30

Send a track to another user as a personal recommendation

Project Overview

ListenBrainz is an amazing platform to interact with people based on music preference. Which brings the question, wouldn’t it be wonderful if one could recommend music to a few select followers, since all of your followers might not have the same taste. This project tries to do exactly that, a system of features that will enable a user to recommend a recording to only a few select users.

My PRs: Check Out

My Commits: Check Out

(Note: This project idea in the ideas list only suggest developing a frontend, but it was my desire to code both the backend and frontend as a long project, please refer to the following conversation for reference)

Features

  • A new event type personal_recording_recommendation
  • An autocomplete field to search users who follow you, to suggest a recording.
  • Recommend to multiple users.
  • Drag and drop a ListenCard component on users following you, to instantly suggest the person the song that was in the card.
  • Send personalized note along with personal recommendation
  • Receive such recommendations at your feed.

Implementation

Database

A new event type would be created to handle the requirements of this project. I propose creating a personal_recording_recommendation event. To realize this

  1. I will modify admin/sql/create_types.sql file, and add the event
CREATE TYPE user_timeline_event_type_enum AS ENUM('recording_recommendation', 'notification', 'personal_recording_recommendation');
  1. The metadata field for this event would have JSONB in the following format
{
    "track_name": "Natkhat",
    "artist_name": "Seedhe Maut", 
    "release_name": "न", 
    "recording_mbid": "681a8006-d912-4867-9077-ca29921f5f7f", 
    "recording_msid": "124a8006-d904-4823-9355-ca235235537e", 
    "recommender_id": 24,
    "blurb_content": "Try out these new people in Indian Hip-Hop!"
}    
  • “track_name” represents the name of the recording that is being recommended
  • “artist_name” represents the name of the artist who is involved with the recording
  • “release_name” represents the name of the release the recording is part of
  • “recording_mbid” represents the MusicBrainz identifier of the recording
  • “recording_msid” represents the MessyBrainz identifier of the recording
  • “recommender_id” represents the user row id of the recommender
  • “blurb_content” represents the personalized note

Backend

To accommodate the changes we made in the database, we have to do following changes

  1. Add the new event type to UserTimelineEventType enumeration at the backend
class UserTimelineEventType(Enum):
    RECORDING_RECOMMENDATION = 'recording_recommendation'
    FOLLOW = 'follow'
    LISTEN = 'listen'
    NOTIFICATION = 'notification'
    RECORDING_PIN = 'recording_pin'
    PERSONAL_RECORDING_RECOMMENDATION = 'personal_recording_recommendation'
  1. Create a pydantic data structure for the JSONB metadata for this event
class PersonalRecordingRecommendationMetadata(BaseModel):
    artist_name: constr(min_length=1)
    track_name: constr(min_length=1)
    release_name: Optional[str]
    recording_mbid: Optional[str]
    recording_msid: constr(min_length=1)
    recommender_id: NonNegativeInt
    blurb_content: Optional[str]

    _validate_uuids: classmethod = validator(
        "recording_mbid",
        "recording_msid",
        allow_reuse=True
    )(check_valid_uuid)
  1. Update the UserTimelineEventMetadata data type
UserTimelineEventMetadata = Union[RecordingRecommendationMetadata, NotificationMetadata, PersonalRecordingRecommendationMetadata]
  1. A function to help us to add Personal Recommendation events to the database

def create_personal_recommendation_event(user_id: int, metadata: PersonalRecordingRecommendationMetadata) -> UserTimelineEvent:
    """ Creates a personal recommendation event in the database and returns it.
    """
    return create_user_timeline_event(
        user_id=user_id,
        event_type=UserTimelineEventType.PERSONAL_RECORDING_RECOMMENDATION,
        metadata=metadata,
    )
  1. Now all that is needed is an endpoint that accepts the personal recommendation event from the user
@user_timeline_event_api_bp.route('/user/timeline-event/create/recommend-personal', methods=['POST', 'OPTIONS'])
@crossdomain
@ratelimit()
def create_personal_recording_recommendation_event(user_name):
    """ Make the user recommend a recording to their follower.

    The request should post the following data about the recording being recommended,
    and also the list of followers getting recommended:

    .. code-block:: json

        {
            "metadata": {
                "artist_name": "<The name of the artist, required>",
                "track_name": "<The name of the track, required>",
                "recording_msid": "<The MessyBrainz ID of the recording, required>",
                "release_name": "<The name of the release, optional>",
                "recording_mbid": "<The MusicBrainz ID of the recording, optional>",
                "followers": [<Row IDs (integer) of the follower, required>]
                "blurb_content": "<String containing personalized recommendation>"
            }
        }



    :statuscode 200: Successful query, recording has been recommended!
    :statuscode 400: Bad request, check ``response['error']`` for more details.
    :statuscode 401: Unauthorized, you do not have permissions to recommend personal recordings on the behalf of this user
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*
    """
    user = validate_auth_header()
    events = []

    try:
        data = ujson.loads(request.get_data())
    except ValueError as e:
        raise APIBadRequest(f"Invalid JSON: {str(e)}")

    metadata = data['metadata']
    try:
        metadata_db = {
                    "artist_name": metadata.artist_name,
                    "track_name": metadata.track_name,
                    "recording_msid": metadata.recording_msid,
                    "release_name": metadata.release_name,
                    "recording_mbid": metadata.recording_mbid,
                    "recommender_id": user["id"],
                    "blurb_content": metadata.blurb_content

            }
            metadata_db = PersonalRecordingRecommendationMetadata(**metadata_db)
    except pydantic.ValidationError as e:
        raise APIBadRequest(f"Invalid metadata: {str(e)}")

    try:
        for follower in metadata.followers:
            if not db_user_relationship.is_following_user(follower, user['id']):
                raise APIBadRequest(f"The person doesn't follow you")

            event =  db_user_timeline_event.create_personal_recommendation_event(follower, metadata_db)
            event_data = event.dict()
            event_data['created'] = event_data['created'].timestamp()
            event_data['event_type'] = event_data['event_type'].value
            events.append(event_data)

    except DatabaseException:
        raise APIInternalServerError("Something went wrong, please try again.")

    return jsonify(events)
  1. Now that the events can be stored in the backend, it can be served via the /user/<user_name>/feed/events API like this

{
    "created": 1641330946,
    "event_type": "personal_recording_recommendation",
    "id": 25,
    "metadata": {
        "track_name": "Natkhat",
        "artist_name": "Seedhe Maut", 
        "release_name": "न", 
        "recording_mbid": "681a8006-d912-4867-9077-ca29921f5f7f", 
        "recording_msid": "124a8006-d904-4823-9355-ca235235537e", 
        "recommender_id": 24,
        "blurb_content": "Try out these new people in Indian Hip-Hop!"
    }  
}

(Note: Code written for both Database and Backend sections are example code)

UI Mockup

When it comes to frontend, there would be two ways, one can personally recommend to users

  1. Via context menu
  2. Via Drag and Drop

Context Menu

  1. User will be able to access the option to “Personally Recommend” via the context menu of ListenCard component
    context

  2. Clicking that would lead to popping up of a modal window like the following

  3. To start searching and finding your followers, simply start typing. The frontend, when it loads the modal, will automatically call the backend to get a list of all the followers, to implement an autocomplete system. One can either click the autocompleted name, or simply use arrow keys and either press the add button, or press enter.

  4. Once name(s) are selected, they would look like this (along with a message)

Drag and Drop

In this method of personal recommendation, one has to drag and drop the ListenControl component and drop it on the name of the users who are following you. Since only listens and feed pages have the names of the users who are following you,this feature would only be implemented in those pages. Also, this method won’t have any option to send a personalized message

Now, let us look at how the person, who got recommended, might see all this

Personal Recommendation Event in User Feed

Timeline

Here is a thorough analysis of how I plan to spend time on this page for GSoC.

Pre-Community Bonding (April)

In this period I plan to discuss with monkey the future of ListenBrainz UI, so that the drag and drop feature can be coded in such a way so that very minor code changes need to be made when the new UI is rolled out. I will also discuss with monkey potential drag and drop libraries that he thinks we should be using. I plan to discuss with lucifer how to go about implementing emailing feature whenever one receives personal recommendation.

I also plan to write the backend for hiding events, this would be my first time creating a schema in the ListenBrainz database, ultimately preparing me for the database related work for this project. I would also work on the frontend of the hiding events feature during this time. If time permits, I would also go about implementing an edit feature for pins.

Community Bonding Period

I would go about working on pending PRs, finalizing nitty gritty details of the project with my mentors. I will be making sure that my mentors and I have the same execution plan in mind when going about coding this project.

Week 1

In this period, my mentors and I would discuss the database changes and I would implement them, along with the pydantic data structures. I would also start writing functions in python backend for things related to querying and inserting in database (in listenbrainz/db directory)

Week 2

In this period, I will write tests for the functions I had written in the first week. I will also start writing API endpoint to POST personal recommendations and modify user feed API to serve personal recommendations.

Week 3-4

In this period, I will write tests for the API endpoint I wrote and modified in the previous period. This period will also act as a buffer to catch up with tasks that might have not been completed.

Week 5-6

In this period, I will implement a ListenControl component for the context menu for personal recommendation. I would also implement the modal too, along with the autocomplete feature.

Week 7-8

In this period I will be writing tests for the previous period, and debug too if needed. I believe that the previous period would have a lot of back and forth between me and my mentor, hence, this period would also serve as a buffer, that is, if the previous period gets extended.

Week 9-10

This period would involve going about implementing the drag and drop feature. Before going about implementing this, I plan to discuss with my mentor about how to implement code for this while keeping the future ListenBrainz UI in mind. This discussion would be an extrapolation of things discussed during the pre-bonding period.

Week 11-12

Finish up any pending work, if there is any. Start writing documentation for the code that has been done so far. Discuss with mentors about final changes for final submission of code.

Stretch Goals

  • Implement emailing user if they get personally recommended
  • Implement a feature for editing already suggested personal recommendation
  • Implement hiding or deleting personal recommendation in user feed UI

Detailed Information About Yourself

I am Shatabarto Bhattacharya (I go by hrik2001 or riksucks or rik61072 online, feel free to call me Rik). I am a second year undergraduate who is pursuing a bachelor’s degree from UIET, Panjab University, in engineering, in the field of Information Technology.

I came across MetaBrainz, when me and my friends needed a software to manage books, and we came across BookBrainz. After browsing for a bit more, I found ListenBrainz, and I remember making a mental note that I could probably contribute there, since the stack and the idea sat right with me. Later on, I started contributing from September, since I wanted to get my hands dirty with real life projects. I kept contributing and really started liking the community of MetaBrainz.

I help run a club in our college that promotes Open Source and AI/ML research in my spare time. In my free time, you would probably find me reading tech articles on HackerNews or Reddit, or probably tinkering around with a new technology or trying to solve a problem with my friends, via coding.

Tell us about the computer(s) you have available for working on your SoC project!

I have got ASUS TUF, with 8 GB RAM, Ryzen 7 (4800H).

When did you first start programming?

I started programming when I was in 6th grade in C.

What type of music do you listen to?

I have a very diverse music taste. I may listen to Mac DeMarco one day, and then switch to Prabh Deep the next day.

What aspects of the project you’re applying for the interest you the most?

The fact that I can personally send recommendations to a few of my followers, who are close friends of mine, who have distinct tastes, makes me excited. There are some music that you might want to only share with your close friend circle, hence, ListenBrainz can serve as a platform agnostic music sharing platform that you can use with your friend circle. The possible uses make me excited.

Have you ever used MusicBrainz to tag your files?

Yes!

Have you contributed to other Open Source projects? If so, which projects and can we see some of your code?

ListenBrainz is the only Open Source Project where I have contributed to, as an Open Source Developer.

If you have not contributed to open source projects, do you have other code we can look at?

Sure, here are few projects that might interest you

!answer, macuninstaller, PulseProxy, Saheli, Schwifty

What sorts of programming projects have you done on your own time?

I have mostly done web development projects. I have recently started to learn about microservices and docker containers and am working on a personal project related to that. I have also worked with redis for storing location in real time and caching.

I have also done AI/ML projects, specially computer vision projects, for research purposes.

How much time do you have available, and how would you plan to use it?

I will be able to provide 40 to 45 hours per week for GSoC. In the timeline I provided, I have accounted for possible delays due to examinations and other college academic activities.

8 Likes

Hi! Thanks for the proposal. It looks great.

I have one reservation about the api endpoint /user/<user_name>/timeline-event/create/personal. IMO, <user_name> should not be a part of the endpoint and instead a list of usernames should be accepted as part of the POST body. That way we can make just 1 api call to create the recommendation for all users. Otherwise, we’d need to make multiple api calls to recommend to multiple users. Also, instead of /create/personal we think /create/recommend-personal is a better name.

1 Like

Hello, thank you so much for your feedback.

a list of usernames should be accepted as part of the POST body. That way we can make just 1 api call to create the recommendation for all users

Ah I see, that is indeed better and faster. I have made required changes to my proposal to implement the idea above. Do let me know if there can be any room for more improvements ^^

Thanks :smiley:

hi @mr_monkey, it would really mean a lot if you provide some insight for the frontend aspect of this proposal and suggest improvements for the same.:smiley:

1 Like

The mockups look good !
Everything is clear and I don’t have any major change to suggest.
We can polish up the last details when implementing.

1 Like