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 }