Web UI: setup wizard, auth pages, library browsing, player, admin, streaming, cover art
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
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
|
||||
}
|
||||
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 *database.Artist
|
||||
Albums []AlbumView
|
||||
}{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 := ""
|
||||
if user != nil {
|
||||
userID = user.ID
|
||||
}
|
||||
var rows []SongRow
|
||||
for _, s := range songs {
|
||||
rows = append(rows, toSongRow(h.DB, s, album, userID))
|
||||
}
|
||||
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 := ""
|
||||
if user != nil {
|
||||
userID = user.ID
|
||||
}
|
||||
var rows []SongRow
|
||||
for _, s := range songs {
|
||||
rows = append(rows, toSongRow(h.DB, s, albumOf(h.DB, s), userID))
|
||||
}
|
||||
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 := ""
|
||||
if user != nil {
|
||||
userID = user.ID
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user