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 }