package api import ( "encoding/json" "fmt" "io/fs" "net/http" "os" "path/filepath" "strings" "time" "arimelody-web/controller" "arimelody-web/log" "arimelody-web/model" ) func ServeAllArtists(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var artists = []*model.Artist{} artists, err := controller.GetAllArtists(app.DB) if err != nil { fmt.Printf("WARN: Failed to serve all artists: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json") encoder := json.NewEncoder(w) encoder.SetIndent("", "\t") err = encoder.Encode(artists) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }) } func ServeArtist(app *model.AppState, artist *model.Artist) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { type ( creditJSON struct { ID string `json:"id"` Title string `json:"title"` ReleaseDate time.Time `json:"releaseDate" db:"release_date"` Artwork string `json:"artwork"` Role string `json:"role"` Primary bool `json:"primary"` } artistJSON struct { *model.Artist Credits map[string]creditJSON `json:"credits"` } ) session := r.Context().Value("session").(*model.Session) show_hidden_releases := session != nil && session.Account != nil dbCredits, err := controller.GetArtistCredits(app.DB, artist.ID, show_hidden_releases) if err != nil { fmt.Printf("WARN: Failed to retrieve artist credits for %s: %v\n", artist.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } var credits = map[string]creditJSON{} for _, credit := range dbCredits { credits[credit.Release.ID] = creditJSON{ ID: credit.Release.ID, Title: credit.Release.Title, ReleaseDate: credit.Release.ReleaseDate, Artwork: credit.Release.Artwork, Role: credit.Role, Primary: credit.Primary, } } w.Header().Add("Content-Type", "application/json") encoder := json.NewEncoder(w) encoder.SetIndent("", "\t") err = encoder.Encode(artistJSON{ Artist: artist, Credits: credits, }) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }) } func CreateArtist(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := r.Context().Value("session").(*model.Session) var artist model.Artist err := json.NewDecoder(r.Body).Decode(&artist) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if artist.ID == "" { http.Error(w, "Artist ID cannot be blank\n", http.StatusBadRequest) return } if artist.Name == "" { artist.Name = artist.ID } err = controller.CreateArtist(app.DB, &artist) if err != nil { if strings.Contains(err.Error(), "duplicate key") { http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest) return } fmt.Printf("WARN: Failed to create artist %s: %s\n", artist.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } app.Log.Info(log.TYPE_ARTIST, "Artist \"%s\" created by \"%s\".", artist.Name, session.Account.Username) w.WriteHeader(http.StatusCreated) }) } func UpdateArtist(app *model.AppState, artist *model.Artist) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := r.Context().Value("session").(*model.Session) err := json.NewDecoder(r.Body).Decode(&artist) if err != nil { fmt.Printf("WARN: Failed to update artist: %s\n", err) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if artist.Avatar == "" { artist.Avatar = "/img/default-avatar.png" } else { if strings.Contains(artist.Avatar, ";base64,") { var artworkDirectory = filepath.Join("uploads", "avatar") filename, err := HandleImageUpload(app, &artist.Avatar, artworkDirectory, artist.ID) // clean up files with this ID and different extensions err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error { if path == filepath.Join(artworkDirectory, filename) { return nil } withoutExt := strings.TrimSuffix(path, filepath.Ext(path)) if withoutExt != filepath.Join(artworkDirectory, artist.ID) { return nil } return os.Remove(path) }) if err != nil { fmt.Printf("WARN: Error while cleaning up avatar files: %s\n", err) } artist.Avatar = fmt.Sprintf("/uploads/avatar/%s", filename) } } err = controller.UpdateArtist(app.DB, artist) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) return } fmt.Printf("WARN: Failed to update artist %s: %s\n", artist.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } app.Log.Info(log.TYPE_ARTIST, "Artist \"%s\" updated by \"%s\".", artist.Name, session.Account.Username) }) } func DeleteArtist(app *model.AppState, artist *model.Artist) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := r.Context().Value("session").(*model.Session) err := controller.DeleteArtist(app.DB, artist.ID) if err != nil { if strings.Contains(err.Error(), "no rows") { http.NotFound(w, r) return } fmt.Printf("WARN: Failed to delete artist %s: %s\n", artist.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } app.Log.Info(log.TYPE_ARTIST, "Artist \"%s\" deleted by \"%s\".", artist.Name, session.Account.Username) }) }