commit 5a0dab710ca30af1094bd3d437ca83553b4dee8a
parent 0ca68e5b20e1d1b08ee1780a86bf94f3001186a6
Author: brookjeynes <me@brookjeynes.dev>
Date: Tue, 12 May 2026 18:12:27 +1000
feat: create album page
Signed-off-by: brookjeynes <me@brookjeynes.dev>
Diffstat:
4 files changed, 106 insertions(+), 0 deletions(-)
diff --git a/internal/server/album.go b/internal/server/album.go
@@ -0,0 +1,47 @@
+package server
+
+import (
+ "fmt"
+ "lyrics/internal/db"
+ "lyrics/internal/domain"
+ "lyrics/internal/server/htmx"
+ albumview "lyrics/internal/server/ui/views/album"
+ "net/http"
+ "strconv"
+)
+
+func (ss *ServerState) Album(w http.ResponseWriter, r *http.Request) {
+ logger := ss.logger.With("handler", "Album")
+
+ idStr := r.PathValue("id")
+ id, err := strconv.Atoi(idStr)
+ if err != nil {
+ logger.Error("failed to convert album id to integer", "id", idStr, "err", err)
+ htmx.HxError(w, http.StatusBadRequest, err.Error())
+ return
+ }
+
+ slug := r.PathValue("slug")
+ logger = logger.With("id", id, "slug", slug)
+
+ album, err := db.GetAlbum(ss.db, domain.AlbumID(id))
+ if err != nil {
+ logger.Error("failed to get album", "err", err)
+ htmx.HxError(w, http.StatusInternalServerError, fmt.Sprintf("failed to query for album '%s'", slug))
+ return
+ }
+
+ albumTitle := album.Title
+ if album.LatinName != nil {
+ albumTitle = *album.LatinName
+ }
+
+ if domain.Slug(albumTitle) != slug {
+ htmx.HxRedirect(w, http.StatusMovedPermanently, fmt.Sprintf("/album/%d/%s", id, domain.Slug(albumTitle)))
+ return
+ }
+
+ albumview.Album(albumview.AlbumParams{
+ Album: album,
+ }).Render(r.Context(), w)
+}
diff --git a/internal/server/router.go b/internal/server/router.go
@@ -23,6 +23,9 @@ func (ss *ServerState) Router() http.Handler {
})
router.Route("/album", func(router chi.Router) {
+ router.Route("/{id}", func(router chi.Router) {
+ router.Get("/{slug}", ss.Album)
+ })
router.Get("/new", ss.NewAlbum)
router.Post("/new", ss.NewAlbum)
router.Get("/search", ss.SearchAlbum)
diff --git a/internal/server/ui/views/album/album.go b/internal/server/ui/views/album/album.go
@@ -0,0 +1,7 @@
+package album
+
+import "lyrics/internal/domain"
+
+type AlbumParams struct {
+ Album domain.Album
+}
diff --git a/internal/server/ui/views/album/album.templ b/internal/server/ui/views/album/album.templ
@@ -0,0 +1,49 @@
+package album
+
+import (
+ "fmt"
+ "lyrics/internal/server/ui/layouts/base"
+ "time"
+)
+
+templ Album(params AlbumParams) {
+ @base.Base(base.BaseParams{Title: params.Album.Title}) {
+ <article>
+ <header>
+ <hgroup>
+ <h1>
+ { params.Album.Title }
+ if params.Album.LatinName != nil {
+ <span>({ *params.Album.LatinName })</span>
+ }
+ </h1>
+ </hgroup>
+ <ul>
+ for _, artist := range params.Album.Artists {
+ <li>
+ <a href={ fmt.Sprintf("/artist/%d/%s", artist.Artist.ID, artist.Artist.Name) }>
+ { artist.Artist.Name }
+ if artist.Artist.LatinName != nil {
+ <span>({ *artist.Artist.LatinName })</span>
+ }
+ </a>
+ </li>
+ }
+ </ul>
+ <time datetime={ params.Album.ReleaseDate.Format(time.DateOnly) }>{ params.Album.ReleaseDate.Format("Jan 2, 2006") }</time>
+ </header>
+ <ol>
+ for _, song := range params.Album.Songs {
+ <li>
+ <a href={ fmt.Sprintf("/song/%d/%s", song.ID, song.Title) }>
+ { song.Title }
+ if song.LatinTitle != nil {
+ <span>({ *song.LatinTitle })</span>
+ }
+ </a>
+ </li>
+ }
+ </ol>
+ </article>
+ }
+}