A comprehensive music analytics platform that provides personalized insights from SoundCloud, inspired by Spotify Wrapped. SoundWrapped offers daily featured content, detailed analytics, and interactive visualizations to help users discover and understand their music taste.
Features
Homepage - Daily Featured Content
The homepage showcases three daily rotating features that persist throughout the day using time-seed based caching:
Song of the Day
- Feature: Displays a featured track selected from popular SoundCloud tracks
- Technical Implementation:
- Selection Algorithm (prioritized order):
- Discovery Tracks: High engagement-to-plays ratio tracks (1000+ plays, sorted by engagement)
- Popular Tracks (Positions 11-30): Avoids overlap with "Popular Now" section
- Genre of the Day Tracks: Fallback to tracks from featured genre
- Uses date-based seed (
LocalDate.now().toEpochDay()) for deterministic daily selection - Cached for 24 hours to ensure consistency
Artist of the Day
- Feature: Highlights a featured artist with their popular tracks and biography
- Technical Implementation:
- Artist Selection: Extracts unique artists from popular tracks, calculates trending scores, and uses time-seed to select from top 10 artists
- Description Generation: Research-first approach with AI synthesis:
- Research Phase: Aggregates data from multiple sources:
- Wikipedia API (
/api/rest_v1/page/summary/): Fetches full extract paragraph from Wikipedia articles - Google Knowledge Graph API: Retrieves detailed descriptions from Google's Knowledge Graph
- SerpAPI: Comprehensive web search for additional context
- Wikipedia API (
- AI Generation: Uses Groq API (
llama-3.3-70b-versatile) to synthesize research into 2-3 sentence description (50-100 words) - Fallback: SoundCloud bio or generic description if AI generation fails
- Verification Criteria: More lenient: attempts description generation for all artists, with quality checks
- Track Fetching: Uses multiple fallback strategies:
- Attempts
popular-tracksURL resolution - Falls back to direct user URN track fetching
- Fetches at least 200 tracks before sorting by
playback_countto ensure accurate popularity - Name Matching: Tries multiple name variations (case-insensitive, camelCase) to find Wikipedia pages
- Cached for 24 hours using date-based seed
Genre of the Day
- Feature: Features a music genre with popular tracks and description
- Technical Implementation:
- Genre Selection: Randomly selects from 18 popular genres using time-seed
- Description Generation:
- Research Phase: Aggregates data from multiple sources:
- Google Knowledge Graph API: Fetches genre description (supports obscure subgenres)
- Wikipedia API: Attempts to find genre information
- SerpAPI: Web search for additional context (optional)
- AI Generation: Uses Groq API to synthesize research into 2-3 sentence description (50-100 words)
- Fallback: Hardcoded descriptions for well-known genres or generic description
- Track Filtering: Fetches tracks using
/tracks?tags={genre}endpoint with flexible matching: - Partial tag matching (e.g., "country music" matches "country")
- English titles preferred
- Cached for 24 hours using date-based seed
Popular Now
- Feature: Displays the first 5 tracks from the US Top 50 charts playlist
- Technical Implementation:
- Fetches tracks from SoundCloud playlist URN
1714689261(US Top 50:https://soundcloud.com/music-charts-us/sets/all-music-genres) - Returns tracks in their original playlist order (no sorting) to show the actual top 5
- Uses
/playlists/{id}/tracksendpoint with pagination support
- Fetches tracks from SoundCloud playlist URN
Buzzing
- Feature: Highlights an up-and-coming artist/track daily from SoundCloud's buzzing playlists
- Technical Implementation:
- Fetches playlists from SoundCloud user
buzzing-playlists(/users/buzzing-playlists/playlists?limit=50) - Aggregates all tracks across all buzzing playlists into a single pool
- Uses date-based seed (
year*10000 + month*100 + day) withjava.util.Randomfor deterministic daily selection - Same track shown all day, changes at midnight
- Labels track with "Artist to watch out for"
- Manual in-memory field caching (not Caffeine-managed)
- Fetches playlists from SoundCloud user
Dashboard Analytics
Comprehensive analytics dashboard showing:
- Top Tracks: User's most played tracks
- Top Artists: Most listened-to artists
- Listening Statistics: Total hours, likes given, tracks uploaded
- Activity Timeline: Recent likes, uploads, and follows
- Interactive Charts: Visual representations using Chart.js
- Genre Discovery: Top genres explored with discovery count
- Listening Patterns: Peak hours, peak days, and listening persona (Early Bird, Afternoon Listener, Evening Vibes, Night Owl)
- Genre Constellation: Interactive 3D visualization of genre relationships (HTML5 Canvas)
SoundCloud Wrapped
A Spotify Wrapped-style summary featuring:
- Personalized Stories: Slide-by-slide presentation of music insights
- Top Tracks & Artists: Year-end summary of favorites
- Statistics: Total listening hours, likes, uploads, comments
- Fun Facts: Interesting insights about listening habits
- Peak Year Analysis: Identifies the year with most activity
- Global Taste Comparison: Compares user's taste to global trends
Music Taste Map
Interactive world map visualization showing:
- Similar Listeners by City: Geographic distribution of similar music tastes
- Similarity Scoring: Advanced algorithms to match preferences
- Top Genres by Location: Genre analysis for each city
- Interactive Visualization: Click cities to see detailed insights
Music Doppelgänger
Finds users with similar music taste by:
- Analyzing liked tracks and playlists
- Comparing genre preferences
- Matching with followed users
- Calculating similarity scores
Artist Analytics
For users who upload tracks:
- Track Performance: Playback counts, likes, reposts
- Audience Insights: Follower growth, engagement metrics
- Top Performing Tracks: Best performing uploads
- Recommendations: Artist recommendations based on track analysis
Last.fm Scrobbling Integration
Integrates with Last.fm via Web Auth OAuth to pull long-term listening history beyond SoundCloud limits.
How It Works:
- User installs Web Scrobbler browser extension (SoundCloud is a built-in connector)
- Web Scrobbler sends play events to Last.fm:
SoundCloud Play → Web Scrobbler → Last.fm - User connects Last.fm account in SoundWrapped Dashboard (OAuth Web Auth flow)
- Backend polls Last.fm API every 15 minutes for new scrobbles
- Scrobbles are fuzzy-matched to SoundCloud tracks and stored as
UserActivityrecords
Architecture:
LastFmService: Consolidated REST API client — handles auth URL generation, token exchange, session management, API signature generation, and recent tracks fetchingLastFmScrobblingService: Scheduled sync job (every 15 min) — pollsuser.getRecentTracks, fuzzy-matches to SoundCloud tracks via search, createsUserActivityentries withsource=LASTFMLastFmController: REST endpoints for OAuth flow (/api/lastfm/auth-url,/callback,/status,/disconnect,/sync)- OAuth uses Web Auth mode (only
api_key+cbcallback URL — no request token in auth URL)
Data Enrichment:
UserActivityentity trackssource(INAPP/LASTFM),matchedSoundCloudTrackId,lastFmArtist,lastFmTrack- Unmatched Last.fm scrobbles are still stored for analytics
- Uses
soundcloudTrackSearchCaffeine cache (5000 entries, 24h TTL) to avoid redundant search API calls
Technical Architecture
SoundWrapped follows a Model-View-Controller (MVC) architectural pattern, providing clear separation of concerns and maintainable code structure.
Architecture Pattern: Model-View-Controller (MVC)
Model Layer (Data & Business Logic)
- Entities (
entity/): JPA entities representing database tables (e.g.,Token,UserActivity) - Repositories (
repository/): Data access layer using Spring Data JPA for database operations - Services (
service/): Business logic layer containing core functionality:SoundWrappedService: Main service for SoundCloud API integration, featured content, Wrapped summary, Buzzing trackAnalyticsService: Music analytics and statistics calculationsGenreAnalysisService: Genre extraction, normalization, and distribution analysisListeningPatternService: Time-of-day and day-of-week listening analysisMusicDoppelgangerService: Music taste matching algorithmsArtistAnalyticsService: Artist performance metricsMusicTasteMapService: Geographic taste visualizationLyricsService: Lyrics fetching via Lyrics.ovh APIEnhancedArtistService: Rich artist profiles via TheAudioDBSimilarArtistsService: Similar artists via Last.fm APILastFmService: Last.fm REST API client (auth, session, recent tracks, signatures)LastFmScrobblingService: Scheduled Last.fm scrobble sync (every 15 min)ActivityTrackingService: In-app play/like/repost event trackingGeolocationService: IP-based location resolutionUserLocationService: User location storage and city/country queriesTokenStore: OAuth2 token managementTokenRefreshScheduler: Automatic token refresh
View Layer (Frontend Presentation)
- React Components (
frontend/src/components/): Reusable UI components - Pages (
frontend/src/pages/): Main application pages (Home, Dashboard, Wrapped, etc.) - Contexts (
frontend/src/contexts/): React Context API for state management - Services (
frontend/src/services/): API client services for backend communication
Controller Layer (Request Handling)
- REST Controllers (
controller/): Spring Boot@RestControllerclasses handling HTTP requests:SoundWrappedController: Main API endpoints for music data, featured content, analytics, WrappedOAuthCallbackController: SoundCloud OAuth2 code exchangeLastFmController: Last.fm Web Auth OAuth flow and scrobble managementActivityTrackingController: In-app play/like/repost event tracking (/api/activity)SystemPlaybackController: System-level playback tracking from desktop/extension (/api/tracking)
- Request Mapping: RESTful endpoints with proper HTTP methods (GET, POST, etc.)
- Response Handling: JSON responses with appropriate status codes
Data Flow
- Client Request → Frontend makes HTTP request to backend API
- Controller → Receives request, validates input, delegates to service layer
- Service → Executes business logic, interacts with repositories/APIs
- Repository/API → Fetches data from database or external APIs (SoundCloud, Wikipedia, Google)
- Service → Processes and transforms data
- Controller → Returns JSON response
- View → Frontend receives data and updates UI
Key Architectural Principles
- Separation of Concerns: Each layer has distinct responsibilities
- Dependency Injection: Spring's IoC container manages dependencies
- RESTful Design: Stateless API endpoints following REST conventions
- Service-Oriented: Business logic encapsulated in service classes
- Repository Pattern: Data access abstracted through repository interfaces
Backend (Spring Boot + Java)
API Integration
- SoundCloud API: Full OAuth2 integration with automatic token refresh
- Wikipedia API: REST API (
/api/rest_v1/page/summary/) for artist biographies - Google Knowledge Graph API: Entity search API for descriptions and genre information
- Groq API: AI-powered description and poetry generation using
llama-3.3-70b-versatilemodel (free tier, OpenAI-compatible) - SerpAPI: Comprehensive web search for further context
- TheAudioDB: (Optional) Additional audio artwork and metadata context
- Token Management: Automatic refresh, secure storage in PostgreSQL database
Caching Strategy
- Caffeine In-Memory Cache: Spring Cache with Caffeine for expensive external API calls:
groqDescriptions: 1h TTL, 1000 entries (AI-generated descriptions)enhancedArtists: 24h TTL, 500 entries (TheAudioDB artist info)similarArtists: 12h TTL, 500 entries (Last.fm similar artists)lyrics: 7d TTL, 2000 entries (Lyrics.ovh)popularTracks: 30m TTL, 10 entries (SoundCloud popular tracks)soundcloudTrackSearch: 24h TTL, 5000 entries (Last.fm → SoundCloud track ID mapping)
- Time-Seed Based Caching: Uses
LocalDate.now().toEpochDay()as seed forRandomclass - Daily Persistence: Featured content (Song, Artist, Genre, Buzzing) cached for 24 hours via in-memory fields
- Cache Invalidation: Via
POST /api/soundcloud/featured/clear-cacheendpoint or application startup@PostConstruct
Data Processing
- Pagination Handling: Supports SoundCloud's
linked_partitioningfor large datasets - Error Handling: Comprehensive fallback mechanisms for API failures
- Rate Limiting: Respects SoundCloud API rate limits with retry logic
Frontend (React + TypeScript)
UI Framework
- React 18: Modern React with hooks and context API
- TypeScript: Full type safety throughout
- Tailwind CSS: Utility-first CSS framework
- Framer Motion: Smooth animations and transitions
State Management
- React Context:
AuthContextfor authentication,MusicDataContextfor music data - React Query: Request deduplication, client-side caching, and prefetching via
useMusicQuerieshooks - Custom Hooks:
useMusicQueries.ts(data fetching),useRetry.ts(exponential backoff)
Components
- StatCard: Reusable card component for displaying statistics
- Charts: Interactive charts using Chart.js (
TopTracksChart,TopArtistsChart) - GenreConstellation: 3D Canvas visualization of genre relationships
- DynamicMoodBackground: Energy-reactive WebGL background
- WebGLBackground: Configurable WebGL background with dynamic color props
- ShareableStoryCard: Downloadable story cards with 6 color themes, 3 font sizes, 8 card types
- LastFmConnection: Last.fm connect/disconnect UI with sync status
- ErrorBoundary: Graceful React error handling with dev-mode details
- AnimatedParticleBackground: Particle-based background animation
- RecentActivity: Activity feed component
- Responsive Design: Mobile-first approach with breakpoints
Pages
- HomePage: Landing page with Song/Artist/Genre of the Day, Popular Now, Buzzing
- DashboardPage: Authenticated analytics dashboard with genre constellation
- WrappedPage: Wrapped summary experience with story slides
- ProfilePage: User profile and settings
- MusicTasteMapPage: Interactive geographic taste visualization
- LastFmCallbackPage: Last.fm OAuth callback handler
Getting Started
Prerequisites
- Java 17+
- Node.js 18+
- Maven 3.6+
- PostgreSQL 18+
Backend Setup
cd backend/soundwrapped-backend
# Configure API keys in application.yml
# - SoundCloud Client ID & Secret
# - Google Knowledge Graph API Key
# - Groq API Key
# - SerpAPI Key
# - TheAudioDB API Key
# Run with Maven
mvn spring-boot:run
# Or build and run JAR
mvn clean package
java -jar target/soundwrapped-backend-0.0.1-SNAPSHOT.jar
Frontend Setup
cd frontend
# Install dependencies
npm install
# Configure environment variables
cp env.example .env
# Edit .env with your API base URL
# Run development server
npm run dev
Docker Setup (Local Development)
# From project root
# Ensure backend JAR is built first (for integration tests)
cd backend/soundwrapped-backend
./mvnw clean package -DskipTests
cd ../..
# Start all services
docker-compose up --build
# Services will be available at:
# - Frontend: http://localhost:3000
# - Backend: http://localhost:8080
# - Database: localhost:5432
Note: The Dockerfile uses eclipse-temurin:17-jre as the base image. Environment variables should be provided via docker-compose.yml or .env file, not baked into the image.
Production Deployment (Render)
SoundWrapped is deployed on Render:
| Component | Render Service Type | URL |
|---|---|---|
| Frontend | Static Site | soundwrapped.onrender.com |
| Backend | Web Service (Docker) | soundwrapped-backend.onrender.com |
| Database | PostgreSQL Add-on | Managed by Render |
- Backend: Deployed via the
Dockerfileinbackend/soundwrapped-backend/(multi-stage Maven build →eclipse-temurin:17-jre). Render sets thePORTenvironment variable; the app binds to0.0.0.0:${PORT}(default 10000). - Frontend: Built with
npm install && npm run build, Render serves thedist/directory. Apublic/_redirectsfile (/* /index.html 200) handles SPA client-side routing. - Environment variables: All API keys and database credentials are configured in Render's dashboard, never committed to the repository.
See docs/DEPLOYMENT.md for detailed deployment instructions.
Configuration
Backend Configuration
SoundWrapped supports configuration through both application.yml and environment variables. Environment variables take precedence over application.yml values.
Option 1: Environment Variables (Recommended for Production)
Create a .env file in backend/soundwrapped-backend/:
# SoundCloud API Configuration
SOUNDCLOUD_CLIENT_ID=your_soundcloud_client_id_here
SOUNDCLOUD_CLIENT_SECRET=your_soundcloud_client_secret_here
REDIRECT_URI=http://localhost:8080/callback
# Database Configuration
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
# Spring Boot Configuration (PostgreSQL)
SPRING_PROFILES_ACTIVE=default
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/postgres
SPRING_DATASOURCE_USERNAME=postgres
SPRING_DATASOURCE_PASSWORD=postgres
SPRING_JPA_HIBERNATE_DDL_AUTO=update
SPRING_JPA_SHOW_SQL=false
# Google Knowledge Graph API Configuration (Artist descriptions)
GOOGLE_KNOWLEDGE_GRAPH_API_KEY=your_google_api_key_here
# Groq API Configuration (LLM)
GROQ_API_KEY=your_groq_api_key_here
# SerpAPI Configuration (Web search)
SERPAPI_API_KEY=your_serpapi_api_key_here
# TheAudioDB API Configuration (Enhanced artist info)
THEAUDIODB_API_KEY=your_theaudiodb_api_key_here
# Last.fm API Configuration (Similar artists & scrobbling)
LASTFM_API_KEY=your_lastfm_api_key_here
LASTFM_API_SECRET=your_lastfm_api_secret_here
LASTFM_CALLBACK_URL=http://localhost:8080/api/lastfm/callback
APP_FRONTEND_BASE_URL=http://localhost:3000
VITE_LASTFM_CALLBACK_URL=http://localhost:8080/api/lastfm/callback
Then export them or use a tool like dotenv to load them.
Option 2: application.yml (Development)
soundcloud:
client-id: ${SOUNDCLOUD_CLIENT_ID:your_default_client_id}
client-secret: ${SOUNDCLOUD_CLIENT_SECRET:your_default_client_secret}
api:
base-url: https://api.soundcloud.com
google:
knowledge-graph:
api-key: ${GOOGLE_KNOWLEDGE_GRAPH_API_KEY}
groq:
api-key: ${GROQ_API_KEY}
base-url: https://api.groq.com/openai/v1
serpapi:
api-key: ${SERPAPI_API_KEY}
Note: The ${VARIABLE_NAME:default_value} syntax means "use environment variable if available, otherwise use default value". For production, always use environment variables to keep secrets secure.
Frontend (.env)
VITE_API_BASE_URL=http://localhost:8080/api
VITE_SOUNDCLOUD_CLIENT_ID=YOUR_SOUNDCLOUD_CLIENT_ID
API Endpoints
User Data
GET /api/soundcloud/profile— User profileGET /api/soundcloud/tracks— User top tracks (by tracked plays)GET /api/soundcloud/likes— Liked tracksGET /api/soundcloud/playlists— User playlistsGET /api/soundcloud/followers— User followers
Featured Content
GET /api/soundcloud/featured/track— Song of the Day (with lyrics if available)GET /api/soundcloud/featured/artist?forceRefresh=— Artist of the Day (with enhanced info)GET /api/soundcloud/featured/genre— Genre of the DayGET /api/soundcloud/popular/tracks?limit=4— Popular Now (from Top 50)GET /api/soundcloud/buzzing— Buzzing track of the dayPOST /api/soundcloud/featured/clear-cache— Clear daily featured cacheGET /api/soundcloud/similar-artists?artist=&limit=10— Similar artists via Last.fm
Analytics
GET /api/soundcloud/wrapped/full— Complete Wrapped summaryGET /api/soundcloud/dashboard/analytics— Dashboard analytics (genres, listening patterns)GET /api/soundcloud/music-doppelganger— Music taste matchingGET /api/soundcloud/artist/analytics— Artist performance metricsGET /api/soundcloud/artist/recommendations?trackId=— Artist recommendationsGET /api/soundcloud/music-taste-map— Geographic taste visualizationGET /api/soundcloud/recent-activity?limit=10— Recent activity feedGET /api/soundcloud/online-users— Count users active in last 5 min
Activity Tracking
POST /api/activity/track/play?trackId=&durationMs=— Track play eventPOST /api/activity/track/like?trackId=— Track like eventPOST /api/activity/track/repost?trackId=— Track repost eventPOST /api/tracking/system-playback— System-level playback (desktop/extension)POST /api/tracking/system-like?trackId=— System-level like eventPOST /api/tracking/update-location— IP-based location update
SoundCloud Authentication
GET /callback?code={auth_code}— SoundCloud OAuth2 callbackPOST /api/soundcloud/refresh-token— Proactive token refresh
Last.fm Integration
GET /api/lastfm/auth-url— Get Last.fm Web Auth authorization URLGET /api/lastfm/callback?token=— Last.fm OAuth callback (exchanges token for session, redirects to frontend)GET /api/lastfm/callback/test— Test callback endpoint accessibilityGET /api/lastfm/status— Last.fm connection statusPOST /api/lastfm/disconnect— Disconnect Last.fm accountPOST /api/lastfm/sync— Manually trigger scrobble sync
Debug
GET /api/soundcloud/debug/test-api— Test SoundCloud API connectionGET /api/soundcloud/debug/tokens— Token statusGET /api/soundcloud/debug/oauth-url— Generate OAuth URL
Authentication Flows
SoundCloud OAuth2
- User clicks "Connect SoundCloud" on homepage
- Redirected to SoundCloud OAuth2 authorization page
- User authorizes application
- SoundCloud redirects to
/callback?code={auth_code} - Backend exchanges code for access and refresh tokens
- Tokens stored securely in database
- Automatic token refresh when access token expires (via
TokenRefreshScheduler)
Last.fm Web Auth
- User clicks "Connect Last.fm" on the Dashboard
- Frontend fetches auth URL from
GET /api/lastfm/auth-url - Auth URL uses Web Auth mode:
https://www.last.fm/api/auth?api_key=KEY&cb=CALLBACK - User authorizes on Last.fm, which redirects to
GET /api/lastfm/callback?token=TOKEN - Backend exchanges token for a session key via
auth.getSession(with API signature) - Session key and username stored in
LastFmTokenentity - Scrobble sync begins automatically every 15 minutes
Note: Last.fm Web Auth uses only api_key + cb (callback URL) in the auth URL — no request token is included, which ensures Last.fm uses its Web Auth flow and properly redirects back to the application.
QA Testing
Backend Tests
cd backend/soundwrapped-backend
mvn test # Unit tests
mvn verify # Integration tests with Testcontainers
Frontend Tests
cd frontend
npm test # Unit tests
npm run test:coverage # Coverage report
Project Structure
SoundWrapped/
├── backend/
│ └── soundwrapped-backend/
│ ├── src/main/java/com/soundwrapped/
│ │ ├── config/ # CacheConfig, etc.
│ │ ├── controller/ # REST controllers (SoundWrapped, LastFm, Activity, SystemPlayback, OAuth)
│ │ ├── service/ # Business logic (16+ services)
│ │ ├── entity/ # JPA entities (Token, UserActivity, LastFmToken, UserLocation)
│ │ ├── repository/ # Data access (Spring Data JPA)
│ │ └── exception/ # Custom exceptions
│ └── src/main/resources/
│ └── application.yml # Configuration (profiles: default, test, docker)
├── frontend/
│ └── src/
│ ├── components/ # React components (15+ including GenreConstellation, ShareableStoryCard, etc.)
│ ├── pages/ # Page components (Home, Dashboard, Wrapped, Profile, MusicTasteMap, LastFmCallback)
│ ├── contexts/ # React contexts (Auth, MusicData)
│ ├── hooks/ # Custom hooks (useMusicQueries, useRetry)
│ ├── services/ # API client (Axios with interceptors)
│ └── utils/ # Utility functions
├── docs/ # Documentation
└── docker-compose.yml # Docker orchestration (frontend, backend, postgres)
Tech Stack
Backend
- Spring Boot 3.5.5: Application framework
- Spring Data JPA: Database abstraction
- Spring Cache + Caffeine: In-memory caching with configurable TTLs
- PostgreSQL 18: Database
- RestTemplate: HTTP client for API calls
- Maven: Build tool
- Docker: Containerization with multi-stage builds
Frontend
- React 18: UI library
- TypeScript: Type-safe JavaScript
- Vite 7.3: Build tool with HMR
- Tailwind CSS: Styling
- Framer Motion: Animations
- Chart.js: Data visualization
- React Router: Navigation
- React Query: Server state management, caching, and prefetching
- html2canvas: Story card image generation
External APIs
- SoundCloud API: Music data, authentication, and OAuth2
- Last.fm API: Similar artists, Web Auth OAuth, scrobble syncing
- Wikipedia REST API: Artist biographies
- Google Knowledge Graph API: Entity descriptions
- Groq API: AI-powered descriptions and poetry generation using
llama-3.3-70b-versatile(free tier) - SerpAPI: Comprehensive web search for additional context
- TheAudioDB API: Enhanced artist profiles, artwork, discographies (optional)
- Lyrics.ovh: Lyrics fetching (free, no auth required)
Known Limitations / Architecture Tradeoffs
Single-User Token Storage
The backend stores one SoundCloud OAuth token at a time (via the tokens table). If two users share the same deployed backend, they will overwrite each other's session. This is by design — SoundWrapped is a personal analytics tool meant for a single user's SoundCloud account.
In-App vs. Last.fm Play Tracking
SoundWrapped does not provide in-app playback. Play tracking relies entirely on Last.fm via Web Scrobbler, which captures all plays on SoundCloud.com across any browser or device. Both Last.fm-sourced and any legacy in-app activity records are merged in the user_activities table with a source column (INAPP or LASTFM).
trackId Column Duality
In the user_activities table, trackId serves a dual purpose:
- For Last.fm scrobbles matched to a SoundCloud track: stores the numeric SoundCloud track ID
- For unmatched scrobbles: stores a composite
artist|titlestring
Queries that assume trackId is always a numeric ID must account for this. The matchedSoundCloudTrackId column provides the canonical SoundCloud ID when a match was found.
Phase Documentation & localhost URLs
The phase docs (docs/PHASE_1_2_IMPLEMENTATION.md, docs/PHASE_3_COMPLETE.md, etc.) are accurate historical records of feature development. They reference localhost URLs throughout — these reflect the development environment and do not affect the deployed application.
Frontend Performance Optimizations
frontend/PERFORMANCE_OPTIMIZATIONS.md documents frontend-specific optimizations (code splitting, React Query, PWA/Service Worker, bundle chunking). MusicDataContext is still available for backward compatibility, but new code should prefer the React Query hooks in useMusicQueries.ts.
License
See LICENSE file for details.
Author
Acknowledgments
- Inspired by Spotify Wrapped, SoundCloud Playback, and volt.fm
- SoundCloud API for music data
- Last.fm for scrobbling and similar artists
- Wikipedia and Google Knowledge Graph for rich descriptions
- Groq API for AI-powered content generation (free tier)
- SerpAPI for comprehensive web search
- TheAudioDB for enhanced artist profiles
- Lyrics.ovh for lyrics data
Additional Documentation
- API Documentation - Full REST API reference
- API Keys Setup - How to obtain and configure API keys
- Features Technical Overview - Interview-ready feature and architecture guide (includes Groq, Last.fm, SerpAPI details)
- Performance Analysis - Codebase performance analysis and optimizations
- Phase 1 & 2 Features - Phase 1 & 2 feature implementations
- Phase 1 & 2 Implementation - Phase 1 & 2 implementation notes
- Phase 3 Complete - Phase 3 implementation summary
- Last.fm Scrobbling - Last.fm scrobbling integration guide
- Last.fm API Setup - Last.fm API key and callback configuration
- Deployment - Deployment guide
Built With
- dockerfile
- java
- react
- spring
- typescript
Log in or sign up for Devpost to join the conversation.