Skip to content

feat: Discogs user integration — authenticate, sync collection & wantlist #60

Description

@SimplicityGuy

Overview

Add first-party Discogs user integration to discogsography: users create an account, connect their Discogs profile via OAuth 1.0a, then sync their personal collection and wantlist to both Neo4j and PostgreSQL. Unlocks a new category of personalized features on top of the existing global Discogs knowledge graph.

Motivation

The current discogsography pipeline ingests the complete Discogs data dump (50GB+) into Neo4j and PostgreSQL, enabling exploration of the full music graph. Adding user identity and personal data creates:

  • Personal collection browsing within the knowledge graph
  • Wantlist analysis with graph-powered context
  • Recommendations based on collection graph traversal
  • Collection analytics (by genre, decade, label, condition, etc.)
  • Foundation for future features (price tracking, social comparison, collection completeness scores)

Reference Implementation

vinyldigger (/Users/Robert/Code/public/vinyldigger) has a fully working implementation of all the key pieces:

Feature vinyldigger location
Discogs OAuth 1.0a (OOB flow) backend/src/api/v1/endpoints/oauth.py
DiscogsOAuth1Auth httpx class backend/src/services/discogs.py
Redis request token store backend/src/core/token_store.py
Collection/wantlist sync backend/src/workers/tasks.py
Celery task setup backend/src/workers/celery_app.py
OAuth token model backend/src/models/oauth_token.py
Collection/item models backend/src/models/collection.py
Collection/wantlist flags on results backend/src/models/search_result.py
Connect UI (settings page) frontend/src/pages/SettingsPage.tsx
OAuth API client frontend/src/lib/api.ts

Architecture

Three new components:

Component Type Purpose
auth New microservice (FastAPI, port 8004/8005) User accounts + JWT + Discogs OAuth 1.0a
collector New microservice (FastAPI + Celery, port 8010/8011) Collection/wantlist sync to Neo4j + PostgreSQL
Explore extensions Existing service + new endpoints Personalized graph queries and UI

Data Flow

User registers/logs in → auth service (JWT tokens)
     ↓
User connects Discogs → auth service
     OAuth 1.0a OOB flow:
     1. Backend → Discogs: request token (callback_uri="oob")
     2. State stored in Redis (10-min TTL, CSRF protection)
     3. Frontend opens Discogs in popup window
     4. User pastes verification code into app
     5. Backend exchanges verifier → access token + secret
     6. Identity fetched from /oauth/identity
     7. Token stored in oauth_tokens table (unique per user/provider)
     ↓
User triggers sync → collector service
     Celery background task:
     - Paginate Discogs collection API (100/page, 0.5s delay)
     - Paginate Discogs wantlist API
     - Upsert to PostgreSQL (user_collections, user_wantlists)
     - MERGE relationships in Neo4j (COLLECTED, WANTS on existing Release nodes)
     - Track in sync_history
     ↓
User explores → explore service
     New endpoints: /user/collection, /user/wantlist, /user/recommendations
     Existing endpoints: decorated with in_collection/in_wantlist flags

Key Implementation Details from vinyldigger

OAuth Flow: Out-of-Band (OOB)

Discogs supports OOB where the user gets a verification code on the Discogs site and types it into the app. No web callback URL is required. Use callback_uri="oob" in the initial request token call.

Discogs API Response Key Difference

  • Collection: response key is releases, release ID at item["basic_information"]["id"]
  • Wantlist: response key is wants, release ID at item["id"]

This is a critical gotcha — the wantlist ID is at the top level while collection ID is nested.

Rate Limiting

Use asyncio.sleep(0.5) between paginated requests (vinyldigger-validated, keeps well under the 60 req/min limit).

App Credentials Storage

Store Discogs consumer key/secret in app_config database table (admin-configured via API), not environment variables. This matches vinyldigger's per-instance configuration approach.

Background Processing

Use Celery for sync tasks with Redis as broker. Each Celery task creates a fresh SQLAlchemy async engine (required for greenlet context compatibility). APScheduler can be added for periodic resyncs.

New Database Objects

PostgreSQL (new tables)

  • users — email, hashed_password, is_active
  • oauth_tokens — Discogs access_token + secret, provider_username, provider_user_id (unique per user+provider)
  • app_config — Discogs consumer key/secret (admin-managed)
  • user_collections — per-user collection items with title, artist, year, format, label, condition, rating, notes, JSONB metadata
  • user_wantlists — per-user wantlist items with title, artist, year, format, rating, notes, JSONB metadata
  • sync_history — sync job tracking (status, items synced, pages fetched, errors, timestamps)

Neo4j (new nodes + relationships)

  • User node — internal UUID + Discogs username/ID
  • COLLECTED relationship — User → Release (with rating, folder_id, instance_id, date_added, format)
  • WANTS relationship — User → Release (with rating, date_added, notes)

Sub-Tasks (implement in order — each depends on the previous)

Environment Variables Required

# Auth service
JWT_SECRET_KEY=<random-256-bit-key>
REDIS_URL=redis://redis:6379

# Discogs credentials (stored in app_config table, configured via admin API — NOT env vars)
# Register app at: https://www.discogs.com/settings/developers

# Both auth + collector services
DISCOGS_USER_AGENT=discogsography/1.0 +https://github.com/SimplicityGuy/discogsography

Future Features Unlocked

  • Collection value tracking — Discogs marketplace pricing over time
  • Multi-user comparison — "Who else collects what I collect?"
  • Collection completeness scores — "You have 60% of the ECM catalog"
  • Label/pressing deep dives — "Which pressing of X do I own?"
  • Export — collection/wantlist to CSV
  • Price alerts — notify when wantlist items appear at target price on Discogs marketplace

Acceptance Criteria

  • User can create an account and log in via email/password
  • User can connect their Discogs account via OAuth (OOB: popup + verification code)
  • OAuth tokens stored securely in oauth_tokens table
  • User can trigger a sync of their collection and wantlist
  • Sync runs as Celery background task
  • Synced data appears in both Neo4j (COLLECTED/WANTS relationships) and PostgreSQL
  • User can resync at any time (upsert semantics, stale data cleaned)
  • Explore service shows personalized collection/wantlist queries
  • Existing release detail pages show in_collection/in_wantlist flags for authenticated users
  • All user data is private (only accessible to that user via JWT)
  • Discogs API rate limits respected (0.5s delay, backoff on 429)
  • All services integrated in docker-compose

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpythonPull requests that update Python code

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions