lyrics

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

commit 005b97f07e4fe0da72cd00b13616b03dc74c7c24
parent 15e95722dcb0bf2b93123299e814dd4d9e4c1fdf
Author: brookjeynes <me@brookjeynes.dev>
Date:   Sun,  3 May 2026 14:21:06 +1000

feat(ui): add create artist page

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

Diffstat:
Ainternal/server/new-artist.go | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minternal/server/new-song.go | 6+++---
Minternal/server/router.go | 3+++
Ainternal/server/ui/views/new-artist/new-artist.go | 3+++
Ainternal/server/ui/views/new-artist/new-artist.templ | 38++++++++++++++++++++++++++++++++++++++
5 files changed, 117 insertions(+), 3 deletions(-)

diff --git a/internal/server/new-artist.go b/internal/server/new-artist.go @@ -0,0 +1,70 @@ +package server + +import ( + "fmt" + "lyrics/internal/domain" + "lyrics/internal/server/htmx" + newartist "lyrics/internal/server/ui/views/new-artist" + "net/http" +) + +type NewArtistForm struct { + Name string `schema:"name"` + Bio string `schema:"bio"` +} + +func parseNewArtistForm(form NewArtistForm) (domain.Artist, error) { + var artist domain.Artist + + // name + if !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.Name) { + return artist, fmt.Errorf("Artist name must not be empty or have leading/trailing whitespace") + } + + // bio + if !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.Bio) { + return artist, fmt.Errorf("Artist biography must not be empty or have leading/trailing whitespace") + } + + artist = domain.Artist{ + Name: form.Name, + Bio: form.Bio, + } + + return artist, nil +} + +func (ss *ServerState) NewArtist(w http.ResponseWriter, r *http.Request) { + logger := ss.logger.With("handler", "NewArtist") + + switch r.Method { + case http.MethodGet: + newartist.NewArtist(newartist.NewArtistParams{}).Render(r.Context(), w) + case http.MethodPost: + err := r.ParseForm() + if err != nil { + logger.Error("failed to parse http form", "err", err) + htmx.HxError(w, http.StatusBadRequest, "Failed to add artist.") + return + } + + var newArtistForm NewArtistForm + err = decoder.Decode(&newArtistForm, r.PostForm) + if err != nil { + logger.Error("failed to decode http form", "err", err) + htmx.HxError(w, http.StatusBadRequest, err.Error()) + return + } + + newArtist, err := parseNewArtistForm(newArtistForm) + if err != nil { + logger.Error("failed to parse new artist form", "err", err) + htmx.HxError(w, http.StatusBadRequest, err.Error()) + return + } + + logger.Debug("added artist", "artist", newArtist) + + htmx.HxRedirect(w, "/") + } +} diff --git a/internal/server/new-song.go b/internal/server/new-song.go @@ -36,7 +36,7 @@ func parseNewSongForm(form NewSongForm) (domain.Song, error) { return song, fmt.Errorf("Title must not be empty or have leading/trailing whitespace") } - if form.Lyrics == "" { + if !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.Lyrics) { return song, fmt.Errorf("Lyrics must not be empty") } @@ -109,7 +109,7 @@ func (ss *ServerState) NewSong(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { logger.Error("failed to parse http form", "err", err) - htmx.HxError(w, http.StatusBadRequest, "Failed to add new song.") + htmx.HxError(w, http.StatusBadRequest, "Failed to add song.") return } @@ -128,7 +128,7 @@ func (ss *ServerState) NewSong(w http.ResponseWriter, r *http.Request) { return } - logger.Debug("added new song", "song", newSong) + logger.Debug("added song", "song", newSong) htmx.HxRedirect(w, "/") } diff --git a/internal/server/router.go b/internal/server/router.go @@ -17,6 +17,9 @@ 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.Handle("/static/*", ss.Static()) return router diff --git a/internal/server/ui/views/new-artist/new-artist.go b/internal/server/ui/views/new-artist/new-artist.go @@ -0,0 +1,3 @@ +package newartist + +type NewArtistParams struct{} diff --git a/internal/server/ui/views/new-artist/new-artist.templ b/internal/server/ui/views/new-artist/new-artist.templ @@ -0,0 +1,38 @@ +package newartist + +import ( + "lyrics/internal/domain" + "lyrics/internal/server/ui/layouts/base" +) + +templ NewArtist(params NewArtistParams) { + @base.Base(base.BaseParams{Title: "new artist"}) { + <h1>Add an artist</h1> + <form + hx-post="/artist/new" + hx-swap="none" + hx-disable-elt="#add-artist-btn" + > + <label> + name: + <input + name="name" + type="text" + pattern={ domain.NoTrailingOrLeadingSpaceRegex.String() } + required + /> + </label> + <label> + biography: + <textarea + name="biography" + rows="20" + spellcheck + ></textarea> + </label> + <button id="add-artist-btn" type="submit"> + add artist + </button> + </form> + } +}