122 lines
3.5 KiB
TypeScript
122 lines
3.5 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import { navidromeGet } from '@/lib/navidrome'
|
|
|
|
const OFFLINE_PLAYLIST_NAME = '__soulsync_offline__'
|
|
|
|
interface SubsonicPlaylist {
|
|
id: string
|
|
name: string
|
|
songCount: number
|
|
coverArt: string
|
|
}
|
|
|
|
interface SubsonicSong {
|
|
id: string
|
|
title: string
|
|
artist: string
|
|
album: string
|
|
albumId: string
|
|
duration: number
|
|
coverArt: string
|
|
}
|
|
|
|
interface PlaylistsResult {
|
|
playlists?: { playlist?: SubsonicPlaylist[] }
|
|
}
|
|
|
|
interface PlaylistResult {
|
|
playlist?: {
|
|
id: string
|
|
name: string
|
|
songCount: number
|
|
entry?: SubsonicSong[]
|
|
}
|
|
}
|
|
|
|
/** Find or create the offline sync playlist, returning its id + songs */
|
|
async function getOrCreateOfflinePlaylist() {
|
|
// Find existing
|
|
const data = await navidromeGet<PlaylistsResult>('getPlaylists.view')
|
|
const existing = (data.playlists?.playlist || []).find(
|
|
(p) => p.name === OFFLINE_PLAYLIST_NAME
|
|
)
|
|
|
|
if (existing) {
|
|
// Fetch full playlist with entries
|
|
const full = await navidromeGet<PlaylistResult>('getPlaylist.view', { id: existing.id })
|
|
const songs = (full.playlist?.entry || []).map((s) => ({
|
|
id: s.id,
|
|
title: s.title,
|
|
artist: s.artist,
|
|
album: s.album,
|
|
albumId: s.albumId,
|
|
duration: s.duration,
|
|
coverArt: s.coverArt,
|
|
}))
|
|
return { id: existing.id, songs }
|
|
}
|
|
|
|
// Create it
|
|
await navidromeGet('createPlaylist.view', { name: OFFLINE_PLAYLIST_NAME })
|
|
// Re-fetch to get its id
|
|
const data2 = await navidromeGet<PlaylistsResult>('getPlaylists.view')
|
|
const created = (data2.playlists?.playlist || []).find(
|
|
(p) => p.name === OFFLINE_PLAYLIST_NAME
|
|
)
|
|
return { id: created?.id || '', songs: [] }
|
|
}
|
|
|
|
/** GET: return offline playlist id + songs */
|
|
export async function GET() {
|
|
try {
|
|
const result = await getOrCreateOfflinePlaylist()
|
|
return NextResponse.json(result)
|
|
} catch (error) {
|
|
console.error('Offline playlist error:', error)
|
|
return NextResponse.json({ error: 'Failed to get offline playlist' }, { status: 502 })
|
|
}
|
|
}
|
|
|
|
/** POST: add a song to the offline playlist */
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const { songId } = await request.json()
|
|
if (!songId) {
|
|
return NextResponse.json({ error: 'songId required' }, { status: 400 })
|
|
}
|
|
const { id: playlistId } = await getOrCreateOfflinePlaylist()
|
|
await navidromeGet('updatePlaylist.view', {
|
|
playlistId,
|
|
songIdToAdd: songId,
|
|
})
|
|
return NextResponse.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Add to offline playlist error:', error)
|
|
return NextResponse.json({ error: 'Failed to add song' }, { status: 502 })
|
|
}
|
|
}
|
|
|
|
/** DELETE: remove a song from the offline playlist by songId */
|
|
export async function DELETE(request: Request) {
|
|
try {
|
|
const { songId } = await request.json()
|
|
if (!songId) {
|
|
return NextResponse.json({ error: 'songId required' }, { status: 400 })
|
|
}
|
|
const { id: playlistId, songs } = await getOrCreateOfflinePlaylist()
|
|
// Subsonic removeFromPlaylist uses songIndexToRemove (0-based index)
|
|
const index = songs.findIndex((s) => s.id === songId)
|
|
if (index === -1) {
|
|
return NextResponse.json({ error: 'Song not in offline playlist' }, { status: 404 })
|
|
}
|
|
await navidromeGet('updatePlaylist.view', {
|
|
playlistId,
|
|
songIndexToRemove: String(index),
|
|
})
|
|
return NextResponse.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Remove from offline playlist error:', error)
|
|
return NextResponse.json({ error: 'Failed to remove song' }, { status: 502 })
|
|
}
|
|
}
|