turns out rewriting all of your database code takes a while
This commit is contained in:
parent
1998a36d6d
commit
965d6f5c3e
|
@ -51,9 +51,8 @@
|
||||||
|
|
||||||
makeMagicList(creditList, ".credit");
|
makeMagicList(creditList, ".credit");
|
||||||
|
|
||||||
creditList.addEventListener("htmx:afterSwap", e => {
|
function rigCredit(el) {
|
||||||
const el = creditList.children[creditList.children.length - 1];
|
console.log(el);
|
||||||
|
|
||||||
const artistID = el.dataset.artist;
|
const artistID = el.dataset.artist;
|
||||||
const deleteBtn = el.querySelector("a.delete");
|
const deleteBtn = el.querySelector("a.delete");
|
||||||
|
|
||||||
|
@ -64,6 +63,12 @@
|
||||||
|
|
||||||
el.addEventListener("dragstart", () => { el.classList.add("moving") });
|
el.addEventListener("dragstart", () => { el.classList.add("moving") });
|
||||||
el.addEventListener("dragend", () => { el.classList.remove("moving") });
|
el.addEventListener("dragend", () => { el.classList.remove("moving") });
|
||||||
|
}
|
||||||
|
|
||||||
|
[...creditList.querySelectorAll(".credit")].map(rigCredit);
|
||||||
|
|
||||||
|
creditList.addEventListener("htmx:afterSwap", () => {
|
||||||
|
rigCredit(creditList.children[creditList.children.length - 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
container.showModal();
|
container.showModal();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<li class="track" data-track="{{.ID}}" data-title="{{.Title}}" data-number="{{.Number}}" draggable="true">
|
<li class="track" data-track="{{.ID}}" data-title="{{.Title}}" data-number="0" draggable="true">
|
||||||
<div>
|
<div>
|
||||||
<p class="track-name">
|
<p class="track-name">
|
||||||
<span class="track-number">{{.Number}}</span>
|
<span class="track-number">0</span>
|
||||||
{{.Title}}
|
{{.Title}}
|
||||||
</p>
|
</p>
|
||||||
<a class="delete">Delete</a>
|
<a class="delete">Delete</a>
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/discord"
|
"arimelody.me/arimelody.me/discord"
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
musicController "arimelody.me/arimelody.me/music/controller"
|
|
||||||
musicModel "arimelody.me/arimelody.me/music/model"
|
musicModel "arimelody.me/arimelody.me/music/model"
|
||||||
|
musicDB "arimelody.me/arimelody.me/music/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
type loginData struct {
|
type loginData struct {
|
||||||
|
@ -29,18 +29,6 @@ func Handler() http.Handler {
|
||||||
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
||||||
mux.Handle("/release/", MustAuthorise(http.StripPrefix("/release", serveRelease())))
|
mux.Handle("/release/", MustAuthorise(http.StripPrefix("/release", serveRelease())))
|
||||||
mux.Handle("/track/", MustAuthorise(http.StripPrefix("/track", serveTrack())))
|
mux.Handle("/track/", MustAuthorise(http.StripPrefix("/track", serveTrack())))
|
||||||
mux.Handle("/createtrack", MustAuthorise(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
track := musicModel.Track{ Title: "Untitled Track" }
|
|
||||||
trackID, err := musicController.CreateTrackDB(global.DB, &track)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to create track: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
track.ID = trackID
|
|
||||||
global.Tracks = append(global.Tracks, &track)
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("/admin/track/%s", trackID), http.StatusTemporaryRedirect)
|
|
||||||
})))
|
|
||||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -54,30 +42,55 @@ func Handler() http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Track struct {
|
|
||||||
*musicModel.Track
|
|
||||||
Lyrics template.HTML
|
|
||||||
// Number int
|
|
||||||
}
|
|
||||||
IndexData struct {
|
IndexData struct {
|
||||||
Releases []*musicModel.Release
|
Releases []musicModel.FullRelease
|
||||||
Artists []*musicModel.Artist
|
Artists []*musicModel.Artist
|
||||||
Tracks []Track
|
Tracks []musicModel.DisplayTrack
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var tracks = []Track{}
|
dbReleases, err := musicDB.GetAllReleases(global.DB)
|
||||||
for _, track := range global.Tracks {
|
if err != nil {
|
||||||
if track.Release != nil { continue }
|
fmt.Printf("FATAL: Failed to pull releases: %s\n", err)
|
||||||
tracks = append(tracks, Track{
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
releases := []musicModel.FullRelease{}
|
||||||
|
for _, release := range dbReleases {
|
||||||
|
fullRelease, err := musicDB.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
|
||||||
|
}
|
||||||
|
releases = append(releases, *fullRelease)
|
||||||
|
}
|
||||||
|
|
||||||
|
artists, err := musicDB.GetAllArtists(global.DB)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Failed to pull artists: %s\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dbTracks, err := musicDB.GetOrphanTracks(global.DB)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Failed to pull orphan tracks: %s\n", err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tracks = []musicModel.DisplayTrack{}
|
||||||
|
for _, track := range dbTracks {
|
||||||
|
tracks = append(tracks, musicModel.DisplayTrack{
|
||||||
Track: track,
|
Track: track,
|
||||||
Lyrics: template.HTML(strings.Replace(track.Lyrics, "\n", "<br>", -1)),
|
Lyrics: template.HTML(strings.Replace(track.Lyrics, "\n", "<br>", -1)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
err := pages["index"].Execute(w, IndexData{
|
err = pages["index"].Execute(w, IndexData{
|
||||||
Releases: global.Releases,
|
Releases: releases,
|
||||||
Artists: global.Artists,
|
Artists: artists,
|
||||||
Tracks: tracks,
|
Tracks: tracks,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,83 +2,79 @@ package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
"arimelody.me/arimelody.me/music/model"
|
"arimelody.me/arimelody.me/music/model"
|
||||||
)
|
controller "arimelody.me/arimelody.me/music/controller"
|
||||||
|
|
||||||
type (
|
|
||||||
gatewayTrack struct {
|
|
||||||
*model.Track
|
|
||||||
Lyrics template.HTML
|
|
||||||
Number int
|
|
||||||
}
|
|
||||||
|
|
||||||
gatewayRelease struct {
|
|
||||||
*model.Release
|
|
||||||
Tracks []gatewayTrack
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveRelease() http.Handler {
|
func serveRelease() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
slices := strings.Split(r.URL.Path[1:], "/")
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
id := slices[0]
|
releaseID := slices[0]
|
||||||
release := global.GetRelease(id)
|
release, err := controller.GetRelease(global.DB, releaseID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Failed to pull release %s: %s\n", releaseID, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
if release == nil {
|
if release == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authorised := GetSession(r) != nil
|
||||||
|
if !authorised && !release.Visible {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
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 {
|
if len(slices) > 1 {
|
||||||
switch slices[1] {
|
switch slices[1] {
|
||||||
case "editcredits":
|
case "editcredits":
|
||||||
serveEditCredits(release).ServeHTTP(w, r)
|
serveEditCredits(fullRelease).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "addcredit":
|
case "addcredit":
|
||||||
serveAddCredit(release).ServeHTTP(w, r)
|
serveAddCredit(fullRelease).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "newcredit":
|
case "newcredit":
|
||||||
serveNewCredit().ServeHTTP(w, r)
|
serveNewCredit().ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "editlinks":
|
case "editlinks":
|
||||||
serveEditLinks(release).ServeHTTP(w, r)
|
serveEditLinks(fullRelease).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "edittracks":
|
case "edittracks":
|
||||||
serveEditTracks(release).ServeHTTP(w, r)
|
serveEditTracks(fullRelease).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "addtrack":
|
case "addtrack":
|
||||||
serveAddTrack(release).ServeHTTP(w, r)
|
serveAddTrack(fullRelease).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case "newtrack":
|
case "newtrack":
|
||||||
serveNewTrack(release).ServeHTTP(w, r)
|
serveNewTrack().ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks := []gatewayTrack{}
|
err = pages["release"].Execute(w, fullRelease)
|
||||||
for i, track := range release.Tracks {
|
|
||||||
tracks = append(tracks, gatewayTrack{
|
|
||||||
Track: track,
|
|
||||||
Lyrics: template.HTML(strings.Replace(track.Lyrics, "\n", "<br>", -1)),
|
|
||||||
Number: i + 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
err := pages["release"].Execute(w, gatewayRelease{release, tracks})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering admin release page for %s: %s\n", id, err)
|
fmt.Printf("Error rendering admin release page for %s: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveEditCredits(release *model.Release) http.Handler {
|
func serveEditCredits(release *model.FullRelease) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
err := components["editcredits"].Execute(w, release)
|
err := components["editcredits"].Execute(w, release)
|
||||||
|
@ -89,20 +85,13 @@ func serveEditCredits(release *model.Release) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveAddCredit(release *model.Release) http.Handler {
|
func serveAddCredit(release *model.FullRelease) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var artists = []*model.Artist{}
|
artists, err := controller.GetArtistsNotOnRelease(global.DB, release.Release)
|
||||||
for _, artist := range global.Artists {
|
if err != nil {
|
||||||
var exists = false
|
fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err)
|
||||||
for _, credit := range release.Credits {
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
if credit.Artist == artist {
|
return
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
artists = append(artists, artist)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type response struct {
|
type response struct {
|
||||||
|
@ -111,7 +100,7 @@ func serveAddCredit(release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
err := components["addcredit"].Execute(w, response{
|
err = components["addcredit"].Execute(w, response{
|
||||||
ReleaseID: release.ID,
|
ReleaseID: release.ID,
|
||||||
Artists: artists,
|
Artists: artists,
|
||||||
})
|
})
|
||||||
|
@ -124,23 +113,28 @@ func serveAddCredit(release *model.Release) http.Handler {
|
||||||
|
|
||||||
func serveNewCredit() http.Handler {
|
func serveNewCredit() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
artist := global.GetArtist(strings.Split(r.URL.Path, "/")[3])
|
artistID := strings.Split(r.URL.Path, "/")[3]
|
||||||
|
artist, err := controller.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)
|
||||||
|
return
|
||||||
|
}
|
||||||
if artist == nil {
|
if artist == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
err := components["newcredit"].Execute(w, artist)
|
err = components["newcredit"].Execute(w, artist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering new credit component for %s: %s\n", artist.ID, err)
|
fmt.Printf("Error rendering new credit component for %s: %s\n", artist.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveEditLinks(release *model.Release) http.Handler {
|
func serveEditLinks(release *model.FullRelease) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
err := components["editlinks"].Execute(w, release)
|
err := components["editlinks"].Execute(w, release)
|
||||||
|
@ -148,49 +142,27 @@ func serveEditLinks(release *model.Release) http.Handler {
|
||||||
fmt.Printf("Error rendering edit links component for %s: %s\n", release.ID, err)
|
fmt.Printf("Error rendering edit links component for %s: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveEditTracks(release *model.Release) http.Handler {
|
func serveEditTracks(release *model.FullRelease) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
type Track struct {
|
err := components["edittracks"].Execute(w, release)
|
||||||
*model.Track
|
|
||||||
Number int
|
|
||||||
}
|
|
||||||
type Release struct {
|
|
||||||
*model.Release
|
|
||||||
Tracks []Track
|
|
||||||
}
|
|
||||||
var data = Release{ release, []Track{} }
|
|
||||||
for i, track := range release.Tracks {
|
|
||||||
data.Tracks = append(data.Tracks, Track{track, i + 1})
|
|
||||||
}
|
|
||||||
|
|
||||||
err := components["edittracks"].Execute(w, data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering edit tracks component for %s: %s\n", release.ID, err)
|
fmt.Printf("Error rendering edit tracks component for %s: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveAddTrack(release *model.Release) http.Handler {
|
func serveAddTrack(release *model.FullRelease) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var tracks = []*model.Track{}
|
tracks, err := controller.GetTracksNotOnRelease(global.DB, release.Release)
|
||||||
for _, track := range global.Tracks {
|
if err != nil {
|
||||||
var exists = false
|
fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err)
|
||||||
for _, t := range release.Tracks {
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
if t == track {
|
return
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
tracks = append(tracks, track)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type response struct {
|
type response struct {
|
||||||
|
@ -199,7 +171,7 @@ func serveAddTrack(release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
err := components["addtrack"].Execute(w, response{
|
err = components["addtrack"].Execute(w, response{
|
||||||
ReleaseID: release.ID,
|
ReleaseID: release.ID,
|
||||||
Tracks: tracks,
|
Tracks: tracks,
|
||||||
})
|
})
|
||||||
|
@ -211,24 +183,22 @@ func serveAddTrack(release *model.Release) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveNewTrack(release *model.Release) http.Handler {
|
func serveNewTrack() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
track := global.GetTrack(strings.Split(r.URL.Path, "/")[3])
|
trackID := strings.Split(r.URL.Path, "/")[3]
|
||||||
|
track, err := controller.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)
|
||||||
|
return
|
||||||
|
}
|
||||||
if track == nil {
|
if track == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type Track struct {
|
|
||||||
*model.Track
|
|
||||||
Number int
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
err := components["newtrack"].Execute(w, Track{
|
err = components["newtrack"].Execute(w, track)
|
||||||
track,
|
|
||||||
len(release.Tracks) + 1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering new track component for %s: %s\n", track.ID, err)
|
fmt.Printf("Error rendering new track component for %s: %s\n", track.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const newReleaseBtn = document.getElementById("create-release");
|
const newReleaseBtn = document.getElementById("create-release");
|
||||||
|
const newTrackBtn = document.getElementById("create-track");
|
||||||
|
|
||||||
newReleaseBtn.addEventListener("click", event => {
|
newReleaseBtn.addEventListener("click", event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -22,3 +23,27 @@ newReleaseBtn.addEventListener("click", event => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
newTrackBtn.addEventListener("click", event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const title = prompt("Enter an title for this track:");
|
||||||
|
if (title == null || title == "") return;
|
||||||
|
|
||||||
|
fetch("/api/v1/track", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({title})
|
||||||
|
}).then(res => {
|
||||||
|
res.text().then(text => {
|
||||||
|
if (res.ok) {
|
||||||
|
location = "/admin/track/" + text;
|
||||||
|
} else {
|
||||||
|
alert("Request failed: " + text);
|
||||||
|
console.error(text);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch(err => {
|
||||||
|
alert("Failed to create release. Check the console for details.");
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -6,19 +6,48 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
|
"arimelody.me/arimelody.me/music/model"
|
||||||
|
"arimelody.me/arimelody.me/music/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveTrack() http.Handler {
|
func serveTrack() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
slices := strings.Split(r.URL.Path[1:], "/")
|
slices := strings.Split(r.URL.Path[1:], "/")
|
||||||
id := slices[0]
|
id := slices[0]
|
||||||
track := global.GetTrack(id)
|
track, err := music.GetTrack(global.DB, 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)
|
||||||
|
return
|
||||||
|
}
|
||||||
if track == nil {
|
if track == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := pages["track"].Execute(w, track)
|
dbReleases, err := music.GetTrackReleases(global.DB, track)
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
releases := []model.FullRelease{}
|
||||||
|
for _, release := range dbReleases {
|
||||||
|
fullRelease, err := music.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
|
||||||
|
}
|
||||||
|
releases = append(releases, *fullRelease)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Track struct {
|
||||||
|
*model.Track
|
||||||
|
Releases []model.FullRelease
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pages["track"].Execute(w, Track{ Track: track, Releases: releases })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
fmt.Printf("Error rendering admin track page for %s: %s\n", id, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
@ -43,8 +43,10 @@
|
||||||
<h2>Featured in</h2>
|
<h2>Featured in</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card releases">
|
<div class="card releases">
|
||||||
{{if .Release}}
|
{{if .Releases}}
|
||||||
{{block "release" .Release}}{{end}}
|
{{range .Releases}}
|
||||||
|
{{block "release" .}}{{end}}
|
||||||
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<p>This track isn't bound to a release.</p>
|
<p>This track isn't bound to a release.</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h1>Releases</h1>
|
<h1>Releases</h1>
|
||||||
<a href="/admin/createrelease" class="create-btn" id="create-release">Create New</a>
|
<a class="create-btn" id="create-release">Create New</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card releases">
|
<div class="card releases">
|
||||||
{{range .Releases}}
|
{{range .Releases}}
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h1>Artists</h1>
|
<h1>Artists</h1>
|
||||||
<a href="/admin/createartist" class="create-btn">Create New</a>
|
<a class="create-btn">Create New</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card artists">
|
<div class="card artists">
|
||||||
{{range $Artist := .Artists}}
|
{{range $Artist := .Artists}}
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h1>Tracks</h1>
|
<h1>Tracks</h1>
|
||||||
<a href="/admin/createtrack" class="create-btn">Create New</a>
|
<a class="create-btn" id="create-track">Create New</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card tracks">
|
<div class="card tracks">
|
||||||
<p><em>"Orphaned" tracks that have not yet been bound to a release.</em></p>
|
<p><em>"Orphaned" tracks that have not yet been bound to a release.</em></p>
|
||||||
|
@ -47,11 +47,6 @@
|
||||||
<div class="track">
|
<div class="track">
|
||||||
<h2 class="track-title">
|
<h2 class="track-title">
|
||||||
<a href="/admin/track/{{$Track.ID}}">{{$Track.Title}}</a>
|
<a href="/admin/track/{{$Track.ID}}">{{$Track.Title}}</a>
|
||||||
{{if $Track.Release}}
|
|
||||||
<small class="track-album">{{$Track.Release.Title}}</small>
|
|
||||||
{{else}}
|
|
||||||
<small class="track-album empty">(no release)</small>
|
|
||||||
{{end}}
|
|
||||||
</h2>
|
</h2>
|
||||||
{{if $Track.Description}}
|
{{if $Track.Description}}
|
||||||
<p class="track-description">{{$Track.Description}}</p>
|
<p class="track-description">{{$Track.Description}}</p>
|
||||||
|
|
49
api/api.go
49
api/api.go
|
@ -1,9 +1,13 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/admin"
|
"arimelody.me/arimelody.me/admin"
|
||||||
|
"arimelody.me/arimelody.me/global"
|
||||||
|
"arimelody.me/arimelody.me/music/model"
|
||||||
music "arimelody.me/arimelody.me/music/view"
|
music "arimelody.me/arimelody.me/music/view"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,16 +17,25 @@ func Handler() http.Handler {
|
||||||
// ARTIST ENDPOINTS
|
// ARTIST ENDPOINTS
|
||||||
|
|
||||||
mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.Handle("/v1/artist/", http.StripPrefix("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var artistID = strings.Split(r.URL.Path[1:], "/")[0]
|
||||||
|
var artist model.Artist
|
||||||
|
err := global.DB.Get(&artist, "SELECT * FROM artist WHERE id=$1", artistID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Error while retrieving artist %s: %s\n", artistID, err)
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// GET /api/v1/artist/{id}
|
// GET /api/v1/artist/{id}
|
||||||
ServeArtist().ServeHTTP(w, r)
|
ServeArtist(artist).ServeHTTP(w, r)
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
// PUT /api/v1/artist/{id} (admin)
|
// PUT /api/v1/artist/{id} (admin)
|
||||||
admin.MustAuthorise(UpdateArtist()).ServeHTTP(w, r)
|
admin.MustAuthorise(UpdateArtist(artist)).ServeHTTP(w, r)
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
// DELETE /api/v1/artist/{id} (admin)
|
// DELETE /api/v1/artist/{id} (admin)
|
||||||
admin.MustAuthorise(DeleteArtist()).ServeHTTP(w, r)
|
admin.MustAuthorise(DeleteArtist(artist)).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
@ -43,16 +56,25 @@ func Handler() http.Handler {
|
||||||
// RELEASE ENDPOINTS
|
// RELEASE ENDPOINTS
|
||||||
|
|
||||||
mux.Handle("/v1/music/", http.StripPrefix("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.Handle("/v1/music/", http.StripPrefix("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var releaseID = strings.Split(r.URL.Path[1:], "/")[0]
|
||||||
|
var release model.Release
|
||||||
|
err := global.DB.Get(&release, "SELECT * FROM musicrelease WHERE id=$1", releaseID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Error while retrieving release %s: %s\n", releaseID, err)
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// GET /api/v1/music/{id}
|
// GET /api/v1/music/{id}
|
||||||
music.ServeRelease().ServeHTTP(w, r)
|
music.ServeRelease(release).ServeHTTP(w, r)
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
// PUT /api/v1/music/{id} (admin)
|
// PUT /api/v1/music/{id} (admin)
|
||||||
admin.MustAuthorise(UpdateRelease()).ServeHTTP(w, r)
|
admin.MustAuthorise(UpdateRelease(release)).ServeHTTP(w, r)
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
// DELETE /api/v1/music/{id} (admin)
|
// DELETE /api/v1/music/{id} (admin)
|
||||||
admin.MustAuthorise(DeleteRelease()).ServeHTTP(w, r)
|
admin.MustAuthorise(DeleteRelease(release)).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
@ -73,16 +95,25 @@ func Handler() http.Handler {
|
||||||
// TRACK ENDPOINTS
|
// TRACK ENDPOINTS
|
||||||
|
|
||||||
mux.Handle("/v1/track/", http.StripPrefix("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.Handle("/v1/track/", http.StripPrefix("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var trackID = strings.Split(r.URL.Path[1:], "/")[0]
|
||||||
|
var track model.Track
|
||||||
|
err := global.DB.Get(&track, "SELECT * FROM musictrack WHERE id=$1", trackID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Error while retrieving track %s: %s\n", trackID, err)
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// GET /api/v1/track/{id} (admin)
|
// GET /api/v1/track/{id} (admin)
|
||||||
admin.MustAuthorise(ServeTrack()).ServeHTTP(w, r)
|
admin.MustAuthorise(ServeTrack(track)).ServeHTTP(w, r)
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
// PUT /api/v1/track/{id} (admin)
|
// PUT /api/v1/track/{id} (admin)
|
||||||
admin.MustAuthorise(UpdateTrack()).ServeHTTP(w, r)
|
admin.MustAuthorise(UpdateTrack(track)).ServeHTTP(w, r)
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
// DELETE /api/v1/track/{id} (admin)
|
// DELETE /api/v1/track/{id} (admin)
|
||||||
admin.MustAuthorise(DeleteTrack()).ServeHTTP(w, r)
|
admin.MustAuthorise(DeleteTrack(track)).ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
|
183
api/artist.go
183
api/artist.go
|
@ -4,10 +4,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
"arimelody.me/arimelody.me/music/model"
|
"arimelody.me/arimelody.me/music/model"
|
||||||
controller "arimelody.me/arimelody.me/music/controller"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type artistJSON struct {
|
type artistJSON struct {
|
||||||
|
@ -19,76 +19,60 @@ type artistJSON struct {
|
||||||
|
|
||||||
func ServeAllArtists() http.Handler {
|
func ServeAllArtists() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Add("Content-Type", "application/json")
|
var artists = []*model.Artist{}
|
||||||
err := json.NewEncoder(w).Encode(global.Artists)
|
err := global.DB.Select(&artists, "SELECT * FROM artist")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Failed to serve all artists: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
err = json.NewEncoder(w).Encode(artists)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeArtist() http.Handler {
|
func ServeArtist(artist model.Artist) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
|
||||||
ServeAllArtists().ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
creditJSON struct {
|
creditJSON struct {
|
||||||
Role string `json:"role"`
|
Release string `json:"release"`
|
||||||
Primary bool `json:"primary"`
|
Role string `json:"role"`
|
||||||
|
Primary bool `json:"primary"`
|
||||||
}
|
}
|
||||||
artistJSON struct {
|
artistJSON struct {
|
||||||
model.Artist
|
model.Artist
|
||||||
Credits map[string]creditJSON `json:"credits"`
|
Credits map[string]creditJSON `json:"credits"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
var artist = artistJSON{}
|
|
||||||
|
|
||||||
artist.ID = r.URL.Path[1:]
|
var credits = map[string]creditJSON{}
|
||||||
var a = global.GetArtist(artist.ID)
|
err := global.DB.Select(&credits, "SELECT release,role,is_primary FROM musiccredit WHERE id=$1", artist.ID)
|
||||||
if a == nil {
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
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
|
return
|
||||||
}
|
}
|
||||||
artist.Name = a.Name
|
|
||||||
artist.Website = a.Website
|
|
||||||
artist.Credits = make(map[string]creditJSON)
|
|
||||||
|
|
||||||
for _, release := range global.Releases {
|
|
||||||
for _, credit := range release.Credits {
|
|
||||||
if credit.Artist.ID != artist.ID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
artist.Credits[release.ID] = creditJSON{
|
|
||||||
Role: credit.Role,
|
|
||||||
Primary: credit.Primary,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
err := json.NewEncoder(w).Encode(artist)
|
err = json.NewEncoder(w).Encode(artistJSON{
|
||||||
|
Artist: artist,
|
||||||
|
Credits: credits,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateArtist() http.Handler {
|
func CreateArtist() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var data artistJSON
|
var data artistJSON
|
||||||
err := json.NewDecoder(r.Body).Decode(&data)
|
err := json.NewDecoder(r.Body).Decode(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to create artist: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -102,11 +86,6 @@ func CreateArtist() http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if global.GetArtist(data.ID) != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Artist %s already exists\n", data.ID), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var artist = model.Artist{
|
var artist = model.Artist{
|
||||||
ID: data.ID,
|
ID: data.ID,
|
||||||
Name: *data.Name,
|
Name: *data.Name,
|
||||||
|
@ -114,116 +93,66 @@ func CreateArtist() http.Handler {
|
||||||
Avatar: *data.Avatar,
|
Avatar: *data.Avatar,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.CreateArtistDB(global.DB, &artist)
|
_, err = global.DB.Exec(
|
||||||
|
"INSERT INTO artist (id, name, website, avatar) "+
|
||||||
|
"VALUES ($1, $2, $3, $4)",
|
||||||
|
artist.ID,
|
||||||
|
artist.Name,
|
||||||
|
artist.Website,
|
||||||
|
artist.Avatar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to create artist %s: %s\n", artist.ID, err)
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
|
http.Error(w, fmt.Sprintf("Artist %s already exists\n", data.ID), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("FATAL: Failed to create artist %s: %s\n", artist.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Artists = append(global.Artists, &artist)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
err = json.NewEncoder(w).Encode(artist)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateArtist() http.Handler {
|
func UpdateArtist(artist model.Artist) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path == "/" {
|
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var data artistJSON
|
var data artistJSON
|
||||||
err := json.NewDecoder(r.Body).Decode(&data)
|
err := json.NewDecoder(r.Body).Decode(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to update artist: %s\n", err)
|
fmt.Printf("FATAL: Failed to update artist: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var artistID = r.URL.Path[1:]
|
if data.ID != "" { artist.ID = data.ID }
|
||||||
var artist = global.GetArtist(artistID)
|
if data.Name != nil { artist.Name = *data.Name }
|
||||||
if artist == nil {
|
if data.Website != nil { artist.Website = *data.Website }
|
||||||
http.Error(w, fmt.Sprintf("Artist %s does not exist\n", artistID), http.StatusBadRequest)
|
if data.Avatar != nil { artist.Avatar = *data.Avatar }
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var update = *artist
|
_, err = global.DB.Exec(
|
||||||
|
"UPDATE artist "+
|
||||||
if data.ID != "" { update.ID = data.ID }
|
"SET name=$2, website=$3, avatar=$4 "+
|
||||||
if data.Name != nil { update.Name = *data.Name }
|
"WHERE id=$1",
|
||||||
if data.Website != nil { update.Website = *data.Website }
|
artist.ID,
|
||||||
if data.Avatar != nil { update.Avatar = *data.Avatar }
|
artist.Name,
|
||||||
|
artist.Website,
|
||||||
err = controller.UpdateArtistDB(global.DB, &update)
|
artist.Avatar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to update artist %s: %s\n", artist.ID, err)
|
fmt.Printf("FATAL: Failed to update artist %s: %s\n", artist.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
artist.ID = update.ID
|
|
||||||
artist.Name = update.Name
|
|
||||||
artist.Website = update.Website
|
|
||||||
artist.Avatar = update.Avatar
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
err = json.NewEncoder(w).Encode(artist)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteArtist() http.Handler {
|
func DeleteArtist(artist model.Artist) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
_, err := global.DB.Exec(
|
||||||
http.NotFound(w, r)
|
"DELETE FROM artist "+
|
||||||
return
|
"WHERE id=$1",
|
||||||
}
|
artist.ID)
|
||||||
|
|
||||||
if r.URL.Path == "/" {
|
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var artistID = r.URL.Path[1:]
|
|
||||||
var artist = global.GetArtist(artistID)
|
|
||||||
if artist == nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Artist %s does not exist\n", artistID), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := controller.DeleteArtistDB(global.DB, artist)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to delete artist %s: %s\n", artist.ID, err)
|
fmt.Printf("FATAL: Failed to delete artist %s: %s\n", artist.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Artists = func () []*model.Artist {
|
|
||||||
var artists = []*model.Artist{}
|
|
||||||
for _, a := range global.Artists {
|
|
||||||
if a.ID == artist.ID { continue }
|
|
||||||
artists = append(artists, a)
|
|
||||||
}
|
|
||||||
return artists
|
|
||||||
}()
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(fmt.Sprintf("Artist %s has been deleted\n", artist.ID)))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
447
api/release.go
447
api/release.go
|
@ -1,8 +1,6 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
@ -14,7 +12,6 @@ import (
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/admin"
|
"arimelody.me/arimelody.me/admin"
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
controller "arimelody.me/arimelody.me/music/controller"
|
|
||||||
"arimelody.me/arimelody.me/music/model"
|
"arimelody.me/arimelody.me/music/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,39 +29,40 @@ type releaseBodyJSON struct {
|
||||||
|
|
||||||
func ServeCatalog() http.Handler {
|
func ServeCatalog() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
type CatalogItem struct {
|
type catalogItem struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
|
||||||
ReleaseType model.ReleaseType `json:"type"`
|
ReleaseType model.ReleaseType `json:"type"`
|
||||||
ReleaseDate time.Time `json:"releaseDate"`
|
ReleaseDate time.Time `json:"releaseDate"`
|
||||||
Artwork string `json:"artwork"`
|
Artwork string `json:"artwork"`
|
||||||
Buyname string `json:"buyname"`
|
|
||||||
Buylink string `json:"buylink"`
|
Buylink string `json:"buylink"`
|
||||||
Links []*model.Link `json:"links"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog := []CatalogItem{}
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog := []catalogItem{}
|
||||||
authorised := admin.GetSession(r) != nil
|
authorised := admin.GetSession(r) != nil
|
||||||
for _, release := range global.Releases {
|
for _, release := range releases {
|
||||||
if !release.Visible && !authorised {
|
if !release.Visible && !authorised {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
catalog = append(catalog, CatalogItem{
|
catalog = append(catalog, catalogItem{
|
||||||
ID: release.ID,
|
ID: release.ID,
|
||||||
Title: release.Title,
|
Title: release.Title,
|
||||||
Description: release.Description,
|
|
||||||
ReleaseType: release.ReleaseType,
|
ReleaseType: release.ReleaseType,
|
||||||
ReleaseDate: release.ReleaseDate,
|
ReleaseDate: release.ReleaseDate,
|
||||||
Artwork: release.Artwork,
|
Artwork: release.Artwork,
|
||||||
Buyname: release.Buyname,
|
|
||||||
Buylink: release.Buylink,
|
Buylink: release.Buylink,
|
||||||
Links: release.Links,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
err := json.NewEncoder(w).Encode(catalog)
|
err = json.NewEncoder(w).Encode(catalog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -122,11 +120,6 @@ func CreateRelease() http.Handler {
|
||||||
buylink := ""
|
buylink := ""
|
||||||
if data.Buylink != nil && *data.Buylink != "" { buylink = *data.Buylink }
|
if data.Buylink != nil && *data.Buylink != "" { buylink = *data.Buylink }
|
||||||
|
|
||||||
if global.GetRelease(data.ID) != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Release %s already exists\n", data.ID), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var release = model.Release{
|
var release = model.Release{
|
||||||
ID: data.ID,
|
ID: data.ID,
|
||||||
Visible: false,
|
Visible: false,
|
||||||
|
@ -137,31 +130,42 @@ func CreateRelease() http.Handler {
|
||||||
Artwork: artwork,
|
Artwork: artwork,
|
||||||
Buyname: buyname,
|
Buyname: buyname,
|
||||||
Buylink: buylink,
|
Buylink: buylink,
|
||||||
Links: []*model.Link{},
|
|
||||||
Credits: []*model.Credit{},
|
|
||||||
Tracks: []*model.Track{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.CreateReleaseDB(global.DB, &release)
|
_, 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
|
http.Error(w, fmt.Sprintf("Release %s already exists\n", data.ID), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Printf("Failed to create release %s: %s\n", release.ID, err)
|
fmt.Printf("Failed to create release %s: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Releases = append([]*model.Release{&release}, global.Releases...)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
err = json.NewEncoder(w).Encode(release)
|
err = json.NewEncoder(w).Encode(release)
|
||||||
if err != nil {
|
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)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateRelease() http.Handler {
|
func UpdateRelease(release model.Release) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -170,124 +174,11 @@ func UpdateRelease() http.Handler {
|
||||||
|
|
||||||
segments := strings.Split(r.URL.Path[1:], "/")
|
segments := strings.Split(r.URL.Path[1:], "/")
|
||||||
var releaseID = segments[0]
|
var releaseID = segments[0]
|
||||||
var release = global.GetRelease(releaseID)
|
var exists int
|
||||||
if release == nil {
|
err := global.DB.Get(&exists, "SELECT count(*) FROM musicrelease WHERE id=$1", releaseID)
|
||||||
http.Error(w, fmt.Sprintf("Release %s does not exist\n", releaseID), http.StatusBadRequest)
|
if err != nil {
|
||||||
return
|
fmt.Printf("Failed to update release: %s\n", err)
|
||||||
}
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
|
||||||
if len(segments) == 1 {
|
|
||||||
var data releaseBodyJSON
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&data)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to update release: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var update = *release
|
|
||||||
if data.ID != "" { update.ID = data.ID }
|
|
||||||
if data.Visible != nil { update.Visible = *data.Visible }
|
|
||||||
if data.Title != nil { update.Title = *data.Title }
|
|
||||||
if data.Description != nil { update.Description = *data.Description }
|
|
||||||
if data.ReleaseType != nil { update.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
|
|
||||||
}
|
|
||||||
update.ReleaseDate = newDate
|
|
||||||
}
|
|
||||||
if data.Artwork != nil {
|
|
||||||
if strings.Contains(*data.Artwork, ";base64,") {
|
|
||||||
split := strings.Split(*data.Artwork, ";base64,")
|
|
||||||
header := split[0]
|
|
||||||
imageData, err := base64.StdEncoding.DecodeString(split[1])
|
|
||||||
ext, _ := strings.CutPrefix(header, "data:image/")
|
|
||||||
|
|
||||||
switch ext {
|
|
||||||
case "png":
|
|
||||||
case "jpg":
|
|
||||||
case "jpeg":
|
|
||||||
default:
|
|
||||||
http.Error(w, "Invalid image type. Allowed: .png, .jpg, .jpeg", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
artworkDirectory := filepath.Join("uploads", "musicart")
|
|
||||||
// ensure directory exists
|
|
||||||
os.MkdirAll(artworkDirectory, os.ModePerm)
|
|
||||||
|
|
||||||
imagePath := filepath.Join(artworkDirectory, fmt.Sprintf("%s.%s", update.ID, ext))
|
|
||||||
file, err := os.Create(imagePath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("FATAL: Failed to create file %s: %s\n", imagePath, err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
buffer := bufio.NewWriter(file)
|
|
||||||
_, err = buffer.Write(imageData)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("FATAL: Failed to write to file %s: %s\n", imagePath, err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := buffer.Flush(); err != nil {
|
|
||||||
fmt.Printf("FATAL: Failed to flush data to file %s: %s\n", imagePath, err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean up files with this ID and different extensions
|
|
||||||
err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error {
|
|
||||||
if path == imagePath { return nil }
|
|
||||||
|
|
||||||
withoutExt := strings.TrimSuffix(path, filepath.Ext(path))
|
|
||||||
if withoutExt != filepath.Join(artworkDirectory, update.ID) { return nil }
|
|
||||||
|
|
||||||
return os.Remove(path)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("WARN: Error while cleaning up artwork files: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
update.Artwork = fmt.Sprintf("/uploads/musicart/%s.%s", update.ID, ext)
|
|
||||||
} else {
|
|
||||||
update.Artwork = *data.Artwork
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Buyname != nil { update.Buyname = *data.Buyname }
|
|
||||||
if data.Buylink != nil { update.Buylink = *data.Buylink }
|
|
||||||
|
|
||||||
err = controller.UpdateReleaseDB(global.DB, &update)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to update release %s: %s\n", release.ID, err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
release.ID = update.ID
|
|
||||||
release.Visible = update.Visible
|
|
||||||
release.Title = update.Title
|
|
||||||
release.Description = update.Description
|
|
||||||
release.ReleaseType = update.ReleaseType
|
|
||||||
release.ReleaseDate = update.ReleaseDate
|
|
||||||
release.Artwork = update.Artwork
|
|
||||||
release.Buyname = update.Buyname
|
|
||||||
release.Buylink = update.Buylink
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
err = json.NewEncoder(w).Encode(release)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,17 +194,80 @@ func UpdateRelease() http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.NotFound(w, r)
|
if len(segments) > 2 {
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateReleaseTracks(release *model.Release) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path == "/" || r.Method != http.MethodPut {
|
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var data releaseBodyJSON
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&data)
|
||||||
|
if err != nil {
|
||||||
|
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,") {
|
||||||
|
var artworkDirectory = filepath.Join("uploads", "musicart")
|
||||||
|
filename, err := HandleImageUpload(data.Artwork, artworkDirectory, data.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)
|
||||||
|
} 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)
|
||||||
|
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{}
|
var trackIDs = []string{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&trackIDs)
|
err := json.NewDecoder(r.Body).Decode(&trackIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -321,123 +275,85 @@ func UpdateReleaseTracks(release *model.Release) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var old_tracks = (*release).Tracks
|
tx := global.DB.MustBegin()
|
||||||
var new_tracks = []*model.Track{}
|
tx.MustExec("DELETE FROM musicreleasetrack WHERE release=$1", release.ID)
|
||||||
for _, trackID := range trackIDs {
|
for i, trackID := range trackIDs {
|
||||||
var track = global.GetTrack(trackID)
|
tx.MustExec(
|
||||||
if track == nil {
|
"INSERT INTO musicreleasetrack "+
|
||||||
http.Error(w, fmt.Sprintf("Track %s does not exist\n", trackID), http.StatusBadRequest)
|
"(release, track, number) "+
|
||||||
return
|
"VALUES ($1, $2, $3)",
|
||||||
}
|
release.ID,
|
||||||
new_tracks = append(new_tracks, track)
|
trackID,
|
||||||
track.Release = release
|
i)
|
||||||
}
|
}
|
||||||
|
err = tx.Commit()
|
||||||
err = controller.UpdateReleaseTracksDB(global.DB, release, new_tracks)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to update tracks for %s: %s\n", release.ID, err)
|
fmt.Printf("Failed to update tracks for %s: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
release.Tracks = new_tracks
|
|
||||||
|
|
||||||
// remove release from orphaned tracks
|
|
||||||
for _, old_track := range old_tracks {
|
|
||||||
var exists = false
|
|
||||||
for _, track := range new_tracks {
|
|
||||||
if track.ID == old_track.ID {
|
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
old_track.Release = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
err = json.NewEncoder(w).Encode(release)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateReleaseCredits(release *model.Release) http.Handler {
|
func UpdateReleaseCredits(release model.Release) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type creditJSON struct {
|
type creditJSON struct {
|
||||||
Artist string
|
Artist string
|
||||||
Role string
|
Role string
|
||||||
Primary bool
|
Primary bool
|
||||||
}
|
}
|
||||||
|
var data []creditJSON
|
||||||
var list []creditJSON
|
err := json.NewDecoder(r.Body).Decode(&data)
|
||||||
err := json.NewDecoder(r.Body).Decode(&list)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var credits = []*model.Credit{}
|
// clear duplicates
|
||||||
for i, data := range list {
|
type Credit struct {
|
||||||
if data.Artist == "" {
|
Role string
|
||||||
http.Error(w, fmt.Sprintf("Artist ID cannot be blank (%d)", i), http.StatusBadRequest)
|
Primary bool
|
||||||
return
|
}
|
||||||
|
var credits = map[string]Credit{}
|
||||||
|
for _, credit := range data {
|
||||||
|
credits[credit.Artist] = Credit{
|
||||||
|
Role: credit.Role,
|
||||||
|
Primary: credit.Primary,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, credit := range credits {
|
|
||||||
if data.Artist == credit.Artist.ID {
|
|
||||||
http.Error(w, fmt.Sprintf("Artist %s credited more than once", data.Artist), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Role == "" {
|
|
||||||
http.Error(w, fmt.Sprintf("Artist role cannot be blank (%d)", i), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var artist = global.GetArtist(data.Artist)
|
|
||||||
if artist == nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Artist %s does not exist\n", data.Artist), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
credits = append(credits, &model.Credit{
|
|
||||||
Artist: artist,
|
|
||||||
Role: data.Role,
|
|
||||||
Primary: data.Primary,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateReleaseCreditsDB(global.DB, release, credits)
|
tx := global.DB.MustBegin()
|
||||||
if err != nil {
|
tx.MustExec("DELETE FROM musiccredit WHERE release=$1", release.ID)
|
||||||
fmt.Printf("Failed to update links %s: %s\n", release.ID, err)
|
for artistID := range credits {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
if credits[artistID].Role == "" {
|
||||||
return
|
http.Error(w, fmt.Sprintf("Artist role cannot be blank (%s)", artistID), http.StatusBadRequest)
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
release.Credits = credits
|
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
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
tx.MustExec(
|
||||||
w.WriteHeader(http.StatusOK)
|
"INSERT INTO musiccredit "+
|
||||||
err = json.NewEncoder(w).Encode(release)
|
"(release, artist, role, is_primary) "+
|
||||||
|
"VALUES ($1, $2, $3, $4)",
|
||||||
|
release.ID,
|
||||||
|
artistID,
|
||||||
|
credits[artistID].Role,
|
||||||
|
credits[artistID].Primary)
|
||||||
|
}
|
||||||
|
err = tx.Commit()
|
||||||
if err != nil {
|
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)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateReleaseLinks(release *model.Release) http.Handler {
|
func UpdateReleaseLinks(release model.Release) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
@ -451,56 +367,31 @@ func UpdateReleaseLinks(release *model.Release) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateReleaseLinksDB(global.DB, release, links)
|
tx := global.DB.MustBegin()
|
||||||
if err != nil {
|
tx.MustExec("DELETE FROM musiclink WHERE release=$1", release.ID)
|
||||||
fmt.Printf("Failed to update links %s: %s\n", release.ID, err)
|
for _, link := range links {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
tx.MustExec(
|
||||||
return
|
"INSERT INTO musiclink "+
|
||||||
|
"(release, name, url) "+
|
||||||
|
"VALUES ($1, $2, $3)",
|
||||||
|
release.ID,
|
||||||
|
link.Name,
|
||||||
|
link.URL)
|
||||||
}
|
}
|
||||||
|
err = tx.Commit()
|
||||||
release.Links = links
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
err = json.NewEncoder(w).Encode(release)
|
|
||||||
if err != nil {
|
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)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteRelease() http.Handler {
|
func DeleteRelease(release model.Release) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
_, err := global.DB.Exec("DELETE FROM musicrelease WHERE id=$1", release.ID)
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var releaseID = r.URL.Path[1:]
|
|
||||||
var release = global.GetRelease(releaseID)
|
|
||||||
if release == nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Release %s does not exist\n", releaseID), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := controller.DeleteReleaseDB(global.DB, release)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to delete release %s: %s\n", release.ID, err)
|
fmt.Printf("Failed to delete release %s: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Releases = func () []*model.Release {
|
|
||||||
var releases = []*model.Release{}
|
|
||||||
for _, r := range global.Releases {
|
|
||||||
if r.ID == release.ID { continue }
|
|
||||||
releases = append(releases, r)
|
|
||||||
}
|
|
||||||
return releases
|
|
||||||
}()
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(fmt.Sprintf("Release %s has been deleted\n", release.ID)))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
173
api/track.go
173
api/track.go
|
@ -7,35 +7,32 @@ import (
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
"arimelody.me/arimelody.me/music/model"
|
"arimelody.me/arimelody.me/music/model"
|
||||||
controller "arimelody.me/arimelody.me/music/controller"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServeAllTracks() http.Handler {
|
func ServeAllTracks() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// type trackJSON struct {
|
type track struct {
|
||||||
// model.Track
|
ID string `json:"id"`
|
||||||
// Release string `json:"release"`
|
Title string `json:"title"`
|
||||||
// }
|
}
|
||||||
// var tracks = []trackJSON{}
|
var tracks = []track{}
|
||||||
//
|
|
||||||
// for _, track := range global.Tracks {
|
err := global.DB.Select(&tracks, "SELECT id, title FROM musictrack")
|
||||||
// for _, release := range global. {
|
if err != nil {
|
||||||
// tracks = append(tracks, {
|
fmt.Printf("FATAL: Failed to pull tracks from DB: %s\n", err)
|
||||||
// track,
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
// Release
|
}
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
err := json.NewEncoder(w).Encode(global.Tracks)
|
err = json.NewEncoder(w).Encode(tracks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Failed to serve all tracks: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeTrack() http.Handler {
|
func ServeTrack(track model.Track) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
ServeAllTracks().ServeHTTP(w, r)
|
ServeAllTracks().ServeHTTP(w, r)
|
||||||
|
@ -43,17 +40,35 @@ func ServeTrack() http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
var trackID = r.URL.Path[1:]
|
var trackID = r.URL.Path[1:]
|
||||||
var track = global.GetTrack(trackID)
|
var track = model.Track{}
|
||||||
if track == nil {
|
err := global.DB.Get(&track, "SELECT * from musictrack WHERE id=$1", trackID)
|
||||||
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
var releases = []*model.Release{}
|
||||||
err := json.NewEncoder(w).Encode(track)
|
err = global.DB.Select(&releases,
|
||||||
if err != nil {
|
"SELECT * FROM musicrelease JOIN musicreleasetrack AS mrt "+
|
||||||
|
"WHERE mrt.track=$1 "+
|
||||||
|
"ORDER BY release_date",
|
||||||
|
track.ID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Failed to pull track releases for %s from DB: %s\n", trackID, 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 })
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Failed to serve track %s: %s\n", trackID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -77,122 +92,92 @@ func CreateTrack() http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trackID, err := controller.CreateTrackDB(global.DB, &track)
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to create track: %s\n", err)
|
fmt.Printf("FATAL: Failed to create track: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
track.ID = trackID
|
w.Header().Add("Content-Type", "text/plain")
|
||||||
global.Tracks = append(global.Tracks, &track)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
err = json.NewEncoder(w).Encode(track)
|
w.Write([]byte(trackID))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateTrack() http.Handler {
|
func UpdateTrack(track model.Track) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut || r.URL.Path == "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Path == "/" {
|
var update model.Track
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
err := json.NewDecoder(r.Body).Decode(&update)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var data model.Track
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to update track: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var trackID = r.URL.Path[1:]
|
if update.Title == "" {
|
||||||
var track = global.GetTrack(trackID)
|
http.Error(w, "Track title cannot be empty\n", http.StatusBadRequest)
|
||||||
if track == nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Track %s does not exist\n", trackID), http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data.ID = trackID
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if data.Title == "" { data.Title = track.Title }
|
_, err = global.DB.Exec(
|
||||||
|
"UPDATE musictrack "+
|
||||||
err = controller.UpdateTrackDB(global.DB, &data)
|
"SET title=$2, description=$3, lyrics=$4, preview_url=$5 "+
|
||||||
|
"WHERE id=$1",
|
||||||
|
track.ID,
|
||||||
|
track.Title,
|
||||||
|
track.Description,
|
||||||
|
track.Lyrics,
|
||||||
|
track.PreviewURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to update track %s: %s\n", track.ID, err)
|
fmt.Printf("Failed to update track %s: %s\n", track.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
track.Title = data.Title
|
|
||||||
track.Description = data.Description
|
|
||||||
track.Lyrics = data.Lyrics
|
|
||||||
track.PreviewURL = data.PreviewURL
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
err = json.NewEncoder(w).Encode(track)
|
err = json.NewEncoder(w).Encode(track)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteTrack() http.Handler {
|
func DeleteTrack(track model.Track) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete || r.URL.Path == "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Path == "/" {
|
|
||||||
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var trackID = r.URL.Path[1:]
|
var trackID = r.URL.Path[1:]
|
||||||
var track = global.GetTrack(trackID)
|
_, err := global.DB.Exec(
|
||||||
if track == nil {
|
"DELETE FROM musictrack "+
|
||||||
http.Error(w, fmt.Sprintf("Track %s does not exist\n", trackID), http.StatusBadRequest)
|
"WHERE id=$1",
|
||||||
return
|
trackID)
|
||||||
}
|
|
||||||
|
|
||||||
err := controller.DeleteTrackDB(global.DB, track)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to delete track %s: %s\n", track.ID, err)
|
fmt.Printf("Failed to delete track %s: %s\n", trackID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear track from releases
|
|
||||||
for _, release := range global.Releases {
|
|
||||||
release.Tracks = func () []*model.Track {
|
|
||||||
var tracks = []*model.Track{}
|
|
||||||
for _, t := range release.Tracks {
|
|
||||||
if t.ID == track.ID { continue }
|
|
||||||
tracks = append(tracks, t)
|
|
||||||
}
|
|
||||||
return tracks
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
global.Tracks = func () []*model.Track {
|
|
||||||
var tracks = []*model.Track{}
|
|
||||||
for _, t := range global.Tracks {
|
|
||||||
if t.ID == track.ID { continue }
|
|
||||||
tracks = append(tracks, t)
|
|
||||||
}
|
|
||||||
return tracks
|
|
||||||
}()
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(fmt.Sprintf("Track %s has been deleted\n", track.ID)))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
49
api/uploads.go
Normal file
49
api/uploads.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleImageUpload(data *string, directory string, filename string) (string, error) {
|
||||||
|
split := strings.Split(*data, ";base64,")
|
||||||
|
header := split[0]
|
||||||
|
imageData, err := base64.StdEncoding.DecodeString(split[1])
|
||||||
|
ext, _ := strings.CutPrefix(header, "data:image/")
|
||||||
|
|
||||||
|
switch ext {
|
||||||
|
case "png":
|
||||||
|
case "jpg":
|
||||||
|
case "jpeg":
|
||||||
|
default:
|
||||||
|
return "", errors.New("Invalid image type. Allowed: .png, .jpg, .jpeg")
|
||||||
|
}
|
||||||
|
filename = fmt.Sprintf("%s.%s", filename, ext)
|
||||||
|
|
||||||
|
// ensure directory exists
|
||||||
|
os.MkdirAll(directory, os.ModePerm)
|
||||||
|
|
||||||
|
imagePath := filepath.Join(directory, filename)
|
||||||
|
file, err := os.Create(imagePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
buffer := bufio.NewWriter(file)
|
||||||
|
_, err = buffer.Write(imageData)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := buffer.Flush(); err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename, nil
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/music/model"
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,34 +43,3 @@ var HTTP_DOMAIN = func() string {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var DB *sqlx.DB
|
var DB *sqlx.DB
|
||||||
|
|
||||||
var Releases []*model.Release
|
|
||||||
var Artists []*model.Artist
|
|
||||||
var Tracks []*model.Track
|
|
||||||
|
|
||||||
func GetRelease(id string) *model.Release {
|
|
||||||
for _, release := range Releases {
|
|
||||||
if release.ID == id {
|
|
||||||
return release
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetArtist(id string) *model.Artist {
|
|
||||||
for _, artist := range Artists {
|
|
||||||
if artist.ID == id {
|
|
||||||
return artist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTrack(id string) *model.Track {
|
|
||||||
for _, track := range Tracks {
|
|
||||||
if track.ID == id {
|
|
||||||
return track
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,9 +3,6 @@ package global
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"html/template"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -64,39 +61,3 @@ func HTTPLog(next http.Handler) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeTemplate(page string, data any) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
lp_layout := filepath.Join("views", "layout.html")
|
|
||||||
lp_header := filepath.Join("views", "header.html")
|
|
||||||
lp_footer := filepath.Join("views", "footer.html")
|
|
||||||
lp_prideflag := filepath.Join("views", "prideflag.html")
|
|
||||||
fp := filepath.Join("views", filepath.Clean(page))
|
|
||||||
|
|
||||||
info, err := os.Stat(fp)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsDir() {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := template.ParseFiles(lp_layout, lp_header, lp_footer, lp_prideflag, fp)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error parsing template files: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = template.ExecuteTemplate(w, "layout.html", data)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error executing template: %s\n", err)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
35
main.go
35
main.go
|
@ -11,8 +11,8 @@ import (
|
||||||
"arimelody.me/arimelody.me/admin"
|
"arimelody.me/arimelody.me/admin"
|
||||||
"arimelody.me/arimelody.me/api"
|
"arimelody.me/arimelody.me/api"
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
musicController "arimelody.me/arimelody.me/music/controller"
|
|
||||||
musicView "arimelody.me/arimelody.me/music/view"
|
musicView "arimelody.me/arimelody.me/music/view"
|
||||||
|
"arimelody.me/arimelody.me/templates"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
|
@ -25,7 +25,7 @@ func main() {
|
||||||
var err error
|
var err error
|
||||||
global.DB, err = sqlx.Connect("postgres", "user=arimelody dbname=arimelody password=fuckingpassword sslmode=disable")
|
global.DB, err = sqlx.Connect("postgres", "user=arimelody dbname=arimelody password=fuckingpassword sslmode=disable")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "unable to create database connection pool: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Unable to create database connection pool: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
global.DB.SetConnMaxLifetime(time.Minute * 3)
|
global.DB.SetConnMaxLifetime(time.Minute * 3)
|
||||||
|
@ -33,34 +33,10 @@ func main() {
|
||||||
global.DB.SetMaxIdleConns(10)
|
global.DB.SetMaxIdleConns(10)
|
||||||
defer global.DB.Close()
|
defer global.DB.Close()
|
||||||
|
|
||||||
// pull artist data from DB
|
|
||||||
global.Artists, err = musicController.PullAllArtists(global.DB)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to pull artists from database: %v\n", err);
|
|
||||||
panic(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("%d artists loaded successfully.\n", len(global.Artists))
|
|
||||||
|
|
||||||
// pull track data from DB
|
|
||||||
global.Tracks, err = musicController.PullAllTracks(global.DB)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to pull tracks from database: %v\n", err);
|
|
||||||
panic(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("%d tracks loaded successfully.\n", len(global.Tracks))
|
|
||||||
|
|
||||||
// pull release data from DB
|
|
||||||
global.Releases, err = musicController.PullAllReleases(global.DB)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Failed to pull releases from database: %v\n", err);
|
|
||||||
panic(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("%d releases loaded successfully.\n", len(global.Releases))
|
|
||||||
|
|
||||||
// start the web server!
|
// start the web server!
|
||||||
mux := createServeMux()
|
mux := createServeMux()
|
||||||
port := DEFAULT_PORT
|
port := DEFAULT_PORT
|
||||||
fmt.Printf("now serving at http://127.0.0.1:%d\n", port)
|
fmt.Printf("Now serving at http://127.0.0.1:%d\n", port)
|
||||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), global.HTTPLog(mux)))
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), global.HTTPLog(mux)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +49,10 @@ func createServeMux() *http.ServeMux {
|
||||||
mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler("uploads")))
|
mux.Handle("/uploads/", http.StripPrefix("/uploads", staticHandler("uploads")))
|
||||||
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||||
global.ServeTemplate("index.html", nil).ServeHTTP(w, r)
|
err := templates.Pages["index"].Execute(w, nil)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
staticHandler("public").ServeHTTP(w, r)
|
staticHandler("public").ServeHTTP(w, r)
|
||||||
|
|
|
@ -7,7 +7,18 @@ import (
|
||||||
|
|
||||||
// DATABASE
|
// DATABASE
|
||||||
|
|
||||||
func PullAllArtists(db *sqlx.DB) ([]*model.Artist, error) {
|
func GetArtist(db *sqlx.DB, id string) (*model.Artist, error) {
|
||||||
|
var artist = model.Artist{}
|
||||||
|
|
||||||
|
err := db.Get(&artist, "SELECT * FROM artist WHERE id=$1", id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &artist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllArtists(db *sqlx.DB) ([]*model.Artist, error) {
|
||||||
var artists = []*model.Artist{}
|
var artists = []*model.Artist{}
|
||||||
|
|
||||||
err := db.Select(&artists, "SELECT * FROM artist")
|
err := db.Select(&artists, "SELECT * FROM artist")
|
||||||
|
@ -18,7 +29,22 @@ func PullAllArtists(db *sqlx.DB) ([]*model.Artist, error) {
|
||||||
return artists, nil
|
return artists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateArtistDB(db *sqlx.DB, artist *model.Artist) error {
|
func GetArtistsNotOnRelease(db *sqlx.DB, release *model.Release) ([]*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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return artists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateArtist(db *sqlx.DB, artist *model.Artist) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"INSERT INTO artist (id, name, website, avatar) "+
|
"INSERT INTO artist (id, name, website, avatar) "+
|
||||||
"VALUES ($1, $2, $3, $4)",
|
"VALUES ($1, $2, $3, $4)",
|
||||||
|
@ -34,7 +60,7 @@ func CreateArtistDB(db *sqlx.DB, artist *model.Artist) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateArtistDB(db *sqlx.DB, artist *model.Artist) error {
|
func UpdateArtist(db *sqlx.DB, artist *model.Artist) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"UPDATE artist "+
|
"UPDATE artist "+
|
||||||
"SET name=$2, website=$3, avatar=$4 "+
|
"SET name=$2, website=$3, avatar=$4 "+
|
||||||
|
@ -51,7 +77,7 @@ func UpdateArtistDB(db *sqlx.DB, artist *model.Artist) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteArtistDB(db *sqlx.DB, artist *model.Artist) error {
|
func DeleteArtist(db *sqlx.DB, artist *model.Artist) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"DELETE FROM artist "+
|
"DELETE FROM artist "+
|
||||||
"WHERE id=$1",
|
"WHERE id=$1",
|
||||||
|
|
|
@ -1,43 +1,29 @@
|
||||||
package music
|
package music
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody.me/arimelody.me/global"
|
|
||||||
"arimelody.me/arimelody.me/music/model"
|
"arimelody.me/arimelody.me/music/model"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DATABASE
|
// DATABASE
|
||||||
|
|
||||||
func PullReleaseCredits(db *sqlx.DB, releaseID string) ([]*model.Credit, error) {
|
func GetReleaseCredits(db *sqlx.DB, release *model.Release) ([]model.Credit, error) {
|
||||||
type creditDB struct {
|
var credits = []model.Credit{}
|
||||||
Artist string
|
|
||||||
Role string
|
|
||||||
Primary bool `db:"is_primary"`
|
|
||||||
}
|
|
||||||
var credit_rows = []creditDB{}
|
|
||||||
var credits = []*model.Credit{}
|
|
||||||
|
|
||||||
err := db.Select(
|
err := db.Select(&credits,
|
||||||
&credit_rows,
|
"SELECT artist.*,role,is_primary FROM musiccredit "+
|
||||||
"SELECT artist, role, is_primary FROM musiccredit WHERE release=$1",
|
"JOIN artist ON artist=id "+
|
||||||
releaseID,
|
"WHERE release=$1",
|
||||||
|
release.ID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range credit_rows {
|
|
||||||
credits = append(credits, &model.Credit{
|
|
||||||
Artist: global.GetArtist(c.Artist),
|
|
||||||
Role: c.Role,
|
|
||||||
Primary: c.Primary,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return credits, nil
|
return credits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateCreditDB(db *sqlx.DB, releaseID string, artistID string, credit *model.Credit) (error) {
|
func CreateCredit(db *sqlx.DB, releaseID string, artistID string, credit *model.Credit) (error) {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"INSERT INTO musiccredit (release, artist, role, is_primary) "+
|
"INSERT INTO musiccredit (release, artist, role, is_primary) "+
|
||||||
"VALUES ($1, $2, $3, $4)",
|
"VALUES ($1, $2, $3, $4)",
|
||||||
|
@ -53,7 +39,7 @@ func CreateCreditDB(db *sqlx.DB, releaseID string, artistID string, credit *mode
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateCreditDB(db *sqlx.DB, releaseID string, artistID string, credit *model.Credit) (error) {
|
func UpdateCredit(db *sqlx.DB, releaseID string, artistID string, credit *model.Credit) (error) {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"UPDATE musiccredit SET "+
|
"UPDATE musiccredit SET "+
|
||||||
"role=$3, is_primary=$4 "+
|
"role=$3, is_primary=$4 "+
|
||||||
|
@ -70,7 +56,7 @@ func UpdateCreditDB(db *sqlx.DB, releaseID string, artistID string, credit *mode
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteCreditDB(db *sqlx.DB, releaseID string, artistID string) (error) {
|
func DeleteCredit(db *sqlx.DB, releaseID string, artistID string) (error) {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"DELETE FROM musiccredit "+
|
"DELETE FROM musiccredit "+
|
||||||
"WHERE release=$1, artist=$2",
|
"WHERE release=$1, artist=$2",
|
||||||
|
|
|
@ -7,14 +7,10 @@ import (
|
||||||
|
|
||||||
// DATABASE
|
// DATABASE
|
||||||
|
|
||||||
func PullReleaseLinks(db *sqlx.DB, releaseID string) ([]*model.Link, error) {
|
func GetReleaseLinks(db *sqlx.DB, release *model.Release) ([]model.Link, error) {
|
||||||
var links = []*model.Link{}
|
var links = []model.Link{}
|
||||||
|
|
||||||
err := db.Select(
|
err := db.Select(&links, "SELECT name,url FROM musiclink WHERE release=$1", release.ID)
|
||||||
&links,
|
|
||||||
"SELECT name, url FROM musiclink WHERE release=$1",
|
|
||||||
releaseID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -22,7 +18,7 @@ func PullReleaseLinks(db *sqlx.DB, releaseID string) ([]*model.Link, error) {
|
||||||
return links, nil
|
return links, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateLinkDB(db *sqlx.DB, releaseID string, link *model.Link) (error) {
|
func CreateLink(db *sqlx.DB, releaseID string, link *model.Link) (error) {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"INSERT INTO musiclink (release, name, url) "+
|
"INSERT INTO musiclink (release, name, url) "+
|
||||||
"VALUES ($1, $2, $3)",
|
"VALUES ($1, $2, $3)",
|
||||||
|
@ -37,7 +33,7 @@ func CreateLinkDB(db *sqlx.DB, releaseID string, link *model.Link) (error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateLinkDB(db *sqlx.DB, releaseID string, link *model.Link) (error) {
|
func UpdateLink(db *sqlx.DB, releaseID string, link *model.Link) (error) {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"UPDATE musiclink SET "+
|
"UPDATE musiclink SET "+
|
||||||
"name=$2, url=$3 "+
|
"name=$2, url=$3 "+
|
||||||
|
@ -53,7 +49,7 @@ func UpdateLinkDB(db *sqlx.DB, releaseID string, link *model.Link) (error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteLinkDB(db *sqlx.DB, releaseID string, link *model.Link) (error) {
|
func DeleteLink(db *sqlx.DB, releaseID string, link *model.Link) (error) {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"DELETE FROM musiclink "+
|
"DELETE FROM musiclink "+
|
||||||
"WHERE release=$1, name=$2",
|
"WHERE release=$1, name=$2",
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
package music
|
package music
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/global"
|
|
||||||
"arimelody.me/arimelody.me/music/model"
|
"arimelody.me/arimelody.me/music/model"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DATABASE
|
func GetRelease(db *sqlx.DB, id string) (*model.Release, error) {
|
||||||
|
var releases = model.Release{}
|
||||||
|
|
||||||
func PullAllReleases(db *sqlx.DB) ([]*model.Release, error) {
|
err := db.Get(&releases, "SELECT * FROM musicrelease WHERE id=$1", id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &releases, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllReleases(db *sqlx.DB) ([]*model.Release, error) {
|
||||||
var releases = []*model.Release{}
|
var releases = []*model.Release{}
|
||||||
|
|
||||||
err := db.Select(&releases, "SELECT * FROM musicrelease ORDER BY release_date DESC")
|
err := db.Select(&releases, "SELECT * FROM musicrelease ORDER BY release_date DESC")
|
||||||
|
@ -19,33 +24,15 @@ func PullAllReleases(db *sqlx.DB) ([]*model.Release, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, release := range releases {
|
|
||||||
release.Credits, err = PullReleaseCredits(global.DB, release.ID)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error pulling credits for %s: %s\n", release.ID, err)
|
|
||||||
release.Credits = []*model.Credit{}
|
|
||||||
}
|
|
||||||
release.Links, err = PullReleaseLinks(global.DB, release.ID)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error pulling links for %s: %s\n", release.ID, err)
|
|
||||||
release.Links = []*model.Link{}
|
|
||||||
}
|
|
||||||
release.Tracks, err = PullReleaseTracksDB(global.DB, release)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error pulling tracks for %s: %s\n", release.ID, err)
|
|
||||||
release.Tracks = []*model.Track{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return releases, nil
|
return releases, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PullReleaseTracksDB(db *sqlx.DB, release *model.Release) ([]*model.Track, error) {
|
func GetReleaseTracks(db *sqlx.DB, release *model.Release) ([]*model.Track, error) {
|
||||||
var track_rows = []string{}
|
|
||||||
var tracks = []*model.Track{}
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
err := db.Select(&track_rows,
|
err := db.Select(&tracks,
|
||||||
"SELECT track FROM musicreleasetrack "+
|
"SELECT musictrack.* FROM musictrack "+
|
||||||
|
"JOIN musicreleasetrack ON track=id "+
|
||||||
"WHERE release=$1 "+
|
"WHERE release=$1 "+
|
||||||
"ORDER BY number ASC",
|
"ORDER BY number ASC",
|
||||||
release.ID,
|
release.ID,
|
||||||
|
@ -54,19 +41,10 @@ func PullReleaseTracksDB(db *sqlx.DB, release *model.Release) ([]*model.Track, e
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, trackID := range track_rows {
|
|
||||||
var track = global.GetTrack(trackID)
|
|
||||||
if track == nil {
|
|
||||||
return nil, errors.New("Recieved a track from the DB that does not exist in memory")
|
|
||||||
}
|
|
||||||
track.Release = release
|
|
||||||
tracks = append(tracks, track)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracks, nil
|
return tracks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateReleaseDB(db *sqlx.DB, release *model.Release) error {
|
func CreateRelease(db *sqlx.DB, release *model.Release) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"INSERT INTO musicrelease "+
|
"INSERT INTO musicrelease "+
|
||||||
"(id, visible, title, description, type, release_date, artwork, buyname, buylink) "+
|
"(id, visible, title, description, type, release_date, artwork, buyname, buylink) "+
|
||||||
|
@ -88,7 +66,7 @@ func CreateReleaseDB(db *sqlx.DB, release *model.Release) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateReleaseDB(db *sqlx.DB, release *model.Release) error {
|
func UpdateRelease(db *sqlx.DB, release *model.Release) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"UPDATE musicrelease SET "+
|
"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 "+
|
||||||
|
@ -110,7 +88,7 @@ func UpdateReleaseDB(db *sqlx.DB, release *model.Release) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateReleaseTracksDB(db *sqlx.DB, release *model.Release, new_tracks []*model.Track) error {
|
func UpdateReleaseTracks(db *sqlx.DB, release *model.Release, new_tracks []*model.Track) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"DELETE FROM musicreleasetrack "+
|
"DELETE FROM musicreleasetrack "+
|
||||||
"WHERE release=$1",
|
"WHERE release=$1",
|
||||||
|
@ -137,7 +115,7 @@ func UpdateReleaseTracksDB(db *sqlx.DB, release *model.Release, new_tracks []*mo
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateReleaseCreditsDB(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(
|
_, err := db.Exec(
|
||||||
"DELETE FROM musiccredit "+
|
"DELETE FROM musiccredit "+
|
||||||
"WHERE release=$1",
|
"WHERE release=$1",
|
||||||
|
@ -165,7 +143,7 @@ func UpdateReleaseCreditsDB(db *sqlx.DB, release *model.Release, new_credits []*
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateReleaseLinksDB(db *sqlx.DB, release *model.Release, new_links []*model.Link) error {
|
func UpdateReleaseLinks(db *sqlx.DB, release *model.Release, new_links []*model.Link) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"DELETE FROM musiclink "+
|
"DELETE FROM musiclink "+
|
||||||
"WHERE release=$1",
|
"WHERE release=$1",
|
||||||
|
@ -192,7 +170,7 @@ func UpdateReleaseLinksDB(db *sqlx.DB, release *model.Release, new_links []*mode
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteReleaseDB(db *sqlx.DB, release *model.Release) error {
|
func DeleteRelease(db *sqlx.DB, release *model.Release) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"DELETE FROM musicrelease "+
|
"DELETE FROM musicrelease "+
|
||||||
"WHERE id=$1",
|
"WHERE id=$1",
|
||||||
|
@ -204,3 +182,34 @@ func DeleteReleaseDB(db *sqlx.DB, release *model.Release) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetFullRelease(db *sqlx.DB, release *model.Release) (*model.FullRelease, error) {
|
||||||
|
// get credits
|
||||||
|
credits, err := GetReleaseCredits(db, release)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get tracks
|
||||||
|
dbTracks, err := GetReleaseTracks(db, release)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tracks := []model.DisplayTrack{}
|
||||||
|
for i, track := range dbTracks {
|
||||||
|
tracks = append(tracks, track.MakeDisplay(i + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// get links
|
||||||
|
links, err := GetReleaseLinks(db, release)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.FullRelease{
|
||||||
|
Release: release,
|
||||||
|
Tracks: tracks,
|
||||||
|
Credits: credits,
|
||||||
|
Links: links,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -7,10 +7,21 @@ import (
|
||||||
|
|
||||||
// DATABASE
|
// DATABASE
|
||||||
|
|
||||||
func PullAllTracks(db *sqlx.DB) ([]*model.Track, error) {
|
func GetTrack(db *sqlx.DB, id string) (*model.Track, error) {
|
||||||
|
var track = model.Track{}
|
||||||
|
|
||||||
|
stmt, _ := db.Preparex("SELECT * FROM musictrack WHERE id=$1")
|
||||||
|
err := stmt.Get(&track, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &track, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||||
var tracks = []*model.Track{}
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
err := db.Select(&tracks, "SELECT id, title, description, lyrics, preview_url FROM musictrack")
|
err := db.Select(&tracks, "SELECT * FROM musictrack")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -18,6 +29,49 @@ func PullAllTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||||
return tracks, nil
|
return tracks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||||
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
|
err := db.Select(&tracks, "SELECT * FROM musictrack WHERE id NOT IN (SELECT track FROM musicreleasetrack)")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTracksNotOnRelease(db *sqlx.DB, release *model.Release) ([]*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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTrackReleases(db *sqlx.DB, track *model.Track) ([]*model.Release, error) {
|
||||||
|
var releases = []*model.Release{}
|
||||||
|
|
||||||
|
err := db.Select(&releases,
|
||||||
|
"SELECT musicrelease.* FROM musicrelease "+
|
||||||
|
"JOIN musicreleasetrack ON release=id "+
|
||||||
|
"WHERE track=$1 "+
|
||||||
|
"ORDER BY release_date",
|
||||||
|
track.ID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
|
||||||
func PullOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
func PullOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||||
var tracks = []*model.Track{}
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
|
@ -33,7 +87,7 @@ func PullOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
||||||
return tracks, nil
|
return tracks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTrackDB(db *sqlx.DB, track *model.Track) (string, error) {
|
func CreateTrack(db *sqlx.DB, track *model.Track) (string, error) {
|
||||||
var trackID string
|
var trackID string
|
||||||
err := db.QueryRow(
|
err := db.QueryRow(
|
||||||
"INSERT INTO musictrack (title, description, lyrics, preview_url) "+
|
"INSERT INTO musictrack (title, description, lyrics, preview_url) "+
|
||||||
|
@ -51,7 +105,7 @@ func CreateTrackDB(db *sqlx.DB, track *model.Track) (string, error) {
|
||||||
return trackID, nil
|
return trackID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateTrackDB(db *sqlx.DB, track *model.Track) error {
|
func UpdateTrack(db *sqlx.DB, track *model.Track) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"UPDATE musictrack "+
|
"UPDATE musictrack "+
|
||||||
"SET title=$2, description=$3, lyrics=$4, preview_url=$5 "+
|
"SET title=$2, description=$3, lyrics=$4, preview_url=$5 "+
|
||||||
|
@ -69,7 +123,7 @@ func UpdateTrackDB(db *sqlx.DB, track *model.Track) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteTrackDB(db *sqlx.DB, track *model.Track) error {
|
func DeleteTrack(db *sqlx.DB, track *model.Track) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"DELETE FROM musictrack "+
|
"DELETE FROM musictrack "+
|
||||||
"WHERE id=$1",
|
"WHERE id=$1",
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Artist struct {
|
Artist struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Website string `json:"website"`
|
Website string `json:"website"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
|
@ -14,8 +16,61 @@ func (artist Artist) GetWebsite() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (artist Artist) GetAvatar() string {
|
func (artist Artist) GetAvatar() string {
|
||||||
if artist.Avatar == "" {
|
if artist.Avatar == "" {
|
||||||
return "/img/default-avatar.png"
|
return "/img/default-avatar.png"
|
||||||
}
|
}
|
||||||
return artist.Avatar
|
return artist.Avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (release FullRelease) GetUniqueArtists(only_primary bool) []*Artist {
|
||||||
|
var artists = []*Artist{}
|
||||||
|
|
||||||
|
for _, credit := range release.Credits {
|
||||||
|
if only_primary && !credit.Primary {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
exists := false
|
||||||
|
for _, a := range artists {
|
||||||
|
if a.ID == credit.Artist.ID {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
artists = append(artists, &credit.Artist)
|
||||||
|
}
|
||||||
|
|
||||||
|
return artists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (release FullRelease) GetUniqueArtistNames(only_primary bool) []string {
|
||||||
|
var names = []string{}
|
||||||
|
for _, artist := range release.GetUniqueArtists(only_primary) {
|
||||||
|
names = append(names, artist.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
func (release FullRelease) PrintArtists(only_primary bool, ampersand bool) string {
|
||||||
|
names := release.GetUniqueArtistNames(only_primary)
|
||||||
|
|
||||||
|
if len(names) == 0 {
|
||||||
|
return "Unknown Artist"
|
||||||
|
} else if len(names) == 1 {
|
||||||
|
return names[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ampersand {
|
||||||
|
res := strings.Join(names[:len(names)-1], ", ")
|
||||||
|
res += " & " + names[len(names)-1]
|
||||||
|
return res
|
||||||
|
} else {
|
||||||
|
return strings.Join(names[:], ", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
type Credit struct {
|
type Credit struct {
|
||||||
Artist *Artist `json:"artist"`
|
Artist `json:"artist"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Primary bool `json:"primary" db:"is_primary"`
|
Primary bool `json:"primary" db:"is_primary"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Link struct {
|
type Link struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (link Link) NormaliseName() string {
|
func (link Link) NormaliseName() string {
|
||||||
|
|
|
@ -1,25 +1,31 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ReleaseType string
|
ReleaseType string
|
||||||
Release struct {
|
|
||||||
ID string `json:"id"`
|
Release struct {
|
||||||
Visible bool `json:"visible"`
|
ID string `json:"id"`
|
||||||
Title string `json:"title"`
|
Visible bool `json:"visible"`
|
||||||
Description string `json:"description"`
|
Title string `json:"title"`
|
||||||
ReleaseType ReleaseType `json:"type" db:"type"`
|
Description string `json:"description"`
|
||||||
ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
|
ReleaseType ReleaseType `json:"type" db:"type"`
|
||||||
Artwork string `json:"artwork"`
|
ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
|
||||||
Buyname string `json:"buyname"`
|
Artwork string `json:"artwork"`
|
||||||
Buylink string `json:"buylink"`
|
Buyname string `json:"buyname"`
|
||||||
Links []*Link `json:"links"`
|
Buylink string `json:"buylink"`
|
||||||
Credits []*Credit `json:"credits"`
|
Copyright string `json:"copyright" db:"copyright"`
|
||||||
Tracks []*Track `json:"tracks"`
|
CopyrightURL string `json:"copyrightURL" db:"copyrighturl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
FullRelease struct {
|
||||||
|
*Release
|
||||||
|
Tracks []DisplayTrack
|
||||||
|
Credits []Credit
|
||||||
|
Links []Link
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,63 +58,10 @@ func (release Release) GetArtwork() string {
|
||||||
return release.Artwork
|
return release.Artwork
|
||||||
}
|
}
|
||||||
|
|
||||||
func (release Release) IsSingle() bool {
|
func (release FullRelease) IsSingle() bool {
|
||||||
return len(release.Tracks) == 1;
|
return len(release.Tracks) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
func (release Release) IsReleased() bool {
|
func (release Release) IsReleased() bool {
|
||||||
return release.ReleaseDate.Before(time.Now())
|
return release.ReleaseDate.Before(time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (release Release) GetUniqueArtists(only_primary bool) []Artist {
|
|
||||||
var artists = []Artist{}
|
|
||||||
|
|
||||||
for _, credit := range release.Credits {
|
|
||||||
if only_primary && !credit.Primary {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
exists := false
|
|
||||||
for _, a := range artists {
|
|
||||||
if a.ID == credit.Artist.ID {
|
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
artists = append(artists, *credit.Artist)
|
|
||||||
}
|
|
||||||
|
|
||||||
return artists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (release Release) GetUniqueArtistNames(only_primary bool) []string {
|
|
||||||
var names = []string{}
|
|
||||||
for _, artist := range release.GetUniqueArtists(only_primary) {
|
|
||||||
names = append(names, artist.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
func (release Release) PrintArtists(only_primary bool, ampersand bool) string {
|
|
||||||
names := release.GetUniqueArtistNames(only_primary)
|
|
||||||
|
|
||||||
if len(names) == 0 {
|
|
||||||
return "Unknown Artist"
|
|
||||||
} else if len(names) == 1 {
|
|
||||||
return names[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if ampersand {
|
|
||||||
res := strings.Join(names[:len(names)-1], ", ")
|
|
||||||
res += " & " + names[len(names)-1]
|
|
||||||
return res
|
|
||||||
} else {
|
|
||||||
return strings.Join(names[:], ", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,30 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
type Track struct {
|
import (
|
||||||
ID string `json:"id"`
|
"html/template"
|
||||||
Title string `json:"title"`
|
"strings"
|
||||||
Description string `json:"description"`
|
)
|
||||||
Lyrics string `json:"lyrics"`
|
|
||||||
PreviewURL string `json:"previewURL" db:"preview_url"`
|
type (
|
||||||
Release *Release `json:"-" db:"-"`
|
Track struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Lyrics string `json:"lyrics"`
|
||||||
|
PreviewURL string `json:"previewURL" db:"preview_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayTrack struct {
|
||||||
|
*Track
|
||||||
|
Lyrics template.HTML
|
||||||
|
Number int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (track Track) MakeDisplay(number int) DisplayTrack {
|
||||||
|
return DisplayTrack{
|
||||||
|
Track: &track,
|
||||||
|
Lyrics: template.HTML(strings.Replace(track.Lyrics, "\n", "<br>", -1)),
|
||||||
|
Number: number,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package view
|
package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
|
music "arimelody.me/arimelody.me/music/controller"
|
||||||
"arimelody.me/arimelody.me/music/model"
|
"arimelody.me/arimelody.me/music/model"
|
||||||
|
"arimelody.me/arimelody.me/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP HANDLER METHODS
|
// HTTP HANDLER METHODS
|
||||||
|
@ -17,7 +20,15 @@ func Handler() http.Handler {
|
||||||
ServeCatalog().ServeHTTP(w, r)
|
ServeCatalog().ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ServeGateway().ServeHTTP(w, r)
|
|
||||||
|
var release model.Release
|
||||||
|
err := global.DB.Get(&release, "SELECT * FROM musicrelease WHERE id=$1", r.URL.Path[1:])
|
||||||
|
if err != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ServeGateway(release).ServeHTTP(w, r)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
|
@ -25,71 +36,30 @@ func Handler() http.Handler {
|
||||||
|
|
||||||
func ServeCatalog() http.Handler {
|
func ServeCatalog() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
releases := []model.Release{}
|
dbReleases, err := music.GetAllReleases(global.DB)
|
||||||
for _, r := range global.Releases {
|
|
||||||
if r.Visible {
|
|
||||||
release := *r
|
|
||||||
if !release.IsReleased() {
|
|
||||||
release.ReleaseType = model.Upcoming
|
|
||||||
}
|
|
||||||
releases = append(releases, release)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
global.ServeTemplate("music.html", releases).ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func ServeArtwork() http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path == "/" {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasSuffix(r.URL.Path, ".png") {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseID := r.URL.Path[1:len(r.URL.Path) - 4]
|
|
||||||
var release = GetRelease(releaseID)
|
|
||||||
if release == nil {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// only allow authorised users to view unreleased releases
|
|
||||||
authorised := r.Context().Value("role") != nil && r.Context().Value("role") == "admin"
|
|
||||||
if !release.IsReleased() && !authorised {
|
|
||||||
admin.MustAuthorise(ServeArtwork()).ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fp := filepath.Join("data", "music-artwork", releaseID + ".png")
|
|
||||||
info, err := os.Stat(fp)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
length := info.Size()
|
|
||||||
|
|
||||||
file, err := os.Open(fp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Printf("FATAL: Failed to pull releases for catalog: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
releases := []*model.FullRelease{}
|
||||||
|
for _, dbRelease := range dbReleases {
|
||||||
|
if !dbRelease.Visible { continue }
|
||||||
|
if !dbRelease.IsReleased() {
|
||||||
|
dbRelease.ReleaseType = model.Upcoming
|
||||||
|
}
|
||||||
|
release, err := music.GetFullRelease(global.DB, dbRelease)
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
releases = append(releases, release)
|
||||||
|
}
|
||||||
|
|
||||||
var bytes = make([]byte, length)
|
err = templates.Pages["music"].Execute(w, releases)
|
||||||
file.Read(bytes)
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
w.Header().Add("Content-Type", "image/png")
|
}
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write(bytes)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -3,59 +3,42 @@ package view
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"arimelody.me/arimelody.me/admin"
|
"arimelody.me/arimelody.me/admin"
|
||||||
"arimelody.me/arimelody.me/global"
|
"arimelody.me/arimelody.me/global"
|
||||||
"arimelody.me/arimelody.me/music/model"
|
"arimelody.me/arimelody.me/music/model"
|
||||||
)
|
db "arimelody.me/arimelody.me/music/controller"
|
||||||
|
"arimelody.me/arimelody.me/templates"
|
||||||
type (
|
|
||||||
gatewayTrack struct {
|
|
||||||
*model.Track
|
|
||||||
Lyrics template.HTML
|
|
||||||
Number int
|
|
||||||
}
|
|
||||||
|
|
||||||
gatewayRelease struct {
|
|
||||||
*model.Release
|
|
||||||
Tracks []gatewayTrack
|
|
||||||
Authorised bool
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP HANDLERS
|
// HTTP HANDLERS
|
||||||
|
|
||||||
func ServeRelease() http.Handler {
|
func ServeRelease(release model.Release) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseID := r.URL.Path[1:]
|
|
||||||
var releaseRef = global.GetRelease(releaseID)
|
|
||||||
if releaseRef == nil {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var release = *releaseRef
|
|
||||||
|
|
||||||
// only allow authorised users to view hidden releases
|
// only allow authorised users to view hidden releases
|
||||||
authorised := admin.GetSession(r) != nil
|
authorised := admin.GetSession(r) != nil
|
||||||
if !authorised && !release.Visible {
|
if !authorised && !release.Visible {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !authorised && !release.IsReleased() {
|
|
||||||
release.Tracks = nil
|
fullRelease := &model.FullRelease{
|
||||||
release.Credits = nil
|
Release: &release,
|
||||||
|
}
|
||||||
|
|
||||||
|
if authorised || release.IsReleased() {
|
||||||
|
fullerRelease, err := db.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
|
||||||
|
}
|
||||||
|
fullRelease = fullerRelease
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
err := json.NewEncoder(w).Encode(release)
|
err := json.NewEncoder(w).Encode(fullRelease)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -63,42 +46,34 @@ func ServeRelease() http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeGateway() http.Handler {
|
func ServeGateway(release model.Release) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
|
||||||
http.Redirect(w, r, "/music", http.StatusPermanentRedirect)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
id := r.URL.Path[1:]
|
|
||||||
release := global.GetRelease(id)
|
|
||||||
if release == nil {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// only allow authorised users to view hidden releases
|
// only allow authorised users to view hidden releases
|
||||||
authorised := admin.GetSession(r) != nil
|
authorised := admin.GetSession(r) != nil
|
||||||
if !release.Visible && !authorised {
|
if !authorised && !release.Visible {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks := []gatewayTrack{}
|
fullRelease := &model.FullRelease{
|
||||||
for i, track := range release.Tracks {
|
Release: &release,
|
||||||
tracks = append(tracks, gatewayTrack{
|
|
||||||
Track: track,
|
|
||||||
Lyrics: template.HTML(strings.Replace(track.Lyrics, "\n", "<br>", -1)),
|
|
||||||
Number: i + 1,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lrw := global.LoggingResponseWriter{ResponseWriter: w, Code: http.StatusOK}
|
if authorised || release.IsReleased() {
|
||||||
|
fullerRelease, err := db.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
|
||||||
|
}
|
||||||
|
fullRelease = fullerRelease
|
||||||
|
}
|
||||||
|
|
||||||
global.ServeTemplate("music-gateway.html", gatewayRelease{release, tracks, authorised}).ServeHTTP(&lrw, r)
|
err := templates.Pages["music-gateway"].Execute(w, fullRelease)
|
||||||
|
|
||||||
if lrw.Code != http.StatusOK {
|
if err != nil {
|
||||||
fmt.Printf("Error rendering music gateway for %s\n", id)
|
fmt.Printf("Error rendering music gateway for %s: %s\n", release.ID, err)
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,7 +21,9 @@ CREATE TABLE public.musicrelease (
|
||||||
release_date TIMESTAMP NOT NULL,
|
release_date TIMESTAMP NOT NULL,
|
||||||
artwork text,
|
artwork text,
|
||||||
buyname text,
|
buyname text,
|
||||||
buylink text
|
buylink text,
|
||||||
|
copyright text,
|
||||||
|
copyrightURL text
|
||||||
);
|
);
|
||||||
ALTER TABLE public.musicrelease ADD CONSTRAINT musicrelease_pk PRIMARY KEY (id);
|
ALTER TABLE public.musicrelease ADD CONSTRAINT musicrelease_pk PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
33
templates/templates.go
Normal file
33
templates/templates.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Pages = map[string]*template.Template{
|
||||||
|
"index": template.Must(template.ParseFiles(
|
||||||
|
filepath.Join("views", "layout.html"),
|
||||||
|
filepath.Join("views", "header.html"),
|
||||||
|
filepath.Join("views", "footer.html"),
|
||||||
|
filepath.Join("views", "prideflag.html"),
|
||||||
|
filepath.Join("views", "index.html"),
|
||||||
|
)),
|
||||||
|
"music": template.Must(template.ParseFiles(
|
||||||
|
filepath.Join("views", "layout.html"),
|
||||||
|
filepath.Join("views", "header.html"),
|
||||||
|
filepath.Join("views", "footer.html"),
|
||||||
|
filepath.Join("views", "prideflag.html"),
|
||||||
|
filepath.Join("views", "music.html"),
|
||||||
|
)),
|
||||||
|
"music-gateway": template.Must(template.ParseFiles(
|
||||||
|
filepath.Join("views", "layout.html"),
|
||||||
|
filepath.Join("views", "header.html"),
|
||||||
|
filepath.Join("views", "footer.html"),
|
||||||
|
filepath.Join("views", "prideflag.html"),
|
||||||
|
filepath.Join("views", "music-gateway.html"),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
|
||||||
|
var Components = map[string]*template.Template{
|
||||||
|
}
|
Loading…
Reference in a new issue