Files
vibesonic/internal/web/pages.go
T
2026-08-23 14:13:18 -05:00

258 lines
6.7 KiB
Go

package web
import (
"net/http"
"strconv"
"strings"
"git.noroutes.net/NoRoutes/vibetunes/internal/auth"
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
)
const pageSize = 60
func (h *Web) handleHome(w http.ResponseWriter, r *http.Request) {
user := auth.UserFromContext(r.Context())
albums, err := database.ListAlbums(h.DB, 12, 0)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
var recentlyAdded []AlbumView
for _, a := range albums {
recentlyAdded = append(recentlyAdded, toAlbumView(h.DB, a))
}
var recentlyPlayedAlbums []AlbumView
if user != nil {
history, err := database.ListRecentlyPlayed(h.DB, user.ID, 30)
if err == nil {
seen := map[string]bool{}
for _, entry := range history {
song, err := database.GetSong(h.DB, entry.SongID)
if err != nil || !song.AlbumID.Valid || seen[song.AlbumID.String] {
continue
}
seen[song.AlbumID.String] = true
album, err := database.GetAlbum(h.DB, song.AlbumID.String)
if err != nil {
continue
}
recentlyPlayedAlbums = append(recentlyPlayedAlbums, toAlbumView(h.DB, album))
if len(recentlyPlayedAlbums) >= 12 {
break
}
}
}
}
hasLibrary := len(recentlyAdded) > 0
h.page(w, r, "home", "home", "Home", struct {
HasLibrary bool
RecentlyAdded []AlbumView
RecentlyPlayedAlbums []AlbumView
}{hasLibrary, recentlyAdded, recentlyPlayedAlbums})
}
func (h *Web) handleArtists(w http.ResponseWriter, r *http.Request) {
artists, err := database.ListArtists(h.DB)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
var views []ArtistView
for _, a := range artists {
views = append(views, toArtistView(a))
}
h.page(w, r, "artists", "artists", "Artists", views)
}
func (h *Web) handleArtist(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
artist, err := database.GetArtist(h.DB, id)
if err != nil {
http.NotFound(w, r)
return
}
h.maybeFetchArtistPhoto(artist)
albums, err := database.ListAlbumsByArtist(h.DB, id)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
var views []AlbumView
for _, a := range albums {
views = append(views, toAlbumView(h.DB, a))
}
h.page(w, r, "artist", "artists", artist.Name, struct {
Artist ArtistView
Albums []AlbumView
}{toArtistView(artist), views})
}
func (h *Web) handleAlbums(w http.ResponseWriter, r *http.Request) {
offset := parseOffset(r)
albums, err := database.ListAlbums(h.DB, pageSize+1, offset)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
hasMore := len(albums) > pageSize
if hasMore {
albums = albums[:pageSize]
}
var views []AlbumView
for _, a := range albums {
views = append(views, toAlbumView(h.DB, a))
}
h.page(w, r, "albums", "albums", "Albums", struct {
Albums []AlbumView
HasMore bool
NextOffset int
}{views, hasMore, offset + pageSize})
}
func (h *Web) handleAlbum(w http.ResponseWriter, r *http.Request) {
user := auth.UserFromContext(r.Context())
id := r.PathValue("id")
album, err := database.GetAlbum(h.DB, id)
if err != nil {
http.NotFound(w, r)
return
}
songs, err := database.ListSongsByAlbum(h.DB, id)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
userID := ""
isAdmin := false
if user != nil {
userID = user.ID
isAdmin = user.IsAdmin
}
var rows []SongRow
for _, s := range songs {
rows = append(rows, toSongRow(h.DB, s, album, userID, isAdmin))
}
starred := false
if user != nil {
starred, _ = database.IsStarred(h.DB, user.ID, "album", album.ID)
}
h.page(w, r, "album", "albums", album.Title, struct {
Album AlbumView
Songs []SongRow
Starred bool
}{toAlbumView(h.DB, album), rows, starred})
}
func (h *Web) handleSongs(w http.ResponseWriter, r *http.Request) {
user := auth.UserFromContext(r.Context())
offset := parseOffset(r)
songs, err := database.ListSongs(h.DB, pageSize+1, offset)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
hasMore := len(songs) > pageSize
if hasMore {
songs = songs[:pageSize]
}
userID := ""
isAdmin := false
if user != nil {
userID = user.ID
isAdmin = user.IsAdmin
}
var rows []SongRow
for _, s := range songs {
rows = append(rows, toSongRow(h.DB, s, albumOf(h.DB, s), userID, isAdmin))
}
h.page(w, r, "songs", "songs", "Songs", struct {
Songs []SongRow
HasMore bool
NextOffset int
}{rows, hasMore, offset + pageSize})
}
func (h *Web) handleGenres(w http.ResponseWriter, r *http.Request) {
genres, err := database.ListGenres(h.DB)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
h.page(w, r, "genres", "genres", "Genres", genres)
}
func (h *Web) handleSearch(w http.ResponseWriter, r *http.Request) {
user := auth.UserFromContext(r.Context())
query := strings.TrimSpace(r.URL.Query().Get("q"))
var artistViews []ArtistView
var albumViews []AlbumView
var songRows []SongRow
if query != "" {
like := "%" + query + "%"
artistRows, _ := h.DB.Query(`SELECT DISTINCT a.id, a.name FROM artists a JOIN album_artists aa ON aa.artist_id = a.id WHERE a.name LIKE ? ORDER BY a.name LIMIT 20`, like)
if artistRows != nil {
defer artistRows.Close()
for artistRows.Next() {
var id, name string
if artistRows.Scan(&id, &name) == nil {
artistViews = append(artistViews, ArtistView{ID: id, Name: name})
}
}
}
albumRows, _ := h.DB.Query(`SELECT id FROM albums WHERE title LIKE ? ORDER BY title LIMIT 20`, like)
if albumRows != nil {
defer albumRows.Close()
for albumRows.Next() {
var id string
if albumRows.Scan(&id) == nil {
if album, err := database.GetAlbum(h.DB, id); err == nil {
albumViews = append(albumViews, toAlbumView(h.DB, album))
}
}
}
}
songIDRows, _ := h.DB.Query(`SELECT id FROM songs WHERE title LIKE ? ORDER BY title LIMIT 40`, like)
if songIDRows != nil {
defer songIDRows.Close()
userID := ""
isAdmin := false
if user != nil {
userID = user.ID
isAdmin = user.IsAdmin
}
for songIDRows.Next() {
var id string
if songIDRows.Scan(&id) == nil {
if song, err := database.GetSong(h.DB, id); err == nil {
songRows = append(songRows, toSongRow(h.DB, song, albumOf(h.DB, song), userID, isAdmin))
}
}
}
}
}
h.page(w, r, "search", "search", "Search", struct {
Query string
Artists []ArtistView
Albums []AlbumView
Songs []SongRow
}{query, artistViews, albumViews, songRows})
}
func parseOffset(r *http.Request) int {
n, err := strconv.Atoi(r.URL.Query().Get("offset"))
if err != nil || n < 0 {
return 0
}
return n
}