-
Notifications
You must be signed in to change notification settings - Fork 4
SpotifyPlayList-dev-010-fix-ml-recommend-api-v4 #213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
84db0e0
a3d2a2d
516c2fb
b0686eb
bc6f209
4c59c00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Project Overview | ||
|
|
||
| This is a full-stack Spotify playlist application with ML-based song recommendations. The application integrates with the Spotify Web API to provide features like song search, playlist creation, and intelligent music recommendations. | ||
|
|
||
| **Architecture:** | ||
| - Backend: Spring Boot (Java 8) with Maven | ||
| - Frontend: Vue.js 2 with Vue CLI | ||
| - Deployment: Docker Compose | ||
| - Integration: Spotify Web API via spotify-web-api-java library | ||
|
|
||
| ## Development Commands | ||
|
|
||
| ### Backend (Spring Boot) | ||
| ```bash | ||
| # Navigate to backend directory | ||
| cd backend/SpotifyPlayList | ||
|
|
||
| # Build the application | ||
| mvn clean package | ||
|
|
||
| # Run tests | ||
| mvn test | ||
|
|
||
| # Run the application locally | ||
| mvn spring-boot:run | ||
|
|
||
| # Skip tests during build | ||
| mvn clean package -DskipTests | ||
|
|
||
| # Run specific test class | ||
| mvn test -Dtest=AlbumControllerTest | ||
| ``` | ||
|
|
||
| ### Frontend (Vue.js) | ||
| ```bash | ||
| # Navigate to frontend directory | ||
| cd frontend/spotify-playlist-ui | ||
|
|
||
| # Install dependencies | ||
| npm install | ||
|
|
||
| # Run development server | ||
| npm run serve | ||
|
|
||
| # Build for production | ||
| npm run build | ||
|
|
||
| # Run linter | ||
| npm run lint | ||
| ``` | ||
|
|
||
| ### Docker Development | ||
| ```bash | ||
| # Set required environment variables | ||
| export SPOTIFY_CLIENT_SECRET=<your_spotify_client_secret> | ||
| export SPOTIFY_REDIRECT_URL=http://<server_ip>:8080/playlist | ||
| export VUE_APP_BASE_URL=http://localhost:8888/ | ||
|
|
||
| # Run full stack application | ||
| docker-compose up | ||
|
|
||
| # For EC2 deployment with sudo | ||
| sudo -E docker-compose up | ||
|
|
||
| # Clean up Docker resources | ||
| docker rm -f $(docker ps -aq) | ||
| docker rmi -f $(docker images -q) | ||
| docker system prune | ||
| ``` | ||
|
|
||
| ## Application Configuration | ||
|
|
||
| ### Required Spotify API Setup | ||
| 1. Register at [Spotify Developer Platform](https://developer.spotify.com/documentation/web-api) | ||
| 2. Update `spotify.client.secret` and `spotify.client.id` in `backend/SpotifyPlayList/src/main/resources/application.properties` | ||
| 3. Configure redirect URL in Spotify app settings to match `spotify.redirect.url` | ||
| 4. Update `baseURL` in `frontend/spotify-playlist-ui/src/App.vue` for frontend API calls | ||
|
|
||
| ### Key Configuration Files | ||
| - **Backend Config:** `backend/SpotifyPlayList/src/main/resources/application.properties` | ||
| - **Frontend Config:** `frontend/spotify-playlist-ui/src/App.vue` (baseURL configuration) | ||
|
Comment on lines
+81
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoding the |
||
| - **Docker Config:** `docker-compose.yml` with environment variable mappings | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ### Backend Architecture | ||
| - **Controllers:** Handle HTTP requests and OAuth flow (`controller/` package) | ||
| - **Services:** Business logic for Spotify API integration (`service/` package) | ||
| - **Models/DTOs:** Data transfer objects and response models (`model/` package) | ||
| - **Config:** Web configuration and CORS setup (`config/` package) | ||
|
|
||
| Key service classes: | ||
| - `AuthService`: Spotify OAuth authentication and token management | ||
| - `RecommendationsService`: ML-based song recommendation logic | ||
| - `PlayListService`: Playlist creation and management | ||
| - `SearchService`: Artist, album, and track search functionality | ||
|
|
||
| ### Frontend Architecture | ||
| - **Views:** Main application pages (`views/` directory) | ||
| - **Components:** Reusable UI components (`components/` directory) | ||
| - **Router:** Vue Router configuration (`router/index.js`) | ||
|
|
||
| Key views: | ||
| - `GetRecommendation.vue`: ML recommendation interface | ||
| - `CreatePlayList.vue`: Playlist creation functionality | ||
| - Search views for albums, artists, and tracks | ||
|
|
||
| ## API Endpoints | ||
|
|
||
| - **Backend API:** http://localhost:8888/swagger-ui.html | ||
| - **Frontend UI:** http://localhost:8080 | ||
| - **Production ports:** Backend on 8888, Frontend on 8080 | ||
|
|
||
| ## Testing | ||
|
|
||
| The project includes comprehensive test coverage: | ||
| - **Unit Tests:** Service layer tests with Mockito | ||
| - **Integration Tests:** Controller tests for API endpoints | ||
| - **Development Tests:** Manual API testing classes in `test/java/.../dev/` | ||
|
|
||
| Run backend tests with `mvn test` or specific test classes with `mvn test -Dtest=<TestClassName>`. | ||
|
|
||
| ## Dependencies | ||
|
|
||
| ### Key Backend Dependencies | ||
| - Spring Boot 2.4.5 (Web, Test, RestTemplate) | ||
| - spotify-web-api-java 8.3.6 (Legacy - used for auth/playlist/search only) | ||
| - **Direct HTTP Integration**: Recommendations API uses RestTemplate for direct Spotify API calls | ||
| - Swagger 2.7.0 (API documentation) | ||
| - Mockito 5.2.0 (Testing) | ||
| - Lombok (Code generation) | ||
|
|
||
| ## Important Architecture Notes | ||
|
|
||
| ### Recommendation API Implementation | ||
| The recommendation functionality (`RecommendationsService`) has been **modernized to use direct HTTP calls** instead of the spotify-web-api-java library due to compatibility issues: | ||
|
|
||
| - **New Classes:** | ||
| - `SpotifyHttpClient` - HTTP request builder and client utilities | ||
| - `SpotifyRecommendationsResponse` - Complete DTOs for Spotify API responses with all fields | ||
| - `LegacyRecommendationsResponse` - Frontend-compatible response format | ||
| - `RecommendationsResponseMapper` - Comprehensive mapping between formats | ||
| - `RecommendationsValidator` - Response validation and data integrity checks | ||
| - `SpotifyErrorHandler` - Enhanced error handling with detailed error messages | ||
| - `SpotifyApiException` - Custom exception with status codes and context | ||
|
|
||
| - **Modified Services:** | ||
| - `RecommendationsService` - Now uses RestTemplate + response mapper | ||
| - `RecommendationsController` - Updated to use legacy response format | ||
| - `WebConfig` - Added RestTemplate bean with error handler | ||
|
|
||
| - **Endpoints Affected:** `/recommend/` and `/recommend/playlist/{id}` | ||
| - **Frontend Compatibility:** ✅ Complete - maintains exact response structure | ||
| - Preserves `tracks.tracks[]` array structure | ||
| - Maintains `externalUrls.externalUrls.spotify` nested format | ||
| - Converts `preview_url` to `previewUrl` camelCase | ||
| - All existing frontend code works without changes | ||
|
|
||
| ### Enhanced Response Mapping (Phase 4) | ||
| **Complete Field Coverage:** | ||
| - ✅ All Spotify API fields mapped: `restrictions`, `linked_from`, `available_markets` | ||
| - ✅ Enhanced error responses with helpful context and user-friendly messages | ||
| - ✅ Comprehensive validation for data integrity and frontend compatibility | ||
| - ✅ Edge case handling: null fields, empty responses, malformed data | ||
| - ✅ Robust error categorization: auth errors, rate limits, bad requests | ||
| - ✅ 17 comprehensive tests covering all scenarios and error conditions | ||
|
|
||
| ### Key Frontend Dependencies | ||
| - Vue.js 2.6.14 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vue.js 2 has reached its End of Life (EOL) on December 31, 2023. Continuing to use an EOL version is a security risk as it will no longer receive security updates or bug fixes. It is strongly recommended to prioritize migrating the frontend application to Vue 3 to ensure the project's security and long-term maintainability. |
||
| - Vue Router 3.5.1 | ||
| - Axios 1.6.8 (HTTP client) | ||
| - SweetAlert 2.1.2 (UI notifications) | ||
|
|
||
| ## Quick Update Shortcuts | ||
|
|
||
| ### Adding Information to CLAUDE.md | ||
| Use this shortcut pattern to quickly add important information: | ||
|
|
||
| ```bash | ||
| # Quick CLAUDE.md update pattern: | ||
| # 1. Identify the section (Architecture, Dependencies, Commands, etc.) | ||
| # 2. Use Edit tool with specific section markers | ||
| # 3. Always maintain existing structure | ||
|
|
||
| # Example: Adding new architecture notes | ||
| Edit CLAUDE.md -> Find "## Important Architecture Notes" -> Add after existing content | ||
|
|
||
| # Example: Adding new dependencies | ||
| Edit CLAUDE.md -> Find "### Key Backend Dependencies" -> Add to list | ||
|
|
||
| # Example: Adding new commands | ||
| Edit CLAUDE.md -> Find "### Backend (Spring Boot)" -> Add new command with description | ||
| ``` | ||
|
|
||
| ### Common Update Patterns: | ||
| 1. **New Service/Class**: Add to "Important Architecture Notes" section | ||
| 2. **New Command**: Add to appropriate "Development Commands" section | ||
| 3. **Configuration Change**: Update "Application Configuration" section | ||
| 4. **Dependency Change**: Update "Dependencies" section | ||
| 5. **Endpoint Change**: Update "API Endpoints" section | ||
|
|
||
| ### Template for Architecture Updates: | ||
| ```markdown | ||
| - **New Feature/Service**: Brief description | ||
| - `ClassName` - What it does and why it's important | ||
| - Key methods or functionality | ||
| - Integration points with existing services | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
|
|
||
| Problematic Areas: | ||
| - RecommendationsService:42 - Uses spotify-web-api-java library's | ||
| GetRecommendationsRequest.execute() | ||
| - RecommendationsService:94 - Same issue for playlist-based recommendations | ||
| - Both methods rely on se.michaelthelin.spotify classes that are failing | ||
|
|
||
| Current Flow: | ||
| 1. Frontend sends POST to /recommend/ or GET to /recommend/playlist/{id} | ||
| 2. RecommendationsController calls RecommendationsService | ||
| 3. Service uses Java library to build and execute Spotify API requests | ||
| 4. Returns se.michaelthelin.spotify.model_objects.specification.Recommendations object | ||
|
|
||
| Implementation Plan | ||
|
|
||
| Phase 1: Create Direct HTTP Client Infrastructure | ||
| 1. Add HTTP Dependencies: Include RestTemplate or WebClient configuration in Spring | ||
| Boot | ||
| 2. Create Response DTOs: Build custom response classes to replace Recommendations | ||
| object | ||
| 3. Create Request Builder: Utility class to construct proper Spotify API URLs and | ||
| headers | ||
|
|
||
| Phase 2: Replace Service Methods | ||
| 1. Replace getRecommendation(): | ||
| - Build HTTP GET request to https://api.spotify.com/v1/recommendations | ||
| - Map GetRecommendationsDto parameters to query string | ||
| - Handle OAuth token from AuthService | ||
| 2. Replace getRecommendationWithPlayList(): | ||
| - Keep existing playlist analysis logic (calculating averages) | ||
| - Replace only the final Spotify API call with direct HTTP | ||
|
|
||
| Phase 3: Maintain Existing Interfaces | ||
| 1. Controller Compatibility: Keep same endpoints and request/response structure | ||
| 2. Frontend Compatibility: No changes needed to Vue.js components | ||
| 3. DTO Compatibility: Reuse existing GetRecommendationsDto and | ||
| GetRecommendationsWithFeatureDto | ||
|
|
||
| Phase 4: Response Mapping | ||
| 1. Custom Response Objects: Create POJOs matching Spotify API JSON response | ||
| 2. Backward Compatibility: Ensure response structure matches what frontend expects | ||
| 3. Error Handling: Map HTTP errors to existing exception handling | ||
|
|
||
| Key Technical Details | ||
|
|
||
| Spotify API Endpoint: | ||
| - URL: GET https://api.spotify.com/v1/recommendations | ||
| - Auth: Bearer token from existing AuthService | ||
| - Query Parameters: Map from existing DTOs | ||
|
|
||
| Required Changes: | ||
| - RecommendationsService.java: Replace library calls with HTTP clients | ||
| - Add new response DTOs under model/dto/Response/ | ||
| - Keep all existing controller endpoints unchanged | ||
| - Maintain integration with AuthService for token management | ||
|
|
||
| Benefits: | ||
| - Direct control over API calls | ||
| - No dependency on potentially broken Java library | ||
| - Easier debugging and customization | ||
| - Better error handling and logging | ||
|
|
||
| This approach isolates the fix to recommendation functionality only, preserving all | ||
| other Spotify integrations and maintaining full backward compatibility with the | ||
| frontend. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,14 @@ | ||||||||||||||||||
| package com.yen.SpotifyPlayList.controller; | ||||||||||||||||||
|
|
||||||||||||||||||
| import com.yen.SpotifyPlayList.exception.SpotifyApiException; | ||||||||||||||||||
| import com.yen.SpotifyPlayList.model.dto.GetRecommendationsDto; | ||||||||||||||||||
| import com.yen.SpotifyPlayList.model.dto.Response.LegacyRecommendationsResponse; | ||||||||||||||||||
| import com.yen.SpotifyPlayList.service.RecommendationsService; | ||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||||||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||||||
| import org.springframework.http.ResponseEntity; | ||||||||||||||||||
| import org.springframework.web.bind.annotation.*; | ||||||||||||||||||
| import se.michaelthelin.spotify.model_objects.specification.Recommendations; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Slf4j | ||||||||||||||||||
| @RestController | ||||||||||||||||||
|
|
@@ -21,23 +22,31 @@ public class RecommendationsController { | |||||||||||||||||
| public ResponseEntity getRecommendation(@RequestBody GetRecommendationsDto getRecommendationsDto) { | ||||||||||||||||||
| try { | ||||||||||||||||||
| log.info("(getRecommendation) getRecommendationsDto = " + getRecommendationsDto.toString()); | ||||||||||||||||||
| Recommendations recommendations = recommendationsService.getRecommendation(getRecommendationsDto); | ||||||||||||||||||
| LegacyRecommendationsResponse recommendations = recommendationsService.getRecommendation(getRecommendationsDto); | ||||||||||||||||||
| return ResponseEntity.status(HttpStatus.OK).body(recommendations); | ||||||||||||||||||
| } catch (SpotifyApiException e) { | ||||||||||||||||||
| log.error("getRecommendation Spotify API error: {}", e.getMessage()); | ||||||||||||||||||
| HttpStatus status = HttpStatus.valueOf(e.getStatusCode()); | ||||||||||||||||||
| return ResponseEntity.status(status).body(e.getMessage()); | ||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||
| log.error("getRecommendation error : " + e); | ||||||||||||||||||
| return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); | ||||||||||||||||||
| log.error("getRecommendation unexpected error: {}", e.getMessage()); | ||||||||||||||||||
| return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); | ||||||||||||||||||
|
Comment on lines
31
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exposing raw exception messages to the client via
Suggested change
|
||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| @GetMapping("/playlist/{playListId}") | ||||||||||||||||||
| public ResponseEntity getRecommendationWithPlayList(@PathVariable("playListId") String playListId) { | ||||||||||||||||||
| try { | ||||||||||||||||||
| log.info("(getRecommendationWithPlayList) playListId = " + playListId); | ||||||||||||||||||
| Recommendations recommendations = recommendationsService.getRecommendationWithPlayList(playListId); | ||||||||||||||||||
| LegacyRecommendationsResponse recommendations = recommendationsService.getRecommendationWithPlayList(playListId); | ||||||||||||||||||
| return ResponseEntity.status(HttpStatus.OK).body(recommendations); | ||||||||||||||||||
| } catch (SpotifyApiException e) { | ||||||||||||||||||
| log.error("getRecommendationWithPlayList Spotify API error: {}", e.getMessage()); | ||||||||||||||||||
| HttpStatus status = HttpStatus.valueOf(e.getStatusCode()); | ||||||||||||||||||
| return ResponseEntity.status(status).body(e.getMessage()); | ||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||
| log.error("getRecommendationWithPlayList error : " + e); | ||||||||||||||||||
| return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); | ||||||||||||||||||
| log.error("getRecommendationWithPlayList unexpected error: {}", e.getMessage()); | ||||||||||||||||||
| return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); | ||||||||||||||||||
|
Comment on lines
47
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the other endpoint, exposing raw exception messages via
Suggested change
|
||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The placeholder
<server_ip>in theSPOTIFY_REDIRECT_URLmight be confusing for local development setup. It would be clearer to specifylocalhostfor local environments and add a note that this needs to be replaced with the actual server IP for deployment. For example:export SPOTIFY_REDIRECT_URL=http://localhost:8080/playlist # Use localhost for local dev, replace with server IP for deployment.