Compare commits

..

4 commits

22 changed files with 214 additions and 223 deletions

View file

@ -3,10 +3,11 @@
.air.toml/ .air.toml/
.gitattributes .gitattributes
.gitignore .gitignore
uploads/* uploads/
test/ test/
tmp/ tmp/
db/ db/
res/
docker-compose.yml docker-compose.yml
docker-compose-test.yml docker-compose-test.yml
Dockerfile Dockerfile

2
.gitignore vendored
View file

@ -3,5 +3,5 @@
db/ db/
tmp/ tmp/
test/ test/
uploads/* uploads/
docker-compose-test.yml docker-compose-test.yml

View file

@ -6,15 +6,15 @@ import (
"strings" "strings"
"arimelody-web/global" "arimelody-web/global"
"arimelody-web/music/model" "arimelody-web/model"
"arimelody-web/music/controller" "arimelody-web/controller"
) )
func serveArtist() http.Handler { func serveArtist() 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]
artist, err := music.GetArtist(global.DB, id) artist, err := controller.GetArtist(global.DB, id)
if err != nil { if err != nil {
if artist == nil { if artist == nil {
http.NotFound(w, r) http.NotFound(w, r)
@ -25,7 +25,7 @@ func serveArtist() http.Handler {
return return
} }
credits, err := music.GetArtistCredits(global.DB, artist.ID, true) credits, err := controller.GetArtistCredits(global.DB, artist.ID, true)
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)

View file

@ -11,8 +11,8 @@ import (
"arimelody-web/discord" "arimelody-web/discord"
"arimelody-web/global" "arimelody-web/global"
musicDB "arimelody-web/music/controller" "arimelody-web/controller"
musicModel "arimelody-web/music/model" "arimelody-web/model"
) )
type loginData struct { type loginData struct {
@ -41,21 +41,21 @@ func Handler() http.Handler {
return return
} }
releases, err := musicDB.GetAllReleases(global.DB, false, 0, true) releases, err := controller.GetAllReleases(global.DB, false, 0, true)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull releases: %s\n", err) fmt.Printf("FATAL: Failed to pull releases: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
artists, err := musicDB.GetAllArtists(global.DB) artists, err := controller.GetAllArtists(global.DB)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull artists: %s\n", err) fmt.Printf("FATAL: Failed to pull artists: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
tracks, err := musicDB.GetOrphanTracks(global.DB) tracks, err := controller.GetOrphanTracks(global.DB)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull orphan tracks: %s\n", err) fmt.Printf("FATAL: Failed to pull orphan tracks: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -63,9 +63,9 @@ func Handler() http.Handler {
} }
type IndexData struct { type IndexData struct {
Releases []*musicModel.Release Releases []*model.Release
Artists []*musicModel.Artist Artists []*model.Artist
Tracks []*musicModel.Track Tracks []*model.Track
} }
err = pages["index"].Execute(w, IndexData{ err = pages["index"].Execute(w, IndexData{

View file

@ -6,8 +6,8 @@ import (
"strings" "strings"
"arimelody-web/global" "arimelody-web/global"
db "arimelody-web/music/controller" "arimelody-web/controller"
"arimelody-web/music/model" "arimelody-web/model"
) )
func serveRelease() http.Handler { func serveRelease() http.Handler {
@ -15,7 +15,7 @@ func serveRelease() http.Handler {
slices := strings.Split(r.URL.Path[1:], "/") slices := strings.Split(r.URL.Path[1:], "/")
releaseID := slices[0] releaseID := slices[0]
release, err := db.GetRelease(global.DB, releaseID, true) release, err := controller.GetRelease(global.DB, releaseID, true)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)
@ -81,7 +81,7 @@ func serveEditCredits(release *model.Release) http.Handler {
func serveAddCredit(release *model.Release) http.Handler { func serveAddCredit(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) {
artists, err := db.GetArtistsNotOnRelease(global.DB, release.ID) artists, err := controller.GetArtistsNotOnRelease(global.DB, release.ID)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err) fmt.Printf("FATAL: Failed to pull artists not on %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -108,7 +108,7 @@ 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) {
artistID := strings.Split(r.URL.Path, "/")[3] artistID := strings.Split(r.URL.Path, "/")[3]
artist, err := db.GetArtist(global.DB, artistID) artist, err := controller.GetArtist(global.DB, artistID)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull artists %s: %s\n", artistID, err) fmt.Printf("FATAL: Failed to pull artists %s: %s\n", artistID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -152,7 +152,7 @@ func serveEditTracks(release *model.Release) http.Handler {
func serveAddTrack(release *model.Release) http.Handler { func serveAddTrack(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) {
tracks, err := db.GetTracksNotOnRelease(global.DB, release.ID) tracks, err := controller.GetTracksNotOnRelease(global.DB, release.ID)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err) fmt.Printf("FATAL: Failed to pull tracks not on %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -180,7 +180,7 @@ func serveAddTrack(release *model.Release) http.Handler {
func serveNewTrack() 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) {
trackID := strings.Split(r.URL.Path, "/")[3] trackID := strings.Split(r.URL.Path, "/")[3]
track, err := db.GetTrack(global.DB, trackID) track, err := controller.GetTrack(global.DB, trackID)
if err != nil { if err != nil {
fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err) fmt.Printf("Error rendering new track component for %s: %s\n", trackID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)

View file

@ -6,15 +6,15 @@ import (
"strings" "strings"
"arimelody-web/global" "arimelody-web/global"
"arimelody-web/music/model" "arimelody-web/model"
"arimelody-web/music/controller" "arimelody-web/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, err := music.GetTrack(global.DB, id) track, err := controller.GetTrack(global.DB, id)
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)
@ -25,7 +25,7 @@ func serveTrack() http.Handler {
return return
} }
releases, err := music.GetTrackReleases(global.DB, track.ID, true) releases, err := controller.GetTrackReleases(global.DB, track.ID, true)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err) fmt.Printf("FATAL: Failed to pull releases for %s: %s\n", id, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)

View file

@ -7,8 +7,7 @@ import (
"arimelody-web/admin" "arimelody-web/admin"
"arimelody-web/global" "arimelody-web/global"
music "arimelody-web/music/controller" "arimelody-web/controller"
musicView "arimelody-web/music/view"
) )
func Handler() http.Handler { func Handler() http.Handler {
@ -24,7 +23,7 @@ func Handler() http.Handler {
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 artistID = strings.Split(r.URL.Path[1:], "/")[0]
artist, err := music.GetArtist(global.DB, artistID) artist, err := controller.GetArtist(global.DB, artistID)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)
@ -66,7 +65,7 @@ func Handler() http.Handler {
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 releaseID = strings.Split(r.URL.Path[1:], "/")[0]
release, err := music.GetRelease(global.DB, releaseID, true) release, err := controller.GetRelease(global.DB, releaseID, true)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)
@ -80,7 +79,7 @@ func Handler() http.Handler {
switch r.Method { switch r.Method {
case http.MethodGet: case http.MethodGet:
// GET /api/v1/music/{id} // GET /api/v1/music/{id}
musicView.ServeRelease(release).ServeHTTP(w, r) 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(release)).ServeHTTP(w, r) admin.MustAuthorise(UpdateRelease(release)).ServeHTTP(w, r)
@ -108,7 +107,7 @@ func Handler() http.Handler {
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 trackID = strings.Split(r.URL.Path[1:], "/")[0]
track, err := music.GetTrack(global.DB, trackID) track, err := controller.GetTrack(global.DB, trackID)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)

View file

@ -8,18 +8,18 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"arimelody-web/admin" "arimelody-web/admin"
"arimelody-web/global" "arimelody-web/global"
db "arimelody-web/music/controller" "arimelody-web/controller"
music "arimelody-web/music/controller" "arimelody-web/model"
"arimelody-web/music/model"
) )
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) {
var artists = []*model.Artist{} var artists = []*model.Artist{}
artists, err := db.GetAllArtists(global.DB) artists, err := controller.GetAllArtists(global.DB)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to serve all artists: %s\n", err) 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)
@ -38,6 +38,10 @@ 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) {
type ( type (
creditJSON struct { creditJSON struct {
ID string `json:"id"`
Title string `json:"title"`
ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
Artwork string `json:"artwork"`
Role string `json:"role"` Role string `json:"role"`
Primary bool `json:"primary"` Primary bool `json:"primary"`
} }
@ -50,7 +54,7 @@ func ServeArtist(artist *model.Artist) http.Handler {
show_hidden_releases := admin.GetSession(r) != nil show_hidden_releases := admin.GetSession(r) != nil
var dbCredits []*model.Credit var dbCredits []*model.Credit
dbCredits, err := db.GetArtistCredits(global.DB, artist.ID, show_hidden_releases) dbCredits, err := controller.GetArtistCredits(global.DB, artist.ID, show_hidden_releases)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to retrieve artist credits for %s: %s\n", artist.ID, err) fmt.Printf("FATAL: Failed to retrieve artist credits 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)
@ -60,6 +64,10 @@ func ServeArtist(artist *model.Artist) http.Handler {
var credits = map[string]creditJSON{} var credits = map[string]creditJSON{}
for _, credit := range dbCredits { for _, credit := range dbCredits {
credits[credit.Release.ID] = creditJSON{ credits[credit.Release.ID] = creditJSON{
ID: credit.Release.ID,
Title: credit.Release.Title,
ReleaseDate: credit.Release.ReleaseDate,
Artwork: credit.Release.Artwork,
Role: credit.Role, Role: credit.Role,
Primary: credit.Primary, Primary: credit.Primary,
} }
@ -91,7 +99,7 @@ func CreateArtist() http.Handler {
} }
if artist.Name == "" { artist.Name = artist.ID } if artist.Name == "" { artist.Name = artist.ID }
err = music.CreateArtist(global.DB, &artist) err = controller.CreateArtist(global.DB, &artist)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "duplicate key") { if strings.Contains(err.Error(), "duplicate key") {
http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest) http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest)
@ -139,7 +147,7 @@ func UpdateArtist(artist *model.Artist) http.Handler {
} }
} }
err = music.UpdateArtist(global.DB, artist) err = controller.UpdateArtist(global.DB, artist)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)
@ -153,7 +161,7 @@ func UpdateArtist(artist *model.Artist) http.Handler {
func DeleteArtist(artist *model.Artist) 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) {
err := music.DeleteArtist(global.DB, artist.ID) err := controller.DeleteArtist(global.DB, artist.ID)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)

View file

@ -12,13 +12,109 @@ import (
"arimelody-web/admin" "arimelody-web/admin"
"arimelody-web/global" "arimelody-web/global"
music "arimelody-web/music/controller" "arimelody-web/controller"
"arimelody-web/music/model" "arimelody-web/model"
) )
func ServeRelease(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// only allow authorised users to view hidden releases
authorised := admin.GetSession(r) != nil
if !authorised && !release.Visible {
http.NotFound(w, r)
return
}
type (
Track struct {
Title string `json:"title"`
Description string `json:"description"`
Lyrics string `json:"lyrics"`
}
Credit struct {
*model.Artist
Role string `json:"role"`
Primary bool `json:"primary"`
}
Release struct {
*model.Release
Tracks []Track `json:"tracks"`
Credits []Credit `json:"credits"`
Links map[string]string `json:"links"`
}
)
response := Release{
Release: release,
Tracks: []Track{},
Credits: []Credit{},
Links: make(map[string]string),
}
if authorised || release.IsReleased() {
// get credits
credits, err := controller.GetReleaseCredits(global.DB, release.ID)
if err != nil {
fmt.Printf("FATAL: Failed to serve release %s: Credits: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
for _, credit := range credits {
artist, err := controller.GetArtist(global.DB, credit.Artist.ID)
if err != nil {
fmt.Printf("FATAL: Failed to serve release %s: Artists: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
response.Credits = append(response.Credits, Credit{
Artist: artist,
Role: credit.Role,
Primary: credit.Primary,
})
}
// get tracks
tracks, err := controller.GetReleaseTracks(global.DB, release.ID)
if err != nil {
fmt.Printf("FATAL: Failed to serve release %s: Tracks: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
for _, track := range tracks {
response.Tracks = append(response.Tracks, Track{
Title: track.Title,
Description: track.Description,
Lyrics: track.Lyrics,
})
}
// get links
links, err := controller.GetReleaseLinks(global.DB, release.ID)
if err != nil {
fmt.Printf("FATAL: Failed to serve release %s: Links: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
for _, link := range links {
response.Links[link.Name] = link.URL
}
}
w.Header().Add("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(response)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
})
}
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, err := music.GetAllReleases(global.DB, false, 0, true) releases, err := controller.GetAllReleases(global.DB, false, 0, true)
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
@ -95,7 +191,7 @@ func CreateRelease() http.Handler {
if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" } if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" }
err = music.CreateRelease(global.DB, &release) err = controller.CreateRelease(global.DB, &release)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "duplicate key") { if strings.Contains(err.Error(), "duplicate key") {
http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest) http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest)
@ -173,7 +269,7 @@ func UpdateRelease(release *model.Release) http.Handler {
} }
} }
err = music.UpdateRelease(global.DB, release) err = controller.UpdateRelease(global.DB, release)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)
@ -194,7 +290,7 @@ func UpdateReleaseTracks(release *model.Release) http.Handler {
return return
} }
err = music.UpdateReleaseTracks(global.DB, release.ID, trackIDs) err = controller.UpdateReleaseTracks(global.DB, release.ID, trackIDs)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)
@ -231,7 +327,7 @@ func UpdateReleaseCredits(release *model.Release) http.Handler {
}) })
} }
err = music.UpdateReleaseCredits(global.DB, release.ID, credits) err = controller.UpdateReleaseCredits(global.DB, release.ID, credits)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "duplicate key") { if strings.Contains(err.Error(), "duplicate key") {
http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest) http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest)
@ -261,7 +357,7 @@ func UpdateReleaseLinks(release *model.Release) http.Handler {
return return
} }
err = music.UpdateReleaseLinks(global.DB, release.ID, links) err = controller.UpdateReleaseLinks(global.DB, release.ID, links)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)
@ -275,7 +371,7 @@ func UpdateReleaseLinks(release *model.Release) http.Handler {
func DeleteRelease(release *model.Release) 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) {
err := music.DeleteRelease(global.DB, release.ID) err := controller.DeleteRelease(global.DB, release.ID)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no rows") { if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r) http.NotFound(w, r)

View file

@ -6,8 +6,8 @@ import (
"net/http" "net/http"
"arimelody-web/global" "arimelody-web/global"
music "arimelody-web/music/controller" "arimelody-web/controller"
"arimelody-web/music/model" "arimelody-web/model"
) )
type ( type (
@ -26,7 +26,7 @@ func ServeAllTracks() http.Handler {
var tracks = []Track{} var tracks = []Track{}
var dbTracks = []*model.Track{} var dbTracks = []*model.Track{}
dbTracks, err := music.GetAllTracks(global.DB) dbTracks, err := controller.GetAllTracks(global.DB)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull tracks from DB: %s\n", err) fmt.Printf("FATAL: Failed to pull tracks from DB: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -50,7 +50,7 @@ func ServeAllTracks() http.Handler {
func ServeTrack(track *model.Track) 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) {
dbReleases, err := music.GetTrackReleases(global.DB, track.ID, false) dbReleases, err := controller.GetTrackReleases(global.DB, track.ID, false)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull track releases for %s from DB: %s\n", track.ID, err) fmt.Printf("FATAL: Failed to pull track releases for %s from DB: %s\n", track.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -89,7 +89,7 @@ func CreateTrack() http.Handler {
return return
} }
id, err := music.CreateTrack(global.DB, &track) id, err := controller.CreateTrack(global.DB, &track)
if err != nil { if err != nil {
fmt.Printf("FATAL: 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)
@ -120,7 +120,7 @@ func UpdateTrack(track *model.Track) http.Handler {
return return
} }
err = music.UpdateTrack(global.DB, track) err = controller.UpdateTrack(global.DB, track)
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)
@ -143,7 +143,7 @@ func DeleteTrack(track *model.Track) http.Handler {
} }
var trackID = r.URL.Path[1:] var trackID = r.URL.Path[1:]
err := music.DeleteTrack(global.DB, trackID) err := controller.DeleteTrack(global.DB, trackID)
if err != nil { if err != nil {
fmt.Printf("Failed to delete track %s: %s\n", trackID, 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)

View file

@ -1,7 +1,7 @@
package music package controller
import ( import (
"arimelody-web/music/model" "arimelody-web/model"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
@ -45,7 +45,7 @@ func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, err
} }
func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.Credit, error) { func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.Credit, error) {
var query string = "SELECT release.id,release.title,release.artwork,artist.id,artist.name,artist.website,artist.avatar,role,is_primary "+ var query string = "SELECT release.id,title,artwork,release_date,artist.id,name,website,avatar,role,is_primary "+
"FROM musiccredit "+ "FROM musiccredit "+
"JOIN musicrelease AS release ON release=release.id "+ "JOIN musicrelease AS release ON release=release.id "+
"JOIN artist ON artist=artist.id "+ "JOIN artist ON artist=artist.id "+
@ -69,6 +69,7 @@ func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.
&credit.Release.ID, &credit.Release.ID,
&credit.Release.Title, &credit.Release.Title,
&credit.Release.Artwork, &credit.Release.Artwork,
&credit.Release.ReleaseDate,
&credit.Artist.ID, &credit.Artist.ID,
&credit.Artist.Name, &credit.Artist.Name,
&credit.Artist.Website, &credit.Artist.Website,

View file

@ -1,10 +1,10 @@
package music package controller
import ( import (
"errors" "errors"
"fmt" "fmt"
"arimelody-web/music/model" "arimelody-web/model"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )

View file

@ -1,7 +1,7 @@
package music package controller
import ( import (
"arimelody-web/music/model" "arimelody-web/model"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )

View file

@ -13,7 +13,7 @@ import (
"arimelody-web/admin" "arimelody-web/admin"
"arimelody-web/api" "arimelody-web/api"
"arimelody-web/global" "arimelody-web/global"
musicView "arimelody-web/music/view" "arimelody-web/view"
"arimelody-web/templates" "arimelody-web/templates"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
@ -210,7 +210,7 @@ func createServeMux() *http.ServeMux {
mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler())) mux.Handle("/admin/", http.StripPrefix("/admin", admin.Handler()))
mux.Handle("/api/", http.StripPrefix("/api", api.Handler())) mux.Handle("/api/", http.StripPrefix("/api", api.Handler()))
mux.Handle("/music/", http.StripPrefix("/music", musicView.Handler())) mux.Handle("/music/", http.StripPrefix("/music", view.MusicHandler()))
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" {

View file

@ -1,136 +0,0 @@
package view
import (
"encoding/json"
"fmt"
"net/http"
"arimelody-web/admin"
"arimelody-web/global"
"arimelody-web/music/model"
db "arimelody-web/music/controller"
"arimelody-web/templates"
)
type (
Track struct {
Title string `json:"title"`
Description string `json:"description"`
Lyrics string `json:"lyrics"`
}
Credit struct {
*model.Artist
Role string `json:"role"`
Primary bool `json:"primary"`
}
Release struct {
*model.Release
Tracks []Track `json:"tracks"`
Credits []Credit `json:"credits"`
Links map[string]string `json:"links"`
}
)
func ServeRelease(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// only allow authorised users to view hidden releases
authorised := admin.GetSession(r) != nil
if !authorised && !release.Visible {
http.NotFound(w, r)
return
}
response := Release{
Release: release,
Tracks: []Track{},
Credits: []Credit{},
Links: make(map[string]string),
}
if authorised || release.IsReleased() {
// get credits
credits, err := db.GetReleaseCredits(global.DB, release.ID)
if err != nil {
fmt.Printf("FATAL: Failed to serve release %s: Credits: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
for _, credit := range credits {
artist, err := db.GetArtist(global.DB, credit.Artist.ID)
if err != nil {
fmt.Printf("FATAL: Failed to serve release %s: Artists: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
response.Credits = append(response.Credits, Credit{
Artist: artist,
Role: credit.Role,
Primary: credit.Primary,
})
}
// get tracks
tracks, err := db.GetReleaseTracks(global.DB, release.ID)
if err != nil {
fmt.Printf("FATAL: Failed to serve release %s: Tracks: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
for _, track := range tracks {
response.Tracks = append(response.Tracks, Track{
Title: track.Title,
Description: track.Description,
Lyrics: track.Lyrics,
})
}
// get links
links, err := db.GetReleaseLinks(global.DB, release.ID)
if err != nil {
fmt.Printf("FATAL: Failed to serve release %s: Links: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
for _, link := range links {
response.Links[link.Name] = link.URL
}
}
w.Header().Add("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(response)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
})
}
func ServeGateway(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// only allow authorised users to view hidden releases
authorised := admin.GetSession(r) != nil
if !authorised && !release.Visible {
http.NotFound(w, r)
return
}
response := *release
if authorised || release.IsReleased() {
response.Tracks = release.Tracks
response.Credits = release.Credits
response.Links = release.Links
}
err := templates.Pages["music-gateway"].Execute(w, response)
if err != nil {
fmt.Printf("Error rendering music gateway for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
})
}

View file

@ -4,15 +4,16 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"arimelody-web/admin"
"arimelody-web/controller"
"arimelody-web/global" "arimelody-web/global"
music "arimelody-web/music/controller" "arimelody-web/model"
"arimelody-web/music/model"
"arimelody-web/templates" "arimelody-web/templates"
) )
// HTTP HANDLER METHODS // HTTP HANDLER METHODS
func Handler() http.Handler { func MusicHandler() http.Handler {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -21,7 +22,7 @@ func Handler() http.Handler {
return return
} }
release, err := music.GetRelease(global.DB, r.URL.Path[1:], true) release, err := controller.GetRelease(global.DB, r.URL.Path[1:], true)
if err != nil { if err != nil {
http.NotFound(w, r) http.NotFound(w, r)
return return
@ -35,7 +36,7 @@ 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, err := music.GetAllReleases(global.DB, true, 0, true) releases, err := controller.GetAllReleases(global.DB, true, 0, true)
if err != nil { if err != nil {
fmt.Printf("FATAL: Failed to pull releases for catalog: %s\n", err) 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)
@ -54,3 +55,30 @@ func ServeCatalog() http.Handler {
} }
}) })
} }
func ServeGateway(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// only allow authorised users to view hidden releases
authorised := admin.GetSession(r) != nil
if !authorised && !release.Visible {
http.NotFound(w, r)
return
}
response := *release
if authorised || release.IsReleased() {
response.Tracks = release.Tracks
response.Credits = release.Credits
response.Links = release.Links
}
err := templates.Pages["music-gateway"].Execute(w, response)
if err != nil {
fmt.Printf("Error rendering music gateway for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
})
}

View file

@ -59,11 +59,11 @@
</h2> </h2>
<div class="answer"> <div class="answer">
<p> <p>
<strong class="big">yes!</strong> well, in most cases... <strong class="big">yes!*</strong> <em>in most cases...</em>
</p> </p>
<p> <p>
from <a href="/music/dream">Dream (2022)</a> onward, all of my <em>self-released</em> songs are all of my <em>self-released</em> songs are licensed under
licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Creative Commons Attribution-ShareAlike 4.0</a>. <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Creative Commons Attribution-ShareAlike 4.0</a>.
anyone may use and remix these songs freely, so long as they provide credit back to me and link back to this license! anyone may use and remix these songs freely, so long as they provide credit back to me and link back to this license!
please note that all derivative works must inherit this license. please note that all derivative works must inherit this license.
</p> </p>
@ -71,23 +71,17 @@
a great example of some credit text would be as follows: a great example of some credit text would be as follows:
</p> </p>
<blockquote> <blockquote>
music used: mellodoot - Dream<br> music used: ari melody - free2play<br>
<a href="/music/dream">https://arimelody.me/music/dream</a><br> <a href="/music/free2play">https://arimelody.me/music/free2play</a><br>
licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a>. licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a>.
</blockquote> </blockquote>
<p> <p>
for any songs prior to this, they were all either released by me (in which case, i honestly if the song you want to use is <em>not</em> released by me (i.e. under a record label), their usage rights
don't mind), or in collaboration with chill people who i don't see having an issue with it. will likely trump whatever i'd otherwise have in mind. i'll try to negotiate some nice terms, though!
do be sure to ask them about it, though!
</p> </p>
<p> <p>
in the event the song you want to use is released under some other label, their usage rights i believe that encouraging creative use of artistic works is better than stifling any use at all.
will more than likely trump whatever i'd otherwise have in mind. i'll try to negotiate some if you do happen to use my work in something you're particularly proud of, feel free to send it my way!
nice terms, though! ;3
</p>
<p>
i love the idea of other creators using my songs in their work, so if you do happen to use
my stuff in a work you're particularly proud of, feel free to send it my way!
</p> </p>
<p> <p>
&gt; <a href="mailto:ari@arimelody.me">ari@arimelody.me</a> &gt; <a href="mailto:ari@arimelody.me">ari@arimelody.me</a>