Files
minstrel/internal/api/types.go
T
bvandeusen 1abaf15051 feat(api): extend ArtistRef/AlbumRef + add HomePayload for M6a
- ArtistRef gains SortName and CoverURL fields
- AlbumRef gains SortTitle field
- albumRefFrom now populates SortTitle from dbq.Album.SortTitle
- artistRefFrom now populates SortName from dbq.Artist.SortName
- New artistRefFromCovered helper builds CoverURL from nullable cover_album_id
- New HomePayload view type for GET /api/home response body
2026-05-01 17:56:19 -04:00

115 lines
4.3 KiB
Go

package api
import "github.com/jackc/pgx/v5/pgtype"
// LoginRequest is the POST /api/auth/login body. Kept boring on purpose —
// web and Flutter both send the same payload.
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
// LoginResponse carries the opaque session token AND the authenticated user
// so the SPA can hydrate its auth store in one round-trip. The token is also
// set as a cookie; SPAs ignore the body token, Flutter uses it as bearer.
type LoginResponse struct {
Token string `json:"token"`
User UserView `json:"user"`
}
// UserView is the /api/* view of a user. Narrower than dbq.User — no hash,
// no api_token, no subsonic_password.
type UserView struct {
ID pgtype.UUID `json:"id"`
Username string `json:"username"`
IsAdmin bool `json:"is_admin"`
}
// Page is the generic pagination envelope used by list and search endpoints.
// Items is always non-nil at JSON encode time — handlers must initialize
// empty slices explicitly so absent results render as [] rather than null.
type Page[T any] struct {
Items []T `json:"items"`
Total int `json:"total"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}
// ArtistRef is the lightweight artist shape used in lists and search results.
// SortName is exposed so the SPA's alphabetical-grid divider can group rows
// by the catalogue sort order ("The Beatles" → "B"). CoverURL is derived from
// a representative album (most-recent with cover_art_path); empty when the
// artist's discography has no cover art.
type ArtistRef struct {
ID string `json:"id"`
Name string `json:"name"`
SortName string `json:"sort_name"`
AlbumCount int `json:"album_count"`
CoverURL string `json:"cover_url"`
}
// AlbumRef is the lightweight album shape used in lists, artist details,
// and search results. SortTitle is exposed so the SPA's alphabetical-grid
// divider can group rows by sort order ("The Wall" → "W"). CoverURL points
// to /api/albums/{id}/cover.
type AlbumRef struct {
ID string `json:"id"`
Title string `json:"title"`
SortTitle string `json:"sort_title"`
ArtistID string `json:"artist_id"`
ArtistName string `json:"artist_name"`
Year int `json:"year,omitempty"`
TrackCount int `json:"track_count"`
DurationSec int `json:"duration_sec"`
CoverURL string `json:"cover_url"`
}
// TrackRef is the lightweight track shape used in album details and search.
// StreamURL points to /api/tracks/{id}/stream which Plan 3 implements.
type TrackRef struct {
ID string `json:"id"`
Title string `json:"title"`
AlbumID string `json:"album_id"`
AlbumTitle string `json:"album_title"`
ArtistID string `json:"artist_id"`
ArtistName string `json:"artist_name"`
TrackNumber int `json:"track_number,omitempty"`
DiscNumber int `json:"disc_number,omitempty"`
DurationSec int `json:"duration_sec"`
StreamURL string `json:"stream_url"`
}
// ArtistDetail is the response body of GET /api/artists/{id}. Embeds the ref
// so callers read id/name/album_count from the same path as list entries.
type ArtistDetail struct {
ArtistRef
Albums []AlbumRef `json:"albums"`
}
// AlbumDetail is the response body of GET /api/albums/{id}.
type AlbumDetail struct {
AlbumRef
Tracks []TrackRef `json:"tracks"`
}
// SearchResponse is the body of GET /api/search. Each facet carries its own
// total + limit + offset; the client sends one shared limit/offset and the
// server applies it uniformly to each facet's LIMIT/OFFSET query.
type SearchResponse struct {
Artists Page[ArtistRef] `json:"artists"`
Albums Page[AlbumRef] `json:"albums"`
Tracks Page[TrackRef] `json:"tracks"`
}
// HomePayload is the response body of GET /api/home. Field counts:
// 50 (recently_added) / 25 (rediscover_albums) / 25 (rediscover_artists)
// / 75 (most_played) / 25 (last_played). All slices are non-nil at JSON
// encode time so empty sections render as [] rather than null.
type HomePayload struct {
RecentlyAddedAlbums []AlbumRef `json:"recently_added_albums"`
RediscoverAlbums []AlbumRef `json:"rediscover_albums"`
RediscoverArtists []ArtistRef `json:"rediscover_artists"`
MostPlayedTracks []TrackRef `json:"most_played_tracks"`
LastPlayedArtists []ArtistRef `json:"last_played_artists"`
}