Web UI: setup wizard, auth pages, library browsing, player, admin, streaming, cover art

This commit is contained in:
2026-08-22 23:42:09 -05:00
parent aa29f13846
commit e0bd8bc3cb
42 changed files with 3012 additions and 8 deletions
+111
View File
@@ -0,0 +1,111 @@
package web
import (
"database/sql"
"time"
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
)
// View-model structs shape database rows for templates, keeping SQL
// scanning details (sql.Null*) out of the .html files.
type ArtistView struct {
ID string
Name string
}
type AlbumView struct {
ID string
Title string
ArtistName string
Year int64
CoverArtID string
}
type SongRow struct {
ID string
Title string
ArtistName string
AlbumTitle string
AlbumID string
CoverArtID string
TrackNumber int64
DurationSeconds float64
Starred bool
}
func toArtistView(a *database.Artist) ArtistView {
return ArtistView{ID: a.ID, Name: a.Name}
}
func toAlbumView(db *sql.DB, a *database.Album) AlbumView {
v := AlbumView{ID: a.ID, Title: a.Title}
if a.Year.Valid {
v.Year = a.Year.Int64
}
if a.CoverArtID.Valid {
v.CoverArtID = a.CoverArtID.String
}
if a.PrimaryAlbumArtistID.Valid {
if artist, err := database.GetArtist(db, a.PrimaryAlbumArtistID.String); err == nil {
v.ArtistName = artist.Name
}
}
return v
}
// toSongRow builds a display row for a song, given its owning album (may
// be nil for albumless tracks) and the current user (for the starred
// flag; pass "" userID to skip that lookup).
func toSongRow(db *sql.DB, s *database.Song, album *database.Album, userID string) SongRow {
row := SongRow{
ID: s.ID,
Title: s.Title,
DurationSeconds: 0,
}
if s.TrackNumber.Valid {
row.TrackNumber = s.TrackNumber.Int64
}
if names, err := database.ListSongArtistNames(db, s.ID); err == nil && len(names) > 0 {
row.ArtistName = names[0]
for _, n := range names[1:] {
row.ArtistName += ", " + n
}
}
if album != nil {
row.AlbumTitle = album.Title
row.AlbumID = album.ID
if album.CoverArtID.Valid {
row.CoverArtID = album.CoverArtID.String
}
}
if mf, err := database.GetMediaFile(db, s.MediaFileID); err == nil {
row.DurationSeconds = mf.DurationSeconds
}
if userID != "" {
if starred, err := database.IsStarred(db, userID, "song", s.ID); err == nil {
row.Starred = starred
}
}
return row
}
// albumOf loads a song's album, tolerating albumless songs.
func albumOf(db *sql.DB, s *database.Song) *database.Album {
if !s.AlbumID.Valid {
return nil
}
album, err := database.GetAlbum(db, s.AlbumID.String)
if err != nil {
return nil
}
return album
}
type SessionView struct {
ID string
UserAgent string
IPAddress string
CreatedAt time.Time
}