lyrics

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

new-artist.go (4379B)


      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 	newartist "lyrics/internal/server/ui/views/new-artist"
     11 	"net/http"
     12 )
     13 
     14 type NewArtistForm struct {
     15 	Name                string               `schema:"artist"`
     16 	Bio                 string               `schema:"biography"`
     17 	LatinName           string               `schema:"latin-name"`
     18 	Disambiguation      string               `schema:"disambiguation"`
     19 	MusicBrainzArtistID domain.MusicBrainzID `schema:"musicbrainz-artist-id"`
     20 }
     21 
     22 func parseNewArtistForm(form NewArtistForm) (domain.Artist, error) {
     23 	var artist domain.Artist
     24 
     25 	// name
     26 	if !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.Name) {
     27 		return artist, errors.New("Artist name must not be empty or have leading/trailing whitespace")
     28 	}
     29 
     30 	// latin name
     31 	var latinName *string
     32 	if form.LatinName != "" {
     33 		if !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.LatinName) {
     34 			return artist, errors.New("Latin name must not be empty or have leading/trailing whitespace")
     35 		}
     36 		if !domain.IsValidSortNameRegex.MatchString(form.LatinName) {
     37 			return artist, errors.New("Latin name contains invalid characters")
     38 		}
     39 		latinName = &form.LatinName
     40 	}
     41 
     42 	// bio
     43 	var bio *string
     44 	if form.Bio != "" && !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.Bio) {
     45 		return artist, errors.New("Artist biography must not be empty or have leading/trailing whitespace")
     46 	}
     47 	if form.Bio != "" {
     48 		bio = &form.Bio
     49 	}
     50 
     51 	// disambiguation
     52 	var disambiguation *string
     53 	if form.Disambiguation != "" && !domain.NoTrailingOrLeadingSpaceRegex.MatchString(form.Disambiguation) {
     54 		return artist, errors.New("Artist disambiguation must not be empty or have leading/trailing whitespace")
     55 	}
     56 	if form.Disambiguation != "" {
     57 		disambiguation = &form.Disambiguation
     58 	}
     59 
     60 	// musicbrainz release id
     61 	var musicbrainzArtistID *domain.MusicBrainzID
     62 	if form.MusicBrainzArtistID != "" {
     63 		if !domain.MusicBrainzIDIsValid(string(form.MusicBrainzArtistID)) {
     64 			return artist, fmt.Errorf("'%s' must be a valid MusicBrainz ID", form.MusicBrainzArtistID)
     65 		}
     66 		musicbrainzArtistID = &form.MusicBrainzArtistID
     67 	}
     68 
     69 	artist = domain.Artist{
     70 		Name:                form.Name,
     71 		LatinName:           latinName,
     72 		Bio:                 bio,
     73 		Disambiguation:      disambiguation,
     74 		MusicBrainzArtistID: musicbrainzArtistID,
     75 	}
     76 
     77 	return artist, nil
     78 }
     79 
     80 func (ss *ServerState) NewArtist(w http.ResponseWriter, r *http.Request) {
     81 	logger := ss.logger.With("handler", "NewArtist")
     82 
     83 	switch r.Method {
     84 	case http.MethodGet:
     85 		err := newartist.NewArtist(newartist.NewArtistParams{}).Render(r.Context(), w)
     86 		if err != nil {
     87 			if errors.Is(err, context.Canceled) {
     88 				return
     89 			}
     90 
     91 			logger.Error("failed to render new artist page", "err", err)
     92 			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
     93 			return
     94 		}
     95 	case http.MethodPost:
     96 		err := r.ParseForm()
     97 		if err != nil {
     98 			logger.Error("failed to parse http form", "err", err)
     99 			htmx.HxError(w, http.StatusBadRequest, "Failed to add artist.")
    100 			return
    101 		}
    102 
    103 		var newArtistForm NewArtistForm
    104 		err = decoder.Decode(&newArtistForm, r.PostForm)
    105 		if err != nil {
    106 			logger.Error("failed to decode http form", "err", err)
    107 			htmx.HxError(w, http.StatusBadRequest, err.Error())
    108 			return
    109 		}
    110 
    111 		newArtist, err := parseNewArtistForm(newArtistForm)
    112 		if err != nil {
    113 			logger.Error("failed to parse new artist form", "err", err)
    114 			htmx.HxError(w, http.StatusBadRequest, err.Error())
    115 			return
    116 		}
    117 
    118 		tx, err := ss.db.BeginTx(r.Context(), nil)
    119 		if err != nil {
    120 			logger.Error("failed to begin db transaction", "err", err)
    121 			htmx.HxError(w, http.StatusInternalServerError, "Failed to add artist.")
    122 			return
    123 		}
    124 
    125 		err = db.InsertArtist(tx, newArtist)
    126 		if err != nil {
    127 			if rollbackErr := tx.Rollback(); rollbackErr != nil {
    128 				logger.Error("failed to rollback db transaction", "err", rollbackErr)
    129 			}
    130 			logger.Error("failed to save new artist into db", "err", err)
    131 			htmx.HxError(w, http.StatusInternalServerError, "Failed to add artist.")
    132 			return
    133 		}
    134 
    135 		if err := tx.Commit(); err != nil {
    136 			logger.Error("failed to commit db transaction", "err", err)
    137 			htmx.HxError(w, http.StatusInternalServerError, "Failed to add artist.")
    138 			return
    139 		}
    140 
    141 		logger.Info("added artist", "artist", newArtist)
    142 
    143 		htmx.HxRedirect(w, http.StatusOK, "/")
    144 	}
    145 }