lyrics

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

edit-artist.go (5098B)


      1 package server
      2 
      3 import (
      4 	"context"
      5 	"errors"
      6 	"fmt"
      7 	"lyrics/internal/db"
      8 	"lyrics/internal/domain"
      9 	"lyrics/internal/server/htmx"
     10 	"lyrics/internal/server/routes"
     11 	editartist "lyrics/internal/server/ui/views/edit-artist"
     12 	"net/http"
     13 	"strconv"
     14 )
     15 
     16 type EditArtistForm struct {
     17 	Name                string               `schema:"artist"`
     18 	Bio                 string               `schema:"biography"`
     19 	LatinName           string               `schema:"latin-name"`
     20 	Disambiguation      string               `schema:"disambiguation"`
     21 	MusicBrainzArtistID domain.MusicBrainzID `schema:"musicbrainz-artist-id"`
     22 }
     23 
     24 func parseEditArtistForm(form EditArtistForm) (domain.Artist, error) {
     25 	var artist domain.Artist
     26 
     27 	// name
     28 	if !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.Name) {
     29 		return artist, errors.New("Artist name must not be empty or have leading/trailing whitespace")
     30 	}
     31 
     32 	// latin name
     33 	var latinName *string
     34 	if form.LatinName != "" {
     35 		if !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.LatinName) {
     36 			return artist, errors.New("Latin name must not be empty or have leading/trailing whitespace")
     37 		}
     38 		if !domain.IsValidSortNameRegex.MatchString(form.LatinName) {
     39 			return artist, errors.New("Latin name contains invalid characters")
     40 		}
     41 		latinName = &form.LatinName
     42 	}
     43 
     44 	// bio
     45 	var bio *string
     46 	if form.Bio != "" && !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.Bio) {
     47 		return artist, errors.New("Artist biography must not be empty or have leading/trailing whitespace")
     48 	}
     49 	if form.Bio != "" {
     50 		bio = &form.Bio
     51 	}
     52 
     53 	// disambiguation
     54 	var disambiguation *string
     55 	if form.Disambiguation != "" && !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.Disambiguation) {
     56 		return artist, errors.New("Artist disambiguation must not be empty or have leading/trailing whitespace")
     57 	}
     58 	if form.Disambiguation != "" {
     59 		disambiguation = &form.Disambiguation
     60 	}
     61 
     62 	// musicbrainz artist id
     63 	var musicbrainzArtistID *domain.MusicBrainzID
     64 	if form.MusicBrainzArtistID != "" {
     65 		if !domain.MusicBrainzIDIsValid(string(form.MusicBrainzArtistID)) {
     66 			return artist, fmt.Errorf("'%s' must be a valid MusicBrainz ID", form.MusicBrainzArtistID)
     67 		}
     68 		musicbrainzArtistID = &form.MusicBrainzArtistID
     69 	}
     70 
     71 	artist = domain.Artist{
     72 		Name:                form.Name,
     73 		LatinName:           latinName,
     74 		Bio:                 bio,
     75 		Disambiguation:      disambiguation,
     76 		MusicBrainzArtistID: musicbrainzArtistID,
     77 	}
     78 
     79 	return artist, nil
     80 }
     81 
     82 func (ss *ServerState) EditArtist(w http.ResponseWriter, r *http.Request) {
     83 	logger := ss.logger.With("handler", "EditArtist")
     84 
     85 	idStr := r.PathValue("id")
     86 	id, err := strconv.Atoi(idStr)
     87 	if err != nil {
     88 		logger.Error("failed to convert artist id to integer", "id", idStr, "err", err)
     89 		htmx.HxError(w, http.StatusBadRequest, err.Error())
     90 		return
     91 	}
     92 
     93 	slug := r.PathValue("slug")
     94 	logger = logger.With("id", id, "slug", slug)
     95 
     96 	switch r.Method {
     97 	case http.MethodGet:
     98 		artist, _, _, _, err := db.Artist(ss.db, domain.ArtistID(id))
     99 		if err != nil {
    100 			logger.Error("failed to get artist", "err", err)
    101 			htmx.HxError(w, http.StatusInternalServerError, fmt.Sprintf("failed to query for artist '%s'", slug))
    102 			return
    103 		}
    104 
    105 		err = editartist.EditArtist(editartist.EditArtistParams{
    106 			Artist: artist,
    107 		}).Render(r.Context(), w)
    108 		if err != nil {
    109 			if errors.Is(err, context.Canceled) {
    110 				return
    111 			}
    112 
    113 			logger.Error("failed to render edit artist page", "err", err)
    114 			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
    115 			return
    116 		}
    117 	case http.MethodPost:
    118 		err := r.ParseForm()
    119 		if err != nil {
    120 			logger.Error("failed to parse http form", "err", err)
    121 			htmx.HxError(w, http.StatusBadRequest, "Failed to edit artist.")
    122 			return
    123 		}
    124 
    125 		var editArtistForm EditArtistForm
    126 		err = decoder.Decode(&editArtistForm, r.PostForm)
    127 		if err != nil {
    128 			logger.Error("failed to decode http form", "err", err)
    129 			htmx.HxError(w, http.StatusBadRequest, err.Error())
    130 			return
    131 		}
    132 
    133 		editedArtist, err := parseEditArtistForm(editArtistForm)
    134 		if err != nil {
    135 			logger.Error("failed to parse edit artist form", "err", err)
    136 			htmx.HxError(w, http.StatusBadRequest, err.Error())
    137 			return
    138 		}
    139 		editedArtist.ID = domain.ArtistID(id)
    140 
    141 		tx, err := ss.db.BeginTx(r.Context(), nil)
    142 		if err != nil {
    143 			logger.Error("failed to begin db transaction", "err", err)
    144 			htmx.HxError(w, http.StatusInternalServerError, "Failed to edit artist.")
    145 			return
    146 		}
    147 
    148 		err = db.UpdateArtist(tx, editedArtist)
    149 		if err != nil {
    150 			if rollbackErr := tx.Rollback(); rollbackErr != nil {
    151 				logger.Error("failed to rollback db transaction", "err", rollbackErr)
    152 			}
    153 			logger.Error("failed to save edited artist into db", "err", err)
    154 			htmx.HxError(w, http.StatusInternalServerError, "Failed to edit artist.")
    155 			return
    156 		}
    157 
    158 		if err := tx.Commit(); err != nil {
    159 			logger.Error("failed to commit db transaction", "err", err)
    160 			htmx.HxError(w, http.StatusInternalServerError, "Failed to edit artist.")
    161 			return
    162 		}
    163 
    164 		logger.Info("edited artist", "artist", editedArtist)
    165 
    166 		htmx.HxRedirect(w, http.StatusOK, routes.Artist(editedArtist))
    167 	}
    168 }