Files
MusicServer/internal/server/statistics_handler.go
T
Sansan a705d7b476 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.
2026-10-07 20:03:03 +02:00

77 lines
3.3 KiB
Go

package server
import (
"context"
"net/http"
"music-server/internal/backend"
"github.com/danielgtaylor/huma/v2"
)
const maxStatsLimit = 100
type limitInput struct {
Limit int `query:"limit" default:"10" minimum:"1" doc:"Number of results. Values above 100 are capped to 100."`
}
func (in *limitInput) capped() int32 {
return int32(min(in.Limit, maxStatsLimit))
}
func (s *Server) registerStatistics(a huma.API) {
st := s.stats
op := func(id, path, summary, description string) route {
return route{op: huma.Operation{
OperationID: id,
Method: http.MethodGet,
Path: "/statistics" + path,
Summary: summary,
Description: description,
Tags: []string{"statistics"},
}}
}
soundtracks := func(id, path, summary, description string,
query func(context.Context, int32) ([]backend.SoundtrackWithSongs, error)) {
register(s, a, op(id, path, summary, description),
func(ctx context.Context, in *limitInput) (*jsonBody[[]backend.SoundtrackWithSongs], error) {
return ok(query(ctx, in.capped()))
})
}
songs := func(id, path, summary, description string,
query func(context.Context, int32) ([]backend.SongInfoForStats, error)) {
register(s, a, op(id, path, summary, description),
func(ctx context.Context, in *limitInput) (*jsonBody[[]backend.SongInfoForStats], error) {
return ok(query(ctx, in.capped()))
})
}
soundtracks("get-most-played-soundtracks", "/soundtracks/most-played", "Most played soundtracks",
"Returns the most played soundtracks with their songs.", st.MostPlayedSoundtracks)
soundtracks("get-least-played-soundtracks", "/soundtracks/least-played", "Least played soundtracks",
"Returns the least played soundtracks with their songs.", st.LeastPlayedSoundtracks)
soundtracks("get-last-played-soundtracks", "/soundtracks/last-played", "Recently played soundtracks",
"Returns the most recently played soundtracks with their songs.", st.LastPlayedSoundtracks)
soundtracks("get-oldest-played-soundtracks", "/soundtracks/oldest-played", "Least recently played soundtracks",
"Returns the soundtracks played longest ago (played at least once) with their songs.", st.OldestPlayedSoundtracks)
songs("get-most-played-songs", "/songs/most-played", "Most played songs",
"Returns the most played songs with their soundtrack.", st.MostPlayedSongs)
songs("get-least-played-songs", "/songs/least-played", "Least played songs",
"Returns the least played songs with their soundtrack.", st.LeastPlayedSongs)
register(s, a, op("get-all-soundtrack-statistics", "/soundtracks", "All soundtracks with statistics",
"Returns every soundtrack with its songs and play counts, sorted by name."),
func(ctx context.Context, _ *struct{}) (*jsonBody[[]backend.SoundtrackWithSongs], error) {
return ok(st.AllSoundtracks(ctx))
})
register(s, a, op("get-never-played-soundtracks", "/soundtracks/never-played", "Never played soundtracks",
"Returns all soundtracks that have never been played, with their songs."),
func(ctx context.Context, _ *struct{}) (*jsonBody[[]backend.SoundtrackWithSongs], error) {
return ok(st.NeverPlayedSoundtracks(ctx))
})
register(s, a, op("get-statistics-summary", "/summary", "Statistics summary", "Returns overall play statistics."),
func(ctx context.Context, _ *struct{}) (*jsonBody[backend.StatisticsSummary], error) {
return ok(st.Summary(ctx))
})
}