weee
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
|
||||
"git.noroutes.net/NoRoutes/vibetunes/internal/netguard"
|
||||
)
|
||||
|
||||
// --- Artists ---
|
||||
|
||||
func (h *Web) handleAdminArtistEdit(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
artist, err := database.GetArtist(h.DB, id)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
h.page(w, r, "admin_artist_edit", "artists", "Edit artist: "+artist.Name, artist)
|
||||
}
|
||||
|
||||
func (h *Web) handleAdminArtistUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
if _, err := database.GetArtist(h.DB, id); err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if err := r.ParseMultipartForm(maxArtistPhotoBytes); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.UpdateArtist(h.DB, id, r.FormValue("name"), r.FormValue("sort_name"), r.FormValue("bio")); err != nil {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case r.FormValue("remove_photo") == "1":
|
||||
if err := database.SetArtistCoverArt(h.DB, id, sql.NullString{}, false); err != nil {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
default:
|
||||
if !h.applyPhotoOverride(w, r, "photo_file", "photo_url", func(localPath, contentHash, mimeType string) error {
|
||||
_, err := database.UpsertArtistCoverArt(h.DB, id, "external", localPath, mimeType, contentHash, 0, 0, true)
|
||||
return err
|
||||
}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/artists/"+id, http.StatusFound)
|
||||
}
|
||||
|
||||
// --- Albums ---
|
||||
|
||||
func (h *Web) handleAdminAlbumEdit(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
album, err := database.GetAlbum(h.DB, id)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
h.page(w, r, "admin_album_edit", "albums", "Edit album: "+album.Title, album)
|
||||
}
|
||||
|
||||
func (h *Web) handleAdminAlbumUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
if _, err := database.GetAlbum(h.DB, id); err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if err := r.ParseMultipartForm(maxArtistPhotoBytes); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.UpdateAlbum(h.DB, id, r.FormValue("title"), r.FormValue("sort_title"),
|
||||
formNullInt(r, "year"), formNullInt(r, "original_year"), r.FormValue("record_label")); err != nil {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !h.applyPhotoOverride(w, r, "cover_file", "cover_url", func(localPath, contentHash, mimeType string) error {
|
||||
_, err := database.UpsertAlbumCoverArt(h.DB, id, "external", localPath, "", mimeType, contentHash, 0, 0)
|
||||
return err
|
||||
}) {
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/albums/"+id, http.StatusFound)
|
||||
}
|
||||
|
||||
// --- Songs ---
|
||||
|
||||
func (h *Web) handleAdminSongEdit(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
song, err := database.GetSong(h.DB, id)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
h.page(w, r, "admin_song_edit", "songs", "Edit song: "+song.Title, song)
|
||||
}
|
||||
|
||||
func (h *Web) handleAdminSongUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
if _, err := database.GetSong(h.DB, id); err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
locked := r.FormValue("metadata_locked") == "1"
|
||||
if err := database.UpdateSongMetadata(h.DB, id, r.FormValue("title"), r.FormValue("sort_title"),
|
||||
formNullInt(r, "track_number"), formNullInt(r, "disc_number"), r.FormValue("composer"), r.FormValue("conductor"), locked); err != nil {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/songs", http.StatusFound)
|
||||
}
|
||||
|
||||
// --- shared helpers ---
|
||||
|
||||
// formNullInt parses a form field as an integer, returning an invalid
|
||||
// sql.NullInt64 when the field is blank or unparsable rather than
|
||||
// erroring the request — the same tolerant behavior as the rest of
|
||||
// VibeSonic's admin forms.
|
||||
func formNullInt(r *http.Request, name string) sql.NullInt64 {
|
||||
raw := strings.TrimSpace(r.FormValue(name))
|
||||
if raw == "" {
|
||||
return sql.NullInt64{}
|
||||
}
|
||||
n, err := strconv.ParseInt(raw, 10, 64)
|
||||
if err != nil {
|
||||
return sql.NullInt64{}
|
||||
}
|
||||
return sql.NullInt64{Int64: n, Valid: true}
|
||||
}
|
||||
|
||||
// applyPhotoOverride implements the common "upload a file, or give a
|
||||
// URL, or leave the existing photo/cover alone" logic shared by the
|
||||
// artist and album edit forms. store is called with the resulting local
|
||||
// file's path/hash/mime once one is available. Returns false (having
|
||||
// already written an HTTP error response) on failure.
|
||||
func (h *Web) applyPhotoOverride(w http.ResponseWriter, r *http.Request, fileField, urlField string, store func(localPath, contentHash, mimeType string) error) bool {
|
||||
if file, _, err := r.FormFile(fileField); err == nil {
|
||||
defer file.Close()
|
||||
body, err := io.ReadAll(io.LimitReader(file, maxArtistPhotoBytes+1))
|
||||
if err != nil {
|
||||
http.Error(w, "could not read uploaded image", http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
if len(body) > maxArtistPhotoBytes {
|
||||
http.Error(w, "uploaded image exceeds maximum allowed size", http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
localPath, contentHash, mimeType, err := h.storeImageBytes(body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
if err := store(localPath, contentHash, mimeType); err != nil {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
photoURL := strings.TrimSpace(r.FormValue(urlField))
|
||||
if photoURL == "" {
|
||||
return true
|
||||
}
|
||||
if err := netguard.ValidateURL(photoURL); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
localPath, contentHash, mimeType, err := h.downloadImage(r.Context(), photoURL)
|
||||
if err != nil {
|
||||
http.Error(w, "could not download image: "+err.Error(), http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
if err := store(localPath, contentHash, mimeType); err != nil {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user