diff --git a/app.js b/app.js index 6427517..dcd1e7b 100644 --- a/app.js +++ b/app.js @@ -2,14 +2,14 @@ const express = require('express'); const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const dotenv = require('dotenv'); -const cors = require('cors'); +const cors = require('./middleware/cors'); const authRoutes = require('./routes/authRoutes'); const playlistRoutes = require('./routes/playlistRoutes'); dotenv.config(); const app = express(); -app.use(cors()); +app.use(cors); app.use(bodyParser.json()); app.use(cookieParser()); diff --git a/controllers/authController.js b/controllers/authController.js index 2d145ad..a727bb5 100644 --- a/controllers/authController.js +++ b/controllers/authController.js @@ -12,13 +12,16 @@ const callback = async (req, res) => { const data = await spotifyApi.authorizationCodeGrant(code); const { access_token, refresh_token } = data.body; + console.log('Granted Scopes:', data.body.scope); + + spotifyApi.setAccessToken(access_token); spotifyApi.setRefreshToken(refresh_token); res.cookie('spotify_access_token', access_token, { httpOnly: true }); - res.redirect('/playlist/'); + res.redirect('https://main.d1n7z7zw3v28b1.amplifyapp.com/home'); } catch (error) { - res.status(500).json({ error: error.message }); + res.redirect('https://main.d1n7z7zw3v28b1.amplifyapp.com/login?error=auth_failed'); } }; diff --git a/controllers/playlistController.js b/controllers/playlistController.js index 6d733fb..692bb08 100644 --- a/controllers/playlistController.js +++ b/controllers/playlistController.js @@ -10,18 +10,50 @@ const getPlaylists = async (req, res) => { } }; +const getLikedSongs = async (req, res) => { + try { + const likedSongs = await spotifyService.getLikedSongs(); + res.json(likedSongs); + } catch (error) { + console.error('Error in getLikedSongs controller:', error); + res.status(error.statusCode || 500).json({ + error: error.message || 'An error occurred while fetching liked songs' + }); + } +}; + +const getPlaylistSongs = async (req, res) => { + try { + const { playlistId } = req.params; + const playlist = await spotifyService.getPlaylist(playlistId); + const tracks = await spotifyService.getPlaylistTracks(playlistId); + + res.json({ + playlist: playlist, + songs: tracks.items.map(item => item.track) + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}; + const createPlaylist = async (req, res) => { try { + console.log('Create playlist request body:', req.body); + console.log('User ID:', req.query.userId); const { seedTrackId } = req.body; - const userId = req.query.userId; - const playlistId = await spotifyService.createPlaylistFromSeedTrack(userId, seedTrackId); + const playlistId = await spotifyService.createPlaylistFromSeedTrack(req.query.userId, seedTrackId); res.json({ message: 'Playlist created successfully', playlistId }); } catch (error) { + console.log('Error in createPlaylist:', error); res.status(500).json({ error: error.message }); } }; + module.exports = { getPlaylists, + getLikedSongs, + getPlaylistSongs, createPlaylist, }; diff --git a/images/1.jpg b/images/1.jpg new file mode 100644 index 0000000..461df1a Binary files /dev/null and b/images/1.jpg differ diff --git a/images/2.jpg b/images/2.jpg new file mode 100644 index 0000000..720ac85 Binary files /dev/null and b/images/2.jpg differ diff --git a/images/3.jpg b/images/3.jpg new file mode 100644 index 0000000..7ceb9cc Binary files /dev/null and b/images/3.jpg differ diff --git a/images/4.jpg b/images/4.jpg new file mode 100644 index 0000000..a8d316b Binary files /dev/null and b/images/4.jpg differ diff --git a/middleware/authMiddleware.js b/middleware/authMiddleware.js new file mode 100644 index 0000000..ff5880c --- /dev/null +++ b/middleware/authMiddleware.js @@ -0,0 +1,12 @@ +const auth = (req, res, next) => { + const token = req.cookies.spotify_access_token; + + if (!token) { + return res.status(401).json({ error: 'Authentication required' }); + } + + req.spotifyToken = token; + next(); + }; + + module.exports = auth; \ No newline at end of file diff --git a/middleware/cors.js b/middleware/cors.js new file mode 100644 index 0000000..681b690 --- /dev/null +++ b/middleware/cors.js @@ -0,0 +1,10 @@ +const cors = require('cors'); + +const corsOptions = { + origin: ['http://localhost:5173', 'https://main.d1n7z7zw3v28b1.amplifyapp.com'], + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'] +}; + +module.exports = cors(corsOptions); \ No newline at end of file diff --git a/routes/playlistRoutes.js b/routes/playlistRoutes.js index b3c436a..e0fc5f3 100644 --- a/routes/playlistRoutes.js +++ b/routes/playlistRoutes.js @@ -1,9 +1,16 @@ const express = require('express'); +const spotifyApi = require('../utils/spotifyApi'); const playlistController = require('../controllers/playlistController'); +const auth = require('../middleware/authMiddleware'); const router = express.Router(); +router.get('/liked-songs', playlistController.getLikedSongs); router.get('/', playlistController.getPlaylists); -router.post('/create', playlistController.createPlaylist); +router.get('/:playlistId', playlistController.getPlaylistSongs); +router.post('/create', auth, playlistController.createPlaylist); + + + module.exports = router; diff --git a/services/spotifyService.js b/services/spotifyService.js index 175e561..4075d7d 100644 --- a/services/spotifyService.js +++ b/services/spotifyService.js @@ -1,5 +1,21 @@ const spotifyApi = require('../utils/spotifyApi'); +const path = require('path'); +const playlistImages = [ + path.join(__dirname, '../images/1.jpg'), + path.join(__dirname, '../images/2.jpg'), + path.join(__dirname, '../images/3.jpg'), + path.join(__dirname, '../images/4.jpg') +]; + + +const convertImageToBase64 = async (imagePath) => { + const fs = require('fs').promises; + const buffer = await fs.readFile(imagePath); + return buffer.toString('base64'); +}; + + const getAuthUrl = () => { const scopes = ['user-library-read', 'playlist-modify-private', 'playlist-modify-public']; return spotifyApi.createAuthorizeURL(scopes, 'state-key'); @@ -9,17 +25,78 @@ const setAccessToken = (accessToken) => { spotifyApi.setAccessToken(accessToken); }; -const getUserPlaylists = async (offset = 0, limit = 20) => { - const response = await spotifyApi.getUserPlaylists({ offset, limit }); +const getUserPlaylists = async (offset = 0) => { + const playlistResponse = await spotifyApi.getUserPlaylists({ offset }); + + const likedSongsResponse = await spotifyApi.getMySavedTracks(); + + return { + playlists: playlistResponse.body, + likedSongs: likedSongsResponse.body + }; +}; + +const getLikedSongs = async () => { + try { + const response = await spotifyApi.getMySavedTracks({ + limit: 50, + offset: 0 + }); + return response.body; + } catch (error) { + console.error('Error fetching liked songs:', error); + if (error.statusCode) { + throw new Error(`Spotify API error: ${error.statusCode} - ${error.message}`); + } + throw error; + } +}; + + +const getPlaylist = async (playlistId) => { + const response = await spotifyApi.getPlaylist(playlistId); + return response.body; +}; + +const getPlaylistTracks = async (playlistId) => { + const response = await spotifyApi.getPlaylistTracks(playlistId); return response.body; }; const getTrackRecommendations = async (seedTrackId) => { + const seedTrackFeatures = await spotifyApi.getAudioFeaturesForTrack(seedTrackId); + const response = await spotifyApi.getRecommendations({ seed_tracks: [seedTrackId], - limit: 10, + limit: 100, + target_instrumentalness: seedTrackFeatures.body.instrumentalness, + target_acousticness: seedTrackFeatures.body.acousticness, + min_instrumentalness: Math.max(0, seedTrackFeatures.body.instrumentalness - 0.2), + max_instrumentalness: Math.min(1, seedTrackFeatures.body.instrumentalness + 0.2), + target_key: seedTrackFeatures.body.key, + target_mode: seedTrackFeatures.body.mode, + target_time_signature: seedTrackFeatures.body.time_signature, + min_popularity: 20, }); - return response.body.tracks; + + const tracks = response.body.tracks; + let totalDurationMs = 0; + const TARGET_DURATION_MS = 60 * 60 * 1000; + const MARGIN_MS = 2 * 60 * 1000; + const selectedTracks = []; + + for (const track of tracks) { + if (totalDurationMs + track.duration_ms <= TARGET_DURATION_MS + MARGIN_MS) { + selectedTracks.push(track); + totalDurationMs += track.duration_ms; + } + + if (totalDurationMs >= TARGET_DURATION_MS - MARGIN_MS) { + break; + } + } + + return selectedTracks; }; const createPlaylist = async (userId, name, description, trackUris) => { @@ -31,6 +108,12 @@ const createPlaylist = async (userId, name, description, trackUris) => { const playlistId = playlistResponse.body.id; await spotifyApi.addTracksToPlaylist(playlistId, trackUris); + const randomImageIndex = Math.floor(Math.random() * playlistImages.length); + const imagePath = playlistImages[randomImageIndex]; + + const imageData = await convertImageToBase64(imagePath); + await spotifyApi.uploadCustomPlaylistCoverImage(playlistId, imageData); + return playlistId; }; @@ -41,7 +124,7 @@ const createPlaylistFromSeedTrack = async (userId, seedTrackId) => { const recommendations = await getTrackRecommendations(seedTrackId); const trackUris = recommendations.map(track => track.uri); - const playlistName = 'Music for You'; + const playlistName = 'Groovz'; const description = `Similar songs to ${seedTrackName}`; const playlistId = await createPlaylist(userId, playlistName, description, trackUris); @@ -52,6 +135,9 @@ module.exports = { getAuthUrl, setAccessToken, getUserPlaylists, + getLikedSongs, + getPlaylist, + getPlaylistTracks, getTrackRecommendations, createPlaylistFromSeedTrack, }; diff --git a/utils/spotifyApi.js b/utils/spotifyApi.js index 4ac3a5c..81542b7 100644 --- a/utils/spotifyApi.js +++ b/utils/spotifyApi.js @@ -1,9 +1,9 @@ const SpotifyWebApi = require('spotify-web-api-node'); const spotifyApi = new SpotifyWebApi({ - clientId: process.env.SPOTIFY_CLIENT_ID, - clientSecret: process.env.SPOTIFY_CLIENT_SECRET, - redirectUri: process.env.SPOTIFY_REDIRECT_URI, + clientId: '5e3eef3570b74a37af3438268b820e32', + clientSecret: 'ecda63e51490449d9c94b26f9fd9571a', + redirectUri: 'https://groovz-backend-js.onrender.com/auth/callback', }); module.exports = spotifyApi; \ No newline at end of file