GSoC 2026 Application — Showcase MusicBrainz Events in ListenBrainz
Contact Information
| Field | Details |
|---|---|
| Nickname | 22ashwaniyadav |
| Matrix | @22ashwaniyadav:matrix.org |
| 22ashwaniyadav@gmail.com | |
| GitHub | NAME-ASHWANIYADAV (ASHWANI YADAV) · GitHub |
| MusicBrainz | Editor “22ashwaniyadav” - MusicBrainz |
| ListenBrainz | ListenBrainz |
| University | Delhi Technological University (DTU), Batch 2023–2027 |
| Timezone | IST (UTC+5:30) |
Introduction
Hi! I am Ashwani Yadav, a 3rd year student at Delhi Technological University (DTU). I have been contributing to ListenBrainz for several months and have 5 merged pull requests on the listenbrainz-server repository. I am applying for the “Showcase MusicBrainz Events in ListenBrainz” project idea for GSoC 2026.
I love music — I listen to everything from Imagine Dragons and Sabrina Carpenter to Justin Bieber. Discovering that an artist I love is performing nearby but finding out too late has happened to me more than once. That is exactly the problem this project solves — bridging the gap between a user’s listening history and real-world music events they would actually care about.
Prior Contributions to ListenBrainz
I have 5 merged pull requests on listenbrainz-server, which directly demonstrate my understanding of the codebase I am proposing to extend:
| PR | Ticket | Title | Relevance to Proposal |
|---|---|---|---|
| #3584 | LB-1632 | Spinitron CSV Import | Data import pipeline — same pattern as event cache ingestion |
| #3578 | LB-1834 | Standardize stats range field | Backend API — same Flask pattern as new events endpoints |
| #3577 | LB-1838 | Fix Sphinx references in pin API docs | API documentation — events API will need thorough docs |
| #3569 | LB-1838 | Update docstrings for pin/unpin API | Code quality — events API functions follow same standard |
| #3570 | LB-1932 | Make Beta/Archived sections collapsible | Frontend React — same component pattern as EventsPage tabs |
Proposed Project
Problem Statement
MusicBrainz has a rich database of musical events — concerts, festivals, competitions — but ListenBrainz has no way to surface this data to users. A ListenBrainz user who listens to Imagine Dragons every day has no way to know through ListenBrainz if Imagine Dragons is performing in their city next month. This project fixes that.
Project Goals
This project integrates MusicBrainz event data into ListenBrainz across six main surfaces:
-
Artist pages show upcoming and past events for that artist
-
A dedicated Events page (similar to Fresh Releases) with global and personalized “For You” views
-
Individual event pages with full details, Event Art, and Interested/Going buttons
-
Event notifications in the user feed when followed or top-listened artists announce events
-
Events tab in search results
-
User location setting (LB-1538) for nearby event filtering
Technical Design
Database Schema Relationship Diagram
Architecture Overview
The implementation mirrors the existing Fresh Releases architecture — MusicBrainz data is fetched from the locally replicated musicbrainz schema in LB’s PostgreSQL instance, cached in a TimescaleDB mapping table, and served through Flask APIs consumed by React frontend components.
Key architectural decision: Unlike Fresh Releases (which uses Spark + CouchDB for per-user data), Events will use a simpler architecture — a single mb_event_metadata_cache table in TimescaleDB, queried directly with SQL JOINs. This is appropriate because event data is structured (MBIDs, dates, venues) rather than computed recommendation scores.
1. Database Schema Changes
Three new tables are added, following ListenBrainz’s exact naming conventions found in admin/sql/:
1a. mapping.mb_event_metadata_cache
Added to admin/timescale/create_tables.sql — mirrors the existing mb_artist_metadata_cache pattern:
CREATE TABLE mapping.mb_event_metadata_cache (
dirty BOOLEAN DEFAULT FALSE,
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW(),
event_mbid UUID NOT NULL,
data JSONB NOT NULL
);
-- admin/timescale/create_indexes.sql:
CREATE UNIQUE INDEX mb_event_metadata_cache_idx_event_mbid
ON mapping.mb_event_metadata_cache (event_mbid);
CREATE INDEX mb_event_metadata_cache_idx_dirty
ON mapping.mb_event_metadata_cache (dirty);
1b. user_artist_follow
Artist follow system built from scratch (LB-1351 has no prior implementation — confirmed by searching codebase):
-- admin/sql/create_tables.sql
CREATE TABLE user_artist_follow (
user_id INT NOT NULL,
artist_mbid UUID NOT NULL,
created TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- admin/sql/create_primary_keys.sql
ALTER TABLE user_artist_follow
ADD CONSTRAINT user_artist_follow_pkey PRIMARY KEY (user_id, artist_mbid);
-- admin/sql/create_foreign_keys.sql
ALTER TABLE user_artist_follow
ADD CONSTRAINT user_artist_follow_user_id_foreign_key
FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE;
-- admin/sql/create_indexes.sql
CREATE INDEX artist_mbid_ndx_user_artist_follow ON user_artist_follow (artist_mbid);
1c. event_interaction
Tracks whether a user is ‘interested’ or ‘going’ to an event:
-- admin/sql/create_types.sql
CREATE TYPE event_interaction_type_enum AS ENUM('interested', 'going');
-- admin/sql/create_tables.sql
CREATE TABLE event_interaction (
id SERIAL,
user_id INTEGER NOT NULL,
event_mbid UUID NOT NULL,
interaction_type event_interaction_type_enum NOT NULL,
created TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
);
-- Constraints follow LB naming convention: tablename_columnname_foreign_key
ALTER TABLE event_interaction
ADD CONSTRAINT event_interaction_user_id_foreign_key
FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE;
CREATE UNIQUE INDEX user_id_event_mbid_ndx_event_interaction
ON event_interaction (user_id, event_mbid);
1d. user_setting — LB-1538
PR #2801 was never merged — implementing from scratch:
-- admin/sql/updates/ (migration file)
ALTER TABLE user_setting ADD COLUMN area_mbid UUID;
2. MusicBrainz Event Metadata Cache
A new file mbid_mapping/mapping/mb_event_metadata_cache.py inherits from MusicBrainzEntityMetadataCache — the same base class used by mb_artist_metadata_cache and mb_recording_metadata_cache. The cache queries the locally replicated musicbrainz schema — no external API calls needed.
Core SQL Query
WITH event_artists AS (
SELECT e.gid AS event_mbid,
jsonb_agg(DISTINCT jsonb_build_object(
'artist_mbid', a.gid::TEXT,
'artist_credit_name', a.name,
'type', lt.name
)) AS event_artists
FROM musicbrainz.event e
JOIN musicbrainz.l_artist_event lae ON lae.entity1 = e.id
JOIN musicbrainz.artist a ON lae.entity0 = a.id
JOIN musicbrainz.link l ON lae.link = l.id
JOIN musicbrainz.link_type lt ON l.link_type = lt.id
GROUP BY e.gid
), event_places AS (
SELECT e.gid AS event_mbid,
jsonb_agg(DISTINCT jsonb_build_object(
'place_mbid', p.gid::TEXT,
'place_name', p.name,
'area_mbid', area.gid::TEXT,
'area_name', area.name
)) AS event_places
FROM musicbrainz.event e
JOIN musicbrainz.l_event_place lep ON lep.entity0 = e.id
JOIN musicbrainz.place p ON lep.entity1 = p.id
JOIN musicbrainz.area area ON p.area = area.id
GROUP BY e.gid
)
SELECT e.gid::TEXT AS event_mbid,
e.name AS event_name,
e.begin_date_year, e.begin_date_month, e.begin_date_day,
e.end_date_year, e.time, e.cancelled,
etype.name AS event_type,
ea.event_artists, ep.event_places
FROM musicbrainz.event e
LEFT JOIN musicbrainz.event_type etype ON e.type = etype.id
LEFT JOIN event_artists ea ON ea.event_mbid = e.gid
LEFT JOIN event_places ep ON ep.event_mbid = e.gid
Cron Integration
The event cache integrates into the existing pipeline — no new crontab entries needed:
# In cron_build_all_mb_caches() — runs first Thursday of every month:
create_mb_event_metadata_cache(True) # one line addition
# In cron_update_all_mb_caches() — runs every 4 hours (0 */4 * * *):
incremental_update_mb_event_metadata_cache(True) # one line addition
3. Features
Feature 1 — Events on Artist Pages
What it does: When a user visits an artist’s page — for example, Arijit Singh — they will now see a new “Events” section below Albums and Similar Artists. This section shows upcoming concerts and festivals that the artist is part of, with a “Past” tab for historical events. A “Follow Artist” button is added to the artist header.
Why it’s needed: Today, a user who listens to Imagine Dragons on ListenBrainz has no way to know through the platform if Imagine Dragons is performing nearby. Artist pages are the most natural place to surface this — the user is already looking at that artist.
How it works: ArtistPage.tsx queries GET /1/user/<n>/events with the artist’s MBID. Backend fetches from mapping.mb_event_metadata_cache. Event Art is loaded from https://eventartarchive.org/event/{mbid}/front-500 (9,500+ images as of early 2026) with LB placeholder as fallback. Follow button calls POST /1/user/<n>/follow/artist which writes to user_artist_follow table.
Feature 2 — Dedicated Events Page
What it does: A brand new page at /explore/events/ — similar to the existing Fresh Releases page. Two tabs: “For You” shows personalized events based on your listening history, “Global” shows all upcoming events worldwide. Filter sidebar (event type, status, location) and timeline scrubber on the right.
Why it’s needed: ListenBrainz already knows which artists a user loves. The Events page connects that listening data to real-world experiences — showing only events that actually matter to the user based on who they listen to.
How it works: New EventsPage.tsx mirrors FreshReleases.tsx — same useQuery + Pill tab pattern. “For You” data = followed artists (user_artist_follow table) UNION top artists (CouchDB via db_stats.get()). Both tabs served by GET /1/explore/events/. Route added to frontend/js/src/explore/routes/index.tsx.
Feature 3 — Individual Event Pages
What it does: A dedicated page for each event at /event/<mbid>/. Shows the event poster (from Event Art Archive), date and time, all performers with their roles, venue and city, external ticketing links, and “Interested”/“Going” buttons with a count of other users.
Why it’s needed: This page was explicitly requested by mentor @mr_monkey during review of competing proposals. It gives events first-class citizenship in ListenBrainz — just like albums and artists already have their own pages.
How it works: New route /event/<event_mbid>/ fetches full event data via POST /1/metadata/event/. Event Art from https://eventartarchive.org/event/{mbid}/front-500. “Interested”/“Going” buttons call POST /1/user/<n>/event/<mbid>/interested and /going which write to event_interaction table.
Feature 4 — Feed Notifications
What it does: When a new event is announced for an artist the user follows or frequently listens to, a notification appears in their feed — “New event announced: Arijit Singh — Mumbai, 2026-04-15”. The feed also shows “You started following Arijit Singh” when a user follows an artist.
Why it’s needed: Without notifications, users would have to manually check the Events page every day. With notifications, the platform proactively tells the user when something relevant is happening — exactly what a music platform should do.
How it works: New event_notification value added to user_timeline_event_type_enum. The existing NotificationMetadata model (creator + message fields) is reused — no new DB schema needed. A daily management command notify_users_of_new_events() queries the cache for events added in the last 24 hours, finds relevant users via followed/top artists, creates feed entries. UserFeed.tsx adds a faCalendarPlus icon for the new type.
message = (f'New event: <a href="/event/{event_mbid}">'
f'{event_name}</a> — {venue}, {date}')
metadata = NotificationMetadata(creator='listenbrainz-events-bot',
message=message)
create_event_notification_event(db_conn, user_id, metadata)
Notification Flow:
Feature 5 — Events in Search
What it does: A new “Events” tab in the ListenBrainz search page. Search for “Justin Bieber” → click Events tab → see all concerts and festivals linked to Justin Bieber in MusicBrainz.
Why it’s needed: Search is often the first thing a user does when discovering an artist. Showing events in search results means event discovery happens at the most natural moment.
How it works: No backend changes needed. ListenBrainz already queries the MusicBrainz WS/2 API directly from the frontend for artist/album/track searches (MBBaseURI = '``https://musicbrainz.org/ws/2``'). A new EventSearch.tsx component calls the same API with entity type event. Only 3 frontend changes: add Events tab to Search.tsx, create EventSearch.tsx, add eventLookup() to APIService.ts.
Feature 6 — User Location Setting (LB-1538)
What it does: A new “Event Preferences” section in Settings. Users can set their country and city/area, and toggle event notifications on or off. Choose whether to be notified for top artists only or all followed artists.
Why it’s needed: An event in London is useless to a user in Mumbai. LB-1538 is an open ticket specifically requesting this feature, discussed by aerozol and monkey. PR #2801 was submitted but never merged — this proposal implements it cleanly from scratch as part of the events feature.
How it works: New area_mbid UUID column added to user_setting table. New get_user_area() and set_user_area() functions in user_setting.py following the same pattern as existing set_timezone(). New SelectArea.tsx uses same useAutoSave hook as SelectTimezone.tsx. Location is public data — clearly noted in UI.
-- Filtering events by user area (JSONB query):
SELECT e.data FROM mapping.mb_event_metadata_cache e,
LATERAL jsonb_array_elements(e.data->'event_places') AS place
WHERE place->'area'->>'area_mbid' = (
SELECT area_mbid::TEXT FROM user_setting WHERE user_id = :user_id
);
My Events page (bonus feature) — a hub showing all events the user has marked as Going or Interested:
4. New API Endpoints
All endpoints follow ListenBrainz’s exact patterns: @crossdomain, @ratelimit(), validate_auth_header() for auth, orjson.loads() for body parsing, raise APIBadRequest/APIForbidden for errors, jsonify({"payload": {...}}) for responses.
| Endpoint | Method | Description |
|---|---|---|
/1/explore/events/ |
GET | Global/personalized events list (added to explore_api_bp) |
/1/metadata/event/ |
POST | Batch fetch event metadata by MBIDs (added to metadata_api.py) |
/1/user/<n>/event/<mbid>/interested |
POST | Mark event as interested (requires auth) |
/1/user/<n>/event/<mbid>/going |
POST | Mark event as going (requires auth) |
/1/user/<n>/events |
GET | Get user’s marked events (interested + going) |
/1/user/<n>/follow/artist |
POST | Follow an artist for event notifications |
/1/user/<n>/unfollow/artist |
POST | Unfollow an artist |
/1/user/<n>/following/artists |
GET | List artists user is following |
/1/user/<n>/area |
POST | Set user location for event filtering (LB-1538) |
5. Frontend TypeScript Interfaces
New type definitions added to frontend/js/src/utils/types.d.ts:
type MBEvent = {
event_mbid: string;
event_name: string;
time?: string;
type?: string;
cancelled: boolean;
life_span: { begin: string; end: string | null; };
event_artists: Array<EventArtist>;
event_places: Array<EventVenue>;
event_tags: Array<string>;
};
type EventArtist = {
artist_mbid: string;
artist_credit_name: string;
join_phrase?: string;
};
type EventVenue = {
place_mbid: string; place_name: string;
area: { area_mbid: string; area_name: string; };
};
type UserEventInteraction = {
event_mbid: string;
status: 'interested' | 'going' | 'none';
};
6. Testing Plan
Tests follow ListenBrainz’s OOP test pattern — subclassing DatabaseTestCase and IntegrationTestCase:
# listenbrainz/db/tests/test_events.py
from listenbrainz.db.testing import DatabaseTestCase
import listenbrainz.db.user_artist_follow as db_follow
class EventsDBTestCase(DatabaseTestCase):
def test_follow_artist(self):
db_follow.follow_artist(self.db_conn, 1,
'012151a8-0f9a-44c9-997f-ebd68b5389f9') # Imagine Dragons
artists = db_follow.get_followed_artists(self.db_conn, 1)
self.assertEqual(len(artists), 1)
def test_unfollow_artist(self):
db_follow.follow_artist(self.db_conn, 1,
'012151a8-0f9a-44c9-997f-ebd68b5389f9')
db_follow.unfollow_artist(self.db_conn, 1,
'012151a8-0f9a-44c9-997f-ebd68b5389f9')
self.assertEqual(len(db_follow.get_followed_artists(self.db_conn, 1)), 0)
# listenbrainz/webserver/views/test/test_events_api.py
from listenbrainz.tests.integration import IntegrationTestCase
class EventsAPITestCase(IntegrationTestCase):
def setUp(self):
super().setUp()
user = db_user.get_or_create(self.db_conn, 1, 'testuser')
self.temporary_login(user['login_id'])
def test_mark_event_interested(self):
response = self.client.post(
'/1/user/testuser/event/e45dc5ae-1f7c-40ad-beff-3bf68bbbd8f7/interested',
json={'interested': True})
self.assert200(response)
self.assertEqual(response.json['status'], 'ok')
Run commands:
-
./test.sh listenbrainz/db/tests/test_events.py— DB tests -
./test.sh listenbrainz/webserver/views/test/test_events_api.py— API tests -
docker compose run --rm frontend_tester npm test -- EventsPage.test.tsx— React tests
Timeline (350 Hours — 12 Weeks)
Community Bonding (May 8 – May 25): Set up dev environment, discuss schema design with mentors, review existing mb_artist_metadata_cache implementation, post weekly updates.
| Week | Tasks & Deliverables |
|---|---|
| Week 1 (May 26 – Jun 1) | DB schema — create all SQL files for mb_event_metadata_cache, user_artist_follow, event_interaction tables. Write migrations and DB-level tests. Deliverable: All tables created, indexed, tested. |
| Week 2 (Jun 2 – Jun 8) | MB Event Cache — implement MusicBrainzEventMetadataCache class in mbid_mapping/mapping/mb_event_metadata_cache.py. Wire into manage_cron.py. Test incremental update. Deliverable: Cache builds and updates correctly. |
| Week 3 (Jun 9 – Jun 15) | Backend DB layer — implement listenbrainz/db/user_artist_follow.py and listenbrainz/db/events.py. Comprehensive DB tests. Deliverable: All DB functions with tests passing. |
| Week 4 (Jun 16 – Jun 22) | Flask API layer — all 9 new endpoints in explore_api.py, metadata_api.py, user_settings_api.py, user_artist_follow_api.py. API integration tests. Deliverable: All endpoints tested. [MIDTERM EVALUATION] |
| Week 5 (Jun 23 – Jun 29) | Artist Page frontend — modify ArtistPage.tsx, add Events section + Follow button, implement EventCard.tsx. Deliverable: Artist page shows events from cache. |
| Week 6 (Jun 30 – Jul 6) | EventsPage — implement full EventsPage.tsx with For You/Global tabs, filters, timeline scrubber. Add route. Deliverable: /explore/events/ fully functional. |
| Week 7 (Jul 7 – Jul 13) | Individual Event Page — /event/<mbid>/ route, Event Art Archive integration, performers, venue, Interested/Going buttons. Deliverable: Full event detail page. |
| Week 8 (Jul 14 – Jul 20) | Feed notifications — event_notification enum, create_event_notification_event(), notify_users_of_new_events(), UserFeed.tsx update. Deliverable: Notifications in user feed. |
| Week 9 (Jul 21 – Jul 27) | LB-1538 — SelectArea.tsx, set_user_area()/get_user_area(), POST /1/user/<n>/area, location filter in EventsPage. Deliverable: Users can set location, events filtered. |
| Week 10 (Jul 28 – Aug 3) | Search integration — Events tab in Search.tsx, EventSearch.tsx component. Frontend tests for all new components. Deliverable: Events in search results. |
| Week 11 (Aug 4 – Aug 10) | Polish — fix edge cases, loading skeletons, mobile responsiveness, missing tests, Sphinx docstrings. Deliverable: All features production-quality. |
| Week 12 (Aug 11 – Aug 18) | Final review, documentation, mentor feedback, GSoC final report. Deliverable: Final submission. [FINAL EVALUATION] |
Community Affinities
Music I listen to:
| Artist | MusicBrainz ID |
|---|---|
| Justin Bieber | e0140a67-e4d1-4f13-8a01-364355bee46e |
| Sabrina Carpenter | 1882fe91-cdd9-49c9-9956-8e06a3810bd4 |
| Imagine Dragons | 012151a8-0f9a-44c9-997f-ebd68b5389f9 |
I use ListenBrainz to track my listening history. The problem this project solves — discovering events for artists I love — is something I experience personally.
What interests me most about ListenBrainz: I am most interested in the intersection of listening data and real-world music discovery. ListenBrainz already knows which artists a user loves — connecting that to live events is a natural and valuable extension. I appreciate the open-source, privacy-respecting philosophy of MetaBrainz.
Programming Precedents
| First started programming | 2019 — competitive programming and web development |
| Open source contributions | ListenBrainz (5 merged PRs on listenbrainz-server), contributions to other open source projects |
| Personal projects | DTU-RAKSHAK (AI vehicle monitoring system using YOLOv8m + Node.js + PostgreSQL + React), AI text humanizer, ANPR research paper targeting Scopus-indexed conferences |
| Current stack | Python, React, TypeScript, Node.js, PostgreSQL, Docker, Flask |
| Competitive programming | 379 LeetCode problems solved including hard problems |
Practical Requirements
| Computer | Personal laptop — sufficient for running LB dev environment (Docker, PostgreSQL, React dev server) |
| Available time | 35–40 hours per week during GSoC period |
| Time plan | Weekdays: 5–6 hours/day on implementation. Weekends: review, testing, documentation, mentor sync. Weekly community updates on MetaBrainz forum. |
| Other commitments | No job or university courses during the project period that would impair work. |
| Communication | Available on Matrix (@22ashwaniyadav:matrix.org) daily. Weekly progress updates on MetaBrainz community forum. |
Why Me
I am the strongest candidate for this project for three concrete reasons:
-
Proven codebase understanding: My 5 merged PRs on listenbrainz-server cover the backend (LB-1834 stats API), frontend (LB-1932 React components), documentation (LB-1838), and data import (LB-1632 Spinitron CSV) — exactly the same areas this proposal touches.
-
No ramp-up needed: I have already set up the LB development environment, understand the Flask blueprint pattern, the TimescaleDB mapping schema, the React component architecture with
useQueryandGlobalAppContext, and the test patterns usingDatabaseTestCaseandIntegrationTestCase. -
Technical depth: This proposal includes exact SQL matching LB’s codebase conventions, exact function signatures, a decision to integrate into the existing cron pipeline rather than adding new crontab entries, and a testing plan with actual test file paths — demonstrating that I have read and understood the actual code.
I would love feedback from @mr_monkey , @aerozol and @anshgoyal31 on the technical approach, especially the cache architecture and notification design.












