diff --git a/src/lib/components/AppleMusicSearchModal.svelte b/src/lib/components/AppleMusicSearchModal.svelte new file mode 100644 index 0000000..678c08a --- /dev/null +++ b/src/lib/components/AppleMusicSearchModal.svelte @@ -0,0 +1,441 @@ + + +{#if isOpen} + +{/if} + + \ No newline at end of file diff --git a/src/lib/components/DebugPanel.svelte b/src/lib/components/DebugPanel.svelte new file mode 100644 index 0000000..9543a6b --- /dev/null +++ b/src/lib/components/DebugPanel.svelte @@ -0,0 +1,1058 @@ + + +{#if dev} +
+
isMinimized = !isMinimized}> +

Debug Panel

+ +
+ + {#if !isMinimized} +
+
+ + + +
+ +
+ {#if activeTab === 'nowplaying'} +
+

Connection

+

+ Status: {#if connected} Connected{:else} Disconnected{/if} +

+

+ Last Update: {lastUpdate ? lastUpdate.toLocaleTimeString() : 'Never'} +

+

Next Update: {formatTime(nextUpdateIn)}

+

Interval: {updateInterval}s {trackRemainingTime > 0 ? `(smart mode)` : nowPlaying ? '(fast mode)' : '(normal)'}

+ {#if trackRemainingTime > 0} +

Track Remaining: {formatTime(trackRemainingTime)}

+ {/if} +
+ +
+

Now Playing

+ {#if nowPlaying} +
+

{nowPlaying.album.artist.name}

+

{nowPlaying.album.name}

+ {#if nowPlaying.track} +

{nowPlaying.track}

+ {/if} + {#if nowPlaying.album.appleMusicData} +

+ Preview: {#if nowPlaying.album.appleMusicData.previewUrl} Available{:else} Not found{/if} +

+ {/if} +
+ {:else} +

No music playing

+ {/if} +
+ {/if} + + {#if activeTab === 'albums'} +
+

Recent Albums ({albums.length})

+
+ {#each albums as album} + {@const albumId = `${album.artist.name}:${album.name}`} +
+
expandedAlbumId = expandedAlbumId === albumId ? null : albumId}> +
+
+ {album.name} + by {album.artist.name} +
+ {#if album.isNowPlaying} + NOW + {/if} +
+ {#if album.appleMusicData} + + {album.appleMusicData.tracks?.length || 0} tracks + + + {#if album.appleMusicData.previewUrl} Preview{:else} No preview{/if} + + {:else} + No Apple Music data + {/if} +
+
+ +
+ + {#if expandedAlbumId === albumId} +
+ {#if album.appleMusicData} + {#if album.appleMusicData.searchMetadata} +
Search Information
+ + {/if} + + {#if album.appleMusicData.appleMusicId} +
Apple Music Details
+

Apple Music ID: {album.appleMusicData.appleMusicId}

+ {/if} + + {#if album.appleMusicData.releaseDate} +

Release Date: {album.appleMusicData.releaseDate}

+ {/if} + + {#if album.appleMusicData.recordLabel} +

Label: {album.appleMusicData.recordLabel}

+ {/if} + + {#if album.appleMusicData.genres?.length} +

Genres: {album.appleMusicData.genres.join(', ')}

+ {/if} + + {#if album.appleMusicData.previewUrl} +

Preview URL: {album.appleMusicData.previewUrl}

+ {/if} + + {#if album.appleMusicData.tracks?.length} +
+
Tracks ({album.appleMusicData.tracks.length})
+
+ {#each album.appleMusicData.tracks as track, i} +
+ {i + 1}. + {track.name} + {#if track.durationMs} + {Math.floor(track.durationMs / 60000)}:{String(Math.floor((track.durationMs % 60000) / 1000)).padStart(2, '0')} + {/if} + {#if track.previewUrl} + + {/if} +
+ {/each} +
+
+ {/if} + +
+
Raw Data
+
{JSON.stringify(album.appleMusicData, null, 2)}
+
+ {:else} +
No Apple Music Data
+

This album was not searched in Apple Music or the search is pending.

+ {/if} +
+ {/if} +
+ {/each} +
+
+ {/if} + + {#if activeTab === 'cache'} +
+

Redis Cache Management

+ +
+ + +
+ +
+ + + + + +
+ +
+

Key format: apple:album:ArtistName:AlbumName

+

Example: apple:album:藤井風:Hachikō

+
+
+ {/if} +
+
+ {/if} +
+ + +{/if} + + \ No newline at end of file diff --git a/src/routes/api/admin/debug/apple-music-search/+server.ts b/src/routes/api/admin/debug/apple-music-search/+server.ts new file mode 100644 index 0000000..c82a96b --- /dev/null +++ b/src/routes/api/admin/debug/apple-music-search/+server.ts @@ -0,0 +1,33 @@ +import type { RequestHandler } from './$types' +import { searchAlbumsAndSongs } from '$lib/server/apple-music-client' +import { dev } from '$app/environment' + +export const POST: RequestHandler = async ({ request }) => { + // Only allow in development + if (!dev) { + return new Response('Not found', { status: 404 }) + } + + try { + const { query, storefront } = await request.json() + + if (!query) { + return new Response('Query is required', { status: 400 }) + } + + // Perform the search + const results = await searchAlbumsAndSongs(query, 25, storefront || 'us') + + return new Response(JSON.stringify(results), { + headers: { 'Content-Type': 'application/json' } + }) + } catch (error) { + console.error('Apple Music search error:', error) + return new Response(JSON.stringify({ + error: error instanceof Error ? error.message : 'Unknown error' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }) + } +} \ No newline at end of file diff --git a/src/routes/api/admin/debug/clear-cache/+server.ts b/src/routes/api/admin/debug/clear-cache/+server.ts new file mode 100644 index 0000000..15c28d9 --- /dev/null +++ b/src/routes/api/admin/debug/clear-cache/+server.ts @@ -0,0 +1,50 @@ +import type { RequestHandler } from './$types' +import redis from '../../../redis-client' +import { logger } from '$lib/server/logger' +import { dev } from '$app/environment' + +export const POST: RequestHandler = async ({ request }) => { + // Only allow in development + if (!dev) { + return new Response('Not found', { status: 404 }) + } + + try { + const { key, pattern } = await request.json() + + if (!key && !pattern) { + return new Response('Key or pattern is required', { status: 400 }) + } + + let deleted = 0 + + if (pattern) { + // Delete by pattern (e.g., "apple:album:*") + logger.music('debug', `Clearing cache by pattern: ${pattern}`) + + // Get all matching keys + const keys = await redis.keys(pattern) + + if (keys.length > 0) { + // Delete all matching keys + deleted = await redis.del(...keys) + logger.music('debug', `Deleted ${deleted} keys matching pattern: ${pattern}`) + } + } else if (key) { + // Delete specific key + logger.music('debug', `Clearing cache for key: ${key}`) + deleted = await redis.del(key) + } + + return new Response(JSON.stringify({ + success: true, + deleted, + key: key || pattern + }), { + headers: { 'Content-Type': 'application/json' } + }) + } catch (error) { + logger.error('Failed to clear cache:', error as Error) + return new Response('Internal server error', { status: 500 }) + } +} \ No newline at end of file diff --git a/src/routes/api/admin/debug/redis-keys/+server.ts b/src/routes/api/admin/debug/redis-keys/+server.ts new file mode 100644 index 0000000..59cafc1 --- /dev/null +++ b/src/routes/api/admin/debug/redis-keys/+server.ts @@ -0,0 +1,41 @@ +import type { RequestHandler } from './$types' +import redis from '../../../redis-client' +import { dev } from '$app/environment' + +export const GET: RequestHandler = async ({ url }) => { + // Only allow in development + if (!dev) { + return new Response('Not found', { status: 404 }) + } + + try { + const pattern = url.searchParams.get('pattern') || '*' + + // Get all keys matching pattern + const keys = await redis.keys(pattern) + + // Get values for each key (limit to first 100 to avoid overload) + const keysWithValues = await Promise.all( + keys.slice(0, 100).map(async (key) => { + const value = await redis.get(key) + const ttl = await redis.ttl(key) + return { + key, + value: value ? (value.length > 200 ? value.substring(0, 200) + '...' : value) : null, + ttl + } + }) + ) + + return new Response(JSON.stringify({ + total: keys.length, + showing: keysWithValues.length, + keys: keysWithValues + }), { + headers: { 'Content-Type': 'application/json' } + }) + } catch (error) { + console.error('Failed to get Redis keys:', error) + return new Response('Internal server error', { status: 500 }) + } +} \ No newline at end of file diff --git a/src/routes/api/admin/debug/test-find-album/+server.ts b/src/routes/api/admin/debug/test-find-album/+server.ts new file mode 100644 index 0000000..c4013b3 --- /dev/null +++ b/src/routes/api/admin/debug/test-find-album/+server.ts @@ -0,0 +1,33 @@ +import type { RequestHandler } from './$types' +import { findAlbum } from '$lib/server/apple-music-client' +import { dev } from '$app/environment' + +export const GET: RequestHandler = async ({ url }) => { + if (!dev) { + return new Response('Not found', { status: 404 }) + } + + const artist = url.searchParams.get('artist') || '藤井風' + const album = url.searchParams.get('album') || 'Hachikō' + + console.log(`Testing findAlbum for "${album}" by "${artist}"`) + + try { + const result = await findAlbum(artist, album) + return new Response(JSON.stringify({ + artist, + album, + found: !!result, + result + }), { + headers: { 'Content-Type': 'application/json' } + }) + } catch (error) { + return new Response(JSON.stringify({ + error: error instanceof Error ? error.message : 'Unknown error' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }) + } +} \ No newline at end of file diff --git a/src/routes/api/admin/debug/test-simple-search/+server.ts b/src/routes/api/admin/debug/test-simple-search/+server.ts new file mode 100644 index 0000000..ee5b013 --- /dev/null +++ b/src/routes/api/admin/debug/test-simple-search/+server.ts @@ -0,0 +1,55 @@ +import type { RequestHandler } from './$types' +import { searchAlbumsAndSongs } from '$lib/server/apple-music-client' +import { dev } from '$app/environment' + +export const GET: RequestHandler = async () => { + if (!dev) { + return new Response('Not found', { status: 404 }) + } + + // Test simple search + const searchQuery = '藤井風 Hachikō' + console.log(`Testing simple search for: ${searchQuery}`) + + try { + // Search in both storefronts + const jpResults = await searchAlbumsAndSongs(searchQuery, 5, 'jp') + const usResults = await searchAlbumsAndSongs(searchQuery, 5, 'us') + + // Check if we found the song in either storefront + const jpSongs = jpResults.results?.songs?.data || [] + const usSongs = usResults.results?.songs?.data || [] + + const hachiko = [...jpSongs, ...usSongs].find(s => + s.attributes?.name?.toLowerCase() === 'hachikō' && + s.attributes?.artistName?.includes('藤井') + ) + + return new Response(JSON.stringify({ + searchQuery, + jpSongsFound: jpSongs.length, + usSongsFound: usSongs.length, + hachikoFound: !!hachiko, + hachikoDetails: hachiko ? { + name: hachiko.attributes?.name, + artist: hachiko.attributes?.artistName, + album: hachiko.attributes?.albumName, + preview: hachiko.attributes?.previews?.[0]?.url + } : null, + allSongs: [...jpSongs, ...usSongs].map(s => ({ + name: s.attributes?.name, + artist: s.attributes?.artistName, + album: s.attributes?.albumName + })) + }), { + headers: { 'Content-Type': 'application/json' } + }) + } catch (error) { + return new Response(JSON.stringify({ + error: error instanceof Error ? error.message : 'Unknown error' + }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }) + } +} \ No newline at end of file