diff --git a/backend/app/api/endpoints/youtube_music.py b/backend/app/api/endpoints/youtube_music.py index 44e7889..66deeb3 100644 --- a/backend/app/api/endpoints/youtube_music.py +++ b/backend/app/api/endpoints/youtube_music.py @@ -1,3 +1,6 @@ +import asyncio +from functools import partial + from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy import select @@ -50,9 +53,12 @@ async def import_youtube_playlist( detail="Free tier limited to 1 playlist. Upgrade to Pro for unlimited.", ) - # Fetch tracks from YouTube Music + # Fetch tracks from YouTube Music (run sync ytmusicapi in thread) + loop = asyncio.get_event_loop() try: - playlist_name, playlist_image, raw_tracks = get_playlist_tracks(data.url) + playlist_name, playlist_image, raw_tracks = await loop.run_in_executor( + None, partial(get_playlist_tracks, data.url) + ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception: @@ -102,8 +108,11 @@ async def search_youtube_music( if not data.query.strip(): raise HTTPException(status_code=400, detail="Query cannot be empty") + loop = asyncio.get_event_loop() try: - results = search_track(data.query.strip()) + results = await loop.run_in_executor( + None, partial(search_track, data.query.strip()) + ) except Exception: raise HTTPException(status_code=500, detail="Failed to search YouTube Music") diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b03e2f2..9b20440 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,7 +4,6 @@ import Layout from './components/Layout' import Landing from './pages/Landing' import Login from './pages/Login' import Register from './pages/Register' -import SpotifyCallback from './pages/SpotifyCallback' import Dashboard from './pages/Dashboard' import Playlists from './pages/Playlists' import PlaylistDetail from './pages/PlaylistDetail' @@ -33,7 +32,6 @@ function AppRoutes() { } /> } /> } /> - } /> - {/* Connected Accounts */} -
-

Connected Accounts

