Compare commits

..

No commits in common. "819ec891e729dec63f8cddc0723693aac2cf0249" and "f7edece0af6c312886f5bf923499f70da1c4cf52" have entirely different histories.

22 changed files with 223 additions and 214 deletions

View file

@ -3,11 +3,10 @@
.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/model" "arimelody-web/music/model"
"arimelody-web/controller" "arimelody-web/music/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 := controller.GetArtist(global.DB, id) artist, err := music.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 := controller.GetArtistCredits(global.DB, artist.ID, true) credits, err := music.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"
"arimelody-web/controller" musicDB "arimelody-web/music/controller"
"arimelody-web/model" musicModel "arimelody-web/music/model"
) )
type loginData struct { type loginData struct {
@ -41,21 +41,21 @@ func Handler() http.Handler {
return return
} }
releases, err := controller.GetAllReleases(global.DB, false, 0, true) releases, err := musicDB.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 := controller.GetAllArtists(global.DB) artists, err := musicDB.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 := controller.GetOrphanTracks(global.DB) tracks, err := musicDB.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 []*model.Release Releases []*musicModel.Release
Artists []*model.Artist Artists []*musicModel.Artist
Tracks []*model.Track Tracks []*musicModel.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"
"arimelody-web/controller" db "arimelody-web/music/controller"
"arimelody-web/model" "arimelody-web/music/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 := controller.GetRelease(global.DB, releaseID, true) release, err := db.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 := controller.GetArtistsNotOnRelease(global.DB, release.ID) artists, err := db.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 := controller.GetArtist(global.DB, artistID) artist, err := db.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 := controller.GetTracksNotOnRelease(global.DB, release.ID) tracks, err := db.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 := controller.GetTrack(global.DB, trackID) track, err := db.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/model" "arimelody-web/music/model"
"arimelody-web/controller" "arimelody-web/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, err := controller.GetTrack(global.DB, id) track, err := music.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 := controller.GetTrackReleases(global.DB, track.ID, true) releases, err := music.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,7 +7,8 @@ import (
"arimelody-web/admin" "arimelody-web/admin"
"arimelody-web/global" "arimelody-web/global"
"arimelody-web/controller" music "arimelody-web/music/controller"
musicView "arimelody-web/music/view"
) )
func Handler() http.Handler { func Handler() http.Handler {
@ -23,7 +24,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 := controller.GetArtist(global.DB, artistID) artist, err := music.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)
@ -65,7 +66,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 := controller.GetRelease(global.DB, releaseID, true) release, err := music.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)
@ -79,7 +80,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}
ServeRelease(release).ServeHTTP(w, r) musicView.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)
@ -107,7 +108,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 := controller.GetTrack(global.DB, trackID) track, err := music.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"
"arimelody-web/controller" db "arimelody-web/music/controller"
"arimelody-web/model" music "arimelody-web/music/controller"
"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 := controller.GetAllArtists(global.DB) artists, err := db.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,12 +38,8 @@ 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"` Role string `json:"role"`
Title string `json:"title"` Primary bool `json:"primary"`
ReleaseDate time.Time `json:"releaseDate" db:"release_date"`
Artwork string `json:"artwork"`
Role string `json:"role"`
Primary bool `json:"primary"`
} }
artistJSON struct { artistJSON struct {
*model.Artist *model.Artist
@ -54,7 +50,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 := controller.GetArtistCredits(global.DB, artist.ID, show_hidden_releases) dbCredits, err := db.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)
@ -64,10 +60,6 @@ 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,
} }
@ -99,7 +91,7 @@ func CreateArtist() http.Handler {
} }
if artist.Name == "" { artist.Name = artist.ID } if artist.Name == "" { artist.Name = artist.ID }
err = controller.CreateArtist(global.DB, &artist) err = music.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)
@ -147,7 +139,7 @@ func UpdateArtist(artist *model.Artist) http.Handler {
} }
} }
err = controller.UpdateArtist(global.DB, artist) err = music.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)
@ -161,7 +153,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 := controller.DeleteArtist(global.DB, artist.ID) err := music.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,109 +12,13 @@ import (
"arimelody-web/admin" "arimelody-web/admin"
"arimelody-web/global" "arimelody-web/global"
"arimelody-web/controller" music "arimelody-web/music/controller"
"arimelody-web/model" "arimelody-web/music/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 := controller.GetAllReleases(global.DB, false, 0, true) releases, err := music.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
@ -191,7 +95,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 = controller.CreateRelease(global.DB, &release) err = music.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)
@ -269,7 +173,7 @@ func UpdateRelease(release *model.Release) http.Handler {
} }
} }
err = controller.UpdateRelease(global.DB, release) err = music.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)
@ -290,7 +194,7 @@ func UpdateReleaseTracks(release *model.Release) http.Handler {
return return
} }
err = controller.UpdateReleaseTracks(global.DB, release.ID, trackIDs) err = music.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)
@ -327,7 +231,7 @@ func UpdateReleaseCredits(release *model.Release) http.Handler {
}) })
} }
err = controller.UpdateReleaseCredits(global.DB, release.ID, credits) err = music.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)
@ -357,7 +261,7 @@ func UpdateReleaseLinks(release *model.Release) http.Handler {
return return
} }
err = controller.UpdateReleaseLinks(global.DB, release.ID, links) err = music.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)
@ -371,7 +275,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 := controller.DeleteRelease(global.DB, release.ID) err := music.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"
"arimelody-web/controller" music "arimelody-web/music/controller"
"arimelody-web/model" "arimelody-web/music/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 := controller.GetAllTracks(global.DB) dbTracks, err := music.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 := controller.GetTrackReleases(global.DB, track.ID, false) dbReleases, err := music.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 := controller.CreateTrack(global.DB, &track) id, err := music.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 = controller.UpdateTrack(global.DB, track) err = music.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 := controller.DeleteTrack(global.DB, trackID) err := music.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

@ -13,7 +13,7 @@ import (
"arimelody-web/admin" "arimelody-web/admin"
"arimelody-web/api" "arimelody-web/api"
"arimelody-web/global" "arimelody-web/global"
"arimelody-web/view" musicView "arimelody-web/music/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", view.MusicHandler())) mux.Handle("/music/", http.StripPrefix("/music", musicView.Handler()))
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,7 +1,7 @@
package controller package music
import ( import (
"arimelody-web/model" "arimelody-web/music/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,title,artwork,release_date,artist.id,name,website,avatar,role,is_primary "+ var query string = "SELECT release.id,release.title,release.artwork,artist.id,artist.name,artist.website,artist.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,7 +69,6 @@ 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 controller package music
import ( import (
"errors" "errors"
"fmt" "fmt"
"arimelody-web/model" "arimelody-web/music/model"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )

View file

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

View file

@ -4,16 +4,15 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"arimelody-web/admin"
"arimelody-web/controller"
"arimelody-web/global" "arimelody-web/global"
"arimelody-web/model" music "arimelody-web/music/controller"
"arimelody-web/music/model"
"arimelody-web/templates" "arimelody-web/templates"
) )
// HTTP HANDLER METHODS // HTTP HANDLER METHODS
func MusicHandler() http.Handler { func Handler() 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) {
@ -22,7 +21,7 @@ func MusicHandler() http.Handler {
return return
} }
release, err := controller.GetRelease(global.DB, r.URL.Path[1:], true) release, err := music.GetRelease(global.DB, r.URL.Path[1:], true)
if err != nil { if err != nil {
http.NotFound(w, r) http.NotFound(w, r)
return return
@ -36,7 +35,7 @@ func MusicHandler() 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 := controller.GetAllReleases(global.DB, true, 0, true) releases, err := music.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)
@ -55,30 +54,3 @@ 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
}
})
}

136
music/view/release.go Normal file
View file

@ -0,0 +1,136 @@
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

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