mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-27 16:08:40 +01:00
322 lines
9.8 KiB
Go
322 lines
9.8 KiB
Go
package nativeapi
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/Masterminds/squirrel"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/navidrome/navidrome/log"
|
|
"github.com/navidrome/navidrome/model"
|
|
)
|
|
|
|
// Library management endpoints (admin only)
|
|
func (n *Router) addLibraryRoute(r chi.Router) {
|
|
r.Route("/library", func(r chi.Router) {
|
|
r.Get("/", getLibraries(n.ds))
|
|
r.Post("/", createLibrary(n.ds))
|
|
r.Route("/{id}", func(r chi.Router) {
|
|
r.Use(parseIDMiddleware)
|
|
r.Get("/", getLibrary(n.ds))
|
|
r.Put("/", updateLibrary(n.ds))
|
|
r.Delete("/", deleteLibrary(n.ds))
|
|
})
|
|
})
|
|
}
|
|
|
|
// User-library association endpoints (admin only)
|
|
func (n *Router) addUserLibraryRoute(r chi.Router) {
|
|
r.Route("/user/{id}/library", func(r chi.Router) {
|
|
r.Use(parseUserIDMiddleware)
|
|
r.Get("/", getUserLibraries(n.ds))
|
|
r.Put("/", setUserLibraries(n.ds))
|
|
})
|
|
}
|
|
|
|
// Middleware to parse library ID from URL
|
|
func parseIDMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
idStr := chi.URLParam(r, "id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
http.Error(w, "Invalid library ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
ctx := context.WithValue(r.Context(), "libraryID", id)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
// Middleware to parse user ID from URL
|
|
func parseUserIDMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
userID := chi.URLParam(r, "id")
|
|
if userID == "" {
|
|
http.Error(w, "Invalid user ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
ctx := context.WithValue(r.Context(), "userID", userID)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
// Library CRUD handlers
|
|
|
|
func getLibraries(ds model.DataStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
libraries, err := ds.Library(r.Context()).GetAll()
|
|
if err != nil {
|
|
log.Error(r.Context(), "Error getting libraries", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(libraries); err != nil {
|
|
log.Error(r.Context(), "Error encoding libraries response", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func getLibrary(ds model.DataStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
libraryID := r.Context().Value("libraryID").(int)
|
|
|
|
library, err := ds.Library(r.Context()).Get(libraryID)
|
|
if err != nil {
|
|
if err == model.ErrNotFound {
|
|
http.Error(w, "Library not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
log.Error(r.Context(), "Error getting library", "libraryID", libraryID, err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(library); err != nil {
|
|
log.Error(r.Context(), "Error encoding library response", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func createLibrary(ds model.DataStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var library model.Library
|
|
if err := json.NewDecoder(r.Body).Decode(&library); err != nil {
|
|
log.Error(r.Context(), "Error decoding library request", err)
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Validate required fields
|
|
if library.Name == "" {
|
|
http.Error(w, "Library name is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if library.Path == "" {
|
|
http.Error(w, "Library path is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := ds.Library(r.Context()).Put(&library); err != nil {
|
|
log.Error(r.Context(), "Error creating library", err)
|
|
http.Error(w, "Failed to create library", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
if err := json.NewEncoder(w).Encode(library); err != nil {
|
|
log.Error(r.Context(), "Error encoding library response", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateLibrary(ds model.DataStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
libraryID := r.Context().Value("libraryID").(int)
|
|
|
|
var library model.Library
|
|
if err := json.NewDecoder(r.Body).Decode(&library); err != nil {
|
|
log.Error(r.Context(), "Error decoding library request", err)
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Ensure the ID matches the URL parameter
|
|
library.ID = libraryID
|
|
|
|
// Validate required fields
|
|
if library.Name == "" {
|
|
http.Error(w, "Library name is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if library.Path == "" {
|
|
http.Error(w, "Library path is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := ds.Library(r.Context()).Put(&library); err != nil {
|
|
if err == model.ErrNotFound {
|
|
http.Error(w, "Library not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
log.Error(r.Context(), "Error updating library", "libraryID", libraryID, err)
|
|
http.Error(w, "Failed to update library", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(library); err != nil {
|
|
log.Error(r.Context(), "Error encoding library response", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func deleteLibrary(ds model.DataStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
libraryID := r.Context().Value("libraryID").(int)
|
|
|
|
if err := ds.Library(r.Context()).Delete(libraryID); err != nil {
|
|
if err == model.ErrNotFound {
|
|
http.Error(w, "Library not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
log.Error(r.Context(), "Error deleting library", "libraryID", libraryID, err)
|
|
http.Error(w, "Failed to delete library", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// User-library association handlers
|
|
|
|
func getUserLibraries(ds model.DataStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.Context().Value("userID").(string)
|
|
|
|
// Verify user exists
|
|
if _, err := ds.User(r.Context()).Get(userID); err != nil {
|
|
if err == model.ErrNotFound {
|
|
http.Error(w, "User not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
log.Error(r.Context(), "Error getting user", "userID", userID, err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
libraries, err := ds.User(r.Context()).GetUserLibraries(userID)
|
|
if err != nil {
|
|
log.Error(r.Context(), "Error getting user libraries", "userID", userID, err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(libraries); err != nil {
|
|
log.Error(r.Context(), "Error encoding user libraries response", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func setUserLibraries(ds model.DataStore) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
userID := r.Context().Value("userID").(string)
|
|
|
|
// Verify user exists
|
|
user, err := ds.User(r.Context()).Get(userID)
|
|
if err != nil {
|
|
if err == model.ErrNotFound {
|
|
http.Error(w, "User not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
log.Error(r.Context(), "Error getting user", "userID", userID, err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var request struct {
|
|
LibraryIDs []int `json:"libraryIds"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
|
log.Error(r.Context(), "Error decoding request", err)
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Admin users get all libraries automatically - don't allow manual assignment
|
|
if user.IsAdmin {
|
|
http.Error(w, "Cannot manually assign libraries to admin users", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Validate library IDs exist
|
|
if len(request.LibraryIDs) > 0 {
|
|
// Use CountAll with IN filter to efficiently check if all library IDs exist
|
|
count, err := ds.Library(r.Context()).CountAll(model.QueryOptions{
|
|
Filters: squirrel.Eq{"id": request.LibraryIDs},
|
|
})
|
|
if err != nil {
|
|
log.Error(r.Context(), "Error counting libraries", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if int(count) != len(request.LibraryIDs) {
|
|
// Some library IDs don't exist - find which ones for better error message
|
|
existingLibraries, err := ds.Library(r.Context()).GetAll(model.QueryOptions{
|
|
Filters: squirrel.Eq{"id": request.LibraryIDs},
|
|
})
|
|
if err != nil {
|
|
log.Error(r.Context(), "Error getting all libraries", err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
libraryMap := make(map[int]bool)
|
|
for _, lib := range existingLibraries {
|
|
libraryMap[lib.ID] = true
|
|
}
|
|
|
|
for _, libID := range request.LibraryIDs {
|
|
if !libraryMap[libID] {
|
|
http.Error(w, fmt.Sprintf("Library ID %d does not exist", libID), http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Regular users must have at least one library
|
|
if len(request.LibraryIDs) == 0 {
|
|
http.Error(w, "At least one library must be assigned to non-admin users", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if err := ds.User(r.Context()).SetUserLibraries(userID, request.LibraryIDs); err != nil {
|
|
log.Error(r.Context(), "Error setting user libraries", "userID", userID, err)
|
|
http.Error(w, "Failed to set user libraries", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Return updated user libraries
|
|
libraries, err := ds.User(r.Context()).GetUserLibraries(userID)
|
|
if err != nil {
|
|
log.Error(r.Context(), "Error getting updated user libraries", "userID", userID, err)
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if err := json.NewEncoder(w).Encode(libraries); err != nil {
|
|
log.Error(r.Context(), "Error encoding user libraries response", err)
|
|
}
|
|
}
|
|
}
|