Store each client's play queue in the database

The queue lived in memory and was shared by all clients, so a restart or a
finished sync cleared it. Every client now has its own queue in the new
queue and queue_song tables (added to the undeployed migration 000006).

The music endpoints return the queue ID in the X-Queue-Id header and expect
it back. A request without an ID, or with an ended one, starts a new queue.
/music/reset ends the queue and returns a new ID, and a sync ends all
running queues. Ended queues are kept as history.

The deprecated legacy routes without the /api/v1 prefix are removed.
This commit is contained in:
Sansan committed 2026-10-07 20:03:03 +02:00
1 parent b5dc42b654
commit a705d7b476
28 files changed
+1103 -320

No files matched your search

+9 -9
View File
@@ -22,7 +22,7 @@ type healthOutput struct {
Body map[string]string
}
func (s *Server) registerMisc(a apis) {
func (s *Server) registerMisc(a huma.API) {
get := func(id, path, tag, summary, description string, errs ...int) huma.Operation {
return huma.Operation{
OperationID: id,
@@ -37,30 +37,30 @@ func (s *Server) registerMisc(a apis) {
register(s, a, route{op: get("get-health", "/health", "server", "Health check",
"Returns {\"status\": \"up\"} when the server and database work, otherwise status \"down\" and an error."),
public: true, legacyPath: "/health"},
public: true},
func(ctx context.Context, _ *struct{}) (*healthOutput, error) {
return &healthOutput{Body: s.db.Health(ctx)}, nil
})
register(s, a, route{op: get("get-version", "/version", "server", "Latest server version",
"Returns the server version and its changelog."), legacyPath: "/version"},
"Returns the server version and its changelog.")},
func(ctx context.Context, _ *struct{}) (*jsonBody[backend.VersionData], error) {
return ok(backend.GetLatestVersion(), nil)
})
register(s, a, route{op: get("get-version-history", "/version/history", "server", "Version history",
"Returns all server versions with their changelogs, newest first."), legacyPath: "/version/history"},
"Returns all server versions with their changelogs, newest first.")},
func(ctx context.Context, _ *struct{}) (*jsonBody[[]backend.VersionData], error) {
return ok(backend.GetVersionHistory(), nil)
})
register(s, a, route{op: get("list-characters", "/character/list", "characters", "List characters",
"Returns the file names of all character images."), legacyPath: "/characters"},
"Returns the file names of all character images.")},
func(ctx context.Context, _ *struct{}) (*jsonBody[[]string], error) {
return ok(s.characters.List())
})
image := get("get-character", "/character", "characters", "Character image",
"Returns a character image.", http.StatusBadRequest, http.StatusNotFound)
image.Responses = fileResponses("The image", "image/png", "image/jpeg")
register(s, a, route{op: image, legacyPath: "/character"},
register(s, a, route{op: image},
func(ctx context.Context, in *characterInput) (*huma.StreamResponse, error) {
f, err := s.characters.Open(in.Name)
if err != nil {
@@ -71,12 +71,12 @@ func (s *Server) registerMisc(a apis) {
r := s.releases
register(s, a, route{op: get("get-latest-client-version", "/download", "download", "Latest client version",
"Returns the name of the latest MusicPlayer release.", http.StatusBadGateway), legacyPath: "/download"},
"Returns the name of the latest MusicPlayer release.", http.StatusBadGateway)},
func(ctx context.Context, _ *struct{}) (*jsonBody[string], error) {
return ok(r.LatestVersion(ctx))
})
register(s, a, route{op: get("list-client-assets", "/download/list", "download", "Latest client assets",
"Returns the file names of the latest MusicPlayer release.", http.StatusBadGateway), legacyPath: "/download/list"},
"Returns the file names of the latest MusicPlayer release.", http.StatusBadGateway)},
func(ctx context.Context, _ *struct{}) (*jsonBody[[]string], error) {
return ok(r.AssetNames(ctx))
})
@@ -85,7 +85,7 @@ func (s *Server) registerMisc(a apis) {
"Redirects to the download URL of the latest MusicPlayer release for "+platform+".",
http.StatusNotFound, http.StatusBadGateway)
op.DefaultStatus = http.StatusFound
register(s, a, route{op: op, legacyPath: path},
register(s, a, route{op: op},
func(ctx context.Context, _ *struct{}) (*redirectOutput, error) {
url, err := r.AssetURL(ctx, suffix)
if err != nil {