feat: add subsonic token auth and starred endpoints
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -26,7 +27,7 @@ type app struct {
|
||||
|
||||
func NewRouter(cfg config.Config, database *sql.DB, scanService *scanner.Service) http.Handler {
|
||||
application := app{
|
||||
auth: auth.NewService(database),
|
||||
auth: auth.NewService(database, cfg.EncryptionKey),
|
||||
library: library.NewService(database),
|
||||
scanner: scanService,
|
||||
}
|
||||
@@ -80,6 +81,10 @@ func NewRouter(cfg config.Config, database *sql.DB, scanService *scanner.Service
|
||||
authed.Get("/getAlbum.view", application.subsonicAlbumByID)
|
||||
authed.Get("/getSong.view", application.subsonicSongByID)
|
||||
authed.Get("/getRandomSongs.view", application.subsonicRandomSongs)
|
||||
authed.Get("/search3.view", application.subsonicSearch3)
|
||||
authed.Get("/getStarred2.view", application.subsonicStarred2)
|
||||
authed.Get("/star.view", application.subsonicStar)
|
||||
authed.Get("/unstar.view", application.subsonicUnstar)
|
||||
authed.Get("/getScanStatus.view", application.subsonicScanStatus)
|
||||
authed.Get("/startScan.view", application.subsonicStartScan)
|
||||
authed.Get("/getCoverArt.view", application.subsonicCoverArt)
|
||||
@@ -222,7 +227,13 @@ func (a app) subsonicArtists(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (a app) subsonicRandomSongs(w http.ResponseWriter, r *http.Request) {
|
||||
tracks, err := a.library.Tracks(r.Context(), 20)
|
||||
size := 20
|
||||
if raw := strings.TrimSpace(r.URL.Query().Get("size")); raw != "" {
|
||||
if parsed := parsePositiveInt(raw); parsed > 0 {
|
||||
size = parsed
|
||||
}
|
||||
}
|
||||
tracks, err := a.library.Tracks(r.Context(), size)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, subsonic.PingResponse())
|
||||
return
|
||||
@@ -230,6 +241,54 @@ func (a app) subsonicRandomSongs(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, subsonic.RandomSongsResponse(tracks))
|
||||
}
|
||||
|
||||
func (a app) subsonicSearch3(w http.ResponseWriter, r *http.Request) {
|
||||
query := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||
if query == "" {
|
||||
writeJSON(w, http.StatusOK, subsonic.Search3Response(library.SearchResults{}))
|
||||
return
|
||||
}
|
||||
|
||||
count := parsePositiveInt(r.URL.Query().Get("songCount"))
|
||||
if count == 0 {
|
||||
count = 20
|
||||
}
|
||||
|
||||
results, err := a.library.Search(r.Context(), query, count)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, subsonic.ErrorResponse(0, "search failed"))
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, subsonic.Search3Response(results))
|
||||
}
|
||||
|
||||
func (a app) subsonicStarred2(w http.ResponseWriter, r *http.Request) {
|
||||
user := currentUserFromContext(r)
|
||||
results, err := a.library.Starred(r.Context(), user.ID)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, subsonic.ErrorResponse(0, "failed to load starred items"))
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, subsonic.Starred2Response(results))
|
||||
}
|
||||
|
||||
func (a app) subsonicStar(w http.ResponseWriter, r *http.Request) {
|
||||
user := currentUserFromContext(r)
|
||||
if err := a.library.Star(r.Context(), user.ID, readMultiValue(r, "id"), readMultiValue(r, "albumId"), readMultiValue(r, "artistId")); err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, subsonic.ErrorResponse(0, "failed to star items"))
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, subsonic.PingResponse())
|
||||
}
|
||||
|
||||
func (a app) subsonicUnstar(w http.ResponseWriter, r *http.Request) {
|
||||
user := currentUserFromContext(r)
|
||||
if err := a.library.Unstar(r.Context(), user.ID, readMultiValue(r, "id"), readMultiValue(r, "albumId"), readMultiValue(r, "artistId")); err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, subsonic.ErrorResponse(0, "failed to unstar items"))
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, subsonic.PingResponse())
|
||||
}
|
||||
|
||||
func (a app) subsonicArtistByID(w http.ResponseWriter, r *http.Request) {
|
||||
item, err := a.library.ArtistByID(r.Context(), r.URL.Query().Get("id"))
|
||||
if err != nil {
|
||||
@@ -401,6 +460,25 @@ func writeJSON(w http.ResponseWriter, status int, payload any) {
|
||||
_ = json.NewEncoder(w).Encode(payload)
|
||||
}
|
||||
|
||||
func readMultiValue(r *http.Request, key string) []string {
|
||||
values := r.URL.Query()[key]
|
||||
if len(values) > 0 {
|
||||
return values
|
||||
}
|
||||
if single := strings.TrimSpace(r.URL.Query().Get(key)); single != "" {
|
||||
return []string{single}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parsePositiveInt(raw string) int {
|
||||
value, err := strconv.Atoi(strings.TrimSpace(raw))
|
||||
if err != nil || value < 1 {
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func spaFallback(next http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/api") || strings.HasPrefix(r.URL.Path, "/rest") || strings.HasPrefix(r.URL.Path, "/health") {
|
||||
|
||||
Reference in New Issue
Block a user