lyrics

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

commit 817f63eaad17ed22317eec173382fb73c1c94ce9
parent ef9ed069961be450b71e869edb46eb00f683448e
Author: brookjeynes <me@brookjeynes.dev>
Date:   Sun,  7 Jun 2026 17:26:09 +1000

refactor: clean up server startup and error handling

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

Diffstat:
M.gitignore | 1+
MMakefile | 2+-
Mcmd/server/main.go | 12+++++++++++-
Minternal/server/album.go | 13++++++++++++-
Minternal/server/artist.go | 13++++++++++++-
Minternal/server/htmx/htmx.go | 2+-
Minternal/server/index.go | 15++++++++++++++-
Minternal/server/new-album.go | 13++++++++++++-
Minternal/server/new-artist.go | 13++++++++++++-
Minternal/server/new-song.go | 13++++++++++++-
Minternal/server/search-album.go | 16++++++++++++++--
Minternal/server/search-artist.go | 16++++++++++++++--
Minternal/server/song.go | 13++++++++++++-
13 files changed, 128 insertions(+), 14 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -3,3 +3,4 @@ static/files *.db *.db-* +build/ diff --git a/Makefile b/Makefile @@ -3,7 +3,7 @@ all: build build: fmt generate assets security vet - go build -o ./build/$(BINARY_NAME) $(CMD) + go build -o ./build/lyrics ./cmd/server/main.go run: fmt generate assets set -a && source ./.env && set +a && go run cmd/server/main.go diff --git a/cmd/server/main.go b/cmd/server/main.go @@ -7,6 +7,7 @@ import ( "lyrics/internal/server" "net/http" "os" + "time" ) func main() { @@ -32,7 +33,16 @@ func main() { logger.Info("Starting server", "addr", config.Core.ListenAddr) - err = http.ListenAndServe(config.Core.ListenAddr, state.Router()) + srv := &http.Server{ + Addr: config.Core.ListenAddr, + Handler: state.Router(), + ReadHeaderTimeout: 5 * time.Second, + ReadTimeout: 10 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + } + + err = srv.ListenAndServe() if err != nil { logger.Error("failed to start server", "err", err) } diff --git a/internal/server/album.go b/internal/server/album.go @@ -1,6 +1,8 @@ package server import ( + "context" + "errors" "fmt" "lyrics/internal/db" "lyrics/internal/domain" @@ -41,7 +43,16 @@ func (ss *ServerState) Album(w http.ResponseWriter, r *http.Request) { return } - albumview.Album(albumview.AlbumParams{ + err = albumview.Album(albumview.AlbumParams{ Album: album, }).Render(r.Context(), w) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + + logger.Error("failed to render album page", "err", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } } diff --git a/internal/server/artist.go b/internal/server/artist.go @@ -1,6 +1,8 @@ package server import ( + "context" + "errors" "fmt" "lyrics/internal/db" "lyrics/internal/domain" @@ -41,10 +43,19 @@ func (ss *ServerState) Artist(w http.ResponseWriter, r *http.Request) { return } - artistview.Artist(artistview.ArtistParams{ + err = artistview.Artist(artistview.ArtistParams{ Artist: artist, Albums: albums, Singles: singles, Featured: featured, }).Render(r.Context(), w) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + + logger.Error("failed to render artist page", "err", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } } diff --git a/internal/server/htmx/htmx.go b/internal/server/htmx/htmx.go @@ -15,7 +15,7 @@ func HxError(w http.ResponseWriter, status int, msg string) { w.Header().Set("HX-Trigger", string(toastJSON)) w.Header().Set("HX-Reswap", "none") w.WriteHeader(status) - w.Write([]byte(msg)) + _, _ = w.Write([]byte(msg)) } func HxRedirect(w http.ResponseWriter, status int, location string) { diff --git a/internal/server/index.go b/internal/server/index.go @@ -1,10 +1,23 @@ package server import ( + "context" + "errors" "lyrics/internal/server/ui/views/index" "net/http" ) func (ss *ServerState) Index(w http.ResponseWriter, r *http.Request) { - index.Index(index.IndexParams{}).Render(r.Context(), w) + logger := ss.logger.With("handler", "Index") + + err := index.Index(index.IndexParams{}).Render(r.Context(), w) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + + logger.Error("failed to render index page", "err", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } } diff --git a/internal/server/new-album.go b/internal/server/new-album.go @@ -1,6 +1,8 @@ package server import ( + "context" + "errors" "fmt" "lyrics/internal/db" "lyrics/internal/domain" @@ -87,7 +89,16 @@ func (ss *ServerState) NewAlbum(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: - newalbum.NewAlbum(newalbum.NewAlbumParams{}).Render(r.Context(), w) + err := newalbum.NewAlbum(newalbum.NewAlbumParams{}).Render(r.Context(), w) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + + logger.Error("failed to render new album page", "err", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } case http.MethodPost: err := r.ParseForm() if err != nil { diff --git a/internal/server/new-artist.go b/internal/server/new-artist.go @@ -1,6 +1,8 @@ package server import ( + "context" + "errors" "fmt" "lyrics/internal/db" "lyrics/internal/domain" @@ -61,7 +63,16 @@ func (ss *ServerState) NewArtist(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: - newartist.NewArtist(newartist.NewArtistParams{}).Render(r.Context(), w) + err := newartist.NewArtist(newartist.NewArtistParams{}).Render(r.Context(), w) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + + logger.Error("failed to render new artist page", "err", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } case http.MethodPost: err := r.ParseForm() if err != nil { diff --git a/internal/server/new-song.go b/internal/server/new-song.go @@ -1,6 +1,8 @@ package server import ( + "context" + "errors" "fmt" "lyrics/internal/db" "lyrics/internal/domain" @@ -142,7 +144,16 @@ func (ss *ServerState) NewSong(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: - newsong.NewSong(newsong.NewSongParams{}).Render(r.Context(), w) + err := newsong.NewSong(newsong.NewSongParams{}).Render(r.Context(), w) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + + logger.Error("failed to render new song page", "err", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } case http.MethodPost: err := r.ParseForm() if err != nil { diff --git a/internal/server/search-album.go b/internal/server/search-album.go @@ -1,7 +1,10 @@ package server import ( + "context" + "errors" "lyrics/internal/db" + "lyrics/internal/server/htmx" albumsearchresults "lyrics/internal/server/ui/partials/album-search-results" similaralbumslist "lyrics/internal/server/ui/partials/similar-albums-list" "net/http" @@ -33,12 +36,21 @@ func (ss *ServerState) SearchAlbum(w http.ResponseWriter, r *http.Request) { switch format { case SearchAlbumFormatSimilar: - similaralbumslist.SimilarAlbumsList(similaralbumslist.SimilarAlbumsListParams{ + err = similaralbumslist.SimilarAlbumsList(similaralbumslist.SimilarAlbumsListParams{ Albums: results, }).Render(r.Context(), w) case SearchAlbumFormatSuggest: - albumsearchresults.AlbumSearchResults(albumsearchresults.AlbumSearchResultsParams{ + err = albumsearchresults.AlbumSearchResults(albumsearchresults.AlbumSearchResultsParams{ Albums: results, }).Render(r.Context(), w) } + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + + logger.Error("failed to render search album partial", "err", err) + htmx.HxError(w, http.StatusInternalServerError, "Failed to render response.") + return + } } diff --git a/internal/server/search-artist.go b/internal/server/search-artist.go @@ -1,7 +1,10 @@ package server import ( + "context" + "errors" "lyrics/internal/db" + "lyrics/internal/server/htmx" artistsearchresults "lyrics/internal/server/ui/partials/artist-search-results" similarartistslist "lyrics/internal/server/ui/partials/similar-artists-list" "net/http" @@ -40,12 +43,21 @@ func (ss *ServerState) SearchArtist(w http.ResponseWriter, r *http.Request) { switch format { case SearchArtistFormatSimilar: - similarartistslist.SimilarArtistsList(similarartistslist.SimilarArtistsListParams{ + err = similarartistslist.SimilarArtistsList(similarartistslist.SimilarArtistsListParams{ Artists: results, }).Render(r.Context(), w) case SearchArtistFormatSuggest: - artistsearchresults.ArtistSearchResults(artistsearchresults.ArtistSearchResultsParams{ + err = artistsearchresults.ArtistSearchResults(artistsearchresults.ArtistSearchResultsParams{ Artists: results, }).Render(r.Context(), w) } + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + + logger.Error("failed to render search artist partial", "err", err) + htmx.HxError(w, http.StatusInternalServerError, "Failed to render response.") + return + } } diff --git a/internal/server/song.go b/internal/server/song.go @@ -1,6 +1,8 @@ package server import ( + "context" + "errors" "fmt" "lyrics/internal/db" "lyrics/internal/domain" @@ -41,5 +43,14 @@ func (ss *ServerState) Song(w http.ResponseWriter, r *http.Request) { return } - song.Song(song.SongParams{Song: s}).Render(r.Context(), w) + err = song.Song(song.SongParams{Song: s}).Render(r.Context(), w) + if err != nil { + if errors.Is(err, context.Canceled) { + return + } + + logger.Error("failed to render song page", "err", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } }