Skip to content

Commits #3

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down
7 changes: 5 additions & 2 deletions controllers/authController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
};

Expand Down
36 changes: 34 additions & 2 deletions controllers/playlistController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Binary file added images/1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions middleware/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -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;
10 changes: 10 additions & 0 deletions middleware/cors.js
Original file line number Diff line number Diff line change
@@ -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);
9 changes: 8 additions & 1 deletion routes/playlistRoutes.js
Original file line number Diff line number Diff line change
@@ -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;
96 changes: 91 additions & 5 deletions services/spotifyService.js
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -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) => {
Expand All @@ -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;
};

Expand All @@ -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);
Expand All @@ -52,6 +135,9 @@ module.exports = {
getAuthUrl,
setAccessToken,
getUserPlaylists,
getLikedSongs,
getPlaylist,
getPlaylistTracks,
getTrackRecommendations,
createPlaylistFromSeedTrack,
};
6 changes: 3 additions & 3 deletions utils/spotifyApi.js
Original file line number Diff line number Diff line change
@@ -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;