arimelody.me/api/release.go

264 lines
9.2 KiB
Go
Raw Normal View History

package api
import (
"encoding/json"
"fmt"
"io/fs"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"arimelody.me/arimelody.me/admin"
"arimelody.me/arimelody.me/global"
2024-09-01 23:15:23 +00:00
music "arimelody.me/arimelody.me/music/controller"
"arimelody.me/arimelody.me/music/model"
)
func ServeCatalog() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
releases := []*model.Release{}
err := global.DB.Select(&releases, "SELECT * FROM musicrelease ORDER BY release_date DESC")
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
2024-09-01 23:15:23 +00:00
catalog := []model.ReleaseShorthand{}
authorised := admin.GetSession(r) != nil
for _, release := range releases {
if !release.Visible && !authorised {
continue
}
2024-09-01 23:15:23 +00:00
catalog = append(catalog, model.ReleaseShorthand{
ID: release.ID,
Title: release.Title,
ReleaseType: release.ReleaseType,
ReleaseDate: release.ReleaseDate,
Artwork: release.Artwork,
Buylink: release.Buylink,
})
}
w.Header().Add("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(catalog)
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)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
2024-09-01 23:15:23 +00:00
if release.ID == "" {
http.Error(w, "Release ID cannot be empty\n", http.StatusBadRequest)
return
}
2024-09-01 23:15:23 +00:00
if release.Title == "" { release.Title = release.ID }
if release.ReleaseType == "" { release.ReleaseType = model.Single }
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-09-01 23:15:23 +00:00
if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" }
2024-09-01 23:15:23 +00:00
err = music.CreateRelease(global.DB, &release)
if err != nil {
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)
return
}
fmt.Printf("Failed to create release %s: %s\n", release.ID, err)
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)
if err != nil {
fmt.Printf("WARN: Release %s created, but failed to send JSON response: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
func UpdateRelease(release model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
http.NotFound(w, r)
return
}
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
}
if len(segments) > 2 {
http.NotFound(w, r)
return
}
2024-09-01 23:15:23 +00:00
err := json.NewDecoder(r.Body).Decode(&release)
if err != nil {
2024-09-01 23:15:23 +00:00
fmt.Printf("WARN: Failed to update release %s: %s\n", release.ID, err)
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,") {
var artworkDirectory = filepath.Join("uploads", "musicart")
2024-09-01 23:15:23 +00:00
filename, err := HandleImageUpload(&release.Artwork, artworkDirectory, release.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, release.ID) { return nil }
return os.Remove(path)
})
if err != nil {
fmt.Printf("WARN: Error while cleaning up artwork files: %s\n", err)
}
release.Artwork = fmt.Sprintf("/uploads/musicart/%s", filename)
}
}
2024-09-01 23:15:23 +00:00
err = music.UpdateRelease(global.DB, &release)
if err != nil {
fmt.Printf("FATAL: Failed to update release %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
func UpdateReleaseTracks(release model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var trackIDs = []string{}
err := json.NewDecoder(r.Body).Decode(&trackIDs)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
2024-09-01 23:15:23 +00:00
err = music.UpdateReleaseTracks(global.DB, &release, trackIDs)
if err != nil {
fmt.Printf("Failed to update tracks for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
func UpdateReleaseCredits(release model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
type creditJSON struct {
Artist string
Role string
Primary bool
}
var data []creditJSON
err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
2024-09-01 23:15:23 +00:00
var credits []model.Credit
for _, credit := range data {
2024-09-01 23:15:23 +00:00
credits = append(credits, model.Credit{
Artist: model.Artist{
ID: credit.Artist,
},
Role: credit.Role,
Primary: credit.Primary,
2024-09-01 23:15:23 +00:00
})
}
2024-09-01 23:15:23 +00:00
err = music.UpdateReleaseCredits(global.DB, &release, credits)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest)
return
}
fmt.Printf("Failed to update links for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
func UpdateReleaseLinks(release model.Release) http.Handler {
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 {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
tx := global.DB.MustBegin()
tx.MustExec("DELETE FROM musiclink WHERE release=$1", release.ID)
for _, link := range links {
tx.MustExec(
"INSERT INTO musiclink "+
"(release, name, url) "+
"VALUES ($1, $2, $3)",
release.ID,
link.Name,
link.URL)
}
err = tx.Commit()
if err != nil {
fmt.Printf("Failed to update links for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}
func DeleteRelease(release model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := global.DB.Exec("DELETE FROM musicrelease WHERE id=$1", release.ID)
if err != nil {
fmt.Printf("Failed to delete release %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
}