interface SpotifyUserData {
profile: {
id: string; // Spotify User ID (unique identifier)
email: string; // User's Spotify email
display_name: string; // User's display name
country?: string; // User's country
product?: string; // Subscription type (free/premium)
images?: Array<{ url: string }>; // Profile images
};
refreshToken: string; // For getting new access tokens
topTracks: Array<{ // User's music preferences
id: string;
name: string;
artists: Array<{ id: string; name: string }>;
album: { id: string; name: string; images: Array<{ url: string }> };
popularity: number;
duration_ms: number;
}>;
recentlyPlayed: Array<{ // Recent listening history
track: SpotifyTopTrack;
played_at: string;
}>;
playlists: Array<{ // User's playlists
id: string;
name: string;
description: string;
images: Array<{ url: string }>;
tracks: { total: number };
owner: { id: string; display_name: string };
}>;
}-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
invitation_code VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Spotify connections table
CREATE TABLE spotify_connections (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
spotify_user_id VARCHAR(255) UNIQUE NOT NULL,
spotify_email VARCHAR(255) NOT NULL,
display_name VARCHAR(255),
country VARCHAR(10),
product VARCHAR(50),
profile_image_url TEXT,
refresh_token TEXT NOT NULL, -- ENCRYPT THIS!
access_token TEXT, -- ENCRYPT THIS!
token_expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Music preferences table
CREATE TABLE spotify_music_preferences (
id UUID PRIMARY KEY,
spotify_connection_id UUID REFERENCES spotify_connections(id),
top_tracks JSONB, -- Store as JSON
recently_played JSONB,
playlists JSONB,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);- Encrypt Sensitive Data: Always encrypt
refresh_tokenandaccess_token - Use Environment Variables: Store encryption keys securely
- Token Rotation: Implement automatic token refresh
- Data Retention: Only store what you need
- GDPR Compliance: Allow users to delete their data
// Check if user has existing Spotify connection
const existingConnection = await checkExistingSpotifyConnection(userId);
if (existingConnection) {
// Check if access token is still valid
if (isTokenValid(existingConnection.access_token)) {
// Token is valid, user can use the app
return { connected: true, userData: existingConnection };
} else {
// Token expired, refresh it
const newTokens = await refreshSpotifyToken(existingConnection.refresh_token);
await updateUserTokens(userId, newTokens);
return { connected: true, userData: existingConnection };
}
} else {
// No existing connection, user needs to connect again
return { connected: false };
}async function refreshSpotifyToken(refreshToken: string) {
const response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${Buffer.from(
`${process.env.SPOTIFY_CLIENT_ID}:${process.env.SPOTIFY_CLIENT_SECRET}`
).toString('base64')}`
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken
})
});
const data = await response.json();
return {
access_token: data.access_token,
expires_in: data.expires_in,
refresh_token: data.refresh_token || refreshToken // Keep old one if not provided
};
}// Use top tracks and recently played for recommendations
const userPreferences = {
favoriteGenres: extractGenresFromTracks(userData.topTracks),
favoriteArtists: extractArtistsFromTracks(userData.topTracks),
listeningPatterns: analyzeListeningHistory(userData.recentlyPlayed)
};// Show user's music taste
const userMusicProfile = {
displayName: userData.profile.display_name,
topTracks: userData.topTracks.slice(0, 5),
favoritePlaylists: userData.playlists.slice(0, 3),
listeningStats: {
totalTracks: userData.topTracks.length,
totalPlaylists: userData.playlists.length
}
};// Track user engagement
const userEngagement = {
tracksListened: userData.recentlyPlayed.length,
playlistsCreated: userData.playlists.filter(p => p.owner.id === userData.profile.id).length,
premiumUser: userData.profile.product === 'premium'
};Create a new endpoint for comprehensive data:
// POST /api/v1/auth/spotify-connect-comprehensive
{
spotifyEmail: string;
invitationCode: string;
spotifyData: SpotifyUserData;
}// Middleware to check and refresh tokens
async function ensureValidSpotifyToken(userId: string) {
const connection = await getSpotifyConnection(userId);
if (!connection) {
throw new Error('No Spotify connection found');
}
if (isTokenExpired(connection.token_expires_at)) {
const newTokens = await refreshSpotifyToken(connection.refresh_token);
await updateSpotifyTokens(userId, newTokens);
return newTokens.access_token;
}
return connection.access_token;
}// In your app, check connection status
const { spotifyUser, spotifyUserData } = useSpotifyAuth();
if (spotifyUser && spotifyUserData) {
// User is connected, show their data
return <UserDashboard userData={spotifyUserData} />;
} else {
// User needs to connect
return <SpotifyConnectButton />;
}- Connection Success Rate: How often users successfully connect
- Token Refresh Success Rate: How often token refresh works
- Data Collection Completeness: How much data you're getting per user
- User Engagement: How users interact with their Spotify data
// Handle common Spotify API errors
try {
const userData = await getSpotifyUserData(accessToken);
} catch (error) {
if (error.status === 401) {
// Token expired, refresh it
await refreshUserToken(userId);
} else if (error.status === 403) {
// User revoked access, remove connection
await removeSpotifyConnection(userId);
}
}- Implement the comprehensive data collection (already done in frontend)
- Create the backend API endpoint for storing comprehensive data
- Add token refresh logic to your backend
- Implement reconnection flow in your app
- Add data encryption for sensitive tokens
- Test the complete flow with real users
This setup will give you everything you need to provide a seamless Spotify experience for your users! π