Files
MusicServer/internal/db/repository/statistics.sql.go
T
SansanandClaude Opus 5.5 f79f51ea4c
CI / integration (push) Failing after 14s
CI / docker (push) Failing after 20s
CI / test (push) Successful in 1m10s
CI / publish (push) Skipped
Expose Prometheus metrics on a separate port
Metrics are served on METRICS_PORT (default 2112, "off" to disable), never on
the public port. Besides the Go runtime and process metrics this adds:

- http_requests_total and http_request_duration_seconds per route pattern,
  from an Echo middleware.
- musicserver_songs_requested_total and musicserver_songs_played_total by
  pick mode (random, low, classic). The mode is now stored with each queued
  song (migration 000006, not released yet).
- Library size, sync duration, last successful sync, broken songs from the
  last full sync, and the database connection pool.

Refs #35, #36, #37

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:57:58 +02:00

573 lines
17 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: statistics.sql
package repository
import (
"context"
"time"
"github.com/google/uuid"
)
const countLibrary = `-- name: CountLibrary :one
SELECT
(SELECT COUNT(*) FROM soundtrack WHERE deleted IS NULL) AS soundtracks,
(SELECT COUNT(*) FROM song s JOIN soundtrack st ON st.id = s.soundtrack_id WHERE st.deleted IS NULL) AS songs
`
type CountLibraryRow struct {
Soundtracks int64 `json:"soundtracks"`
Songs int64 `json:"songs"`
}
// Soundtracks and songs in the library, without deleted soundtracks.
func (q *Queries) CountLibrary(ctx context.Context) (CountLibraryRow, error) {
row := q.db.QueryRow(ctx, countLibrary)
var i CountLibraryRow
err := row.Scan(&i.Soundtracks, &i.Songs)
return i, err
}
const getAllSoundtracksWithSongs = `-- name: GetAllSoundtracksWithSongs :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL
GROUP BY g.id, g.soundtrack_name, g.times_played, g.last_played
ORDER BY g.soundtrack_name
`
type GetAllSoundtracksWithSongsRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played"`
Songs []byte `json:"songs"`
}
// Every soundtrack with its songs, by name
func (q *Queries) GetAllSoundtracksWithSongs(ctx context.Context) ([]GetAllSoundtracksWithSongsRow, error) {
rows, err := q.db.Query(ctx, getAllSoundtracksWithSongs)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAllSoundtracksWithSongsRow
for rows.Next() {
var i GetAllSoundtracksWithSongsRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.SoundtrackLastPlayed,
&i.Songs,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getLastPlayedSoundtracks = `-- name: GetLastPlayedSoundtracks :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL AND g.last_played IS NOT NULL
GROUP BY g.id, g.soundtrack_name, g.times_played, g.last_played
ORDER BY g.last_played DESC
LIMIT $1
`
type GetLastPlayedSoundtracksRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played"`
Songs []byte `json:"songs"`
}
// Last played soundtracks (most recently played)
func (q *Queries) GetLastPlayedSoundtracks(ctx context.Context, limit int32) ([]GetLastPlayedSoundtracksRow, error) {
rows, err := q.db.Query(ctx, getLastPlayedSoundtracks, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetLastPlayedSoundtracksRow
for rows.Next() {
var i GetLastPlayedSoundtracksRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.SoundtrackLastPlayed,
&i.Songs,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getLeastPlayedSongsWithSoundtrack = `-- name: GetLeastPlayedSongsWithSoundtrack :many
SELECT
s.soundtrack_id as soundtrack_id,
g.uuid as soundtrack_uuid,
s.uuid as song_uuid,
g.soundtrack_name,
s.song_name,
s.path,
s.times_played,
s.file_name
FROM song s
JOIN soundtrack g ON s.soundtrack_id = g.id
WHERE g.deleted IS NULL
ORDER BY s.times_played ASC, s.song_name
LIMIT $1
`
type GetLeastPlayedSongsWithSoundtrackRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SongUuid uuid.UUID `json:"song_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SongName string `json:"song_name"`
Path string `json:"path"`
TimesPlayed int32 `json:"times_played"`
FileName *string `json:"file_name"`
}
// Least played songs with their soundtrack info
func (q *Queries) GetLeastPlayedSongsWithSoundtrack(ctx context.Context, limit int32) ([]GetLeastPlayedSongsWithSoundtrackRow, error) {
rows, err := q.db.Query(ctx, getLeastPlayedSongsWithSoundtrack, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetLeastPlayedSongsWithSoundtrackRow
for rows.Next() {
var i GetLeastPlayedSongsWithSoundtrackRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SongUuid,
&i.SoundtrackName,
&i.SongName,
&i.Path,
&i.TimesPlayed,
&i.FileName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getLeastPlayedSoundtracksWithSongs = `-- name: GetLeastPlayedSoundtracksWithSongs :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL
GROUP BY g.id, g.soundtrack_name, g.times_played, g.last_played
ORDER BY g.times_played ASC, g.soundtrack_name
LIMIT $1
`
type GetLeastPlayedSoundtracksWithSongsRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played"`
Songs []byte `json:"songs"`
}
// Least played soundtracks with their songs
func (q *Queries) GetLeastPlayedSoundtracksWithSongs(ctx context.Context, limit int32) ([]GetLeastPlayedSoundtracksWithSongsRow, error) {
rows, err := q.db.Query(ctx, getLeastPlayedSoundtracksWithSongs, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetLeastPlayedSoundtracksWithSongsRow
for rows.Next() {
var i GetLeastPlayedSoundtracksWithSongsRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.SoundtrackLastPlayed,
&i.Songs,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getMostPlayedSongsWithSoundtrack = `-- name: GetMostPlayedSongsWithSoundtrack :many
SELECT
s.soundtrack_id as soundtrack_id,
g.uuid as soundtrack_uuid,
s.uuid as song_uuid,
g.soundtrack_name,
s.song_name,
s.path,
s.times_played,
s.file_name
FROM song s
JOIN soundtrack g ON s.soundtrack_id = g.id
WHERE g.deleted IS NULL
ORDER BY s.times_played DESC, s.song_name
LIMIT $1
`
type GetMostPlayedSongsWithSoundtrackRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SongUuid uuid.UUID `json:"song_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SongName string `json:"song_name"`
Path string `json:"path"`
TimesPlayed int32 `json:"times_played"`
FileName *string `json:"file_name"`
}
// Most played songs with their soundtrack info
func (q *Queries) GetMostPlayedSongsWithSoundtrack(ctx context.Context, limit int32) ([]GetMostPlayedSongsWithSoundtrackRow, error) {
rows, err := q.db.Query(ctx, getMostPlayedSongsWithSoundtrack, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetMostPlayedSongsWithSoundtrackRow
for rows.Next() {
var i GetMostPlayedSongsWithSoundtrackRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SongUuid,
&i.SoundtrackName,
&i.SongName,
&i.Path,
&i.TimesPlayed,
&i.FileName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getMostPlayedSoundtracksWithSongs = `-- name: GetMostPlayedSoundtracksWithSongs :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played,
'file_name', s.file_name
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL
GROUP BY g.id, g.soundtrack_name, g.times_played, g.last_played
ORDER BY g.times_played DESC, g.soundtrack_name
LIMIT $1
`
type GetMostPlayedSoundtracksWithSongsRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played"`
Songs []byte `json:"songs"`
}
// Most played soundtracks with their songs
func (q *Queries) GetMostPlayedSoundtracksWithSongs(ctx context.Context, limit int32) ([]GetMostPlayedSoundtracksWithSongsRow, error) {
rows, err := q.db.Query(ctx, getMostPlayedSoundtracksWithSongs, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetMostPlayedSoundtracksWithSongsRow
for rows.Next() {
var i GetMostPlayedSoundtracksWithSongsRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.SoundtrackLastPlayed,
&i.Songs,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getNeverPlayedSoundtracks = `-- name: GetNeverPlayedSoundtracks :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.added,
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL AND g.times_played = 0
GROUP BY g.id, g.soundtrack_name, g.times_played, g.added
ORDER BY g.soundtrack_name
`
type GetNeverPlayedSoundtracksRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
Added time.Time `json:"added"`
Songs []byte `json:"songs"`
}
// Soundtracks that have never been played (times_played = 0)
func (q *Queries) GetNeverPlayedSoundtracks(ctx context.Context) ([]GetNeverPlayedSoundtracksRow, error) {
rows, err := q.db.Query(ctx, getNeverPlayedSoundtracks)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetNeverPlayedSoundtracksRow
for rows.Next() {
var i GetNeverPlayedSoundtracksRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.Added,
&i.Songs,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getOldestPlayedSoundtracks = `-- name: GetOldestPlayedSoundtracks :many
SELECT
g.id as soundtrack_id,
g.uuid as soundtrack_uuid,
g.soundtrack_name,
g.times_played as soundtrack_played,
g.last_played as soundtrack_last_played,
COALESCE(
json_agg(
json_build_object(
'song_uuid', s.uuid,
'soundtrack_uuid', g.uuid,
'song_name', s.song_name,
'path', s.path,
'times_played', s.times_played
)
) FILTER (WHERE s.uuid IS NOT NULL),
'[]'
)::json as songs
FROM soundtrack g
LEFT JOIN song s ON g.id = s.soundtrack_id
WHERE g.deleted IS NULL AND g.last_played IS NOT NULL
GROUP BY g.id, g.soundtrack_name, g.times_played, g.last_played
ORDER BY g.last_played ASC
LIMIT $1
`
type GetOldestPlayedSoundtracksRow struct {
SoundtrackID int32 `json:"soundtrack_id"`
SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"`
SoundtrackName string `json:"soundtrack_name"`
SoundtrackPlayed int32 `json:"soundtrack_played"`
SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played"`
Songs []byte `json:"songs"`
}
// Oldest played soundtracks (least recently played, but has been played at least once)
func (q *Queries) GetOldestPlayedSoundtracks(ctx context.Context, limit int32) ([]GetOldestPlayedSoundtracksRow, error) {
rows, err := q.db.Query(ctx, getOldestPlayedSoundtracks, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetOldestPlayedSoundtracksRow
for rows.Next() {
var i GetOldestPlayedSoundtracksRow
if err := rows.Scan(
&i.SoundtrackID,
&i.SoundtrackUuid,
&i.SoundtrackName,
&i.SoundtrackPlayed,
&i.SoundtrackLastPlayed,
&i.Songs,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getStatisticsSummary = `-- name: GetStatisticsSummary :one
SELECT
COUNT(*) as total_soundtracks,
COALESCE(SUM(CASE WHEN times_played > 0 THEN 1 ELSE 0 END), 0)::bigint as played_soundtracks,
COALESCE(SUM(CASE WHEN times_played = 0 THEN 1 ELSE 0 END), 0)::bigint as never_played_soundtracks,
COALESCE(SUM(times_played), 0)::bigint as total_soundtrack_plays,
COALESCE(AVG(times_played), 0)::float as avg_soundtrack_plays,
COALESCE(MAX(times_played), 0)::bigint as max_soundtrack_plays,
COALESCE(MIN(times_played), 0)::bigint as min_soundtrack_plays
FROM soundtrack
WHERE deleted IS NULL
`
type GetStatisticsSummaryRow struct {
TotalSoundtracks int64 `json:"total_soundtracks"`
PlayedSoundtracks int64 `json:"played_soundtracks"`
NeverPlayedSoundtracks int64 `json:"never_played_soundtracks"`
TotalSoundtrackPlays int64 `json:"total_soundtrack_plays"`
AvgSoundtrackPlays float64 `json:"avg_soundtrack_plays"`
MaxSoundtrackPlays int64 `json:"max_soundtrack_plays"`
MinSoundtrackPlays int64 `json:"min_soundtrack_plays"`
}
// Get statistics summary
func (q *Queries) GetStatisticsSummary(ctx context.Context) (GetStatisticsSummaryRow, error) {
row := q.db.QueryRow(ctx, getStatisticsSummary)
var i GetStatisticsSummaryRow
err := row.Scan(
&i.TotalSoundtracks,
&i.PlayedSoundtracks,
&i.NeverPlayedSoundtracks,
&i.TotalSoundtrackPlays,
&i.AvgSoundtrackPlays,
&i.MaxSoundtrackPlays,
&i.MinSoundtrackPlays,
)
return i, err
}