2024-08-02 21:48:26 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2024-08-31 00:30:30 +00:00
|
|
|
"io/fs"
|
2024-08-02 21:48:26 +00:00
|
|
|
"net/http"
|
2024-08-31 00:30:30 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2024-08-03 22:24:15 +00:00
|
|
|
"strings"
|
2024-08-02 21:48:26 +00:00
|
|
|
"time"
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
"arimelody-web/admin"
|
|
|
|
"arimelody-web/global"
|
|
|
|
music "arimelody-web/music/controller"
|
|
|
|
"arimelody-web/music/model"
|
2024-08-02 21:48:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func ServeCatalog() http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-09-03 07:07:45 +00:00
|
|
|
releases, err := music.GetAllReleases(global.DB, false, 0, true)
|
2024-09-01 03:43:32 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
type Release struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Title string `json:"title"`
|
|
|
|
ReleaseType model.ReleaseType `json:"type" db:"type"`
|
|
|
|
ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
|
|
|
|
Artwork string `json:"artwork"`
|
|
|
|
Buylink string `json:"buylink"`
|
|
|
|
Copyright string `json:"copyright" db:"copyright"`
|
|
|
|
}
|
|
|
|
|
|
|
|
catalog := []Release{}
|
2024-08-02 21:48:26 +00:00
|
|
|
authorised := admin.GetSession(r) != nil
|
2024-09-01 03:43:32 +00:00
|
|
|
for _, release := range releases {
|
2024-08-03 22:24:15 +00:00
|
|
|
if !release.Visible && !authorised {
|
2024-08-02 21:48:26 +00:00
|
|
|
continue
|
|
|
|
}
|
2024-09-03 07:07:45 +00:00
|
|
|
catalog = append(catalog, Release{
|
2024-08-03 22:24:15 +00:00
|
|
|
ID: release.ID,
|
|
|
|
Title: release.Title,
|
|
|
|
ReleaseType: release.ReleaseType,
|
|
|
|
ReleaseDate: release.ReleaseDate,
|
|
|
|
Artwork: release.Artwork,
|
|
|
|
Buylink: release.Buylink,
|
2024-09-03 07:07:45 +00:00
|
|
|
Copyright: release.Copyright,
|
2024-08-03 22:24:15 +00:00
|
|
|
})
|
2024-08-02 21:48:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Add("Content-Type", "application/json")
|
2024-09-01 03:43:32 +00:00
|
|
|
err = json.NewEncoder(w).Encode(catalog)
|
2024-08-02 21:48:26 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func CreateRelease() http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-01 23:15:23 +00:00
|
|
|
var release model.Release
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&release)
|
2024-08-02 21:48:26 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-01 23:15:23 +00:00
|
|
|
if release.ID == "" {
|
2024-08-03 14:02:01 +00:00
|
|
|
http.Error(w, "Release ID cannot be empty\n", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2024-08-31 00:30:30 +00:00
|
|
|
|
2024-09-01 23:15:23 +00:00
|
|
|
if release.Title == "" { release.Title = release.ID }
|
|
|
|
if release.ReleaseType == "" { release.ReleaseType = model.Single }
|
2024-08-31 00:30:30 +00:00
|
|
|
|
2024-09-01 23:15:23 +00:00
|
|
|
if release.ReleaseDate != time.Unix(0, 0) {
|
|
|
|
release.ReleaseDate = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC)
|
2024-08-31 00:30:30 +00:00
|
|
|
}
|
|
|
|
|
2024-09-01 23:15:23 +00:00
|
|
|
if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" }
|
2024-08-31 00:30:30 +00:00
|
|
|
|
2024-09-01 23:15:23 +00:00
|
|
|
err = music.CreateRelease(global.DB, &release)
|
2024-08-02 21:48:26 +00:00
|
|
|
if err != nil {
|
2024-09-01 03:43:32 +00:00
|
|
|
if strings.Contains(err.Error(), "duplicate key") {
|
2024-09-01 23:15:23 +00:00
|
|
|
http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest)
|
2024-09-01 03:43:32 +00:00
|
|
|
return
|
|
|
|
}
|
2024-09-03 07:07:45 +00:00
|
|
|
fmt.Printf("FATAL: Failed to create release %s: %s\n", release.ID, err)
|
2024-08-02 21:48:26 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Add("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
|
|
err = json.NewEncoder(w).Encode(release)
|
2024-08-03 14:02:01 +00:00
|
|
|
if err != nil {
|
2024-09-01 03:43:32 +00:00
|
|
|
fmt.Printf("WARN: Release %s created, but failed to send JSON response: %s\n", release.ID, err)
|
2024-08-03 14:02:01 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
func UpdateRelease(release *model.Release) http.Handler {
|
2024-08-03 14:02:01 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-08-03 22:24:15 +00:00
|
|
|
if r.URL.Path == "/" {
|
2024-08-03 14:02:01 +00:00
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-03 22:24:15 +00:00
|
|
|
segments := strings.Split(r.URL.Path[1:], "/")
|
|
|
|
|
|
|
|
if len(segments) == 2 {
|
|
|
|
switch segments[1] {
|
|
|
|
case "tracks":
|
|
|
|
UpdateReleaseTracks(release).ServeHTTP(w, r)
|
|
|
|
case "credits":
|
|
|
|
UpdateReleaseCredits(release).ServeHTTP(w, r)
|
|
|
|
case "links":
|
|
|
|
UpdateReleaseLinks(release).ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2024-09-01 03:43:32 +00:00
|
|
|
|
|
|
|
if len(segments) > 2 {
|
2024-08-03 22:24:15 +00:00
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-01 23:15:23 +00:00
|
|
|
err := json.NewDecoder(r.Body).Decode(&release)
|
2024-08-03 22:24:15 +00:00
|
|
|
if err != nil {
|
2024-09-01 23:15:23 +00:00
|
|
|
fmt.Printf("WARN: Failed to update release %s: %s\n", release.ID, err)
|
2024-08-03 22:24:15 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-01 23:15:23 +00:00
|
|
|
if release.Artwork == "" {
|
|
|
|
release.Artwork = "/img/default-cover-art.png"
|
|
|
|
} else {
|
|
|
|
if strings.Contains(release.Artwork, ";base64,") {
|
2024-09-01 03:43:32 +00:00
|
|
|
var artworkDirectory = filepath.Join("uploads", "musicart")
|
2024-09-01 23:15:23 +00:00
|
|
|
filename, err := HandleImageUpload(&release.Artwork, artworkDirectory, release.ID)
|
2024-08-03 22:24:15 +00:00
|
|
|
|
2024-09-01 03:43:32 +00:00
|
|
|
// 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 }
|
2024-08-03 22:24:15 +00:00
|
|
|
|
2024-09-01 03:43:32 +00:00
|
|
|
withoutExt := strings.TrimSuffix(path, filepath.Ext(path))
|
|
|
|
if withoutExt != filepath.Join(artworkDirectory, release.ID) { return nil }
|
2024-08-03 22:24:15 +00:00
|
|
|
|
2024-09-01 03:43:32 +00:00
|
|
|
return os.Remove(path)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("WARN: Error while cleaning up artwork files: %s\n", err)
|
2024-08-03 22:24:15 +00:00
|
|
|
}
|
2024-09-01 03:43:32 +00:00
|
|
|
|
|
|
|
release.Artwork = fmt.Sprintf("/uploads/musicart/%s", filename)
|
2024-08-03 22:24:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
err = music.UpdateRelease(global.DB, release)
|
2024-08-03 22:24:15 +00:00
|
|
|
if err != nil {
|
2024-09-03 07:07:45 +00:00
|
|
|
if strings.Contains(err.Error(), "no rows") {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
2024-09-01 03:43:32 +00:00
|
|
|
fmt.Printf("FATAL: Failed to update release %s: %s\n", release.ID, err)
|
2024-08-03 22:24:15 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
func UpdateReleaseTracks(release *model.Release) http.Handler {
|
2024-08-03 22:24:15 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-09-01 03:43:32 +00:00
|
|
|
var trackIDs = []string{}
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&trackIDs)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
2024-08-03 22:24:15 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
err = music.UpdateReleaseTracks(global.DB, release.ID, trackIDs)
|
2024-09-01 03:43:32 +00:00
|
|
|
if err != nil {
|
2024-09-03 07:07:45 +00:00
|
|
|
if strings.Contains(err.Error(), "no rows") {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fmt.Printf("FATAL: Failed to update tracks for %s: %s\n", release.ID, err)
|
2024-09-01 03:43:32 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
func UpdateReleaseCredits(release *model.Release) http.Handler {
|
2024-09-01 03:43:32 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-08-03 22:24:15 +00:00
|
|
|
type creditJSON struct {
|
|
|
|
Artist string
|
|
|
|
Role string
|
|
|
|
Primary bool
|
|
|
|
}
|
2024-09-01 03:43:32 +00:00
|
|
|
var data []creditJSON
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&data)
|
2024-08-03 22:24:15 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
var credits []*model.Credit
|
2024-09-01 03:43:32 +00:00
|
|
|
for _, credit := range data {
|
2024-09-03 07:07:45 +00:00
|
|
|
credits = append(credits, &model.Credit{
|
2024-09-01 23:15:23 +00:00
|
|
|
Artist: model.Artist{
|
|
|
|
ID: credit.Artist,
|
|
|
|
},
|
2024-09-01 03:43:32 +00:00
|
|
|
Role: credit.Role,
|
|
|
|
Primary: credit.Primary,
|
2024-09-01 23:15:23 +00:00
|
|
|
})
|
2024-09-01 03:43:32 +00:00
|
|
|
}
|
2024-08-03 22:24:15 +00:00
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
err = music.UpdateReleaseCredits(global.DB, release.ID, credits)
|
2024-09-01 23:15:23 +00:00
|
|
|
if err != nil {
|
|
|
|
if strings.Contains(err.Error(), "duplicate key") {
|
|
|
|
http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest)
|
2024-08-03 22:24:15 +00:00
|
|
|
return
|
|
|
|
}
|
2024-09-03 07:07:45 +00:00
|
|
|
if strings.Contains(err.Error(), "no rows") {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fmt.Printf("FATAL: Failed to update links for %s: %s\n", release.ID, err)
|
2024-08-03 22:24:15 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
func UpdateReleaseLinks(release *model.Release) http.Handler {
|
2024-08-03 22:24:15 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method != http.MethodPut {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var links = []*model.Link{}
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&links)
|
|
|
|
if err != nil {
|
2024-08-03 14:02:01 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
err = music.UpdateReleaseLinks(global.DB, release.ID, links)
|
2024-08-03 22:24:15 +00:00
|
|
|
if err != nil {
|
2024-09-03 07:07:45 +00:00
|
|
|
if strings.Contains(err.Error(), "no rows") {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fmt.Printf("FATAL: Failed to update links for %s: %s\n", release.ID, err)
|
2024-08-03 22:24:15 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-09-03 07:07:45 +00:00
|
|
|
func DeleteRelease(release *model.Release) http.Handler {
|
2024-08-03 22:24:15 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-09-03 07:07:45 +00:00
|
|
|
err := music.DeleteRelease(global.DB, release.ID)
|
2024-08-03 14:02:01 +00:00
|
|
|
if err != nil {
|
2024-09-03 07:07:45 +00:00
|
|
|
if strings.Contains(err.Error(), "no rows") {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fmt.Printf("FATAL: Failed to delete release %s: %s\n", release.ID, err)
|
2024-08-03 14:02:01 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
}
|
2024-08-02 21:48:26 +00:00
|
|
|
})
|
|
|
|
}
|