-
-
- - - - Spotify - {user?.spotify_connected ? ( - - ) : ( - - )} - - {user?.spotify_connected ? 'Connected' : 'Not connected'} - -
-
-
- {/* Recent Recommendations */} {recentRecs.length > 0 && (
diff --git a/frontend/src/pages/Landing.tsx b/frontend/src/pages/Landing.tsx index e5c62e5..f8db516 100644 --- a/frontend/src/pages/Landing.tsx +++ b/frontend/src/pages/Landing.tsx @@ -5,7 +5,7 @@ const features = [ { icon: ListMusic, title: 'Import Your Music', - description: 'Connect Spotify and import your playlists to build your taste profile.', + description: 'Import your playlists from YouTube Music, Last.fm, or paste your songs to build your taste profile.', }, { icon: Sparkles, diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 0eb1fae..6d13a5b 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { Disc3, Mail, Lock, Loader2 } from 'lucide-react' import { useAuth } from '../lib/auth' -import { login as apiLogin, getSpotifyAuthUrl } from '../lib/api' +import { login as apiLogin } from '../lib/api' export default function Login() { const [email, setEmail] = useState('') @@ -28,15 +28,6 @@ export default function Login() { } } - const handleSpotifyLogin = async () => { - try { - const { url } = await getSpotifyAuthUrl() - window.location.href = url - } catch { - setError('Could not connect to Spotify') - } - } - return (
{/* Header */} @@ -113,22 +104,6 @@ export default function Login() { -
-
- or -
-
- - -

Don't have an account?{' '} diff --git a/frontend/src/pages/Playlists.tsx b/frontend/src/pages/Playlists.tsx index c1cdd5d..c01e3bb 100644 --- a/frontend/src/pages/Playlists.tsx +++ b/frontend/src/pages/Playlists.tsx @@ -1,17 +1,13 @@ import { useState, useEffect } from 'react' import { Link } from 'react-router-dom' -import { ListMusic, Plus, Loader2, Music, ChevronRight, Download, X, Play, Link2, ClipboardPaste } from 'lucide-react' -import { getPlaylists, getSpotifyPlaylists, importSpotifyPlaylist, importYouTubePlaylist, previewLastfm, importLastfm, importPastedSongs, type PlaylistResponse, type SpotifyPlaylistItem, type LastfmPreviewResponse } from '../lib/api' +import { ListMusic, Loader2, Music, ChevronRight, Download, X, Play, Link2, ClipboardPaste } from 'lucide-react' +import { getPlaylists, importYouTubePlaylist, previewLastfm, importLastfm, importPastedSongs, type PlaylistResponse, type LastfmPreviewResponse } from '../lib/api' export default function Playlists() { const [playlists, setPlaylists] = useState([]) - const [spotifyPlaylists, setSpotifyPlaylists] = useState([]) - const [showImport, setShowImport] = useState(false) const [showYouTubeImport, setShowYouTubeImport] = useState(false) const [youtubeUrl, setPlayUrl] = useState('') const [importingYouTube, setImportingYouTube] = useState(false) - const [importing, setImporting] = useState(null) - const [loadingSpotify, setLoadingSpotify] = useState(false) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [showLastfmImport, setShowLastfmImport] = useState(false) @@ -40,19 +36,6 @@ export default function Playlists() { } } - const openImportModal = async () => { - setShowImport(true) - setLoadingSpotify(true) - try { - const data = await getSpotifyPlaylists() - setSpotifyPlaylists(data) - } catch { - setError('Failed to load Spotify playlists. Make sure your Spotify account is connected.') - } finally { - setLoadingSpotify(false) - } - } - const handleYouTubeImport = async () => { if (!youtubeUrl.trim()) return setImportingYouTube(true) @@ -122,19 +105,6 @@ export default function Playlists() { .split('\n') .filter((line) => line.trim().length > 0).length - const handleImport = async (playlistId: string) => { - setImporting(playlistId) - try { - const imported = await importSpotifyPlaylist(playlistId) - setPlaylists((prev) => [...prev, imported]) - setSpotifyPlaylists((prev) => prev.filter((p) => p.id !== playlistId)) - } catch (err: any) { - setError(err.response?.data?.detail || 'Failed to import playlist') - } finally { - setImporting(null) - } - } - if (loading) { return (

@@ -151,13 +121,6 @@ export default function Playlists() {

Manage your imported playlists

-

No playlists yet

- Import your playlists from Spotify or YouTube Music to start getting personalized music recommendations + Import your playlists from YouTube Music, Last.fm, or paste your songs to start getting personalized music recommendations

-
)} - {/* Import Modal */} - {showImport && ( -
-
-
-

Import from Spotify

- -
- -
- {loadingSpotify ? ( -
- -
- ) : spotifyPlaylists.length === 0 ? ( -
-

No playlists found on Spotify

-
- ) : ( -
- {spotifyPlaylists.map((sp) => ( -
-
- {sp.image_url ? ( - {sp.name} - ) : ( - - )} -
-
-

{sp.name}

-

- {sp.track_count} tracks · {sp.owner} -

-
- -
- ))} -
- )} -
-
-
- )}
) } diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx index 613b5d0..cae57a4 100644 --- a/frontend/src/pages/Register.tsx +++ b/frontend/src/pages/Register.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { Disc3, Mail, Lock, User, Loader2 } from 'lucide-react' import { useAuth } from '../lib/auth' -import { register as apiRegister, getSpotifyAuthUrl } from '../lib/api' +import { register as apiRegister } from '../lib/api' export default function Register() { const [name, setName] = useState('') @@ -29,15 +29,6 @@ export default function Register() { } } - const handleSpotifyLogin = async () => { - try { - const { url } = await getSpotifyAuthUrl() - window.location.href = url - } catch { - setError('Could not connect to Spotify') - } - } - return (
{/* Header */} @@ -133,22 +124,6 @@ export default function Register() { -
-
- or -
-
- - -

Already have an account?{' '} diff --git a/frontend/src/pages/TasteProfilePage.tsx b/frontend/src/pages/TasteProfilePage.tsx index 9d2297b..ee38bca 100644 --- a/frontend/src/pages/TasteProfilePage.tsx +++ b/frontend/src/pages/TasteProfilePage.tsx @@ -93,7 +93,7 @@ export default function TasteProfilePage() {

Genre DNA

{profile.genre_breakdown.length === 0 ? (

- No genre data yet. Import playlists with Spotify to see your genre breakdown. + No genre data yet. Import playlists to see your genre breakdown.

) : (