lyrics

lyrics
git clone git://brookjeynes.dev/bjeynes/lyrics.git
Log | Files | Refs

commit dfbd40d7a6ef15db10ed805a354c6851349fe93c
parent 95ef2977add2edf137f8cafda5bdb15c59247913
Author: brookjeynes <me@brookjeynes.dev>
Date:   Sun,  3 May 2026 14:46:19 +1000

feat: artist lookup in new song page

Signed-off-by: brookjeynes <me@brookjeynes.dev>

Diffstat:
Minternal/db/artist.go | 31+++++++++++++++++++++++++++++++
Minternal/server/router.go | 7+++++--
Ainternal/server/search-artist.go | 26++++++++++++++++++++++++++
Ainternal/server/ui/partials/artist-search-datalist/artist-search-datalist.go | 7+++++++
Ainternal/server/ui/partials/artist-search-datalist/artist-search-datalist.templ | 9+++++++++
Minternal/server/ui/views/new-song/new-song.templ | 6++++++
6 files changed, 84 insertions(+), 2 deletions(-)

diff --git a/internal/db/artist.go b/internal/db/artist.go @@ -3,6 +3,7 @@ package db import ( "fmt" "lyrics/internal/domain" + "strings" "time" ) @@ -24,3 +25,33 @@ func UpsertArtist(e Execer, artist domain.Artist) error { return nil } + +func SearchArtist(e Execer, artistName string) ([]domain.Artist, error) { + artistName = strings.TrimSpace(artistName) + if artistName == "" { + return nil, nil + } + + rows, err := e.Query(` + select id, name, bio + from artists + where name like ? collate nocase + order by name + limit 20 + `, "%"+artistName+"%") + if err != nil { + return nil, fmt.Errorf("failed to query artists: %w", err) + } + defer rows.Close() + + var results []domain.Artist + for rows.Next() { + var artist domain.Artist + if err := rows.Scan(&artist.ID, &artist.Name, &artist.Bio); err != nil { + return nil, fmt.Errorf("failed to scan artist row: %w", err) + } + results = append(results, artist) + } + + return results, rows.Err() +} diff --git a/internal/server/router.go b/internal/server/router.go @@ -17,8 +17,11 @@ func (ss *ServerState) Router() http.Handler { router.Get("/new", ss.NewSong) router.Post("/new", ss.NewSong) - router.Get("/artist/new", ss.NewArtist) - router.Post("/artist/new", ss.NewArtist) + router.Route("/artist", func(router chi.Router) { + router.Get("/new", ss.NewArtist) + router.Post("/new", ss.NewArtist) + router.Get("/search", ss.SearchArtist) + }) router.Handle("/static/*", ss.Static()) diff --git a/internal/server/search-artist.go b/internal/server/search-artist.go @@ -0,0 +1,26 @@ +package server + +import ( + "lyrics/internal/db" + artistsearchdatalist "lyrics/internal/server/ui/partials/artist-search-datalist" + "net/http" +) + +func (ss *ServerState) SearchArtist(w http.ResponseWriter, r *http.Request) { + logger := ss.logger.With("handler", "SearchArtist") + + switch r.Method { + case http.MethodGet: + artist := r.URL.Query().Get("artist") + + results, err := db.SearchArtist(ss.db, artist) + if err != nil { + logger.Error("failed to query db for artists", "err", err) + return + } + + artistsearchdatalist.ArtistSearchDatalist(artistsearchdatalist.ArtistSearchDatalistParams{ + Artists: results, + }).Render(r.Context(), w) + } +} diff --git a/internal/server/ui/partials/artist-search-datalist/artist-search-datalist.go b/internal/server/ui/partials/artist-search-datalist/artist-search-datalist.go @@ -0,0 +1,7 @@ +package artistsearchdatalist + +import "lyrics/internal/domain" + +type ArtistSearchDatalistParams struct { + Artists []domain.Artist +} diff --git a/internal/server/ui/partials/artist-search-datalist/artist-search-datalist.templ b/internal/server/ui/partials/artist-search-datalist/artist-search-datalist.templ @@ -0,0 +1,9 @@ +package artistsearchdatalist + +templ ArtistSearchDatalist(params ArtistSearchDatalistParams) { + <datalist id="artist-suggestions"> + for _, a := range params.Artists { + <option value={ a.Name }></option> + } + </datalist> +} diff --git a/internal/server/ui/views/new-song/new-song.templ b/internal/server/ui/views/new-song/new-song.templ @@ -26,8 +26,13 @@ templ NewSong(params NewSongParams) { <label> <span>artist</span> <input + hx-get="/artist/search" + hx-trigger="input changed delay:500ms, load" + hx-target="#artist-suggestions" + hx-swap="outerHTML" type="text" name="artist" + list="artist-suggestions" :required="i === 0" pattern={ domain.NoTrailingOrLeadingSpaceRegex.String() } /> @@ -41,6 +46,7 @@ templ NewSong(params NewSongParams) { </li> </template> </ol> + <datalist id="artist-suggestions"></datalist> </fieldset> <label> title: