Files
SansanandClaude Opus 5.5 c70eb4d13c
CI / docker (push) Failing after 20s
CI / test (push) Successful in 23s
CI / integration (push) Successful in 25s
CI / publish (push) Skipped
Add GET /api/v1/download/linux-package
Redirects to the newest MusicPlayer release's _linux.tar.gz, which has the
icon, a menu entry and install.sh. /download/linux keeps serving the bare
.x86_64, which updating in place needs. Releases without the package get 404.

Closes #34

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:40:22 +02:00

108 lines
4.3 KiB
Go

package server
import (
"context"
"net/http"
"music-server/internal/backend"
"github.com/danielgtaylor/huma/v2"
)
type characterInput struct {
Name string `query:"name" required:"true" doc:"File name of the character image, from the character list"`
}
type redirectOutput struct {
Status int
Location string `header:"Location" doc:"Download URL of the asset"`
}
type healthOutput struct {
Body map[string]string
}
func (s *Server) registerMisc(a huma.API) {
get := func(id, path, tag, summary, description string, errs ...int) huma.Operation {
return huma.Operation{
OperationID: id,
Method: http.MethodGet,
Path: path,
Summary: summary,
Description: description,
Tags: []string{tag},
Errors: errs,
}
}
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},
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.")},
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.")},
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.")},
func(ctx context.Context, _ *struct{}) (*jsonBody[[]string], error) {
return ok(s.characters.List())
})
register(s, a, route{op: get("list-character-details", "/character/details", "characters",
"List characters with change times",
"Returns every character image with the time it was last changed, so clients can cache images and download only the changed ones.")},
func(ctx context.Context, _ *struct{}) (*jsonBody[[]backend.CharacterDetails], error) {
return ok(s.characters.ListDetails())
})
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},
func(ctx context.Context, in *characterInput) (*huma.StreamResponse, error) {
f, err := s.characters.Open(in.Name)
if err != nil {
return nil, apiError(err)
}
return streamOpenFile(f, "", false)
})
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)},
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)},
func(ctx context.Context, _ *struct{}) (*jsonBody[[]string], error) {
return ok(r.AssetNames(ctx))
})
download := func(id, path, platform, suffix string) {
op := get(id, path, "download", "Download the latest client for "+platform,
"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},
func(ctx context.Context, _ *struct{}) (*redirectOutput, error) {
url, err := r.AssetURL(ctx, suffix)
if err != nil {
return nil, apiError(err)
}
return &redirectOutput{Status: http.StatusFound, Location: url}, nil
})
}
download("download-client-windows", "/download/windows", "Windows", ".exe")
download("download-client-linux", "/download/linux", "Linux", ".x86_64")
// The package has the icon, a menu entry and install.sh. Releases before 1.9 have none (404).
download("download-client-linux-package", "/download/linux-package", "Linux (package with installer)", "_linux.tar.gz")
}