diff --git a/admin/components/release/release-list-item.html b/admin/components/release/release-list-item.html index 3a17ad5..677318d 100644 --- a/admin/components/release/release-list-item.html +++ b/admin/components/release/release-list-item.html @@ -1,7 +1,7 @@ {{define "release"}}
- +

diff --git a/admin/http.go b/admin/http.go index f9b26f9..820eddf 100644 --- a/admin/http.go +++ b/admin/http.go @@ -57,7 +57,7 @@ func Handler() http.Handler { } releases := []musicModel.FullRelease{} for _, release := range dbReleases { - fullRelease, err := musicDB.GetFullRelease(global.DB, release) + fullRelease, err := musicDB.GetFullRelease(global.DB, release.ID) if err != nil { fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/admin/releasehttp.go b/admin/releasehttp.go index 86b4aa1..c7b6528 100644 --- a/admin/releasehttp.go +++ b/admin/releasehttp.go @@ -6,24 +6,25 @@ import ( "strings" "arimelody.me/arimelody.me/global" + db "arimelody.me/arimelody.me/music/controller" "arimelody.me/arimelody.me/music/model" - controller "arimelody.me/arimelody.me/music/controller" ) func serveRelease() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { slices := strings.Split(r.URL.Path[1:], "/") releaseID := slices[0] - release, err := controller.GetRelease(global.DB, releaseID) + + release, err := db.GetFullRelease(global.DB, releaseID) if err != nil { - fmt.Printf("FATAL: Failed to pull release %s: %s\n", releaseID, err) + if strings.Contains(err.Error(), "no rows") { + http.NotFound(w, r) + return + } + fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - if release == nil { - http.NotFound(w, r) - return - } authorised := GetSession(r) != nil if !authorised && !release.Visible { @@ -31,32 +32,25 @@ func serveRelease() http.Handler { return } - fullRelease, err := controller.GetFullRelease(global.DB, release) - if err != nil { - fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - if len(slices) > 1 { switch slices[1] { case "editcredits": - serveEditCredits(fullRelease).ServeHTTP(w, r) + serveEditCredits(release).ServeHTTP(w, r) return case "addcredit": - serveAddCredit(fullRelease).ServeHTTP(w, r) + serveAddCredit(release).ServeHTTP(w, r) return case "newcredit": serveNewCredit().ServeHTTP(w, r) return case "editlinks": - serveEditLinks(fullRelease).ServeHTTP(w, r) + serveEditLinks(release).ServeHTTP(w, r) return case "edittracks": - serveEditTracks(fullRelease).ServeHTTP(w, r) + serveEditTracks(release).ServeHTTP(w, r) return case "addtrack": - serveAddTrack(fullRelease).ServeHTTP(w, r) + serveAddTrack(release).ServeHTTP(w, r) return case "newtrack": serveNewTrack().ServeHTTP(w, r) @@ -66,7 +60,7 @@ func serveRelease() http.Handler { return } - err = pages["release"].Execute(w, fullRelease) + err = pages["release"].Execute(w, release) if err != nil { fmt.Printf("Error rendering admin release page for %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -87,7 +81,7 @@ func serveEditCredits(release *model.FullRelease) http.Handler { func serveAddCredit(release *model.FullRelease) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - artists, err := controller.GetArtistsNotOnRelease(global.DB, release.Release) + artists, err := db.GetArtistsNotOnRelease(global.DB, release.Release.ID) if err != nil { fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -114,7 +108,7 @@ func serveAddCredit(release *model.FullRelease) http.Handler { func serveNewCredit() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { artistID := strings.Split(r.URL.Path, "/")[3] - artist, err := controller.GetArtist(global.DB, artistID) + artist, err := db.GetArtist(global.DB, artistID) if err != nil { fmt.Printf("FATAL: Failed to pull artists %s: %s\n", artistID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -158,7 +152,7 @@ func serveEditTracks(release *model.FullRelease) http.Handler { func serveAddTrack(release *model.FullRelease) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tracks, err := controller.GetTracksNotOnRelease(global.DB, release.Release) + tracks, err := db.GetTracksNotOnRelease(global.DB, release.Release.ID) if err != nil { fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -186,7 +180,7 @@ func serveAddTrack(release *model.FullRelease) http.Handler { func serveNewTrack() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { trackID := strings.Split(r.URL.Path, "/")[3] - track, err := controller.GetTrack(global.DB, trackID) + track, err := db.GetTrack(global.DB, trackID) if err != nil { fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/admin/static/edit-release.css b/admin/static/edit-release.css index e11c562..f3ae6e2 100644 --- a/admin/static/edit-release.css +++ b/admin/static/edit-release.css @@ -18,8 +18,8 @@ input[type="text"] { .release-artwork { width: 200px; + text-align: center; } - .release-artwork img { width: 100%; aspect-ratio: 1; @@ -28,6 +28,9 @@ input[type="text"] { outline: 1px solid #808080; cursor: pointer; } +.release-artwork #remove-artwork { + padding: .3em .4em; +} .release-info { width: 0; @@ -342,7 +345,7 @@ dialog div.dialog-actions { background-color: #8cff83 } -.card.links a.button[data-name="applemusic"] { +.card.links a.button[data-name="apple music"] { background-color: #8cd9ff } diff --git a/admin/static/edit-release.js b/admin/static/edit-release.js index 4273d97..2de1c66 100644 --- a/admin/static/edit-release.js +++ b/admin/static/edit-release.js @@ -3,45 +3,36 @@ import Stateful from "/script/silver.min.js" const releaseID = document.getElementById("release").dataset.id; const titleInput = document.getElementById("title"); const artworkImg = document.getElementById("artwork"); +const removeArtworkBtn = document.getElementById("remove-artwork"); const artworkInput = document.getElementById("artwork-file"); const typeInput = document.getElementById("type"); const descInput = document.getElementById("description"); const dateInput = document.getElementById("release-date"); const buynameInput = document.getElementById("buyname"); const buylinkInput = document.getElementById("buylink"); +const copyrightInput = document.getElementById("copyright"); +const copyrightURLInput = document.getElementById("copyright-url"); const visInput = document.getElementById("visibility"); const saveBtn = document.getElementById("save"); const deleteBtn = document.getElementById("delete"); var artworkData = artworkImg.attributes.src.value; -var edited = new Stateful(false); -var releaseData = updateData(undefined); - -function updateData(old) { - var releaseData = { - visible: visInput.value === "true", - title: titleInput.value, - description: descInput.value, - type: typeInput.value, - releaseDate: dateInput.value, - artwork: artworkData, - buyname: buynameInput.value, - buylink: buylinkInput.value, - }; - - if (releaseData && releaseData != old) { - edited.set(true); - } - - return releaseData; -} - -function saveRelease() { - console.table(releaseData); +saveBtn.addEventListener("click", () => { fetch("/api/v1/music/" + releaseID, { method: "PUT", - body: JSON.stringify(releaseData), + body: JSON.stringify({ + visible: visInput.value === "true", + title: titleInput.value, + description: descInput.value, + type: typeInput.value, + releaseDate: dateInput.value + ":00Z", + artwork: artworkData, + buyname: buynameInput.value, + buylink: buylinkInput.value, + copyright: copyrightInput.value, + copyrightURL: copyrightURLInput.value, + }), headers: { "Content-Type": "application/json" } }).then(res => { if (!res.ok) { @@ -54,9 +45,13 @@ function saveRelease() { location = location; }); -} +}); -function deleteRelease() { +deleteBtn.addEventListener("click", () => { + if (releaseID != prompt( + "You are about to permanently delete " + releaseID + ". " + + "This action is irreversible. " + + "Please enter \"" + releaseID + "\" to continue.")) return; fetch("/api/v1/music/" + releaseID, { method: "DELETE", }).then(res => { @@ -70,15 +65,17 @@ function deleteRelease() { location = "/admin"; }); -} - -edited.onUpdate(edited => { - saveBtn.disabled = !edited; -}) - -titleInput.addEventListener("change", () => { - releaseData = updateData(releaseData); }); + +[titleInput, typeInput, descInput, dateInput, buynameInput, buylinkInput, copyrightInput, copyrightURLInput, visInput].forEach(input => { + input.addEventListener("change", () => { + saveBtn.disabled = false; + }); + input.addEventListener("keypress", () => { + saveBtn.disabled = false; + }); +}); + artworkImg.addEventListener("click", () => { artworkInput.addEventListener("change", () => { if (artworkInput.files.length > 0) { @@ -87,41 +84,17 @@ artworkImg.addEventListener("click", () => { const data = e.target.result; artworkImg.src = data; artworkData = data; - releaseData = updateData(releaseData); + saveBtn.disabled = false; }; reader.readAsDataURL(artworkInput.files[0]); } }); artworkInput.click(); }); -typeInput.addEventListener("change", () => { - releaseData = updateData(releaseData); -}); -descInput.addEventListener("change", () => { - releaseData = updateData(releaseData); -}); -dateInput.addEventListener("change", () => { - releaseData = updateData(releaseData); -}); -buynameInput.addEventListener("change", () => { - releaseData = updateData(releaseData); -}); -buylinkInput.addEventListener("change", () => { - releaseData = updateData(releaseData); -}); -visInput.addEventListener("change", () => { - releaseData = updateData(releaseData); -}); -saveBtn.addEventListener("click", () => { - if (!edited.get()) return; - saveRelease(); -}); -deleteBtn.addEventListener("click", () => { - if (releaseID != prompt( - "You are about to permanently delete " + releaseID + ". " + - "This action is irreversible. " + - "Please enter \"" + releaseID + "\" to continue.")) return; - deleteRelease(); +removeArtworkBtn.addEventListener("click", () => { + artworkImg.src = "/img/default-cover-art.png" + artworkData = ""; + saveBtn.disabled = false; }); diff --git a/admin/trackhttp.go b/admin/trackhttp.go index 3400f21..bd32636 100644 --- a/admin/trackhttp.go +++ b/admin/trackhttp.go @@ -25,7 +25,7 @@ func serveTrack() http.Handler { return } - dbReleases, err := music.GetTrackReleases(global.DB, track) + dbReleases, err := music.GetTrackReleases(global.DB, track.ID) if err != nil { fmt.Printf("Error rendering admin track page for %s: %s\n", id, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -33,7 +33,7 @@ func serveTrack() http.Handler { } releases := []model.FullRelease{} for _, release := range dbReleases { - fullRelease, err := music.GetFullRelease(global.DB, release) + fullRelease, err := music.GetFullRelease(global.DB, release.ID) if err != nil { fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/admin/views/edit-release.html b/admin/views/edit-release.html index 5a9b4f0..18600c6 100644 --- a/admin/views/edit-release.html +++ b/admin/views/edit-release.html @@ -12,10 +12,11 @@
+

- +

@@ -53,19 +54,31 @@ + + + + + + + + diff --git a/api/api.go b/api/api.go index c0fcf12..443904f 100644 --- a/api/api.go +++ b/api/api.go @@ -21,8 +21,12 @@ func Handler() http.Handler { var artist model.Artist err := global.DB.Get(&artist, "SELECT * FROM artist WHERE id=$1", artistID) if err != nil { + if strings.Contains(err.Error(), "no rows") { + http.NotFound(w, r) + return + } fmt.Printf("FATAL: Error while retrieving artist %s: %s\n", artistID, err) - http.NotFound(w, r) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } @@ -60,8 +64,12 @@ func Handler() http.Handler { var release model.Release err := global.DB.Get(&release, "SELECT * FROM musicrelease WHERE id=$1", releaseID) if err != nil { + if strings.Contains(err.Error(), "no rows") { + http.NotFound(w, r) + return + } fmt.Printf("FATAL: Error while retrieving release %s: %s\n", releaseID, err) - http.NotFound(w, r) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } @@ -99,8 +107,12 @@ func Handler() http.Handler { var track model.Track err := global.DB.Get(&track, "SELECT * FROM musictrack WHERE id=$1", trackID) if err != nil { + if strings.Contains(err.Error(), "no rows") { + http.NotFound(w, r) + return + } fmt.Printf("FATAL: Error while retrieving track %s: %s\n", trackID, err) - http.NotFound(w, r) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } diff --git a/api/artist.go b/api/artist.go index b4b4faf..164e535 100644 --- a/api/artist.go +++ b/api/artist.go @@ -8,6 +8,7 @@ import ( "arimelody.me/arimelody.me/global" "arimelody.me/arimelody.me/music/model" + db "arimelody.me/arimelody.me/music/controller" ) type artistJSON struct { @@ -20,7 +21,7 @@ type artistJSON struct { func ServeAllArtists() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var artists = []*model.Artist{} - err := global.DB.Select(&artists, "SELECT * FROM artist") + artists, err := db.GetAllArtists(global.DB) if err != nil { fmt.Printf("FATAL: Failed to serve all artists: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -39,7 +40,6 @@ func ServeArtist(artist model.Artist) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { type ( creditJSON struct { - Release string `json:"release"` Role string `json:"role"` Primary bool `json:"primary"` } @@ -49,14 +49,22 @@ func ServeArtist(artist model.Artist) http.Handler { } ) - var credits = map[string]creditJSON{} - err := global.DB.Select(&credits, "SELECT release,role,is_primary FROM musiccredit WHERE id=$1", artist.ID) + var dbCredits []*model.Credit + dbCredits, err := db.GetArtistCredits(global.DB, artist.ID) if err != nil { fmt.Printf("FATAL: Failed to retrieve artist credits for %s: %s\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{ + Role: credit.Role, + Primary: credit.Primary, + } + } + w.Header().Add("Content-Type", "application/json") err = json.NewEncoder(w).Encode(artistJSON{ Artist: artist, diff --git a/api/release.go b/api/release.go index 088fe98..fb1ea54 100644 --- a/api/release.go +++ b/api/release.go @@ -12,31 +12,12 @@ import ( "arimelody.me/arimelody.me/admin" "arimelody.me/arimelody.me/global" + music "arimelody.me/arimelody.me/music/controller" "arimelody.me/arimelody.me/music/model" ) -type releaseBodyJSON struct { - ID string `json:"id"` - Visible *bool `json:"visible"` - Title *string `json:"title"` - Description *string `json:"description"` - ReleaseType *model.ReleaseType `json:"type"` - ReleaseDate *string `json:"releaseDate"` - Artwork *string `json:"artwork"` - Buyname *string `json:"buyname"` - Buylink *string `json:"buylink"` -} - func ServeCatalog() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - type catalogItem struct { - ID string `json:"id"` - Title string `json:"title"` - ReleaseType model.ReleaseType `json:"type"` - ReleaseDate time.Time `json:"releaseDate"` - Artwork string `json:"artwork"` - Buylink string `json:"buylink"` - } releases := []*model.Release{} err := global.DB.Select(&releases, "SELECT * FROM musicrelease ORDER BY release_date DESC") @@ -45,13 +26,13 @@ func ServeCatalog() http.Handler { return } - catalog := []catalogItem{} + catalog := []model.ReleaseShorthand{} authorised := admin.GetSession(r) != nil for _, release := range releases { if !release.Visible && !authorised { continue } - catalog = append(catalog, catalogItem{ + catalog = append(catalog, model.ReleaseShorthand{ ID: release.ID, Title: release.Title, ReleaseType: release.ReleaseType, @@ -77,77 +58,31 @@ func CreateRelease() http.Handler { return } - var data releaseBodyJSON - err := json.NewDecoder(r.Body).Decode(&data) + var release model.Release + err := json.NewDecoder(r.Body).Decode(&release) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - if data.ID == "" { + if release.ID == "" { http.Error(w, "Release ID cannot be empty\n", http.StatusBadRequest) return } - title := data.ID - if data.Title != nil && *data.Title != "" { - title = *data.Title - } - - description := "" - if data.Description != nil && *data.Description != "" { description = *data.Description } + if release.Title == "" { release.Title = release.ID } + if release.ReleaseType == "" { release.ReleaseType = model.Single } - releaseType := model.Single - if data.ReleaseType != nil && *data.ReleaseType != "" { releaseType = *data.ReleaseType } - - releaseDate := time.Time{} - if data.ReleaseDate != nil && *data.ReleaseDate != "" { - releaseDate, err = time.Parse("2006-01-02T15:04", *data.ReleaseDate) - if err != nil { - http.Error(w, "Invalid release date", http.StatusBadRequest) - return - } - } else { - releaseDate = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC) + 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) } - artwork := "/img/default-cover-art.png" - if data.Artwork != nil && *data.Artwork != "" { artwork = *data.Artwork } + if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" } - buyname := "" - if data.Buyname != nil && *data.Buyname != "" { buyname = *data.Buyname } - - buylink := "" - if data.Buylink != nil && *data.Buylink != "" { buylink = *data.Buylink } - - var release = model.Release{ - ID: data.ID, - Visible: false, - Title: title, - Description: description, - ReleaseType: releaseType, - ReleaseDate: releaseDate, - Artwork: artwork, - Buyname: buyname, - Buylink: buylink, - } - - _, err = global.DB.Exec( - "INSERT INTO musicrelease "+ - "(id, visible, title, description, type, release_date, artwork, buyname, buylink) "+ - "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", - release.ID, - release.Visible, - release.Title, - release.Description, - release.ReleaseType, - release.ReleaseDate.Format("2006-01-02 15:04:05"), - release.Artwork, - release.Buyname, - release.Buylink) + err = music.CreateRelease(global.DB, &release) if err != nil { if strings.Contains(err.Error(), "duplicate key") { - http.Error(w, fmt.Sprintf("Release %s already exists\n", data.ID), http.StatusBadRequest) + 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) @@ -173,14 +108,6 @@ func UpdateRelease(release model.Release) http.Handler { } segments := strings.Split(r.URL.Path[1:], "/") - var releaseID = segments[0] - var exists int - err := global.DB.Get(&exists, "SELECT count(*) FROM musicrelease WHERE id=$1", releaseID) - if err != nil { - fmt.Printf("Failed to update release: %s\n", err) - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } if len(segments) == 2 { switch segments[1] { @@ -199,30 +126,19 @@ func UpdateRelease(release model.Release) http.Handler { return } - var data releaseBodyJSON - err = json.NewDecoder(r.Body).Decode(&data) + err := json.NewDecoder(r.Body).Decode(&release) if err != nil { + fmt.Printf("WARN: Failed to update release %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - if data.ID != "" { release.ID = data.ID } - if data.Visible != nil { release.Visible = *data.Visible } - if data.Title != nil { release.Title = *data.Title } - if data.Description != nil { release.Description = *data.Description } - if data.ReleaseType != nil { release.ReleaseType = *data.ReleaseType } - if data.ReleaseDate != nil { - newDate, err := time.Parse("2006-01-02T15:04", *data.ReleaseDate) - if err != nil { - http.Error(w, "Invalid release date", http.StatusBadRequest) - return - } - release.ReleaseDate = newDate - } - if data.Artwork != nil { - if strings.Contains(*data.Artwork, ";base64,") { + if release.Artwork == "" { + release.Artwork = "/img/default-cover-art.png" + } else { + if strings.Contains(release.Artwork, ";base64,") { var artworkDirectory = filepath.Join("uploads", "musicart") - filename, err := HandleImageUpload(data.Artwork, artworkDirectory, data.ID) + 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 { @@ -238,27 +154,10 @@ func UpdateRelease(release model.Release) http.Handler { } release.Artwork = fmt.Sprintf("/uploads/musicart/%s", filename) - } else { - release.Artwork = *data.Artwork } } - if data.Buyname != nil { release.Buyname = *data.Buyname } - if data.Buylink != nil { release.Buylink = *data.Buylink } - - _, err = global.DB.Exec( - "UPDATE musicrelease SET "+ - "visible=$2, title=$3, description=$4, type=$5, release_date=$6, artwork=$7, buyname=$8, buylink=$9 "+ - "WHERE id=$1", - release.ID, - release.Visible, - release.Title, - release.Description, - release.ReleaseType, - release.ReleaseDate.Format("2006-01-02 15:04:05"), - release.Artwork, - release.Buyname, - release.Buylink) + 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) @@ -275,18 +174,7 @@ func UpdateReleaseTracks(release model.Release) http.Handler { return } - tx := global.DB.MustBegin() - tx.MustExec("DELETE FROM musicreleasetrack WHERE release=$1", release.ID) - for i, trackID := range trackIDs { - tx.MustExec( - "INSERT INTO musicreleasetrack "+ - "(release, track, number) "+ - "VALUES ($1, $2, $3)", - release.ID, - trackID, - i) - } - err = tx.Commit() + 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) @@ -308,45 +196,23 @@ func UpdateReleaseCredits(release model.Release) http.Handler { return } - // clear duplicates - type Credit struct { - Role string - Primary bool - } - var credits = map[string]Credit{} + var credits []model.Credit for _, credit := range data { - credits[credit.Artist] = Credit{ + credits = append(credits, model.Credit{ + Artist: model.Artist{ + ID: credit.Artist, + }, Role: credit.Role, Primary: credit.Primary, - } + }) } - tx := global.DB.MustBegin() - tx.MustExec("DELETE FROM musiccredit WHERE release=$1", release.ID) - for artistID := range credits { - if credits[artistID].Role == "" { - http.Error(w, fmt.Sprintf("Artist role cannot be blank (%s)", artistID), http.StatusBadRequest) - return - } - - var exists int - _ = global.DB.Get(&exists, "SELECT count(*) FROM artist WHERE id=$1", artistID) - if exists == 0 { - http.Error(w, fmt.Sprintf("Artist %s does not exist\n", artistID), http.StatusBadRequest) - return - } - - tx.MustExec( - "INSERT INTO musiccredit "+ - "(release, artist, role, is_primary) "+ - "VALUES ($1, $2, $3, $4)", - release.ID, - artistID, - credits[artistID].Role, - credits[artistID].Primary) - } - err = tx.Commit() + 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) } diff --git a/api/track.go b/api/track.go index 3b7f184..75080c5 100644 --- a/api/track.go +++ b/api/track.go @@ -6,23 +6,39 @@ import ( "net/http" "arimelody.me/arimelody.me/global" + music "arimelody.me/arimelody.me/music/controller" "arimelody.me/arimelody.me/music/model" ) +type ( + Track struct { + model.Track + Releases []model.ReleaseShorthand + } +) + func ServeAllTracks() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - type track struct { + type Track struct { ID string `json:"id"` Title string `json:"title"` } - var tracks = []track{} + var tracks = []Track{} - err := global.DB.Select(&tracks, "SELECT id, title FROM musictrack") + var dbTracks = []*model.Track{} + dbTracks, err := music.GetAllTracks(global.DB) if err != nil { fmt.Printf("FATAL: Failed to pull tracks from DB: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } + for _, track := range dbTracks { + tracks = append(tracks, Track{ + ID: track.ID, + Title: track.Title, + }) + } + w.Header().Add("Content-Type", "application/json") err = json.NewEncoder(w).Encode(tracks) if err != nil { @@ -34,40 +50,16 @@ func ServeAllTracks() http.Handler { func ServeTrack(track model.Track) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" { - ServeAllTracks().ServeHTTP(w, r) - return - } - - var trackID = r.URL.Path[1:] - var track = model.Track{} - err := global.DB.Get(&track, "SELECT * from musictrack WHERE id=$1", trackID) - if err != nil { - http.NotFound(w, r) - return - } - - var releases = []*model.Release{} - err = global.DB.Select(&releases, - "SELECT * FROM musicrelease JOIN musicreleasetrack AS mrt "+ - "WHERE mrt.track=$1 "+ - "ORDER BY release_date", - track.ID, - ) + releases, err := music.GetTrackReleases(global.DB, track.ID) if err != nil { - fmt.Printf("FATAL: Failed to pull track releases for %s from DB: %s\n", trackID, err) + fmt.Printf("FATAL: Failed to pull track releases for %s from DB: %s\n", track.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } - type response struct { - model.Track - Releases []*model.Release - } - w.Header().Add("Content-Type", "application/json") - err = json.NewEncoder(w).Encode(response{ track, releases }) + err = json.NewEncoder(w).Encode(Track{ track, releases }) if err != nil { - fmt.Printf("FATAL: Failed to serve track %s: %s\n", trackID, err) + fmt.Printf("FATAL: Failed to serve track %s: %s\n", track.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }) @@ -92,15 +84,7 @@ func CreateTrack() http.Handler { return } - var trackID string - err = global.DB.Get(&trackID, - "INSERT INTO musictrack (title, description, lyrics, preview_url) "+ - "VALUES ($1, $2, $3, $4) "+ - "RETURNING id", - track.Title, - track.Description, - track.Lyrics, - track.PreviewURL) + id, err := music.CreateTrack(global.DB, &track) if err != nil { fmt.Printf("FATAL: Failed to create track: %s\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -109,7 +93,7 @@ func CreateTrack() http.Handler { w.Header().Add("Content-Type", "text/plain") w.WriteHeader(http.StatusCreated) - w.Write([]byte(trackID)) + w.Write([]byte(id)) }) } @@ -120,35 +104,18 @@ func UpdateTrack(track model.Track) http.Handler { return } - var update model.Track - err := json.NewDecoder(r.Body).Decode(&update) + err := json.NewDecoder(r.Body).Decode(&track) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } - if update.Title == "" { + if track.Title == "" { http.Error(w, "Track title cannot be empty\n", http.StatusBadRequest) return } - var trackID = r.URL.Path[1:] - var track = model.Track{} - err = global.DB.Get(&track, "SELECT * from musictrack WHERE id=$1", trackID) - if err != nil { - http.NotFound(w, r) - return - } - - _, err = global.DB.Exec( - "UPDATE musictrack "+ - "SET title=$2, description=$3, lyrics=$4, preview_url=$5 "+ - "WHERE id=$1", - track.ID, - track.Title, - track.Description, - track.Lyrics, - track.PreviewURL) + err = music.UpdateTrack(global.DB, &track) if err != nil { fmt.Printf("Failed to update track %s: %s\n", track.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -171,10 +138,7 @@ func DeleteTrack(track model.Track) http.Handler { } var trackID = r.URL.Path[1:] - _, err := global.DB.Exec( - "DELETE FROM musictrack "+ - "WHERE id=$1", - trackID) + err := music.DeleteTrack(global.DB, trackID) if err != nil { fmt.Printf("Failed to delete track %s: %s\n", trackID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/music/controller/artist.go b/music/controller/artist.go index 58b2d63..b6a48f5 100644 --- a/music/controller/artist.go +++ b/music/controller/artist.go @@ -29,14 +29,14 @@ func GetAllArtists(db *sqlx.DB) ([]*model.Artist, error) { return artists, nil } -func GetArtistsNotOnRelease(db *sqlx.DB, release *model.Release) ([]*model.Artist, error) { +func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, error) { var artists = []*model.Artist{} err := db.Select(&artists, "SELECT * FROM artist "+ "WHERE id NOT IN "+ "(SELECT artist FROM musiccredit WHERE release=$1)", - release.ID) + releaseID) if err != nil { return nil, err } @@ -44,6 +44,33 @@ func GetArtistsNotOnRelease(db *sqlx.DB, release *model.Release) ([]*model.Artis return artists, nil } +func GetArtistCredits(db *sqlx.DB, artistID string) ([]*model.Credit, error) { + type DBCredit struct { + Release string + Artist string + Role string + Primary bool `db:"is_primary"` + } + var dbCredits []DBCredit + + err := db.Select(&dbCredits, "SELECT * FROM musiccredit WHERE artist=$1", artistID) + if err != nil { + return nil, err + } + + var credits []*model.Credit + for _, credit := range dbCredits { + credits = append(credits, &model.Credit{ + Release: model.Release{ ID: credit.Release }, + Artist: model.Artist{ ID: credit.Artist }, + Role: credit.Role, + Primary: credit.Primary, + }) + } + + return credits, nil +} + func CreateArtist(db *sqlx.DB, artist *model.Artist) error { _, err := db.Exec( "INSERT INTO artist (id, name, website, avatar) "+ diff --git a/music/controller/credit.go b/music/controller/credit.go deleted file mode 100644 index 59f2fdb..0000000 --- a/music/controller/credit.go +++ /dev/null @@ -1,71 +0,0 @@ -package music - -import ( - "arimelody.me/arimelody.me/music/model" - "github.com/jmoiron/sqlx" -) - -// DATABASE - -func GetReleaseCredits(db *sqlx.DB, release *model.Release) ([]model.Credit, error) { - var credits = []model.Credit{} - - err := db.Select(&credits, - "SELECT artist.*,role,is_primary FROM musiccredit "+ - "JOIN artist ON artist=id "+ - "WHERE release=$1", - release.ID, - ) - if err != nil { - return nil, err - } - - return credits, nil -} - -func CreateCredit(db *sqlx.DB, releaseID string, artistID string, credit *model.Credit) (error) { - _, err := db.Exec( - "INSERT INTO musiccredit (release, artist, role, is_primary) "+ - "VALUES ($1, $2, $3, $4)", - releaseID, - artistID, - credit.Role, - credit.Primary, - ) - if err != nil { - return err - } - - return nil -} - -func UpdateCredit(db *sqlx.DB, releaseID string, artistID string, credit *model.Credit) (error) { - _, err := db.Exec( - "UPDATE musiccredit SET "+ - "role=$3, is_primary=$4 "+ - "WHERE release=$1, artist=$2", - releaseID, - artistID, - credit.Role, - credit.Primary, - ) - if err != nil { - return err - } - - return nil -} - -func DeleteCredit(db *sqlx.DB, releaseID string, artistID string) (error) { - _, err := db.Exec( - "DELETE FROM musiccredit "+ - "WHERE release=$1, artist=$2", - releaseID, - artistID, - ) - if err != nil { - return err - } - - return nil -} diff --git a/music/controller/link.go b/music/controller/link.go deleted file mode 100644 index 5e0f6b1..0000000 --- a/music/controller/link.go +++ /dev/null @@ -1,64 +0,0 @@ -package music - -import ( - "arimelody.me/arimelody.me/music/model" - "github.com/jmoiron/sqlx" -) - -// DATABASE - -func GetReleaseLinks(db *sqlx.DB, release *model.Release) ([]model.Link, error) { - var links = []model.Link{} - - err := db.Select(&links, "SELECT name,url FROM musiclink WHERE release=$1", release.ID) - if err != nil { - return nil, err - } - - return links, nil -} - -func CreateLink(db *sqlx.DB, releaseID string, link *model.Link) (error) { - _, err := db.Exec( - "INSERT INTO musiclink (release, name, url) "+ - "VALUES ($1, $2, $3)", - releaseID, - link.Name, - link.URL, - ) - if err != nil { - return err - } - - return nil -} - -func UpdateLink(db *sqlx.DB, releaseID string, link *model.Link) (error) { - _, err := db.Exec( - "UPDATE musiclink SET "+ - "name=$2, url=$3 "+ - "WHERE release=$1", - releaseID, - link.Name, - link.URL, - ) - if err != nil { - return err - } - - return nil -} - -func DeleteLink(db *sqlx.DB, releaseID string, link *model.Link) (error) { - _, err := db.Exec( - "DELETE FROM musiclink "+ - "WHERE release=$1, name=$2", - releaseID, - link.Name, - ) - if err != nil { - return err - } - - return nil -} diff --git a/music/controller/release.go b/music/controller/release.go index 36c5ac6..c314109 100644 --- a/music/controller/release.go +++ b/music/controller/release.go @@ -1,6 +1,9 @@ package music import ( + "errors" + "fmt" + "arimelody.me/arimelody.me/music/model" "github.com/jmoiron/sqlx" ) @@ -27,28 +30,11 @@ func GetAllReleases(db *sqlx.DB) ([]*model.Release, error) { return releases, nil } -func GetReleaseTracks(db *sqlx.DB, release *model.Release) ([]*model.Track, error) { - var tracks = []*model.Track{} - - err := db.Select(&tracks, - "SELECT musictrack.* FROM musictrack "+ - "JOIN musicreleasetrack ON track=id "+ - "WHERE release=$1 "+ - "ORDER BY number ASC", - release.ID, - ) - if err != nil { - return nil, err - } - - return tracks, nil -} - func CreateRelease(db *sqlx.DB, release *model.Release) error { _, err := db.Exec( "INSERT INTO musicrelease "+ - "(id, visible, title, description, type, release_date, artwork, buyname, buylink) "+ - "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + "(id, visible, title, description, type, release_date, artwork, buyname, buylink, copyright, copyrighturl) "+ + "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", release.ID, release.Visible, release.Title, @@ -58,6 +44,8 @@ func CreateRelease(db *sqlx.DB, release *model.Release) error { release.Artwork, release.Buyname, release.Buylink, + release.Copyright, + release.CopyrightURL, ) if err != nil { return err @@ -69,7 +57,7 @@ func CreateRelease(db *sqlx.DB, release *model.Release) error { func UpdateRelease(db *sqlx.DB, release *model.Release) error { _, err := db.Exec( "UPDATE musicrelease SET "+ - "visible=$2, title=$3, description=$4, type=$5, release_date=$6, artwork=$7, buyname=$8, buylink=$9 "+ + "visible=$2, title=$3, description=$4, type=$5, release_date=$6, artwork=$7, buyname=$8, buylink=$9, copyright=$10, copyrighturl=$11 "+ "WHERE id=$1", release.ID, release.Visible, @@ -80,6 +68,8 @@ func UpdateRelease(db *sqlx.DB, release *model.Release) error { release.Artwork, release.Buyname, release.Buylink, + release.Copyright, + release.CopyrightURL, ) if err != nil { return err @@ -88,7 +78,7 @@ func UpdateRelease(db *sqlx.DB, release *model.Release) error { return nil } -func UpdateReleaseTracks(db *sqlx.DB, release *model.Release, new_tracks []*model.Track) error { +func UpdateReleaseTracks(db *sqlx.DB, release *model.Release, new_tracks []string) error { _, err := db.Exec( "DELETE FROM musicreleasetrack "+ "WHERE release=$1", @@ -98,13 +88,13 @@ func UpdateReleaseTracks(db *sqlx.DB, release *model.Release, new_tracks []*mode return err } - for i, track := range new_tracks { + for i, trackID := range new_tracks { _, err = db.Exec( "INSERT INTO musicreleasetrack "+ "(release, track, number) "+ "VALUES ($1, $2, $3)", release.ID, - track.ID, + trackID, i, ) if err != nil { @@ -115,7 +105,7 @@ func UpdateReleaseTracks(db *sqlx.DB, release *model.Release, new_tracks []*mode return nil } -func UpdateReleaseCredits(db *sqlx.DB, release *model.Release, new_credits []*model.Credit) error { +func UpdateReleaseCredits(db *sqlx.DB, release *model.Release, new_credits []model.Credit) error { _, err := db.Exec( "DELETE FROM musiccredit "+ "WHERE release=$1", @@ -183,17 +173,32 @@ func DeleteRelease(db *sqlx.DB, release *model.Release) error { return nil } -func GetFullRelease(db *sqlx.DB, release *model.Release) (*model.FullRelease, error) { - // get credits - credits, err := GetReleaseCredits(db, release) +func GetFullRelease(db *sqlx.DB, releaseID string) (*model.FullRelease, error) { + // get release + release, err := GetRelease(db, releaseID) if err != nil { return nil, err } - // get tracks - dbTracks, err := GetReleaseTracks(db, release) + // get credits + credits, err := GetReleaseCredits(db, releaseID) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("Credits: %s", err)) + } + + // get artists + for i, credit := range credits { + artist, err := GetArtist(db, credit.Artist.ID) + if err != nil { + return nil, errors.New(fmt.Sprintf("FATAL: Failed to serve release %s: Artists: %s\n", release.ID, err)) + } + credits[i].Artist = *artist + } + + // get tracks + dbTracks, err := GetReleaseTracks(db, releaseID) + if err != nil { + return nil, errors.New(fmt.Sprintf("Tracks: %s", err)) } tracks := []model.DisplayTrack{} for i, track := range dbTracks { @@ -201,9 +206,9 @@ func GetFullRelease(db *sqlx.DB, release *model.Release) (*model.FullRelease, er } // get links - links, err := GetReleaseLinks(db, release) + links, err := GetReleaseLinks(db, releaseID) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("Links: %s", err)) } return &model.FullRelease{ @@ -213,3 +218,63 @@ func GetFullRelease(db *sqlx.DB, release *model.Release) (*model.FullRelease, er Links: links, }, nil } + +func GetReleaseTracks(db *sqlx.DB, releaseID string) ([]model.Track, error) { + var tracks = []model.Track{} + + err := db.Select(&tracks, + "SELECT musictrack.* FROM musictrack "+ + "JOIN musicreleasetrack ON track=id "+ + "WHERE release=$1 "+ + "ORDER BY number ASC", + releaseID, + ) + if err != nil { + return nil, err + } + + return tracks, nil +} + +func GetReleaseCredits(db *sqlx.DB, releaseID string) ([]model.Credit, error) { + type DBCredit struct { + Release string + Artist string + Role string + Primary bool `db:"is_primary"` + } + var dbCredits []DBCredit + + err := db.Select(&dbCredits, + "SELECT musiccredit.* FROM musiccredit "+ + "JOIN artist ON artist=id "+ + "WHERE release=$1", + releaseID, + ) + if err != nil { + return nil, err + } + + var credits []model.Credit + for _, credit := range dbCredits { + credits = append(credits, model.Credit{ + Release: model.Release{ ID: credit.Release }, + Artist: model.Artist{ ID: credit.Artist }, + Role: credit.Role, + Primary: credit.Primary, + }) + } + + return credits, nil +} + +func GetReleaseLinks(db *sqlx.DB, releaseID string) ([]model.Link, error) { + var links = []model.Link{} + + err := db.Select(&links, "SELECT name,url FROM musiclink WHERE release=$1", releaseID) + if err != nil { + return nil, err + } + + return links, nil +} diff --git a/music/controller/track.go b/music/controller/track.go index 43627a7..a070bff 100644 --- a/music/controller/track.go +++ b/music/controller/track.go @@ -40,14 +40,14 @@ func GetOrphanTracks(db *sqlx.DB) ([]*model.Track, error) { return tracks, nil } -func GetTracksNotOnRelease(db *sqlx.DB, release *model.Release) ([]*model.Track, error) { +func GetTracksNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Track, error) { var tracks = []*model.Track{} err := db.Select(&tracks, "SELECT * FROM musictrack "+ "WHERE id NOT IN "+ "(SELECT track FROM musicreleasetrack WHERE release=$1)", - release.ID) + releaseID) if err != nil { return nil, err } @@ -55,15 +55,15 @@ func GetTracksNotOnRelease(db *sqlx.DB, release *model.Release) ([]*model.Track, return tracks, nil } -func GetTrackReleases(db *sqlx.DB, track *model.Track) ([]*model.Release, error) { - var releases = []*model.Release{} +func GetTrackReleases(db *sqlx.DB, trackID string) ([]model.ReleaseShorthand, error) { + var releases = []model.ReleaseShorthand{} err := db.Select(&releases, - "SELECT musicrelease.* FROM musicrelease "+ + "SELECT id,title,type,release_date,artwork,buylink FROM musicrelease "+ "JOIN musicreleasetrack ON release=id "+ "WHERE track=$1 "+ "ORDER BY release_date", - track.ID, + trackID, ) if err != nil { return nil, err @@ -123,11 +123,11 @@ func UpdateTrack(db *sqlx.DB, track *model.Track) error { return nil } -func DeleteTrack(db *sqlx.DB, track *model.Track) error { +func DeleteTrack(db *sqlx.DB, trackID string) error { _, err := db.Exec( "DELETE FROM musictrack "+ "WHERE id=$1", - track.ID, + trackID, ) if err != nil { return err diff --git a/music/model/credit.go b/music/model/credit.go index df83326..9b62b54 100644 --- a/music/model/credit.go +++ b/music/model/credit.go @@ -1,7 +1,8 @@ package model type Credit struct { - Artist `json:"artist"` + Release Release `json:"release"` + Artist Artist `json:"artist"` Role string `json:"role"` Primary bool `json:"primary" db:"is_primary"` } diff --git a/music/model/release.go b/music/model/release.go index 9df6a3b..d9840a4 100644 --- a/music/model/release.go +++ b/music/model/release.go @@ -8,24 +8,33 @@ type ( ReleaseType string Release struct { - ID string `json:"id"` - Visible bool `json:"visible"` - Title string `json:"title"` - Description string `json:"description"` - ReleaseType ReleaseType `json:"type" db:"type"` - ReleaseDate time.Time `json:"releaseDate" db:"release_date"` - Artwork string `json:"artwork"` - Buyname string `json:"buyname"` - Buylink string `json:"buylink"` - Copyright string `json:"copyright" db:"copyright"` - CopyrightURL string `json:"copyrightURL" db:"copyrighturl"` + ID string `json:"id"` + Visible bool `json:"visible"` + Title string `json:"title"` + Description string `json:"description"` + ReleaseType ReleaseType `json:"type" db:"type"` + ReleaseDate time.Time `json:"releaseDate" db:"release_date"` + Artwork string `json:"artwork"` + Buyname string `json:"buyname"` + Buylink string `json:"buylink"` + Copyright string `json:"copyright" db:"copyright"` + CopyrightURL string `json:"copyrightURL" db:"copyrighturl"` } FullRelease struct { *Release - Tracks []DisplayTrack - Credits []Credit - Links []Link + Tracks []DisplayTrack `json:"tracks"` + Credits []Credit `json:"credits"` + Links []Link `json:"links"` + } + + ReleaseShorthand struct { + ID string `json:"id"` + Title string `json:"title"` + ReleaseType ReleaseType `json:"type" db:"type"` + ReleaseDate time.Time `json:"releaseDate" db:"release_date"` + Artwork string `json:"artwork"` + Buylink string `json:"buylink"` } ) diff --git a/music/model/track.go b/music/model/track.go index 2bb8bf4..47b2c46 100644 --- a/music/model/track.go +++ b/music/model/track.go @@ -10,14 +10,14 @@ type ( ID string `json:"id"` Title string `json:"title"` Description string `json:"description"` - Lyrics string `json:"lyrics"` + Lyrics string `json:"lyrics" db:"lyrics"` PreviewURL string `json:"previewURL" db:"preview_url"` } DisplayTrack struct { *Track - Lyrics template.HTML - Number int + Lyrics template.HTML `json:"lyrics"` + Number int `json:"-"` } ) diff --git a/music/view/music.go b/music/view/music.go index 49bfb52..041680e 100644 --- a/music/view/music.go +++ b/music/view/music.go @@ -48,7 +48,7 @@ func ServeCatalog() http.Handler { if !dbRelease.IsReleased() { dbRelease.ReleaseType = model.Upcoming } - release, err := music.GetFullRelease(global.DB, dbRelease) + release, err := music.GetFullRelease(global.DB, dbRelease.ID) if err != nil { fmt.Printf("FATAL: Failed to pull full release for %s: %s\n", dbRelease.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/music/view/release.go b/music/view/release.go index 266f06e..306c97e 100644 --- a/music/view/release.go +++ b/music/view/release.go @@ -12,7 +12,26 @@ import ( "arimelody.me/arimelody.me/templates" ) -// HTTP HANDLERS +type ( + Track struct { + Title string `json:"title"` + Description string `json:"description"` + Lyrics string `json:"lyrics"` + } + + Credit struct { + *model.Artist + Role string `json:"role"` + Primary bool `json:"primary"` + } + + Release struct { + model.Release + Tracks []Track `json:"tracks"` + Credits []Credit `json:"credits"` + Links map[string]string `json:"links"` + } +) func ServeRelease(release model.Release) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -23,22 +42,65 @@ func ServeRelease(release model.Release) http.Handler { return } - fullRelease := &model.FullRelease{ - Release: &release, + response := Release{ + Release: release, + Tracks: []Track{}, + Credits: []Credit{}, + Links: make(map[string]string), } if authorised || release.IsReleased() { - fullerRelease, err := db.GetFullRelease(global.DB, &release) + // get credits + credits, err := db.GetReleaseCredits(global.DB, release.ID) if err != nil { - fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err) + fmt.Printf("FATAL: Failed to serve release %s: Credits: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - fullRelease = fullerRelease + for _, credit := range credits { + artist, err := db.GetArtist(global.DB, credit.Artist.ID) + if err != nil { + fmt.Printf("FATAL: Failed to serve release %s: Artists: %s\n", release.ID, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + response.Credits = append(response.Credits, Credit{ + Artist: artist, + Role: credit.Role, + Primary: credit.Primary, + }) + } + + // get tracks + tracks, err := db.GetReleaseTracks(global.DB, release.ID) + if err != nil { + fmt.Printf("FATAL: Failed to serve release %s: Tracks: %s\n", release.ID, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + for _, track := range tracks { + response.Tracks = append(response.Tracks, Track{ + Title: track.Title, + Description: track.Description, + Lyrics: track.Lyrics, + }) + } + + // get links + links, err := db.GetReleaseLinks(global.DB, release.ID) + if err != nil { + fmt.Printf("FATAL: Failed to serve release %s: Links: %s\n", release.ID, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + for _, link := range links { + response.Links[link.Name] = link.URL + } } w.Header().Add("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(fullRelease) + err := json.NewEncoder(w).Encode(response) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return @@ -60,7 +122,7 @@ func ServeGateway(release model.Release) http.Handler { } if authorised || release.IsReleased() { - fullerRelease, err := db.GetFullRelease(global.DB, &release) + fullerRelease, err := db.GetFullRelease(global.DB, release.ID) if err != nil { fmt.Printf("FATAL: Failed to pull full release data for %s: %s\n", release.ID, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) diff --git a/public/style/music-gateway.css b/public/style/music-gateway.css index 5075b5e..68ad65c 100644 --- a/public/style/music-gateway.css +++ b/public/style/music-gateway.css @@ -311,6 +311,7 @@ ul#links li { flex-grow: 1; } +#buylink, ul#links a { width: calc(100% - 1.6em); padding: .5em .8em; @@ -324,7 +325,9 @@ ul#links a { transition: filter .1s,-webkit-filter .1s } -ul#links a.buy { +#buylink { + margin-top: 1em; + margin-bottom: -.5em; background-color: #ff94e9 } @@ -390,7 +393,7 @@ ul#links a:hover { animation: share-after 2s cubic-bezier(.5,0,1,.5) forwards } -h2 { +#info h2 { width: fit-content; padding: .3em 1em; font-size: 1em; diff --git a/views/music-gateway.html b/views/music-gateway.html index 8a98e69..93a6950 100644 --- a/views/music-gateway.html +++ b/views/music-gateway.html @@ -29,7 +29,7 @@ {{end}} {{define "content"}} -
+
@@ -65,13 +65,10 @@ {{end}} {{if .IsReleased}} + {{if .Buylink}} + {{or .Buyname "buy"}} + {{end}}
Release Date - +
Buy Name - +
Buy Link - + +
Copyright + +
Copyright URL +