843 lines
24 KiB
Go
Executable File
843 lines
24 KiB
Go
Executable File
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Playlist represents a user's playlist
|
|
type Playlist struct {
|
|
Name string `json:"name"`
|
|
Songs []MusicItem `json:"songs"`
|
|
}
|
|
|
|
// PlaylistManager manages all playlists
|
|
type PlaylistManager struct {
|
|
mu sync.RWMutex
|
|
Playlists map[string]*Playlist // Legacy playlists (backward compatible)
|
|
UserPlaylists map[string][]*UserPlaylist // key: user ID, value: user's playlists
|
|
filePath string
|
|
userFilePath string
|
|
}
|
|
|
|
var playlistManager *PlaylistManager
|
|
|
|
// InitPlaylistManager initializes the playlist manager
|
|
func InitPlaylistManager() {
|
|
playlistManager = &PlaylistManager{
|
|
Playlists: make(map[string]*Playlist),
|
|
UserPlaylists: make(map[string][]*UserPlaylist),
|
|
filePath: "./files/playlists.json",
|
|
userFilePath: "./files/user_playlists.json",
|
|
}
|
|
|
|
// Create files directory if it doesn't exist
|
|
os.MkdirAll("./files", 0755)
|
|
|
|
// Load existing playlists (backward compatible)
|
|
playlistManager.loadFromFile()
|
|
|
|
// Load user playlists
|
|
playlistManager.loadUserPlaylists()
|
|
|
|
// Initialize "我喜欢" playlist if it doesn't exist (backward compatible)
|
|
if _, exists := playlistManager.Playlists["favorite"]; !exists {
|
|
playlistManager.Playlists["favorite"] = &Playlist{
|
|
Name: "我喜欢",
|
|
Songs: []MusicItem{},
|
|
}
|
|
playlistManager.saveToFile()
|
|
}
|
|
}
|
|
|
|
// loadFromFile loads playlists from JSON file
|
|
func (pm *PlaylistManager) loadFromFile() error {
|
|
pm.mu.Lock()
|
|
defer pm.mu.Unlock()
|
|
|
|
data, err := os.ReadFile(pm.filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil // File doesn't exist yet, that's ok
|
|
}
|
|
return err
|
|
}
|
|
|
|
return json.Unmarshal(data, &pm.Playlists)
|
|
}
|
|
|
|
// saveToFile saves playlists to JSON file
|
|
// NOTE: Caller must hold the lock!
|
|
func (pm *PlaylistManager) saveToFile() error {
|
|
data, err := json.MarshalIndent(pm.Playlists, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(pm.filePath, data, 0644)
|
|
}
|
|
|
|
// AddToPlaylist adds a song to a playlist
|
|
func (pm *PlaylistManager) AddToPlaylist(playlistName string, song MusicItem) error {
|
|
pm.mu.Lock()
|
|
defer pm.mu.Unlock()
|
|
|
|
playlist, exists := pm.Playlists[playlistName]
|
|
if !exists {
|
|
playlist = &Playlist{
|
|
Name: playlistName,
|
|
Songs: []MusicItem{},
|
|
}
|
|
pm.Playlists[playlistName] = playlist
|
|
}
|
|
|
|
// Check if song already exists in playlist
|
|
for _, s := range playlist.Songs {
|
|
if s.Title == song.Title && s.Artist == song.Artist {
|
|
return fmt.Errorf("song already exists in playlist")
|
|
}
|
|
}
|
|
|
|
playlist.Songs = append(playlist.Songs, song)
|
|
return pm.saveToFile()
|
|
}
|
|
|
|
// RemoveFromPlaylist removes a song from a playlist
|
|
func (pm *PlaylistManager) RemoveFromPlaylist(playlistName string, title, artist string) error {
|
|
pm.mu.Lock()
|
|
defer pm.mu.Unlock()
|
|
|
|
playlist, exists := pm.Playlists[playlistName]
|
|
if !exists {
|
|
return fmt.Errorf("playlist not found")
|
|
}
|
|
|
|
for i, song := range playlist.Songs {
|
|
if song.Title == title && song.Artist == artist {
|
|
playlist.Songs = append(playlist.Songs[:i], playlist.Songs[i+1:]...)
|
|
return pm.saveToFile()
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("song not found in playlist")
|
|
}
|
|
|
|
// GetPlaylist returns a playlist
|
|
func (pm *PlaylistManager) GetPlaylist(playlistName string) (*Playlist, error) {
|
|
pm.mu.RLock()
|
|
defer pm.mu.RUnlock()
|
|
|
|
playlist, exists := pm.Playlists[playlistName]
|
|
if !exists {
|
|
return nil, fmt.Errorf("playlist not found")
|
|
}
|
|
|
|
return playlist, nil
|
|
}
|
|
|
|
// HTTP Handlers
|
|
|
|
// HandleAddToFavorite handles adding a song to favorites (user-specific)
|
|
func HandleAddToFavorite(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Printf("[API] Add to favorite request received from %s\n", r.RemoteAddr)
|
|
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var userID string
|
|
|
|
// First, check for ESP32 device token (X-Device-Token header)
|
|
deviceToken := r.Header.Get("X-Device-Token")
|
|
if deviceToken != "" {
|
|
dm := GetDeviceManager()
|
|
device, err := dm.VerifyToken(deviceToken)
|
|
if err == nil {
|
|
userID = userStore.GetUserIDByUsername(device.Username)
|
|
fmt.Printf("[API] ESP32 device adding favorite (user: %s, userID: %s)\n", device.Username, userID)
|
|
}
|
|
}
|
|
|
|
// If not ESP32, check for web user token (Authorization header)
|
|
if userID == "" {
|
|
token := r.Header.Get("Authorization")
|
|
if token != "" {
|
|
token = strings.TrimPrefix(token, "Bearer ")
|
|
user, err := userStore.GetUserByToken(token)
|
|
if err == nil {
|
|
userID = user.ID
|
|
}
|
|
}
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
fmt.Printf("[API] Error reading body: %v\n", err)
|
|
http.Error(w, "Error reading request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
fmt.Printf("[API] Received body: %s\n", string(body))
|
|
|
|
var song MusicItem
|
|
err = json.Unmarshal(body, &song)
|
|
if err != nil {
|
|
fmt.Printf("[API] Error parsing JSON: %v\n", err)
|
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("[API] Adding to favorites: %s - %s (userID: %s)\n", song.Artist, song.Title, userID)
|
|
|
|
// If user is authenticated, add to user's "我喜欢" playlist
|
|
if userID != "" {
|
|
// Get user's "我喜欢" playlist (first playlist)
|
|
playlists := playlistManager.GetUserPlaylists(userID)
|
|
|
|
// If user has no playlists, initialize them first
|
|
if len(playlists) == 0 {
|
|
fmt.Printf("[API] User %s has no playlists, initializing...\n", userID)
|
|
playlistManager.InitializeUserPlaylists(userID)
|
|
playlists = playlistManager.GetUserPlaylists(userID)
|
|
}
|
|
|
|
if len(playlists) > 0 {
|
|
favoritePlaylist := playlists[0] // "我喜欢" is always the first playlist
|
|
err = playlistManager.AddSongToUserPlaylist(userID, favoritePlaylist.ID, song)
|
|
if err != nil {
|
|
if err.Error() == "song already exists in playlist" {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"status": "success",
|
|
"message": "歌曲已在收藏列表中",
|
|
})
|
|
return
|
|
}
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
} else {
|
|
// Playlist initialization failed
|
|
fmt.Printf("[Error] Failed to initialize playlists for user %s\n", userID)
|
|
http.Error(w, "Failed to initialize user playlists", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
} else {
|
|
// Fallback to global favorite playlist for anonymous users
|
|
err = playlistManager.AddToPlaylist("favorite", song)
|
|
if err != nil {
|
|
if err.Error() == "song already exists in playlist" {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"status": "success",
|
|
"message": "歌曲已在收藏列表中",
|
|
})
|
|
return
|
|
}
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
fmt.Printf("[Info] Added to favorites: %s - %s\n", song.Artist, song.Title)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"status": "success",
|
|
"message": "收藏成功",
|
|
})
|
|
}
|
|
|
|
// HandleRemoveFromFavorite handles removing a song from favorites (user-specific)
|
|
func HandleRemoveFromFavorite(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Printf("[API] Remove from favorite request received from %s\n", r.RemoteAddr)
|
|
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var userID string
|
|
|
|
// First, check for ESP32 device token (X-Device-Token header)
|
|
deviceToken := r.Header.Get("X-Device-Token")
|
|
if deviceToken != "" {
|
|
dm := GetDeviceManager()
|
|
device, err := dm.VerifyToken(deviceToken)
|
|
if err == nil {
|
|
userID = userStore.GetUserIDByUsername(device.Username)
|
|
fmt.Printf("[API] ESP32 device removing favorite (user: %s, userID: %s)\n", device.Username, userID)
|
|
}
|
|
}
|
|
|
|
// If not ESP32, check for web user token (Authorization header)
|
|
if userID == "" {
|
|
token := r.Header.Get("Authorization")
|
|
if token != "" {
|
|
token = strings.TrimPrefix(token, "Bearer ")
|
|
user, err := userStore.GetUserByToken(token)
|
|
if err == nil {
|
|
userID = user.ID
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read song info from body
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, "Error reading request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var song MusicItem
|
|
err = json.Unmarshal(body, &song)
|
|
if err != nil {
|
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
title := song.Title
|
|
artist := song.Artist
|
|
|
|
if title == "" || artist == "" {
|
|
http.Error(w, "Missing title or artist", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("[API] Removing from favorites: %s - %s (userID: %s)\n", artist, title, userID)
|
|
|
|
// If user is authenticated, remove from user's "我喜欢" playlist
|
|
if userID != "" {
|
|
playlists := playlistManager.GetUserPlaylists(userID)
|
|
|
|
// If user has no playlists, nothing to remove
|
|
if len(playlists) == 0 {
|
|
fmt.Printf("[API] User %s has no playlists, nothing to remove\n", userID)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"status": "success",
|
|
"message": "歌曲不在收藏列表中",
|
|
})
|
|
return
|
|
}
|
|
|
|
favoritePlaylist := playlists[0]
|
|
err = playlistManager.RemoveSongFromUserPlaylist(userID, favoritePlaylist.ID, title, artist)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
} else {
|
|
// Fallback to global favorite playlist
|
|
err = playlistManager.RemoveFromPlaylist("favorite", title, artist)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
fmt.Printf("[Info] Removed from favorites: %s - %s\n", artist, title)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"status": "success",
|
|
"message": "取消收藏成功",
|
|
})
|
|
}
|
|
|
|
// HandleGetFavorites handles getting the favorite playlist (user-specific)
|
|
func HandleGetFavorites(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Printf("[API] Get favorites request received from %s\n", r.RemoteAddr)
|
|
|
|
var userID string
|
|
|
|
// First, check for ESP32 device token (X-Device-Token header)
|
|
deviceToken := r.Header.Get("X-Device-Token")
|
|
if deviceToken != "" {
|
|
dm := GetDeviceManager()
|
|
device, err := dm.VerifyToken(deviceToken)
|
|
if err == nil {
|
|
// Device.Username is the username, need to convert to userID
|
|
userID = userStore.GetUserIDByUsername(device.Username)
|
|
fmt.Printf("[API] ESP32 device authenticated: %s (user: %s, userID: %s)\n", device.MAC, device.Username, userID)
|
|
} else {
|
|
fmt.Printf("[API] Invalid device token: %v\n", err)
|
|
}
|
|
}
|
|
|
|
// If not ESP32, check for web user token (Authorization header)
|
|
if userID == "" {
|
|
token := r.Header.Get("Authorization")
|
|
if token != "" {
|
|
token = strings.TrimPrefix(token, "Bearer ")
|
|
user, err := userStore.GetUserByToken(token)
|
|
if err == nil {
|
|
userID = user.ID
|
|
fmt.Printf("[API] Web user authenticated: %s\n", userID)
|
|
}
|
|
}
|
|
}
|
|
|
|
// If user is authenticated, return user's "我喜欢" playlist
|
|
if userID != "" {
|
|
playlists := playlistManager.GetUserPlaylists(userID)
|
|
|
|
// If user has no playlists, initialize them first
|
|
if len(playlists) == 0 {
|
|
fmt.Printf("[API] User %s has no playlists, initializing...\n", userID)
|
|
playlistManager.InitializeUserPlaylists(userID)
|
|
playlists = playlistManager.GetUserPlaylists(userID)
|
|
}
|
|
|
|
if len(playlists) > 0 {
|
|
favoritePlaylist := playlists[0] // "我喜欢" is always the first playlist
|
|
fmt.Printf("[API] Returning user %s favorites: %d songs\n", userID, len(favoritePlaylist.Songs))
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
// Return just the songs array for frontend compatibility
|
|
json.NewEncoder(w).Encode(favoritePlaylist.Songs)
|
|
return
|
|
}
|
|
|
|
// If still no playlists (initialization failed), return empty array
|
|
fmt.Printf("[API] User %s playlists initialization failed, returning empty\n", userID)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode([]MusicItem{})
|
|
return
|
|
}
|
|
|
|
// Only return global favorite playlist if user is NOT authenticated
|
|
fmt.Printf("[API] No authentication found, returning global favorites\n")
|
|
playlist, err := playlistManager.GetPlaylist("favorite")
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode([]MusicItem{}) // Return empty array
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(playlist.Songs)
|
|
}
|
|
|
|
// HandleCheckFavorite checks if a song is in favorites
|
|
func HandleCheckFavorite(w http.ResponseWriter, r *http.Request) {
|
|
title := r.URL.Query().Get("title")
|
|
artist := r.URL.Query().Get("artist")
|
|
|
|
if title == "" || artist == "" {
|
|
http.Error(w, "Missing title or artist", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
playlist, err := playlistManager.GetPlaylist("favorite")
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]bool{"is_favorite": false})
|
|
return
|
|
}
|
|
|
|
for _, song := range playlist.Songs {
|
|
if song.Title == title && song.Artist == artist {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]bool{"is_favorite": true})
|
|
return
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]bool{"is_favorite": false})
|
|
}
|
|
|
|
// User Playlist Management Functions
|
|
|
|
// loadUserPlaylists loads user playlists from JSON file
|
|
func (pm *PlaylistManager) loadUserPlaylists() error {
|
|
pm.mu.Lock()
|
|
defer pm.mu.Unlock()
|
|
|
|
data, err := os.ReadFile(pm.userFilePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil // File doesn't exist yet, that's ok
|
|
}
|
|
return err
|
|
}
|
|
|
|
return json.Unmarshal(data, &pm.UserPlaylists)
|
|
}
|
|
|
|
// saveUserPlaylists saves user playlists to JSON file
|
|
// NOTE: Caller must hold the lock!
|
|
func (pm *PlaylistManager) saveUserPlaylists() error {
|
|
data, err := json.MarshalIndent(pm.UserPlaylists, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(pm.userFilePath, data, 0644)
|
|
}
|
|
|
|
// InitializeUserPlaylists creates default playlists for a new user
|
|
func (pm *PlaylistManager) InitializeUserPlaylists(userID string) {
|
|
pm.mu.Lock()
|
|
defer pm.mu.Unlock()
|
|
|
|
if _, exists := pm.UserPlaylists[userID]; exists {
|
|
return // Already initialized
|
|
}
|
|
|
|
// Create "我喜欢" playlist for the user
|
|
favoritePlaylist := &UserPlaylist{
|
|
ID: generateID(),
|
|
UserID: userID,
|
|
Name: "我喜欢",
|
|
Description: "我喜欢的音乐",
|
|
Songs: []MusicItem{},
|
|
IsPublic: false,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
pm.UserPlaylists[userID] = []*UserPlaylist{favoritePlaylist}
|
|
pm.saveUserPlaylists()
|
|
}
|
|
|
|
// CreateUserPlaylist creates a new playlist for a user
|
|
func (pm *PlaylistManager) CreateUserPlaylist(userID, name, description string) (*UserPlaylist, error) {
|
|
pm.mu.Lock()
|
|
defer pm.mu.Unlock()
|
|
|
|
playlist := &UserPlaylist{
|
|
ID: generateID(),
|
|
UserID: userID,
|
|
Name: name,
|
|
Description: description,
|
|
Songs: []MusicItem{},
|
|
IsPublic: false,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
|
|
pm.UserPlaylists[userID] = append(pm.UserPlaylists[userID], playlist)
|
|
pm.saveUserPlaylists()
|
|
|
|
return playlist, nil
|
|
}
|
|
|
|
// GetUserPlaylists returns all playlists for a user
|
|
func (pm *PlaylistManager) GetUserPlaylists(userID string) []*UserPlaylist {
|
|
pm.mu.RLock()
|
|
defer pm.mu.RUnlock()
|
|
|
|
return pm.UserPlaylists[userID]
|
|
}
|
|
|
|
// GetUserPlaylistByID returns a specific playlist
|
|
func (pm *PlaylistManager) GetUserPlaylistByID(userID, playlistID string) (*UserPlaylist, error) {
|
|
pm.mu.RLock()
|
|
defer pm.mu.RUnlock()
|
|
|
|
playlists, exists := pm.UserPlaylists[userID]
|
|
if !exists {
|
|
return nil, fmt.Errorf("no playlists found for user")
|
|
}
|
|
|
|
for _, playlist := range playlists {
|
|
if playlist.ID == playlistID {
|
|
return playlist, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("playlist not found")
|
|
}
|
|
|
|
// AddSongToUserPlaylist adds a song to a user's playlist
|
|
func (pm *PlaylistManager) AddSongToUserPlaylist(userID, playlistID string, song MusicItem) error {
|
|
pm.mu.Lock()
|
|
defer pm.mu.Unlock()
|
|
|
|
playlists, exists := pm.UserPlaylists[userID]
|
|
if !exists {
|
|
return fmt.Errorf("no playlists found for user")
|
|
}
|
|
|
|
for _, playlist := range playlists {
|
|
if playlist.ID == playlistID {
|
|
// Check if song already exists
|
|
for _, s := range playlist.Songs {
|
|
if s.Title == song.Title && s.Artist == song.Artist {
|
|
return fmt.Errorf("song already exists in playlist")
|
|
}
|
|
}
|
|
|
|
playlist.Songs = append(playlist.Songs, song)
|
|
playlist.UpdatedAt = time.Now()
|
|
return pm.saveUserPlaylists()
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("playlist not found")
|
|
}
|
|
|
|
// RemoveSongFromUserPlaylist removes a song from a user's playlist
|
|
func (pm *PlaylistManager) RemoveSongFromUserPlaylist(userID, playlistID, title, artist string) error {
|
|
pm.mu.Lock()
|
|
defer pm.mu.Unlock()
|
|
|
|
playlists, exists := pm.UserPlaylists[userID]
|
|
if !exists {
|
|
return fmt.Errorf("no playlists found for user")
|
|
}
|
|
|
|
for _, playlist := range playlists {
|
|
if playlist.ID == playlistID {
|
|
for i, song := range playlist.Songs {
|
|
if song.Title == title && song.Artist == artist {
|
|
playlist.Songs = append(playlist.Songs[:i], playlist.Songs[i+1:]...)
|
|
playlist.UpdatedAt = time.Now()
|
|
return pm.saveUserPlaylists()
|
|
}
|
|
}
|
|
return fmt.Errorf("song not found in playlist")
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("playlist not found")
|
|
}
|
|
|
|
// DeleteUserPlaylist deletes a user's playlist
|
|
func (pm *PlaylistManager) DeleteUserPlaylist(userID, playlistID string) error {
|
|
pm.mu.Lock()
|
|
defer pm.mu.Unlock()
|
|
|
|
playlists, exists := pm.UserPlaylists[userID]
|
|
if !exists {
|
|
return fmt.Errorf("no playlists found for user")
|
|
}
|
|
|
|
for i, playlist := range playlists {
|
|
if playlist.ID == playlistID {
|
|
// Don't allow deleting "我喜欢" playlist
|
|
if playlist.Name == "我喜欢" && i == 0 {
|
|
return fmt.Errorf("cannot delete favorite playlist")
|
|
}
|
|
|
|
pm.UserPlaylists[userID] = append(playlists[:i], playlists[i+1:]...)
|
|
return pm.saveUserPlaylists()
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("playlist not found")
|
|
}
|
|
|
|
// User Playlist HTTP Handlers
|
|
|
|
// HandleCreateUserPlaylist handles creating a new playlist
|
|
func HandleCreateUserPlaylist(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
userID := r.Header.Get("X-User-ID")
|
|
if userID == "" {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, "Error reading request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
}
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if req.Name == "" {
|
|
http.Error(w, "Playlist name is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
playlist, err := playlistManager.CreateUserPlaylist(userID, req.Name, req.Description)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("[Info] User %s created playlist: %s\n", userID, req.Name)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(playlist)
|
|
}
|
|
|
|
// HandleGetUserPlaylists handles getting all user playlists
|
|
func HandleGetUserPlaylists(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.Header.Get("X-User-ID")
|
|
if userID == "" {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
playlists := playlistManager.GetUserPlaylists(userID)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(playlists)
|
|
}
|
|
|
|
// HandleAddSongToUserPlaylist handles adding a song to a playlist
|
|
func HandleAddSongToUserPlaylist(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
userID := r.Header.Get("X-User-ID")
|
|
if userID == "" {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
playlistID := r.URL.Query().Get("playlist_id")
|
|
if playlistID == "" {
|
|
http.Error(w, "Missing playlist_id parameter", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, "Error reading request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
var song MusicItem
|
|
if err := json.Unmarshal(body, &song); err != nil {
|
|
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
err = playlistManager.AddSongToUserPlaylist(userID, playlistID, song)
|
|
if err != nil {
|
|
if err.Error() == "song already exists in playlist" {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"status": "success",
|
|
"message": "歌曲已在歌单中",
|
|
})
|
|
return
|
|
}
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("[Info] User %s added song to playlist %s: %s - %s\n", userID, playlistID, song.Artist, song.Title)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"status": "success",
|
|
"message": "添加成功",
|
|
})
|
|
}
|
|
|
|
// HandleRemoveSongFromUserPlaylist handles removing a song from a playlist
|
|
func HandleRemoveSongFromUserPlaylist(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodDelete {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
userID := r.Header.Get("X-User-ID")
|
|
if userID == "" {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
playlistID := r.URL.Query().Get("playlist_id")
|
|
title := r.URL.Query().Get("title")
|
|
artist := r.URL.Query().Get("artist")
|
|
|
|
if playlistID == "" || title == "" || artist == "" {
|
|
http.Error(w, "Missing required parameters", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
err := playlistManager.RemoveSongFromUserPlaylist(userID, playlistID, title, artist)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("[Info] User %s removed song from playlist %s: %s - %s\n", userID, playlistID, artist, title)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"status": "success",
|
|
"message": "移除成功",
|
|
})
|
|
}
|
|
|
|
// HandleDeleteUserPlaylist handles deleting a playlist
|
|
func HandleDeleteUserPlaylist(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodDelete {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
userID := r.Header.Get("X-User-ID")
|
|
if userID == "" {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
playlistID := r.URL.Query().Get("playlist_id")
|
|
if playlistID == "" {
|
|
http.Error(w, "Missing playlist_id parameter", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
err := playlistManager.DeleteUserPlaylist(userID, playlistID)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("[Info] User %s deleted playlist %s\n", userID, playlistID)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{
|
|
"status": "success",
|
|
"message": "删除成功",
|
|
})
|
|
}
|
|
|