package api import ( "time" "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"` CoverArtSource *string `json:"cover_art_source"` } // 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"` } // HistoryEvent is one play in a user's listening history. Used by // /api/me/history; one event per row from play_events. type HistoryEvent struct { ID string `json:"id"` // play_events.id PlayedAt time.Time `json:"played_at"` // play_events.started_at Track TrackRef `json:"track"` } // HistoryResponse is the body of GET /api/me/history. type HistoryResponse struct { Events []HistoryEvent `json:"events"` HasMore bool `json:"has_more"` }