HEAVY: finish music service migration, tidy up services, more tests
This commit is contained in:
parent
9e311df462
commit
90a671982c
47 changed files with 2698 additions and 902 deletions
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/admin/templates"
|
"arimelody-web/admin/templates"
|
||||||
"arimelody-web/controller"
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/model/app"
|
"arimelody-web/model/app"
|
||||||
)
|
)
|
||||||
|
|
@ -23,7 +22,7 @@ func serveArtists(app *app.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
artists, err := controller.GetAllArtists(app.DB)
|
artists, err := app.MusicService.GetAllArtists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to fetch artists: %s\n", err)
|
fmt.Printf("WARN: Failed to fetch artists: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
@ -50,7 +49,7 @@ func serveArtist(app *app.AppState, artistID string) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
artist, err := controller.GetArtist(app.DB, artistID)
|
artist, err := app.MusicService.GetArtistByID(artistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if artist == nil {
|
if artist == nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
|
|
@ -61,7 +60,7 @@ func serveArtist(app *app.AppState, artistID string) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
credits, err := controller.GetArtistCredits(app.DB, artist.ID, true)
|
credits, err := app.MusicService.GetArtistCredits(artistID, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve admin artist page for %s: %s\n", artistID, err)
|
fmt.Printf("WARN: Failed to serve admin artist page for %s: %s\n", artistID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
||||||
|
|
@ -83,39 +83,39 @@ func AdminIndexHandler(app *app.AppState) http.Handler {
|
||||||
|
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
releases, err := controller.GetAllReleases(app.DB, false, 3, true)
|
releases, err := app.MusicService.GetAllReleases(false, 3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: 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
|
||||||
}
|
}
|
||||||
releaseCount, err := controller.GetReleaseCount(app.DB, false)
|
releaseCount, err := app.MusicService.GetReleaseCount(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases count: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases count: %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(app.DB)
|
artists, err := app.MusicService.GetAllArtists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: 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
|
||||||
}
|
}
|
||||||
artistCount, err := controller.GetArtistCount(app.DB)
|
artistCount, err := app.MusicService.GetArtistCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull artist count: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to pull artist count: %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(app.DB)
|
tracks, err := app.MusicService.GetOrphanTracks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull orphan tracks: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: 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)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
trackCount, err := controller.GetTrackCount(app.DB)
|
trackCount, err := app.MusicService.GetTrackCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to pull track count: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to pull track count: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/admin/templates"
|
"arimelody-web/admin/templates"
|
||||||
"arimelody-web/controller"
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/model/app"
|
"arimelody-web/model/app"
|
||||||
|
"arimelody-web/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveReleases(app *app.AppState) http.Handler {
|
func serveReleases(app *app.AppState) http.Handler {
|
||||||
|
|
@ -34,7 +34,7 @@ func serveReleases(app *app.AppState) http.Handler {
|
||||||
Releases []*model.Release
|
Releases []*model.Release
|
||||||
}
|
}
|
||||||
|
|
||||||
releases, err := controller.GetAllReleases(app.DB, false, 0, true)
|
releases, err := app.MusicService.GetAllFullReleases(false, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch releases: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch releases: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
@ -60,9 +60,13 @@ func serveRelease(app *app.AppState, releaseID string, action string) http.Handl
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
release, err := controller.GetRelease(app.DB, releaseID, true)
|
release, err := app.MusicService.GetFullReleaseByID(releaseID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsValidationError(err) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +134,7 @@ func serveEditCredits(release *model.Release) http.Handler {
|
||||||
|
|
||||||
func serveAddCredit(app *app.AppState, release *model.Release) http.Handler {
|
func serveAddCredit(app *app.AppState, 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(app.DB, release.ID)
|
artists, err := app.MusicService.GetArtistsNotOnRelease(release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to fetch artists not on %s: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to fetch 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)
|
||||||
|
|
@ -158,7 +162,7 @@ func serveNewCredit(app *app.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
split := strings.Split(r.URL.Path, "/")
|
split := strings.Split(r.URL.Path, "/")
|
||||||
artistID := split[len(split) - 1]
|
artistID := split[len(split) - 1]
|
||||||
artist, err := controller.GetArtist(app.DB, artistID)
|
artist, err := app.MusicService.GetArtistByID(artistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to fetch artist %s: %s\n", artistID, err)
|
fmt.Printf("WARN: Failed to fetch artist %s: %s\n", artistID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
@ -207,7 +211,7 @@ func serveEditTracks(release *model.Release) http.Handler {
|
||||||
|
|
||||||
func serveAddTrack(app *app.AppState, release *model.Release) http.Handler {
|
func serveAddTrack(app *app.AppState, 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(app.DB, release.ID)
|
tracks, err := app.MusicService.GetTracksNotOnRelease(release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to fetch tracks not on %s: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to fetch 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)
|
||||||
|
|
@ -235,7 +239,7 @@ func serveNewTrack(app *app.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
split := strings.Split(r.URL.Path, "/")
|
split := strings.Split(r.URL.Path, "/")
|
||||||
trackID := split[len(split) - 1]
|
trackID := split[len(split) - 1]
|
||||||
track, err := controller.GetTrack(app.DB, trackID)
|
track, err := app.MusicService.GetTrackByID(trackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to fetch track %s: %s\n", trackID, err)
|
fmt.Printf("WARN: Failed to fetch track %s: %s\n", trackID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"arimelody-web/admin/templates"
|
"arimelody-web/admin/templates"
|
||||||
"arimelody-web/controller"
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/model/app"
|
"arimelody-web/model/app"
|
||||||
)
|
)
|
||||||
|
|
@ -23,7 +22,7 @@ func serveTracks(app *app.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks, err := controller.GetAllTracks(app.DB)
|
tracks, err := app.MusicService.GetAllTracks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to fetch tracks: %s\n", err)
|
fmt.Printf("WARN: Failed to fetch tracks: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
@ -50,7 +49,7 @@ func serveTrack(app *app.AppState, trackID string) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
track, err := controller.GetTrack(app.DB, trackID)
|
track, err := app.MusicService.GetTrackByID(trackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve admin track page for %s: %s\n", trackID, err)
|
fmt.Printf("WARN: Failed to serve admin track page for %s: %s\n", trackID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
@ -61,7 +60,7 @@ func serveTrack(app *app.AppState, trackID string) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
releases, err := controller.GetTrackReleases(app.DB, track.ID, true)
|
releases, err := app.MusicService.GetTrackReleases(trackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to fetch releases for track %s: %s\n", trackID, err)
|
fmt.Printf("WARN: Failed to fetch releases for track %s: %s\n", trackID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
|
||||||
18
api/api.go
18
api/api.go
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/model/app"
|
"arimelody-web/model/app"
|
||||||
|
"arimelody-web/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Handler(app *app.AppState) http.Handler {
|
func Handler(app *app.AppState) http.Handler {
|
||||||
|
|
@ -21,9 +22,9 @@ func Handler(app *app.AppState) 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(app.DB, artistID)
|
artist, err := app.MusicService.GetArtistByID(artistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -63,9 +64,9 @@ func Handler(app *app.AppState) 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(app.DB, releaseID, true)
|
release, err := app.MusicService.GetFullReleaseByID(releaseID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -105,9 +106,9 @@ func Handler(app *app.AppState) 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(app.DB, trackID)
|
track, err := app.MusicService.GetTrackByID(trackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +188,10 @@ func getSession(app *app.AppState, r *http.Request) (*model.Session, error) {
|
||||||
// fetch existing session
|
// fetch existing session
|
||||||
session, err := controller.GetSession(app, token)
|
session, err := controller.GetSession(app, token)
|
||||||
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "no rows") {
|
if errors.IsValidationError(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if errors.IsNotExistError(err) {
|
||||||
return nil, fmt.Errorf("Failed to retrieve session: %v\n", err)
|
return nil, fmt.Errorf("Failed to retrieve session: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/model/app"
|
"arimelody-web/model/app"
|
||||||
|
"arimelody-web/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServeAllArtists(app *app.AppState) http.Handler {
|
func ServeAllArtists(app *app.AppState) 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(app.DB)
|
artists, err := app.MusicService.GetAllArtists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve all artists: %s\n", err)
|
fmt.Printf("WARN: 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)
|
||||||
|
|
@ -53,9 +53,9 @@ func ServeArtist(app *app.AppState, artist *model.Artist) http.Handler {
|
||||||
)
|
)
|
||||||
|
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
show_hidden_releases := session != nil && session.Account != nil
|
showHiddenReleases := session != nil && session.Account != nil
|
||||||
|
|
||||||
dbCredits, err := controller.GetArtistCredits(app.DB, artist.ID, show_hidden_releases)
|
dbCredits, err := app.MusicService.GetArtistCredits(artist.ID, showHiddenReleases)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to retrieve artist credits for %s: %v\n", artist.ID, err)
|
fmt.Printf("WARN: Failed to retrieve artist credits for %s: %v\n", artist.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
@ -91,31 +91,34 @@ func CreateArtist(app *app.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
var artist model.Artist
|
type CreateArtistDTO struct {
|
||||||
err := json.NewDecoder(r.Body).Decode(&artist)
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
dto := &CreateArtistDTO{}
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(dto)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if artist.ID == "" {
|
err = app.MusicService.CreateArtist(dto.ID, dto.Name, "", "")
|
||||||
http.Error(w, "Artist ID cannot be blank\n", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if artist.Name == "" { artist.Name = artist.ID }
|
|
||||||
|
|
||||||
err = controller.CreateArtist(app.DB, &artist)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if errors.IsValidationError(err) {
|
||||||
http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("WARN: 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", dto.ID), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("WARN: Failed to create artist %s: %s\n", dto.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
app.LogService.Info(model.LOG_ARTIST, "Artist \"%s\" created by \"%s\".", artist.Name, session.Account.Username)
|
app.LogService.Info(model.LOG_ARTIST, "Artist \"%s\" created by \"%s\".", dto.Name, session.Account.Username)
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
})
|
})
|
||||||
|
|
@ -156,9 +159,13 @@ func UpdateArtist(app *app.AppState, artist *model.Artist) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateArtist(app.DB, artist)
|
err = app.MusicService.UpdateArtist(artist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsValidationError(err) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -174,9 +181,13 @@ func DeleteArtist(app *app.AppState, 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) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
err := controller.DeleteArtist(app.DB, artist.ID)
|
err := app.MusicService.DeleteArtist(artist.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsValidationError(err) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
171
api/release.go
171
api/release.go
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"arimelody-web/controller"
|
"arimelody-web/controller"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/model/app"
|
"arimelody-web/model/app"
|
||||||
|
"arimelody-web/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ServeRelease(app *app.AppState, release *model.Release) http.Handler {
|
func ServeRelease(app *app.AppState, release *model.Release) http.Handler {
|
||||||
|
|
@ -39,50 +40,50 @@ func ServeRelease(app *app.AppState, release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Track struct {
|
TrackDTO struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Lyrics string `json:"lyrics"`
|
Lyrics string `json:"lyrics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
Credit struct {
|
CreditDTO struct {
|
||||||
*model.Artist
|
*model.Artist
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Primary bool `json:"primary"`
|
Primary bool `json:"primary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
Release struct {
|
ReleaseDTO struct {
|
||||||
*model.Release
|
*model.Release
|
||||||
Tracks []Track `json:"tracks"`
|
Tracks []TrackDTO `json:"tracks"`
|
||||||
Credits []Credit `json:"credits"`
|
Credits []CreditDTO `json:"credits"`
|
||||||
Links map[string]string `json:"links"`
|
Links map[string]string `json:"links"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
response := Release{
|
response := ReleaseDTO{
|
||||||
Release: release,
|
Release: release,
|
||||||
Tracks: []Track{},
|
Tracks: []TrackDTO{},
|
||||||
Credits: []Credit{},
|
Credits: []CreditDTO{},
|
||||||
Links: make(map[string]string),
|
Links: make(map[string]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
if release.IsReleased() || privileged {
|
if release.IsReleased() || privileged {
|
||||||
// get credits
|
// get credits
|
||||||
credits, err := controller.GetReleaseCredits(app.DB, release.ID)
|
credits, err := app.MusicService.GetReleaseCredits(release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve release %s: Credits: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to serve release %s: Credits: %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
|
||||||
}
|
}
|
||||||
for _, credit := range credits {
|
for _, credit := range credits {
|
||||||
artist, err := controller.GetArtist(app.DB, credit.Artist.ID)
|
artist, err := app.MusicService.GetArtistByID(credit.Artist.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve release %s: Artists: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to serve release %s: Artists: %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
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Credits = append(response.Credits, Credit{
|
response.Credits = append(response.Credits, CreditDTO{
|
||||||
Artist: artist,
|
Artist: artist,
|
||||||
Role: credit.Role,
|
Role: credit.Role,
|
||||||
Primary: credit.Primary,
|
Primary: credit.Primary,
|
||||||
|
|
@ -90,14 +91,14 @@ func ServeRelease(app *app.AppState, release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get tracks
|
// get tracks
|
||||||
tracks, err := controller.GetReleaseTracks(app.DB, release.ID)
|
tracks, err := app.MusicService.GetReleaseTracks(release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve release %s: Tracks: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to serve release %s: Tracks: %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
|
||||||
}
|
}
|
||||||
for _, track := range tracks {
|
for _, track := range tracks {
|
||||||
response.Tracks = append(response.Tracks, Track{
|
response.Tracks = append(response.Tracks, TrackDTO{
|
||||||
Title: track.Title,
|
Title: track.Title,
|
||||||
Description: track.Description,
|
Description: track.Description,
|
||||||
Lyrics: track.Lyrics,
|
Lyrics: track.Lyrics,
|
||||||
|
|
@ -105,7 +106,7 @@ func ServeRelease(app *app.AppState, release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get links
|
// get links
|
||||||
links, err := controller.GetReleaseLinks(app.DB, release.ID)
|
links, err := app.MusicService.GetReleaseLinks(release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to serve release %s: Links: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to serve release %s: Links: %s\n", release.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
@ -129,7 +130,7 @@ func ServeRelease(app *app.AppState, release *model.Release) http.Handler {
|
||||||
|
|
||||||
func ServeCatalog(app *app.AppState) http.Handler {
|
func ServeCatalog(app *app.AppState) 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(app.DB, false, 0, true)
|
releases, err := app.MusicService.GetAllFullReleases(false, 0)
|
||||||
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
|
||||||
|
|
@ -192,47 +193,56 @@ func CreateRelease(app *app.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
var release model.Release
|
type CreateReleaseDTO struct {
|
||||||
err := json.NewDecoder(r.Body).Decode(&release)
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
ReleaseType string `json:"type"`
|
||||||
|
ReleaseDate time.Time `json:"release_date"`
|
||||||
|
Artwork string `json:"artwork"`
|
||||||
|
}
|
||||||
|
var dto CreateReleaseDTO
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&dto)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if release.ID == "" {
|
if dto.ReleaseType == "" { dto.ReleaseType = string(model.Single) }
|
||||||
http.Error(w, "Release ID cannot be empty\n", http.StatusBadRequest)
|
if dto.ReleaseDate != time.Unix(0, 0) {
|
||||||
return
|
dto.ReleaseDate = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC)
|
||||||
}
|
}
|
||||||
|
if dto.Artwork == "" { dto.Artwork = model.DEFAULT_RELEASE_ARTWORK_URL }
|
||||||
|
|
||||||
if release.Title == "" { release.Title = release.ID }
|
err = app.MusicService.CreateRelease(
|
||||||
if release.ReleaseType == "" { release.ReleaseType = model.Single }
|
dto.ID,
|
||||||
|
dto.Title,
|
||||||
if release.ReleaseDate != time.Unix(0, 0) {
|
dto.ReleaseType,
|
||||||
release.ReleaseDate = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC)
|
dto.ReleaseDate,
|
||||||
}
|
dto.Artwork,
|
||||||
|
)
|
||||||
if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" }
|
|
||||||
|
|
||||||
err = controller.CreateRelease(app.DB, &release)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if errors.IsValidationError(err) {
|
||||||
http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("WARN: Failed to create release %s: %s\n", release.ID, err)
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
|
http.Error(w, fmt.Sprintf("Release %s already exists\n", dto.ID), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("WARN: Failed to create release %s: %s\n", dto.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
app.LogService.Info(model.LOG_MUSIC, "Release \"%s\" created by \"%s\".", release.ID, session.Account.Username)
|
app.LogService.Info(model.LOG_MUSIC, "Release \"%s\" created by \"%s\".", dto.ID, session.Account.Username)
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.NewEncoder(w)
|
||||||
encoder.SetIndent("", "\t")
|
encoder.SetIndent("", "\t")
|
||||||
err = encoder.Encode(release)
|
err = encoder.Encode(dto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Release %s created, but failed to send JSON response: %s\n", release.ID, err)
|
fmt.Printf("WARN: Release %s created, but failed to send JSON response: %s\n", dto.ID, err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -297,9 +307,13 @@ func UpdateRelease(app *app.AppState, release *model.Release) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateRelease(app.DB, release)
|
err = app.MusicService.UpdateRelease(release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsValidationError(err) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -315,28 +329,32 @@ func UpdateReleaseTracks(app *app.AppState, 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) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
var trackIDs = []string{}
|
var newTrackIDs = []string{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&trackIDs)
|
err := json.NewDecoder(r.Body).Decode(&newTrackIDs)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateReleaseTracks(app.DB, release.ID, trackIDs)
|
err = app.MusicService.UpdateReleaseTracks(release.ID, newTrackIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if errors.IsValidationError(err) {
|
||||||
http.Error(w, "Release cannot have duplicate tracks", http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
|
http.Error(w, "Release cannot have duplicate tracks", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Printf("WARN: Failed to update tracks for %s: %s\n", release.ID, err)
|
fmt.Printf("WARN: 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.LogService.Info(model.LOG_MUSIC, "Tracklist for release \"%s\" updated by \"%s\".", release.ID, session.Account.Username)
|
app.LogService.Info(model.LOG_MUSIC, "Release \"%s\" tracklist updated by \"%s\".", release.ID, session.Account.Username)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,22 +362,22 @@ func UpdateReleaseCredits(app *app.AppState, release *model.Release) http.Handle
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
type creditJSON struct {
|
type CreditDTO struct {
|
||||||
Artist string
|
Artist string
|
||||||
Role string
|
Role string
|
||||||
Primary bool
|
Primary bool
|
||||||
}
|
}
|
||||||
var data []creditJSON
|
var dto []CreditDTO
|
||||||
err := json.NewDecoder(r.Body).Decode(&data)
|
err := json.NewDecoder(r.Body).Decode(&dto)
|
||||||
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
|
var newCredits []*model.Credit
|
||||||
for _, credit := range data {
|
for _, credit := range dto {
|
||||||
credits = append(credits, &model.Credit{
|
newCredits = append(newCredits, &model.Credit{
|
||||||
Artist: model.Artist{
|
Artist: &model.Artist{
|
||||||
ID: credit.Artist,
|
ID: credit.Artist,
|
||||||
},
|
},
|
||||||
Role: credit.Role,
|
Role: credit.Role,
|
||||||
|
|
@ -367,21 +385,25 @@ func UpdateReleaseCredits(app *app.AppState, release *model.Release) http.Handle
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateReleaseCredits(app.DB, release.ID, credits)
|
err = app.MusicService.UpdateReleaseCredits(release.ID, newCredits)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if errors.IsValidationError(err) {
|
||||||
http.Error(w, "Artists may only be credited once", http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
|
http.Error(w, "Artists may only be credited once", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Printf("WARN: Failed to update credits for %s: %s\n", release.ID, err)
|
fmt.Printf("WARN: Failed to update credits 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.LogService.Info(model.LOG_MUSIC, "Credits for release \"%s\" updated by \"%s\".", release.ID, session.Account.Username)
|
app.LogService.Info(model.LOG_MUSIC, "Release \"%s\" credits updated by \"%s\".", release.ID, session.Account.Username)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -389,28 +411,41 @@ func UpdateReleaseLinks(app *app.AppState, 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) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
var links = []*model.Link{}
|
type LinkDTO struct {
|
||||||
err := json.NewDecoder(r.Body).Decode(&links)
|
Name string `json:"name"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
var dto = []LinkDTO{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&dto)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateReleaseLinks(app.DB, release.ID, links)
|
links := []*model.Link{}
|
||||||
|
for _, link := range dto {
|
||||||
|
links = append(links, &model.Link{ Name: link.Name, URL: link.URL })
|
||||||
|
}
|
||||||
|
|
||||||
|
err = app.MusicService.UpdateReleaseLinks(release.ID, links)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "duplicate key") {
|
if errors.IsValidationError(err) {
|
||||||
http.Error(w, "Release cannot have duplicate link names", http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.Contains(err.Error(), "duplicate key") {
|
||||||
|
http.Error(w, "Release cannot have duplicate link names", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Printf("WARN: Failed to update links for %s: %s\n", release.ID, err)
|
fmt.Printf("WARN: 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.LogService.Info(model.LOG_MUSIC, "Links for release \"%s\" updated by \"%s\".", release.ID, session.Account.Username)
|
app.LogService.Info(model.LOG_MUSIC, "Release \"%s\" links updated by \"%s\".", release.ID, session.Account.Username)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -418,9 +453,13 @@ func DeleteRelease(app *app.AppState, 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) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
err := controller.DeleteRelease(app.DB, release.ID)
|
err := app.MusicService.DeleteRelease(release.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
if errors.IsValidationError(err) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.IsNotExistError(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
api/track.go
53
api/track.go
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"arimelody-web/controller"
|
"arimelody-web/errors"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/model/app"
|
"arimelody-web/model/app"
|
||||||
)
|
)
|
||||||
|
|
@ -26,7 +26,7 @@ func ServeAllTracks(app *app.AppState) http.Handler {
|
||||||
var tracks = []Track{}
|
var tracks = []Track{}
|
||||||
|
|
||||||
var dbTracks = []*model.Track{}
|
var dbTracks = []*model.Track{}
|
||||||
dbTracks, err := controller.GetAllTracks(app.DB)
|
dbTracks, err := app.MusicService.GetAllTracks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to pull tracks from DB: %s\n", err)
|
fmt.Printf("WARN: 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)
|
||||||
|
|
@ -52,7 +52,7 @@ func ServeAllTracks(app *app.AppState) http.Handler {
|
||||||
|
|
||||||
func ServeTrack(app *app.AppState, track *model.Track) http.Handler {
|
func ServeTrack(app *app.AppState, 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(app.DB, track.ID, false)
|
dbReleases, err := app.MusicService.GetTrackReleases(track.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to pull track releases for %s from DB: %s\n", track.ID, err)
|
fmt.Printf("WARN: 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)
|
||||||
|
|
@ -78,26 +78,30 @@ func CreateTrack(app *app.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
var track model.Track
|
type CreateTrackDTO struct {
|
||||||
err := json.NewDecoder(r.Body).Decode(&track)
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Lyrics string `json:"lyrics"`
|
||||||
|
}
|
||||||
|
var dto CreateTrackDTO
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&dto)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if track.Title == "" {
|
id, err := app.MusicService.CreateTrack(dto.Title, dto.Description, dto.Lyrics, "")
|
||||||
http.Error(w, "Track title cannot be empty\n", http.StatusBadRequest)
|
if err != nil {
|
||||||
|
if errors.IsValidationError(err) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := controller.CreateTrack(app.DB, &track)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("WARN: Failed to create track: %s\n", err)
|
fmt.Printf("WARN: 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
|
||||||
}
|
}
|
||||||
|
|
||||||
app.LogService.Info(model.LOG_MUSIC, "Track \"%s\" (%s) created by \"%s\".", track.Title, track.ID, session.Account.Username)
|
app.LogService.Info(model.LOG_MUSIC, "Track \"%s\" (%s) created by \"%s\".", dto.Title, id, session.Account.Username)
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "text/plain")
|
w.Header().Add("Content-Type", "text/plain")
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
|
@ -114,19 +118,28 @@ func UpdateTrack(app *app.AppState, track *model.Track) http.Handler {
|
||||||
|
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&track)
|
type UpdateTrackDTO struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Lyrics string `json:"lyrics"`
|
||||||
|
}
|
||||||
|
var dto UpdateTrackDTO
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&dto)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if track.Title == "" {
|
track.Title = dto.Title
|
||||||
http.Error(w, "Track title cannot be empty\n", http.StatusBadRequest)
|
track.Description = dto.Description
|
||||||
|
track.Lyrics = dto.Lyrics
|
||||||
|
|
||||||
|
err = app.MusicService.UpdateTrack(track)
|
||||||
|
if err != nil {
|
||||||
|
if errors.IsValidationError(err) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.UpdateTrack(app.DB, track)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("WARN: Failed to update track %s: %s\n", track.ID, err)
|
fmt.Printf("WARN: 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
|
||||||
|
|
@ -154,8 +167,12 @@ func DeleteTrack(app *app.AppState, track *model.Track) http.Handler {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
var trackID = r.URL.Path[1:]
|
var trackID = r.URL.Path[1:]
|
||||||
err := controller.DeleteTrack(app.DB, trackID)
|
err := app.MusicService.DeleteTrack(trackID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.IsNotExistError(err) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Printf("WARN: Failed to delete track %s: %s\n", trackID, err)
|
fmt.Printf("WARN: 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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,327 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"arimelody-web/model"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) {
|
|
||||||
var release = model.Release{}
|
|
||||||
|
|
||||||
err := db.Get(&release, "SELECT * FROM musicrelease WHERE id=$1", id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if full {
|
|
||||||
// get credits
|
|
||||||
credits, err := GetReleaseCredits(db, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Credits: %s", err)
|
|
||||||
}
|
|
||||||
for _, credit := range credits {
|
|
||||||
release.Credits = append(release.Credits, credit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get tracks
|
|
||||||
tracks, err := GetReleaseTracks(db, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Tracks: %s", err)
|
|
||||||
}
|
|
||||||
for _, track := range tracks {
|
|
||||||
release.Tracks = append(release.Tracks, track)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get links
|
|
||||||
links, err := GetReleaseLinks(db, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Links: %s", err)
|
|
||||||
}
|
|
||||||
for _, link := range links {
|
|
||||||
release.Links = append(release.Links, link)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &release, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAllReleases(db *sqlx.DB, onlyVisible bool, limit int, full bool) ([]*model.Release, error) {
|
|
||||||
var releases = []*model.Release{}
|
|
||||||
|
|
||||||
query := "SELECT * FROM musicrelease"
|
|
||||||
if onlyVisible {
|
|
||||||
query += " WHERE visible=true"
|
|
||||||
}
|
|
||||||
query += " ORDER BY release_date DESC"
|
|
||||||
var err error
|
|
||||||
if limit > 0 {
|
|
||||||
err = db.Select(&releases, query + " LIMIT $1", limit)
|
|
||||||
} else {
|
|
||||||
err = db.Select(&releases, query)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, release := range releases {
|
|
||||||
// get credits
|
|
||||||
credits, err := GetReleaseCredits(db, release.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Credits: %s", err)
|
|
||||||
}
|
|
||||||
for _, credit := range credits {
|
|
||||||
release.Credits = append(release.Credits, credit)
|
|
||||||
}
|
|
||||||
|
|
||||||
if full {
|
|
||||||
// get tracks
|
|
||||||
tracks, err := GetReleaseTracks(db, release.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Tracks: %s", err)
|
|
||||||
}
|
|
||||||
for _, track := range tracks {
|
|
||||||
release.Tracks = append(release.Tracks, track)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get links
|
|
||||||
links, err := GetReleaseLinks(db, release.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Links: %s", err)
|
|
||||||
}
|
|
||||||
for _, link := range links {
|
|
||||||
release.Links = append(release.Links, link)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return releases, nil
|
|
||||||
}
|
|
||||||
func GetReleaseCount(db *sqlx.DB, onlyVisible bool) (int, error) {
|
|
||||||
query := "SELECT count(*) FROM musicrelease"
|
|
||||||
if onlyVisible {
|
|
||||||
query += " WHERE visible=true"
|
|
||||||
}
|
|
||||||
|
|
||||||
var count int
|
|
||||||
err := db.Get(&count, query)
|
|
||||||
|
|
||||||
return count, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateRelease(db *sqlx.DB, release *model.Release) error {
|
|
||||||
_, err := db.Exec(
|
|
||||||
"INSERT INTO musicrelease "+
|
|
||||||
"(id, visible, title, description, type, release_date, artwork, buyname, buylink, copyright, copyrighturl) "+
|
|
||||||
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
|
||||||
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,
|
|
||||||
release.Copyright,
|
|
||||||
release.CopyrightURL,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateRelease(db *sqlx.DB, release *model.Release) error {
|
|
||||||
_, err := db.Exec(
|
|
||||||
"UPDATE musicrelease SET "+
|
|
||||||
"visible=$2, title=$3, description=$4, type=$5, release_date=$6, artwork=$7, buyname=$8, buylink=$9, copyright=$10, copyrighturl=$11 "+
|
|
||||||
"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,
|
|
||||||
release.Copyright,
|
|
||||||
release.CopyrightURL,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateReleaseTracks(db *sqlx.DB, releaseID string, new_tracks []string) error {
|
|
||||||
tx, err := db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM musicreleasetrack WHERE release=$1", releaseID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i, trackID := range new_tracks {
|
|
||||||
_, err = tx.Exec(
|
|
||||||
"INSERT INTO musicreleasetrack "+
|
|
||||||
"(release, track, number) "+
|
|
||||||
"VALUES ($1, $2, $3)",
|
|
||||||
releaseID,
|
|
||||||
trackID,
|
|
||||||
i)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateReleaseCredits(db *sqlx.DB, releaseID string, new_credits []*model.Credit) error {
|
|
||||||
tx, err := db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM musiccredit WHERE release=$1", releaseID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, credit := range new_credits {
|
|
||||||
_, err = tx.Exec(
|
|
||||||
"INSERT INTO musiccredit "+
|
|
||||||
"(release, artist, role, is_primary) "+
|
|
||||||
"VALUES ($1, $2, $3, $4)",
|
|
||||||
releaseID,
|
|
||||||
credit.Artist.ID,
|
|
||||||
credit.Role,
|
|
||||||
credit.Primary,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateReleaseLinks(db *sqlx.DB, releaseID string, new_links []*model.Link) error {
|
|
||||||
tx, err := db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tx.Exec("DELETE FROM musiclink WHERE release=$1", releaseID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, link := range new_links {
|
|
||||||
_, err := tx.Exec(
|
|
||||||
"INSERT INTO musiclink "+
|
|
||||||
"(release, name, url) "+
|
|
||||||
"VALUES ($1, $2, $3)",
|
|
||||||
releaseID,
|
|
||||||
link.Name,
|
|
||||||
link.URL,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteRelease(db *sqlx.DB, releaseID string) error {
|
|
||||||
_, err := db.Exec(
|
|
||||||
"DELETE FROM musicrelease "+
|
|
||||||
"WHERE id=$1",
|
|
||||||
releaseID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetReleaseTracks(db *sqlx.DB, releaseID string) ([]*model.Track, error) {
|
|
||||||
var tracks = []*model.Track{}
|
|
||||||
|
|
||||||
err := db.Select(&tracks,
|
|
||||||
"SELECT musictrack.* FROM musictrack "+
|
|
||||||
"JOIN musicreleasetrack ON track=id "+
|
|
||||||
"WHERE release=$1 "+
|
|
||||||
"ORDER BY number ASC",
|
|
||||||
releaseID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetReleaseCredits(db *sqlx.DB, releaseID string) ([]*model.Credit, error) {
|
|
||||||
rows, err := db.Query(
|
|
||||||
"SELECT artist.id,artist.name,artist.website,artist.avatar,role,is_primary "+
|
|
||||||
"FROM musiccredit "+
|
|
||||||
"JOIN artist ON artist=artist.id "+
|
|
||||||
"JOIN musicrelease ON release=musicrelease.id "+
|
|
||||||
"WHERE musicrelease.id=$1 "+
|
|
||||||
"ORDER BY is_primary DESC",
|
|
||||||
releaseID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var credits []*model.Credit
|
|
||||||
for rows.Next() {
|
|
||||||
credit := model.Credit{}
|
|
||||||
rows.Scan(
|
|
||||||
&credit.Artist.ID,
|
|
||||||
&credit.Artist.Name,
|
|
||||||
&credit.Artist.Website,
|
|
||||||
&credit.Artist.Avatar,
|
|
||||||
&credit.Role,
|
|
||||||
&credit.Primary)
|
|
||||||
credits = append(credits, &credit)
|
|
||||||
}
|
|
||||||
|
|
||||||
return credits, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetReleaseLinks(db *sqlx.DB, releaseID string) ([]*model.Link, error) {
|
|
||||||
var links = []*model.Link{}
|
|
||||||
|
|
||||||
err := db.Select(&links, "SELECT name,url FROM musiclink WHERE release=$1", releaseID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return links, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"arimelody-web/model"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DATABASE
|
|
||||||
|
|
||||||
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{}
|
|
||||||
|
|
||||||
err := db.Select(&tracks, "SELECT * FROM musictrack")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracks, nil
|
|
||||||
}
|
|
||||||
func GetTrackCount(db *sqlx.DB) (int, error) {
|
|
||||||
var count int
|
|
||||||
err := db.Get(&count, "SELECT count(*) FROM musictrack")
|
|
||||||
return count, err
|
|
||||||
}
|
|
||||||
|
|
||||||
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, releaseID string) ([]*model.Track, error) {
|
|
||||||
var tracks = []*model.Track{}
|
|
||||||
|
|
||||||
err := db.Select(&tracks,
|
|
||||||
"SELECT * FROM musictrack "+
|
|
||||||
"WHERE id NOT IN "+
|
|
||||||
"(SELECT track FROM musicreleasetrack WHERE release=$1)",
|
|
||||||
releaseID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTrackReleases(db *sqlx.DB, trackID string, full bool) ([]*model.Release, error) {
|
|
||||||
var releases = []*model.Release{}
|
|
||||||
|
|
||||||
err := db.Select(&releases,
|
|
||||||
"SELECT id,title,type,release_date,artwork,buylink "+
|
|
||||||
"FROM musicrelease "+
|
|
||||||
"JOIN musicreleasetrack ON release=id "+
|
|
||||||
"WHERE track=$1 "+
|
|
||||||
"ORDER BY release_date",
|
|
||||||
trackID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type NamePrimary struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Primary bool `json:"primary" db:"is_primary"`
|
|
||||||
}
|
|
||||||
for _, release := range releases {
|
|
||||||
// get artists
|
|
||||||
credits := []NamePrimary{}
|
|
||||||
err := db.Select(&credits,
|
|
||||||
"SELECT name,is_primary FROM artist "+
|
|
||||||
"JOIN musiccredit ON artist=artist.id "+
|
|
||||||
"JOIN musicrelease ON release=musicrelease.id "+
|
|
||||||
"WHERE musicrelease.id=$1", release.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, credit := range credits {
|
|
||||||
release.Credits = append(release.Credits, &model.Credit{
|
|
||||||
Artist: model.Artist{
|
|
||||||
Name: credit.Name,
|
|
||||||
},
|
|
||||||
Primary: credit.Primary,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// get tracks
|
|
||||||
tracks := []string{}
|
|
||||||
err = db.Select(&tracks, "SELECT track FROM musicreleasetrack WHERE release=$1", release.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, trackID := range tracks {
|
|
||||||
release.Tracks = append(release.Tracks, &model.Track{
|
|
||||||
ID: trackID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return releases, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func PullOrphanTracks(db *sqlx.DB) ([]*model.Track, error) {
|
|
||||||
var tracks = []*model.Track{}
|
|
||||||
|
|
||||||
err := db.Select(&tracks,
|
|
||||||
"SELECT id, title, description, lyrics, preview_url FROM musictrack "+
|
|
||||||
"WHERE id NOT IN "+
|
|
||||||
"(SELECT track FROM musicreleasetrack)",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTrack(db *sqlx.DB, track *model.Track) (string, error) {
|
|
||||||
var trackID string
|
|
||||||
err := db.QueryRow(
|
|
||||||
"INSERT INTO musictrack (title, description, lyrics, preview_url) "+
|
|
||||||
"VALUES ($1, $2, $3, $4) "+
|
|
||||||
"RETURNING id",
|
|
||||||
track.Title,
|
|
||||||
track.Description,
|
|
||||||
track.Lyrics,
|
|
||||||
track.PreviewURL,
|
|
||||||
).Scan(&trackID)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return trackID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateTrack(db *sqlx.DB, track *model.Track) error {
|
|
||||||
_, err := db.Exec(
|
|
||||||
"UPDATE musictrack "+
|
|
||||||
"SET title=$2, description=$3, lyrics=$4, preview_url=$5 "+
|
|
||||||
"WHERE id=$1",
|
|
||||||
track.ID,
|
|
||||||
track.Title,
|
|
||||||
track.Description,
|
|
||||||
track.Lyrics,
|
|
||||||
track.PreviewURL,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTrack(db *sqlx.DB, trackID string) error {
|
|
||||||
_, err := db.Exec(
|
|
||||||
"DELETE FROM musictrack "+
|
|
||||||
"WHERE id=$1",
|
|
||||||
trackID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
16
errors/notexist.go
Normal file
16
errors/notexist.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
type NotExistError struct {
|
||||||
|
query string
|
||||||
|
}
|
||||||
|
func NewNotExistError(query string) *NotExistError {
|
||||||
|
return &NotExistError{ query: query }
|
||||||
|
}
|
||||||
|
func (err *NotExistError) Error() string {
|
||||||
|
return err.query
|
||||||
|
}
|
||||||
|
func IsNotExistError(err error) bool {
|
||||||
|
_, ok := err.(*NotExistError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
26
errors/notexist_test.go
Normal file
26
errors/notexist_test.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
|
goErrors "errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_NotExistError(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
message := "entity does not exist"
|
||||||
|
|
||||||
|
t.Run("can create error", func(t *testing.T) {
|
||||||
|
err = errors.NewNotExistError(message)
|
||||||
|
assert.Error(t, err, message)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("validator returns true for valid error", func(t *testing.T) {
|
||||||
|
assert.Equal(t, errors.IsNotExistError(err), true)
|
||||||
|
})
|
||||||
|
t.Run("validator returns false for invalid error", func(t *testing.T) {
|
||||||
|
assert.Equal(t, errors.IsNotExistError(goErrors.New("other error")), false)
|
||||||
|
})
|
||||||
|
}
|
||||||
15
errors/validation.go
Normal file
15
errors/validation.go
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
type ValidationError struct {
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
func NewValidationError(message string) *ValidationError {
|
||||||
|
return &ValidationError{ message: message }
|
||||||
|
}
|
||||||
|
func (err *ValidationError) Error() string {
|
||||||
|
return err.message
|
||||||
|
}
|
||||||
|
func IsValidationError(err error) bool {
|
||||||
|
_, ok := err.(*ValidationError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
26
errors/validation_test.go
Normal file
26
errors/validation_test.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
|
goErrors "errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ValidationError(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
message := "invalid input"
|
||||||
|
|
||||||
|
t.Run("can create error", func(t *testing.T) {
|
||||||
|
err = errors.NewValidationError(message)
|
||||||
|
assert.Error(t, err, message)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("validator returns true for valid error", func(t *testing.T) {
|
||||||
|
assert.Equal(t, errors.IsValidationError(err), true)
|
||||||
|
})
|
||||||
|
t.Run("validator returns false for invalid error", func(t *testing.T) {
|
||||||
|
assert.Equal(t, errors.IsValidationError(goErrors.New("other error")), false)
|
||||||
|
})
|
||||||
|
}
|
||||||
13
main.go
13
main.go
|
|
@ -24,12 +24,16 @@ import (
|
||||||
"arimelody-web/cursor"
|
"arimelody-web/cursor"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"arimelody-web/model/app"
|
"arimelody-web/model/app"
|
||||||
|
"arimelody-web/view"
|
||||||
|
|
||||||
accountRepo "arimelody-web/repository/account"
|
accountRepo "arimelody-web/repository/account"
|
||||||
logRepo "arimelody-web/repository/log"
|
logRepo "arimelody-web/repository/log"
|
||||||
|
musicRepo "arimelody-web/repository/music"
|
||||||
|
|
||||||
repo "arimelody-web/repository/postgres"
|
repo "arimelody-web/repository/postgres"
|
||||||
accountService "arimelody-web/service/account"
|
accountService "arimelody-web/service/account"
|
||||||
logService "arimelody-web/service/log"
|
logService "arimelody-web/service/log"
|
||||||
"arimelody-web/view"
|
musicService "arimelody-web/service/music"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
|
|
@ -47,6 +51,7 @@ var publicFS embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// TODO: switch to a new logger. this one kinda sucks
|
// TODO: switch to a new logger. this one kinda sucks
|
||||||
|
// i'll be so forreal i might write my own
|
||||||
logger := log.New(os.Stderr, "main", model.DEFAULT_LOG_FLAGS)
|
logger := log.New(os.Stderr, "main", model.DEFAULT_LOG_FLAGS)
|
||||||
|
|
||||||
logger.Print("made with <3 by ari melody\n\n")
|
logger.Print("made with <3 by ari melody\n\n")
|
||||||
|
|
@ -103,6 +108,12 @@ func main() {
|
||||||
log.New(os.Stderr, "account-repo", model.DEFAULT_LOG_FLAGS),
|
log.New(os.Stderr, "account-repo", model.DEFAULT_LOG_FLAGS),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
musicRepo := musicRepo.NewMusicRepositoryPostgres(psqlDB)
|
||||||
|
app.MusicService = musicService.NewMusicService(
|
||||||
|
musicRepo,
|
||||||
|
log.New(os.Stderr, "music-repo", model.DEFAULT_LOG_FLAGS),
|
||||||
|
)
|
||||||
|
|
||||||
// handle command arguments
|
// handle command arguments
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
arg := os.Args[1]
|
arg := os.Args[1]
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,10 @@ import (
|
||||||
"arimelody-web/model/twitch"
|
"arimelody-web/model/twitch"
|
||||||
|
|
||||||
logService "arimelody-web/service/log"
|
logService "arimelody-web/service/log"
|
||||||
inviteService "arimelody-web/service/invite"
|
//inviteService "arimelody-web/service/invite"
|
||||||
accountService "arimelody-web/service/account"
|
accountService "arimelody-web/service/account"
|
||||||
sessionService "arimelody-web/service/session"
|
//sessionService "arimelody-web/service/session"
|
||||||
artistService "arimelody-web/service/artist"
|
musicService "arimelody-web/service/music"
|
||||||
releaseService "arimelody-web/service/release"
|
|
||||||
trackService "arimelody-web/service/track"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
@ -55,11 +53,9 @@ type (
|
||||||
PublicFS embed.FS
|
PublicFS embed.FS
|
||||||
|
|
||||||
LogService *logService.LogService
|
LogService *logService.LogService
|
||||||
InviteService *inviteService.InviteService
|
//InviteService *inviteService.InviteService
|
||||||
AccountService *accountService.AccountService
|
AccountService *accountService.AccountService
|
||||||
SesisonService *sessionService.SessionService
|
//SesisonService *sessionService.SessionService
|
||||||
ArtistService *artistService.ArtistService
|
MusicService *musicService.MusicService
|
||||||
ReleaseService *releaseService.ReleaseService
|
|
||||||
TrackService *trackService.TrackService
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DEFAULT_AVATAR_URL = "/img/default-avatar.png"
|
||||||
|
|
||||||
func (artist Artist) GetAvatar() string {
|
func (artist Artist) GetAvatar() string {
|
||||||
if artist.Avatar == "" {
|
if artist.Avatar == "" {
|
||||||
return "/img/default-avatar.png"
|
return DEFAULT_AVATAR_URL
|
||||||
}
|
}
|
||||||
return artist.Avatar
|
return artist.Avatar
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,22 @@
|
||||||
package model
|
package model_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Artist_GetAvatar(t *testing.T) {
|
func Test_Artist_GetAvatar(t *testing.T) {
|
||||||
want := "testavatar.png"
|
want := "testavatar.png"
|
||||||
artist := Artist{ Avatar: want }
|
artist := model.Artist{ Avatar: want }
|
||||||
|
|
||||||
got := artist.GetAvatar()
|
got := artist.GetAvatar()
|
||||||
if want != got {
|
if want != got {
|
||||||
t.Errorf(`correct value not returned when avatar is populated (want "%s", got "%s")`, want, got)
|
t.Errorf(`correct value not returned when avatar is populated (want "%s", got "%s")`, want, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
artist = Artist{}
|
artist = model.Artist{}
|
||||||
|
|
||||||
want = "/img/default-avatar.png"
|
want = model.DEFAULT_AVATAR_URL
|
||||||
got = artist.GetAvatar()
|
got = artist.GetAvatar()
|
||||||
if want != got {
|
if want != got {
|
||||||
t.Errorf(`default value not returned when avatar is empty (want "%s", got "%s")`, want, got)
|
t.Errorf(`default value not returned when avatar is empty (want "%s", got "%s")`, want, got)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package model
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Credit struct {
|
Credit struct {
|
||||||
Release Release `json:"release"`
|
Release *Release `json:"release"`
|
||||||
Artist Artist `json:"artist"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package model
|
package model_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Link_NormaliseName(t *testing.T) {
|
func Test_Link_NormaliseName(t *testing.T) {
|
||||||
link := Link{
|
link := model.Link{
|
||||||
Name: "!c@o#o$l%-^a&w*e(s)o_m=e+-[l{i]n}k-0123456789ABCDEF",
|
Name: "!c@o#o$l%-^a&w*e(s)o_m=e+-[l{i]n}k-0123456789ABCDEF",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ const (
|
||||||
EP ReleaseType = "EP"
|
EP ReleaseType = "EP"
|
||||||
Compilation ReleaseType = "compilation"
|
Compilation ReleaseType = "compilation"
|
||||||
Upcoming ReleaseType = "upcoming"
|
Upcoming ReleaseType = "upcoming"
|
||||||
|
|
||||||
|
DEFAULT_RELEASE_ARTWORK_URL = "/img/default-cover-art.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GETTERS
|
// GETTERS
|
||||||
|
|
@ -52,7 +54,7 @@ func (release Release) PrintReleaseDate() string {
|
||||||
|
|
||||||
func (release Release) GetArtwork() string {
|
func (release Release) GetArtwork() string {
|
||||||
if release.Artwork == "" {
|
if release.Artwork == "" {
|
||||||
return "/img/default-cover-art.png"
|
return DEFAULT_RELEASE_ARTWORK_URL
|
||||||
}
|
}
|
||||||
return release.Artwork
|
return release.Artwork
|
||||||
}
|
}
|
||||||
|
|
@ -93,3 +95,20 @@ func (release Release) PrintArtists(only_primary bool, ampersand bool) string {
|
||||||
return strings.Join(names[:], ", ")
|
return strings.Join(names[:], ", ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidReleaseType(releaseType string) (ReleaseType, bool) {
|
||||||
|
switch releaseType {
|
||||||
|
case "single":
|
||||||
|
return Single, true
|
||||||
|
case "album":
|
||||||
|
return Album, true
|
||||||
|
case "EP":
|
||||||
|
return EP, true
|
||||||
|
case "compilation":
|
||||||
|
return Compilation, true
|
||||||
|
case "upcoming":
|
||||||
|
return Upcoming, true
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package model
|
package model_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -10,7 +11,7 @@ import (
|
||||||
|
|
||||||
func Test_Release(t *testing.T) {
|
func Test_Release(t *testing.T) {
|
||||||
t.Run("prints correct description HTML", func(t *testing.T) {
|
t.Run("prints correct description HTML", func(t *testing.T) {
|
||||||
release := Release{
|
release := model.Release{
|
||||||
Description: "this is\na test\n<strong>description!</strong>",
|
Description: "this is\na test\n<strong>description!</strong>",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ func Test_Release(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("prints correct release date", func(t *testing.T) {
|
t.Run("prints correct release date", func(t *testing.T) {
|
||||||
release := Release{
|
release := model.Release{
|
||||||
ReleaseDate: time.Date(2025, time.July, 26, 16, 0, 0, 0, time.UTC),
|
ReleaseDate: time.Date(2025, time.July, 26, 16, 0, 0, 0, time.UTC),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,37 +35,37 @@ func Test_Release(t *testing.T) {
|
||||||
|
|
||||||
t.Run("returns correct artwork", func(t *testing.T) {
|
t.Run("returns correct artwork", func(t *testing.T) {
|
||||||
artwork := "testartwork.png"
|
artwork := "testartwork.png"
|
||||||
release := Release{ Artwork: artwork }
|
release := model.Release{ Artwork: artwork }
|
||||||
assert.Equal(t, release.GetArtwork(), artwork)
|
assert.Equal(t, release.GetArtwork(), artwork)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("returns placeholder artwork when empty", func(t *testing.T) {
|
t.Run("returns placeholder artwork when empty", func(t *testing.T) {
|
||||||
release := Release{}
|
release := model.Release{}
|
||||||
assert.Equal(t, release.GetArtwork(), "/img/default-cover-art.png")
|
assert.Equal(t, release.GetArtwork(), model.DEFAULT_RELEASE_ARTWORK_URL)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("singles", func(t *testing.T) {
|
t.Run("singles", func(t *testing.T) {
|
||||||
release := Release{
|
release := model.Release{
|
||||||
Tracks: []*Track{},
|
Tracks: []*model.Track{},
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("false when no tracks are present", func(t *testing.T) {
|
t.Run("false when no tracks are present", func(t *testing.T) {
|
||||||
assert.Equal(t, release.IsSingle(), false)
|
assert.Equal(t, release.IsSingle(), false)
|
||||||
})
|
})
|
||||||
|
|
||||||
release.Tracks = append(release.Tracks, &Track{})
|
release.Tracks = append(release.Tracks, &model.Track{})
|
||||||
t.Run("true when one track is present", func(t *testing.T) {
|
t.Run("true when one track is present", func(t *testing.T) {
|
||||||
assert.Equal(t, release.IsSingle(), true)
|
assert.Equal(t, release.IsSingle(), true)
|
||||||
})
|
})
|
||||||
|
|
||||||
release.Tracks = append(release.Tracks, &Track{})
|
release.Tracks = append(release.Tracks, &model.Track{})
|
||||||
t.Run("false when >1 tracks are present", func(t *testing.T) {
|
t.Run("false when >1 tracks are present", func(t *testing.T) {
|
||||||
assert.Equal(t, release.IsSingle(), false)
|
assert.Equal(t, release.IsSingle(), false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("released", func(t *testing.T) {
|
t.Run("released", func(t *testing.T) {
|
||||||
release := Release {
|
release := model.Release {
|
||||||
ReleaseDate: time.Now(),
|
ReleaseDate: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,23 +85,23 @@ func Test_Release(t *testing.T) {
|
||||||
artist3 := "idk"
|
artist3 := "idk"
|
||||||
artist4 := "guest"
|
artist4 := "guest"
|
||||||
|
|
||||||
release := Release{}
|
release := model.Release{}
|
||||||
t.Run("prints \"Unknown Artist\" when release has no credits", func(t *testing.T) {
|
t.Run("prints \"Unknown Artist\" when release has no credits", func(t *testing.T) {
|
||||||
assert.Equal(t, release.PrintArtists(false, true), "Unknown Artist")
|
assert.Equal(t, release.PrintArtists(false, true), "Unknown Artist")
|
||||||
})
|
})
|
||||||
|
|
||||||
release.Credits = append(
|
release.Credits = append(
|
||||||
release.Credits,
|
release.Credits,
|
||||||
&Credit{ Artist: Artist{ Name: artist1 }, Primary: true },
|
&model.Credit{ Artist: &model.Artist{ Name: artist1 }, Primary: true },
|
||||||
)
|
)
|
||||||
t.Run("prints ONLY first artist name when release has one credit", func(t *testing.T) {
|
t.Run("prints ONLY first artist name when release has one credit", func(t *testing.T) {
|
||||||
assert.Equal(t, release.PrintArtists(false, true), artist1)
|
assert.Equal(t, release.PrintArtists(false, true), artist1)
|
||||||
})
|
})
|
||||||
|
|
||||||
release.Credits = append(release.Credits, []*Credit{
|
release.Credits = append(release.Credits, []*model.Credit{
|
||||||
{ Artist: Artist{ Name: artist2 }, Primary: true },
|
{ Artist: &model.Artist{ Name: artist2 }, Primary: true },
|
||||||
{ Artist: Artist{ Name: artist3 }, Primary: false },
|
{ Artist: &model.Artist{ Name: artist3 }, Primary: false },
|
||||||
{ Artist: Artist{ Name: artist4 }, Primary: true },
|
{ Artist: &model.Artist{ Name: artist4 }, Primary: true },
|
||||||
}...)
|
}...)
|
||||||
t.Run("can get only unique primary artist names", func(t *testing.T) {
|
t.Run("can get only unique primary artist names", func(t *testing.T) {
|
||||||
assert.Equal(
|
assert.Equal(
|
||||||
|
|
@ -146,4 +147,37 @@ func Test_Release(t *testing.T) {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("validating release types", func(t *testing.T) {
|
||||||
|
t.Run("single", func(t *testing.T) {
|
||||||
|
releaseType, ok := model.ValidReleaseType("single")
|
||||||
|
assert.Equal(t, ok, true)
|
||||||
|
assert.Equal(t, releaseType, model.Single)
|
||||||
|
})
|
||||||
|
t.Run("album", func(t *testing.T) {
|
||||||
|
releaseType, ok := model.ValidReleaseType("album")
|
||||||
|
assert.Equal(t, ok, true)
|
||||||
|
assert.Equal(t, releaseType, model.Album)
|
||||||
|
})
|
||||||
|
t.Run("EP", func(t *testing.T) {
|
||||||
|
releaseType, ok := model.ValidReleaseType("EP")
|
||||||
|
assert.Equal(t, ok, true)
|
||||||
|
assert.Equal(t, releaseType, model.EP)
|
||||||
|
})
|
||||||
|
t.Run("compilation", func(t *testing.T) {
|
||||||
|
releaseType, ok := model.ValidReleaseType("compilation")
|
||||||
|
assert.Equal(t, ok, true)
|
||||||
|
assert.Equal(t, releaseType, model.Compilation)
|
||||||
|
})
|
||||||
|
t.Run("upcoming", func(t *testing.T) {
|
||||||
|
releaseType, ok := model.ValidReleaseType("upcoming")
|
||||||
|
assert.Equal(t, ok, true)
|
||||||
|
assert.Equal(t, releaseType, model.Upcoming)
|
||||||
|
})
|
||||||
|
t.Run("invalid", func(t *testing.T) {
|
||||||
|
releaseType, ok := model.ValidReleaseType("invalid")
|
||||||
|
assert.Equal(t, ok, false)
|
||||||
|
assert.Equal(t, string(releaseType), "")
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package model
|
package model_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Track_DescriptionHTML(t *testing.T) {
|
func Test_Track_DescriptionHTML(t *testing.T) {
|
||||||
track := Track{
|
track := model.Track{
|
||||||
Description: "this is\na test\n<strong>description!</strong>",
|
Description: "this is\na test\n<strong>description!</strong>",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ func Test_Track_DescriptionHTML(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_Track_LyricsHTML(t *testing.T) {
|
func Test_Track_LyricsHTML(t *testing.T) {
|
||||||
track := Track{
|
track := model.Track{
|
||||||
Lyrics: "these are\ntest\n<strong>lyrics!</strong>",
|
Lyrics: "these are\ntest\n<strong>lyrics!</strong>",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,11 @@ type AccountRepository interface {
|
||||||
UpdateAvatarURL(id string, avatarURL string) error
|
UpdateAvatarURL(id string, avatarURL string) error
|
||||||
RemoveAvatar(id string) error
|
RemoveAvatar(id string) error
|
||||||
|
|
||||||
Delete(id string) error
|
|
||||||
|
|
||||||
// Increment the number of account login failure attempts,
|
// Increment the number of account login failure attempts,
|
||||||
// returning the current fail count.
|
// returning the current fail count.
|
||||||
IncrementFails(id string) (int, error)
|
IncrementFails(id string) (int, error)
|
||||||
ResetFails(id string) error
|
ResetFails(id string) error
|
||||||
SetLocked(id string, lock bool) error
|
SetLocked(id string, lock bool) error
|
||||||
|
|
||||||
|
Delete(id string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -24,10 +25,11 @@ func (repo *AccountRepositoryMemory) GetCount() (int, error) {
|
||||||
return len(repo.accounts), nil
|
return len(repo.accounts), nil
|
||||||
}
|
}
|
||||||
func (repo *AccountRepositoryMemory) GetByID(id string) (*model.Account, error) {
|
func (repo *AccountRepositoryMemory) GetByID(id string) (*model.Account, error) {
|
||||||
for _, account := range repo.accounts {
|
index := slices.IndexFunc(repo.accounts, func(account *model.Account) bool {
|
||||||
if account.ID == id { return account, nil }
|
return account.ID == id
|
||||||
}
|
})
|
||||||
return nil, nil
|
if index == -1 { return nil, nil }
|
||||||
|
return repo.accounts[index], nil
|
||||||
}
|
}
|
||||||
func (repo *AccountRepositoryMemory) GetByUsername(username string) (*model.Account, error) {
|
func (repo *AccountRepositoryMemory) GetByUsername(username string) (*model.Account, error) {
|
||||||
for _, account := range repo.accounts {
|
for _, account := range repo.accounts {
|
||||||
|
|
@ -47,9 +49,9 @@ func (repo *AccountRepositoryMemory) GetByEmail(email string) (*model.Account, e
|
||||||
// Create an account, returning the new account ID.
|
// Create an account, returning the new account ID.
|
||||||
func (repo *AccountRepositoryMemory) Create(username string, password string, email *string, avatarURL *string) (string, error) {
|
func (repo *AccountRepositoryMemory) Create(username string, password string, email *string, avatarURL *string) (string, error) {
|
||||||
if account, err := repo.GetByUsername(username); err != nil {
|
if account, err := repo.GetByUsername(username); err != nil {
|
||||||
return "", errors.New("Failed to fetch other acccounts by username")
|
return "", errors.NewNotExistError("Failed to fetch other acccounts by username")
|
||||||
} else if account != nil {
|
} else if account != nil {
|
||||||
return "", errors.New("Account with this username already exists")
|
return "", errors.NewNotExistError("Account with this username already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
emailRef := ""
|
emailRef := ""
|
||||||
|
|
@ -74,9 +76,9 @@ func (repo *AccountRepositoryMemory) Create(username string, password string, em
|
||||||
// more specialised Update* and Remove* functions should be used.
|
// more specialised Update* and Remove* functions should be used.
|
||||||
func (repo *AccountRepositoryMemory) Update(id string, username string, password string, email *string, avatarUrl *string) error {
|
func (repo *AccountRepositoryMemory) Update(id string, username string, password string, email *string, avatarUrl *string) error {
|
||||||
if account, err := repo.GetByUsername(username); err != nil {
|
if account, err := repo.GetByUsername(username); err != nil {
|
||||||
return errors.New("Failed to fetch other acccounts by username")
|
return errors.NewNotExistError("Failed to fetch other acccounts by username")
|
||||||
} else if account != nil && account.ID != id {
|
} else if account != nil && account.ID != id {
|
||||||
return errors.New("Account with this username already exists")
|
return errors.NewNotExistError("Account with this username already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := repo.GetByID(id)
|
account, err := repo.GetByID(id)
|
||||||
|
|
@ -93,14 +95,14 @@ func (repo *AccountRepositoryMemory) Update(id string, username string, password
|
||||||
}
|
}
|
||||||
func (repo *AccountRepositoryMemory) UpdateUsername(id string, username string) error {
|
func (repo *AccountRepositoryMemory) UpdateUsername(id string, username string) error {
|
||||||
if account, err := repo.GetByUsername(username); err != nil {
|
if account, err := repo.GetByUsername(username); err != nil {
|
||||||
return errors.New("Failed to fetch other acccounts by username")
|
return errors.NewNotExistError("Failed to fetch other acccounts by username")
|
||||||
} else if account != nil && account.ID != id {
|
} else if account != nil && account.ID != id {
|
||||||
return errors.New("Account with this username already exists")
|
return errors.NewNotExistError("Account with this username already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := repo.GetByID(id)
|
account, err := repo.GetByID(id)
|
||||||
if err != nil { return err }
|
if err != nil { return err }
|
||||||
if account == nil { return errors.New("Account does not exist") }
|
if account == nil { return errors.NewNotExistError("Account does not exist") }
|
||||||
|
|
||||||
account.Username = username
|
account.Username = username
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -108,7 +110,7 @@ func (repo *AccountRepositoryMemory) UpdateUsername(id string, username string)
|
||||||
func (repo *AccountRepositoryMemory) UpdatePassword(id string, password string) error {
|
func (repo *AccountRepositoryMemory) UpdatePassword(id string, password string) error {
|
||||||
account, err := repo.GetByID(id)
|
account, err := repo.GetByID(id)
|
||||||
if err != nil { return err }
|
if err != nil { return err }
|
||||||
if account == nil { return errors.New("Account does not exist") }
|
if account == nil { return errors.NewNotExistError("Account does not exist") }
|
||||||
|
|
||||||
account.Password = password
|
account.Password = password
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -116,7 +118,7 @@ func (repo *AccountRepositoryMemory) UpdatePassword(id string, password string)
|
||||||
func (repo *AccountRepositoryMemory) UpdateEmail(id string, email string) error {
|
func (repo *AccountRepositoryMemory) UpdateEmail(id string, email string) error {
|
||||||
account, err := repo.GetByID(id)
|
account, err := repo.GetByID(id)
|
||||||
if err != nil { return err }
|
if err != nil { return err }
|
||||||
if account == nil { return errors.New("Account does not exist") }
|
if account == nil { return errors.NewNotExistError("Account does not exist") }
|
||||||
|
|
||||||
account.Email.Valid = true
|
account.Email.Valid = true
|
||||||
account.Email.String = email
|
account.Email.String = email
|
||||||
|
|
@ -125,7 +127,7 @@ func (repo *AccountRepositoryMemory) UpdateEmail(id string, email string) error
|
||||||
func (repo *AccountRepositoryMemory) RemoveEmail(id string) error {
|
func (repo *AccountRepositoryMemory) RemoveEmail(id string) error {
|
||||||
account, err := repo.GetByID(id)
|
account, err := repo.GetByID(id)
|
||||||
if err != nil { return err }
|
if err != nil { return err }
|
||||||
if account == nil { return errors.New("Account does not exist") }
|
if account == nil { return errors.NewNotExistError("Account does not exist") }
|
||||||
|
|
||||||
account.Email.Valid = false
|
account.Email.Valid = false
|
||||||
account.Email.String = ""
|
account.Email.String = ""
|
||||||
|
|
@ -134,7 +136,7 @@ func (repo *AccountRepositoryMemory) RemoveEmail(id string) error {
|
||||||
func (repo *AccountRepositoryMemory) UpdateAvatarURL(id string, avatarURL string) error {
|
func (repo *AccountRepositoryMemory) UpdateAvatarURL(id string, avatarURL string) error {
|
||||||
account, err := repo.GetByID(id)
|
account, err := repo.GetByID(id)
|
||||||
if err != nil { return err }
|
if err != nil { return err }
|
||||||
if account == nil { return errors.New("Account does not exist") }
|
if account == nil { return errors.NewNotExistError("Account does not exist") }
|
||||||
|
|
||||||
account.AvatarURL.Valid = true
|
account.AvatarURL.Valid = true
|
||||||
account.AvatarURL.String = avatarURL
|
account.AvatarURL.String = avatarURL
|
||||||
|
|
@ -143,31 +145,13 @@ func (repo *AccountRepositoryMemory) UpdateAvatarURL(id string, avatarURL string
|
||||||
func (repo *AccountRepositoryMemory) RemoveAvatar(id string) error {
|
func (repo *AccountRepositoryMemory) RemoveAvatar(id string) error {
|
||||||
account, err := repo.GetByID(id)
|
account, err := repo.GetByID(id)
|
||||||
if err != nil { return err }
|
if err != nil { return err }
|
||||||
if account == nil { return errors.New("Account does not exist") }
|
if account == nil { return errors.NewNotExistError("Account does not exist") }
|
||||||
|
|
||||||
account.AvatarURL.Valid = false
|
account.AvatarURL.Valid = false
|
||||||
account.AvatarURL.String = ""
|
account.AvatarURL.String = ""
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *AccountRepositoryMemory) Delete(id string) error {
|
|
||||||
accountIndex := -1
|
|
||||||
for index, account := range repo.accounts {
|
|
||||||
if account.ID == id {
|
|
||||||
accountIndex = index
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if accountIndex == -1 { return nil }
|
|
||||||
|
|
||||||
repo.accounts = append(
|
|
||||||
repo.accounts[:accountIndex],
|
|
||||||
repo.accounts[accountIndex+1:]...,
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the number of account login failure attempts,
|
// Increment the number of account login failure attempts,
|
||||||
// returning the current fail count.
|
// returning the current fail count.
|
||||||
func (repo *AccountRepositoryMemory) IncrementFails(id string) (int, error) {
|
func (repo *AccountRepositoryMemory) IncrementFails(id string) (int, error) {
|
||||||
|
|
@ -188,3 +172,17 @@ func (repo *AccountRepositoryMemory) SetLocked(id string, locked bool) error {
|
||||||
account.Locked = locked
|
account.Locked = locked
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *AccountRepositoryMemory) Delete(id string) (string, error) {
|
||||||
|
var deletedID string
|
||||||
|
newAccounts := []*model.Account{}
|
||||||
|
for _, account := range repo.accounts {
|
||||||
|
if account.ID == id {
|
||||||
|
deletedID = id
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newAccounts = append(newAccounts, account)
|
||||||
|
}
|
||||||
|
repo.accounts = newAccounts
|
||||||
|
return deletedID, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,11 +157,6 @@ func (repo *AccountRepositoryPostgres) RemoveAvatar(id string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *AccountRepositoryPostgres) Delete(id string) error {
|
|
||||||
_, err := repo.db.Exec("DELETE FROM account WHERE id=$1", id)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the number of account login failure attempts,
|
// Increment the number of account login failure attempts,
|
||||||
// returning the current fail count.
|
// returning the current fail count.
|
||||||
func (repo *AccountRepositoryPostgres) IncrementFails(id string) (int, error) {
|
func (repo *AccountRepositoryPostgres) IncrementFails(id string) (int, error) {
|
||||||
|
|
@ -179,3 +174,9 @@ func (repo *AccountRepositoryPostgres) SetLocked(id string, locked bool) error {
|
||||||
_, err := repo.db.Exec("UPDATE account SET locked = $2 WHERE id=$1", id, locked)
|
_, err := repo.db.Exec("UPDATE account SET locked = $2 WHERE id=$1", id, locked)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *AccountRepositoryPostgres) Delete(id string) (string, error) {
|
||||||
|
var deletedID string
|
||||||
|
err := repo.db.Get(&deletedID, "DELETE FROM account WHERE id=$1", id)
|
||||||
|
return deletedID, err
|
||||||
|
}
|
||||||
|
|
|
||||||
135
repository/music/artist_memory.go
Normal file
135
repository/music/artist_memory.go
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
|
"arimelody-web/model"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetAllArtists() ([]*model.Artist, error) {
|
||||||
|
return repo.artists, nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) GetArtistCount() (int, error) {
|
||||||
|
return len(repo.artists), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetArtistByID(id string) (*model.Artist, error) {
|
||||||
|
index := slices.IndexFunc(repo.artists, func(artist *model.Artist) bool {
|
||||||
|
return artist.ID == id
|
||||||
|
})
|
||||||
|
if index == -1 { return nil, nil }
|
||||||
|
return repo.artists[index], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetArtistsNotOnRelease(releaseID string) ([]*model.Artist, error) {
|
||||||
|
release, err := repo.GetReleaseByID(releaseID)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
artists, err := repo.GetAllArtists()
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
|
||||||
|
artistsNotOnRelease := []*model.Artist{}
|
||||||
|
for _, artist := range artists {
|
||||||
|
if !slices.ContainsFunc(release.Credits, func(credit *model.Credit) bool {
|
||||||
|
return credit.Artist.ID == artist.ID
|
||||||
|
}) {
|
||||||
|
artistsNotOnRelease = append(artistsNotOnRelease, artist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return artistsNotOnRelease, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetArtistCredits(artistID string, showHidden bool) ([]*model.Credit, error) {
|
||||||
|
releases, err := repo.GetAllReleases(!showHidden, 0)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
|
||||||
|
credits := []*model.Credit{}
|
||||||
|
for _, release := range releases {
|
||||||
|
credits = append(credits, slices.DeleteFunc(
|
||||||
|
release.Credits,
|
||||||
|
func(credit *model.Credit) bool {
|
||||||
|
return credit.Artist.ID != artistID
|
||||||
|
},
|
||||||
|
)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return credits, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) CreateArtist(
|
||||||
|
id string,
|
||||||
|
name string,
|
||||||
|
website string,
|
||||||
|
avatar string,
|
||||||
|
) error {
|
||||||
|
if artist, err := repo.GetArtistByID(id); err != nil {
|
||||||
|
if !errors.IsNotExistError(err) { return err }
|
||||||
|
} else {
|
||||||
|
if artist != nil { return errors.NewValidationError("Artist with this ID already exists") }
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.artists = append(repo.artists, &model.Artist{
|
||||||
|
ID: id,
|
||||||
|
Name: name,
|
||||||
|
Website: website,
|
||||||
|
Avatar: avatar,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateArtist(artist *model.Artist) error {
|
||||||
|
repoArtist, err := repo.GetArtistByID(artist.ID)
|
||||||
|
if err != nil { return err }
|
||||||
|
if repoArtist == nil { return errors.NewNotExistError("Artist does not exist") }
|
||||||
|
|
||||||
|
repoArtist.Name = artist.Name
|
||||||
|
repoArtist.Website = artist.Website
|
||||||
|
repoArtist.Avatar = artist.Avatar
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateArtistID(oldID string, newID string) error {
|
||||||
|
artist, err := repo.GetArtistByID(oldID)
|
||||||
|
if err != nil { return err }
|
||||||
|
if artist == nil { return errors.NewNotExistError("Artist does not exist") }
|
||||||
|
|
||||||
|
artist.ID = newID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateArtistName(id string, name string) error {
|
||||||
|
artist, err := repo.GetArtistByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if artist == nil { return errors.NewNotExistError("Artist does not exist") }
|
||||||
|
|
||||||
|
artist.Name = name
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateArtistWebsite(id string, website string) error {
|
||||||
|
artist, err := repo.GetArtistByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if artist == nil { return errors.NewNotExistError("Artist does not exist") }
|
||||||
|
|
||||||
|
artist.Website = website
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateArtistAvatar(id string, avatar string) error {
|
||||||
|
artist, err := repo.GetArtistByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if artist == nil { return errors.NewNotExistError("Artist does not exist") }
|
||||||
|
|
||||||
|
artist.Avatar = avatar
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) DeleteArtist(id string) (string, error) {
|
||||||
|
var deletedID string
|
||||||
|
newArtists := []*model.Artist{}
|
||||||
|
for _, artist := range repo.artists {
|
||||||
|
if artist.ID == id {
|
||||||
|
deletedID = id
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newArtists = append(newArtists, artist)
|
||||||
|
}
|
||||||
|
repo.artists = newArtists
|
||||||
|
return deletedID, nil
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,29 @@
|
||||||
package controller
|
package music
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DATABASE
|
func (repo *MusicRepositoryPostgres) GetAllArtists() ([]*model.Artist, error) {
|
||||||
|
var artists = []*model.Artist{}
|
||||||
|
|
||||||
func GetArtist(db *sqlx.DB, id string) (*model.Artist, error) {
|
err := repo.db.Select(&artists, "SELECT * FROM artist")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return artists, nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) GetArtistCount() (int, error) {
|
||||||
|
var count int
|
||||||
|
err := repo.db.Get(&count, "SELECT count(*) FROM artist")
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetArtistByID(id string) (*model.Artist, error) {
|
||||||
var artist = model.Artist{}
|
var artist = model.Artist{}
|
||||||
|
|
||||||
err := db.Get(&artist, "SELECT * FROM artist WHERE id=$1", id)
|
err := repo.db.Get(&artist, "SELECT * FROM artist WHERE id=$1", id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -19,26 +31,10 @@ func GetArtist(db *sqlx.DB, id string) (*model.Artist, error) {
|
||||||
return &artist, nil
|
return &artist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllArtists(db *sqlx.DB) ([]*model.Artist, error) {
|
func (repo *MusicRepositoryPostgres) GetArtistsNotOnRelease(releaseID string) ([]*model.Artist, error) {
|
||||||
var artists = []*model.Artist{}
|
var artists = []*model.Artist{}
|
||||||
|
|
||||||
err := db.Select(&artists, "SELECT * FROM artist")
|
err := repo.db.Select(&artists,
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return artists, nil
|
|
||||||
}
|
|
||||||
func GetArtistCount(db *sqlx.DB) (int, error) {
|
|
||||||
var count int
|
|
||||||
err := db.Get(&count, "SELECT count(*) FROM artist")
|
|
||||||
return count, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, error) {
|
|
||||||
var artists = []*model.Artist{}
|
|
||||||
|
|
||||||
err := db.Select(&artists,
|
|
||||||
"SELECT * FROM artist "+
|
"SELECT * FROM artist "+
|
||||||
"WHERE id NOT IN "+
|
"WHERE id NOT IN "+
|
||||||
"(SELECT artist FROM musiccredit WHERE release=$1)",
|
"(SELECT artist FROM musiccredit WHERE release=$1)",
|
||||||
|
|
@ -50,15 +46,15 @@ func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, err
|
||||||
return artists, nil
|
return artists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.Credit, error) {
|
func (repo *MusicRepositoryPostgres) GetArtistCredits(artistID string, showHidden 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,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 "+
|
||||||
"WHERE artist=$1 "
|
"WHERE artist=$1 "
|
||||||
if !show_hidden { query += "AND visible=true " }
|
if !showHidden { query += "AND visible=true " }
|
||||||
query += "ORDER BY release_date DESC"
|
query += "ORDER BY release_date DESC"
|
||||||
rows, err := db.Query(query, artistID)
|
rows, err := repo.db.Query(query, artistID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +66,10 @@ func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.
|
||||||
}
|
}
|
||||||
var credits []*model.Credit
|
var credits []*model.Credit
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var credit model.Credit
|
credit := &model.Credit{
|
||||||
|
Release: &model.Release{},
|
||||||
|
Artist: &model.Artist{},
|
||||||
|
}
|
||||||
err = rows.Scan(
|
err = rows.Scan(
|
||||||
&credit.Release.ID,
|
&credit.Release.ID,
|
||||||
&credit.Release.Title,
|
&credit.Release.Title,
|
||||||
|
|
@ -85,44 +84,42 @@ func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.
|
||||||
)
|
)
|
||||||
|
|
||||||
otherArtists := []NamePrimary{}
|
otherArtists := []NamePrimary{}
|
||||||
err = db.Select(&otherArtists,
|
err = repo.db.Select(&otherArtists,
|
||||||
"SELECT name,is_primary FROM artist "+
|
"SELECT name,is_primary FROM artist "+
|
||||||
"JOIN musiccredit ON artist=id "+
|
"JOIN musiccredit ON artist=id "+
|
||||||
"WHERE release=$1",
|
"WHERE release=$1",
|
||||||
credit.Release.ID)
|
credit.Release.ID)
|
||||||
for _, otherCredit := range otherArtists {
|
for _, otherCredit := range otherArtists {
|
||||||
credit.Release.Credits = append(credit.Release.Credits, &model.Credit{
|
credit.Release.Credits = append(credit.Release.Credits, &model.Credit{
|
||||||
Artist: model.Artist{
|
Artist: &model.Artist{
|
||||||
Name: otherCredit.Name,
|
Name: otherCredit.Name,
|
||||||
},
|
},
|
||||||
Primary: otherCredit.Primary,
|
Primary: otherCredit.Primary,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
credits = append(credits, &credit)
|
credits = append(credits, credit)
|
||||||
}
|
}
|
||||||
|
|
||||||
return credits, nil
|
return credits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateArtist(db *sqlx.DB, artist *model.Artist) error {
|
func (repo *MusicRepositoryPostgres) CreateArtist(
|
||||||
_, err := db.Exec(
|
id string,
|
||||||
|
name string,
|
||||||
|
website string,
|
||||||
|
avatar string,
|
||||||
|
) error {
|
||||||
|
_, err := repo.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)",
|
||||||
artist.ID,
|
id, name, website, avatar,
|
||||||
artist.Name,
|
|
||||||
artist.Website,
|
|
||||||
artist.Avatar,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateArtist(db *sqlx.DB, artist *model.Artist) error {
|
func (repo *MusicRepositoryPostgres) UpdateArtist(artist *model.Artist) error {
|
||||||
_, err := db.Exec(
|
_, err := repo.db.Exec(
|
||||||
"UPDATE artist "+
|
"UPDATE artist "+
|
||||||
"SET name=$2, website=$3, avatar=$4 "+
|
"SET name=$2, website=$3, avatar=$4 "+
|
||||||
"WHERE id=$1",
|
"WHERE id=$1",
|
||||||
|
|
@ -131,22 +128,27 @@ func UpdateArtist(db *sqlx.DB, artist *model.Artist) error {
|
||||||
artist.Website,
|
artist.Website,
|
||||||
artist.Avatar,
|
artist.Avatar,
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateArtistID(oldID string, newID string) error {
|
||||||
return nil
|
_, err := repo.db.Exec("UPDATE artist SET id=$2 WHERE id=$1", oldID, newID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateArtistName(id string, name string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE artist SET name=$2 WHERE id=$1", id, name)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateArtistWebsite(id string, website string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE artist SET website=$2 WHERE id=$1", id, website)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateArtistAvatar(id string, avatar string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE artist SET avatar=$2 WHERE id=$1", id, avatar)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteArtist(db *sqlx.DB, artistID string) error {
|
func (repo *MusicRepositoryPostgres) DeleteArtist(id string) (string, error) {
|
||||||
_, err := db.Exec(
|
var deletedID string
|
||||||
"DELETE FROM artist "+
|
err := repo.db.Get(&deletedID, "DELETE FROM artist WHERE id=$1", id)
|
||||||
"WHERE id=$1",
|
return deletedID, err
|
||||||
artistID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
110
repository/music/interface.go
Normal file
110
repository/music/interface.go
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MusicRepository interface {
|
||||||
|
// artists
|
||||||
|
|
||||||
|
GetAllArtists() ([]*model.Artist, error)
|
||||||
|
GetArtistCount() (int, error)
|
||||||
|
// Fetches an artist by ID, returning an error if one was encountered.
|
||||||
|
// If the artist does not exist, both response fields are nil.
|
||||||
|
GetArtistByID(id string) (*model.Artist, error)
|
||||||
|
GetArtistsNotOnRelease(releaseID string) ([]*model.Artist, error)
|
||||||
|
GetArtistCredits(artistID string, showHidden bool) ([]*model.Credit, error)
|
||||||
|
|
||||||
|
CreateArtist(id string, name string, website string, avatar string) error
|
||||||
|
|
||||||
|
UpdateArtist(artist *model.Artist) error
|
||||||
|
UpdateArtistID(oldID string, newID string) error
|
||||||
|
UpdateArtistName(id string, name string) error
|
||||||
|
UpdateArtistWebsite(id string, website string) error
|
||||||
|
UpdateArtistAvatar(id string, avatar string) error
|
||||||
|
|
||||||
|
DeleteArtist(id string) (string, error)
|
||||||
|
|
||||||
|
// releases
|
||||||
|
|
||||||
|
// Fetch all releases.
|
||||||
|
// Filters to visible releases if `onlyVisible = true`.
|
||||||
|
// If `limit > 0`, limits the number of results.
|
||||||
|
GetAllReleases(onlyVisible bool, limit int) ([]*model.Release, error)
|
||||||
|
GetReleaseCount(onlyVisible bool) (int, error)
|
||||||
|
GetReleaseByID(id string) (*model.Release, error)
|
||||||
|
|
||||||
|
GetReleaseTracks(id string) ([]*model.Track, error)
|
||||||
|
GetReleaseCredits(id string) ([]*model.Credit, error)
|
||||||
|
GetReleaseLinks(id string) ([]*model.Link, error)
|
||||||
|
|
||||||
|
CreateRelease(id string, title string, releaseType model.ReleaseType, releaseDate time.Time, artworkURL string) error
|
||||||
|
|
||||||
|
UpdateRelease(release *model.Release) error
|
||||||
|
UpdateReleaseID(oldID string, newID string) error
|
||||||
|
UpdateReleaseVisibility(id string, visible bool) error
|
||||||
|
UpdateReleaseTitle(id string, title string) error
|
||||||
|
UpdateReleaseDescription(id string, description string) error
|
||||||
|
UpdateReleaseType(id string, releaseType model.ReleaseType) error
|
||||||
|
UpdateReleaseDate(id string, releaseDate time.Time) error
|
||||||
|
UpdateReleaseArtwork(id string, artwork string) error
|
||||||
|
UpdateReleaseBuyInfo(id string, buyName string, buyLink string) error
|
||||||
|
UpdateReleaseCopyright(id string, copyright string, url string) error
|
||||||
|
UpdateReleaseTracks(id string, newTrackIDs []string) error
|
||||||
|
UpdateReleaseCredits(id string, newCredits []*model.Credit) error
|
||||||
|
UpdateReleaseLinks(id string, newLinks []*model.Link) error
|
||||||
|
|
||||||
|
DeleteRelease(id string) (string, error)
|
||||||
|
|
||||||
|
// tracks
|
||||||
|
|
||||||
|
GetAllTracks() ([]*model.Track, error)
|
||||||
|
GetTrackCount() (int, error)
|
||||||
|
GetTrackByID(id string) (*model.Track, error)
|
||||||
|
GetOrphanTracks() ([]*model.Track, error)
|
||||||
|
GetTracksNotOnRelease(releaseID string) ([]*model.Track, error)
|
||||||
|
GetTrackReleases(trackID string) ([]*model.Release, error)
|
||||||
|
|
||||||
|
CreateTrack(title string, description string, lyrics string, previewURL string) (string, error)
|
||||||
|
|
||||||
|
UpdateTrack(track *model.Track) error
|
||||||
|
UpdateTrackTitle(id string, title string) error
|
||||||
|
UpdateTrackDescription(id string, description string) error
|
||||||
|
UpdateTrackLyrics(id string, lyrics string) error
|
||||||
|
UpdateTrackPreviewURL(id string, previewURL string) error
|
||||||
|
|
||||||
|
DeleteTrack(id string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
MusicRepositoryPostgres struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
MusicRepositoryMemory struct {
|
||||||
|
artists []*model.Artist
|
||||||
|
releases []*model.Release
|
||||||
|
tracks []*model.Track
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ MusicRepository = new(MusicRepositoryPostgres)
|
||||||
|
func NewMusicRepositoryPostgres(db *sqlx.DB) *MusicRepositoryPostgres {
|
||||||
|
return &MusicRepositoryPostgres{ db: db }
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ MusicRepository = new(MusicRepositoryMemory)
|
||||||
|
func NewMusicRepositoryMemory(
|
||||||
|
artists []*model.Artist,
|
||||||
|
releases []*model.Release,
|
||||||
|
tracks []*model.Track,
|
||||||
|
) *MusicRepositoryMemory {
|
||||||
|
return &MusicRepositoryMemory{
|
||||||
|
artists: artists,
|
||||||
|
releases: releases,
|
||||||
|
tracks: tracks,
|
||||||
|
}
|
||||||
|
}
|
||||||
228
repository/music/release_memory.go
Normal file
228
repository/music/release_memory.go
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
|
"arimelody-web/model"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetAllReleases(onlyVisible bool, limit int) ([]*model.Release, error) {
|
||||||
|
releases := []*model.Release{}
|
||||||
|
for _, release := range repo.releases {
|
||||||
|
if !onlyVisible || release.Visible {
|
||||||
|
releases = append(releases, release)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) GetReleaseCount(onlyVisible bool) (int, error) {
|
||||||
|
releaseCount := 0
|
||||||
|
for _, release := range repo.releases {
|
||||||
|
if !onlyVisible || release.Visible {
|
||||||
|
releaseCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return releaseCount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetReleaseByID(id string) (*model.Release, error) {
|
||||||
|
index := slices.IndexFunc(repo.releases, func(release *model.Release) bool {
|
||||||
|
return release.ID == id
|
||||||
|
})
|
||||||
|
if index == -1 { return nil, nil }
|
||||||
|
return repo.releases[index], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetReleaseTracks(id string) ([]*model.Track, error) {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return release.Tracks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetReleaseCredits(id string) ([]*model.Credit, error) {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return release.Credits, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetReleaseLinks(id string) ([]*model.Link, error) {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return release.Links, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) CreateRelease(
|
||||||
|
id string,
|
||||||
|
title string,
|
||||||
|
releaseType model.ReleaseType,
|
||||||
|
releaseDate time.Time,
|
||||||
|
artworkURL string,
|
||||||
|
) error {
|
||||||
|
if release, err := repo.GetReleaseByID(id); err != nil {
|
||||||
|
if !errors.IsNotExistError(err) { return err }
|
||||||
|
} else {
|
||||||
|
if release != nil { return errors.NewValidationError("Release with this ID already exists") }
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.releases = append(repo.releases, &model.Release{
|
||||||
|
ID: id,
|
||||||
|
Title: title,
|
||||||
|
ReleaseType: releaseType,
|
||||||
|
ReleaseDate: releaseDate,
|
||||||
|
Artwork: artworkURL,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateRelease(release *model.Release) error {
|
||||||
|
repoRelease, err := repo.GetReleaseByID(release.ID)
|
||||||
|
if err != nil { return err }
|
||||||
|
if repoRelease == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
|
||||||
|
repoRelease.Visible = release.Visible
|
||||||
|
repoRelease.Title = release.Title
|
||||||
|
repoRelease.Description = release.Description
|
||||||
|
repoRelease.ReleaseType = release.ReleaseType
|
||||||
|
repoRelease.ReleaseDate = release.ReleaseDate
|
||||||
|
repoRelease.Artwork = release.Artwork
|
||||||
|
repoRelease.Buyname = release.Buyname
|
||||||
|
repoRelease.Buylink = release.Buylink
|
||||||
|
repoRelease.Copyright = release.Copyright
|
||||||
|
repoRelease.CopyrightURL = release.CopyrightURL
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseID(oldID string, newID string) error {
|
||||||
|
release, err := repo.GetReleaseByID(oldID)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.ID = newID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseVisibility(id string, visible bool) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.Visible = visible
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseTitle(id string, title string) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.Title = title
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseDescription(id string, description string) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.Description = description
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseType(id string, releaseType model.ReleaseType) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.ReleaseType = releaseType
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseDate(id string, releaseDate time.Time) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.ReleaseDate = releaseDate
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseArtwork(id string, artwork string) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.Artwork = artwork
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseBuyInfo(id string, buyName string, buyLink string) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.Buyname = buyName
|
||||||
|
release.Buylink = buyLink
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseCopyright(id string, copyright string, url string) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.Copyright = copyright
|
||||||
|
release.CopyrightURL = url
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseTracks(id string, newTrackIDs []string) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
|
||||||
|
tracks := []*model.Track{}
|
||||||
|
for _, trackID := range newTrackIDs {
|
||||||
|
track, err := repo.GetTrackByID(trackID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.IsNotExistError(err) {
|
||||||
|
return errors.NewNotExistError(fmt.Sprintf("Track %s does not exist", trackID))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tracks = append(tracks, track)
|
||||||
|
}
|
||||||
|
release.Tracks = tracks
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseCredits(id string, newCredits []*model.Credit) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
|
||||||
|
for _, credit := range newCredits {
|
||||||
|
if credit.Artist == nil { return errors.NewValidationError("Credit artist cannot be empty") }
|
||||||
|
if len(credit.Artist.ID) == 0 { return errors.NewValidationError("Credit artist ID cannot be empty") }
|
||||||
|
if artist, err := repo.GetArtistByID(credit.Artist.ID); err != nil {
|
||||||
|
return err
|
||||||
|
} else if artist == nil {
|
||||||
|
return errors.NewNotExistError(fmt.Sprintf("Artist '%s' does not exist", credit.Artist.ID))
|
||||||
|
} else {
|
||||||
|
credit.Release = release
|
||||||
|
credit.Artist = artist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
release.Credits = newCredits
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateReleaseLinks(id string, newLinks []*model.Link) error {
|
||||||
|
release, err := repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if release == nil { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
release.Links = newLinks
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) DeleteRelease(id string) (string, error) {
|
||||||
|
var deletedID string
|
||||||
|
newReleases := []*model.Release{}
|
||||||
|
for _, release := range repo.releases {
|
||||||
|
if release.ID == id {
|
||||||
|
deletedID = id
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newReleases = append(newReleases, release)
|
||||||
|
}
|
||||||
|
repo.releases = newReleases
|
||||||
|
return deletedID, nil
|
||||||
|
}
|
||||||
303
repository/music/release_postgres.go
Normal file
303
repository/music/release_postgres.go
Normal file
|
|
@ -0,0 +1,303 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetAllReleases(onlyVisible bool, limit int) ([]*model.Release, error) {
|
||||||
|
var releases = []*model.Release{}
|
||||||
|
|
||||||
|
query := "SELECT * FROM musicrelease"
|
||||||
|
if onlyVisible {
|
||||||
|
query += " WHERE visible=true"
|
||||||
|
}
|
||||||
|
query += " ORDER BY release_date DESC"
|
||||||
|
var err error
|
||||||
|
if limit > 0 {
|
||||||
|
err = repo.db.Select(&releases, query + " LIMIT $1", limit)
|
||||||
|
} else {
|
||||||
|
err = repo.db.Select(&releases, query)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) GetReleaseCount(onlyVisible bool) (int, error) {
|
||||||
|
query := "SELECT count(*) FROM musicrelease"
|
||||||
|
if onlyVisible {
|
||||||
|
query += " WHERE visible=true"
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int
|
||||||
|
err := repo.db.Get(&count, query)
|
||||||
|
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetReleaseByID(id string) (*model.Release, error) {
|
||||||
|
var release = model.Release{}
|
||||||
|
err := repo.db.Get(&release, "SELECT * FROM musicrelease WHERE id=$1", id)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return &release, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetReleaseTracks(releaseID string) ([]*model.Track, error) {
|
||||||
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
|
err := repo.db.Select(&tracks,
|
||||||
|
"SELECT musictrack.* FROM musictrack "+
|
||||||
|
"JOIN musicreleasetrack ON track=id "+
|
||||||
|
"WHERE release=$1 "+
|
||||||
|
"ORDER BY number ASC",
|
||||||
|
releaseID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetReleaseCredits(releaseID string) ([]*model.Credit, error) {
|
||||||
|
rows, err := repo.db.Query(
|
||||||
|
"SELECT artist.id,artist.name,artist.website,artist.avatar,role,is_primary "+
|
||||||
|
"FROM musiccredit "+
|
||||||
|
"JOIN artist ON artist=artist.id "+
|
||||||
|
"JOIN musicrelease ON release=musicrelease.id "+
|
||||||
|
"WHERE musicrelease.id=$1 "+
|
||||||
|
"ORDER BY is_primary DESC",
|
||||||
|
releaseID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var credits []*model.Credit
|
||||||
|
for rows.Next() {
|
||||||
|
credit := &model.Credit{
|
||||||
|
Artist: &model.Artist{},
|
||||||
|
}
|
||||||
|
rows.Scan(
|
||||||
|
&credit.Artist.ID,
|
||||||
|
&credit.Artist.Name,
|
||||||
|
&credit.Artist.Website,
|
||||||
|
&credit.Artist.Avatar,
|
||||||
|
&credit.Role,
|
||||||
|
&credit.Primary)
|
||||||
|
credits = append(credits, credit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return credits, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetReleaseLinks(releaseID string) ([]*model.Link, error) {
|
||||||
|
var links = []*model.Link{}
|
||||||
|
|
||||||
|
err := repo.db.Select(&links, "SELECT name,url FROM musiclink WHERE release=$1", releaseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return links, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) CreateRelease(
|
||||||
|
id string,
|
||||||
|
title string,
|
||||||
|
releaseType model.ReleaseType,
|
||||||
|
releaseDate time.Time,
|
||||||
|
artworkURL string,
|
||||||
|
) error {
|
||||||
|
_, err := repo.db.Exec(
|
||||||
|
"INSERT INTO musicrelease "+
|
||||||
|
"(id, title, type, release_date, artwork) "+
|
||||||
|
"VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
releaseType,
|
||||||
|
releaseDate.Format("2006-01-02 15:04:05"),
|
||||||
|
artworkURL,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateRelease(release *model.Release) error {
|
||||||
|
_, err := repo.db.Exec(
|
||||||
|
"UPDATE musicrelease SET "+
|
||||||
|
"visible=$2, title=$3, description=$4, type=$5, release_date=$6, artwork=$7, buyname=$8, buylink=$9, copyright=$10, copyrighturl=$11 "+
|
||||||
|
"WHERE id=$1",
|
||||||
|
release.ID,
|
||||||
|
release.Visible,
|
||||||
|
release.Title,
|
||||||
|
release.Description,
|
||||||
|
release.ReleaseType,
|
||||||
|
release.ReleaseDate.Format("2006-01-02 15:04:05"),
|
||||||
|
release.Artwork,
|
||||||
|
release.Buyname,
|
||||||
|
release.Buylink,
|
||||||
|
release.Copyright,
|
||||||
|
release.CopyrightURL,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseID(oldID string, newID string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musicrelease SET id=$2 WHERE id=$1", oldID, newID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseVisibility(id string, visible bool) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musicrelease SET visible=$2 WHERE id=$1", id, visible)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseTitle(id string, title string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musicrelease SET title=$2 WHERE id=$1", id, title)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseDescription(id string, description string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musicrelease SET description=$2 WHERE id=$1", id, description)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseType(id string, releaseType model.ReleaseType) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musicrelease SET type=$2 WHERE id=$1", id, releaseType)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseDate(id string, releaseDate time.Time) error {
|
||||||
|
_, err := repo.db.Exec(
|
||||||
|
"UPDATE musicrelease SET release_date=$2 WHERE id=$1",
|
||||||
|
id,
|
||||||
|
releaseDate.Format("2006-01-02 15:04:05"),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseArtwork(id string, artwork string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musicrelease SET artwork=$2 WHERE id=$1", id, artwork)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseBuyInfo(id string, buyName string, buyLink string) error {
|
||||||
|
_, err := repo.db.Exec(
|
||||||
|
"UPDATE musicrelease SET buyname=$2,buylink=$3 WHERE id=$1",
|
||||||
|
id, buyName, buyLink,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseCopyright(id string, copyright string, url string) error {
|
||||||
|
_, err := repo.db.Exec(
|
||||||
|
"UPDATE musicrelease SET copyright=$2,copyrighturl=$3 WHERE id=$1",
|
||||||
|
id, copyright, url,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseTracks(releaseID string, newTrackIDs []string) error {
|
||||||
|
tx, err := repo.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec("DELETE FROM musicreleasetrack WHERE release=$1", releaseID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i, trackID := range newTrackIDs {
|
||||||
|
_, err = tx.Exec(
|
||||||
|
"INSERT INTO musicreleasetrack "+
|
||||||
|
"(release, track, number) "+
|
||||||
|
"VALUES ($1, $2, $3)",
|
||||||
|
releaseID,
|
||||||
|
trackID,
|
||||||
|
i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseCredits(releaseID string, newCredits []*model.Credit) error {
|
||||||
|
tx, err := repo.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec("DELETE FROM musiccredit WHERE release=$1", releaseID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, credit := range newCredits {
|
||||||
|
_, err = tx.Exec(
|
||||||
|
"INSERT INTO musiccredit "+
|
||||||
|
"(release, artist, role, is_primary) "+
|
||||||
|
"VALUES ($1, $2, $3, $4)",
|
||||||
|
releaseID,
|
||||||
|
credit.Artist.ID,
|
||||||
|
credit.Role,
|
||||||
|
credit.Primary,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateReleaseLinks(releaseID string, newLinks []*model.Link) error {
|
||||||
|
tx, err := repo.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec("DELETE FROM musiclink WHERE release=$1", releaseID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, link := range newLinks {
|
||||||
|
_, err := tx.Exec(
|
||||||
|
"INSERT INTO musiclink "+
|
||||||
|
"(release, name, url) "+
|
||||||
|
"VALUES ($1, $2, $3)",
|
||||||
|
releaseID,
|
||||||
|
link.Name,
|
||||||
|
link.URL,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) DeleteRelease(id string) (string, error) {
|
||||||
|
var deletedID string
|
||||||
|
err := repo.db.Get(&deletedID, "DELETE FROM musicrelease WHERE id=$1", id)
|
||||||
|
return deletedID, err
|
||||||
|
}
|
||||||
123
repository/music/track_memory.go
Normal file
123
repository/music/track_memory.go
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
|
"arimelody-web/model"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetAllTracks() ([]*model.Track, error) {
|
||||||
|
return repo.tracks, nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) GetTrackCount() (int, error) {
|
||||||
|
return len(repo.tracks), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetTrackByID(id string) (*model.Track, error) {
|
||||||
|
index := slices.IndexFunc(repo.tracks, func(track *model.Track) bool {
|
||||||
|
return track.ID == id
|
||||||
|
})
|
||||||
|
if index == -1 { return nil, nil }
|
||||||
|
return repo.tracks[index], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetOrphanTracks() ([]*model.Track, error) {
|
||||||
|
return slices.DeleteFunc(repo.tracks, func(track *model.Track) bool {
|
||||||
|
return slices.ContainsFunc(repo.releases, func(release *model.Release) bool {
|
||||||
|
return slices.ContainsFunc(release.Tracks, func(releaseTrack *model.Track) bool {
|
||||||
|
return releaseTrack.ID == track.ID
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetTracksNotOnRelease(releaseID string) ([]*model.Track, error) {
|
||||||
|
release, err := repo.GetReleaseByID(releaseID)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
|
||||||
|
return slices.DeleteFunc(repo.tracks, func(track *model.Track) bool {
|
||||||
|
return slices.ContainsFunc(release.Tracks, func(releaseTrack *model.Track) bool {
|
||||||
|
return releaseTrack.ID == track.ID
|
||||||
|
})
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) GetTrackReleases(trackID string) ([]*model.Release, error) {
|
||||||
|
return slices.DeleteFunc(repo.releases, func(release *model.Release) bool {
|
||||||
|
return !slices.ContainsFunc(release.Tracks, func(track *model.Track) bool {
|
||||||
|
return track.ID == trackID
|
||||||
|
})
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) CreateTrack(
|
||||||
|
title string,
|
||||||
|
description string,
|
||||||
|
lyrics string,
|
||||||
|
previewURL string,
|
||||||
|
) (string, error) {
|
||||||
|
id := strconv.Itoa(len(repo.tracks))
|
||||||
|
repo.tracks = append(repo.tracks, &model.Track{
|
||||||
|
ID: id,
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
Lyrics: lyrics,
|
||||||
|
PreviewURL: previewURL,
|
||||||
|
})
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateTrack(track *model.Track) error {
|
||||||
|
repoTrack, err := repo.GetTrackByID(track.ID)
|
||||||
|
if err != nil { return err }
|
||||||
|
if repoTrack == nil { return errors.NewNotExistError("Track does not exist") }
|
||||||
|
|
||||||
|
repoTrack.Title = track.Title
|
||||||
|
repoTrack.Description = track.Description
|
||||||
|
repoTrack.Lyrics = track.Lyrics
|
||||||
|
repoTrack.PreviewURL = track.PreviewURL
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateTrackTitle(id string, title string) error {
|
||||||
|
repoTrack, err := repo.GetTrackByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if repoTrack == nil { return errors.NewNotExistError("Track does not exist") }
|
||||||
|
repoTrack.Title = title
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateTrackDescription(id string, description string) error {
|
||||||
|
repoTrack, err := repo.GetTrackByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if repoTrack == nil { return errors.NewNotExistError("Track does not exist") }
|
||||||
|
repoTrack.Description = description
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateTrackLyrics(id string, lyrics string) error {
|
||||||
|
repoTrack, err := repo.GetTrackByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if repoTrack == nil { return errors.NewNotExistError("Track does not exist") }
|
||||||
|
repoTrack.Lyrics = lyrics
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryMemory) UpdateTrackPreviewURL(id string, previewURL string) error {
|
||||||
|
repoTrack, err := repo.GetTrackByID(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if repoTrack == nil { return errors.NewNotExistError("Track does not exist") }
|
||||||
|
repoTrack.PreviewURL = previewURL
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryMemory) DeleteTrack(id string) (string, error) {
|
||||||
|
var deletedID string
|
||||||
|
newTracks := []*model.Track{}
|
||||||
|
for _, track := range repo.tracks {
|
||||||
|
if track.ID == id {
|
||||||
|
deletedID = id
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newTracks = append(newTracks, track)
|
||||||
|
}
|
||||||
|
repo.tracks = newTracks
|
||||||
|
return deletedID, nil
|
||||||
|
}
|
||||||
137
repository/music/track_postgres.go
Normal file
137
repository/music/track_postgres.go
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetAllTracks() ([]*model.Track, error) {
|
||||||
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
|
err := repo.db.Select(&tracks, "SELECT * FROM musictrack")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks, nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) GetTrackCount() (int, error) {
|
||||||
|
var count int
|
||||||
|
err := repo.db.Get(&count, "SELECT count(*) FROM musictrack")
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetTrackByID(id string) (*model.Track, error) {
|
||||||
|
var track = model.Track{}
|
||||||
|
|
||||||
|
stmt, _ := repo.db.Preparex("SELECT * FROM musictrack WHERE id=$1")
|
||||||
|
err := stmt.Get(&track, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &track, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetOrphanTracks() ([]*model.Track, error) {
|
||||||
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
|
err := repo.db.Select(&tracks, "SELECT * FROM musictrack WHERE id NOT IN (SELECT track FROM musicreleasetrack)")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetTracksNotOnRelease(releaseID string) ([]*model.Track, error) {
|
||||||
|
var tracks = []*model.Track{}
|
||||||
|
|
||||||
|
err := repo.db.Select(&tracks,
|
||||||
|
"SELECT * FROM musictrack "+
|
||||||
|
"WHERE id NOT IN "+
|
||||||
|
"(SELECT track FROM musicreleasetrack WHERE release=$1)",
|
||||||
|
releaseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) GetTrackReleases(trackID string) ([]*model.Release, error) {
|
||||||
|
var releases = []*model.Release{}
|
||||||
|
|
||||||
|
err := repo.db.Select(&releases,
|
||||||
|
"SELECT id,title,type,release_date,artwork,buylink "+
|
||||||
|
"FROM musicrelease "+
|
||||||
|
"JOIN musicreleasetrack ON release=id "+
|
||||||
|
"WHERE track=$1 "+
|
||||||
|
"ORDER BY release_date",
|
||||||
|
trackID,
|
||||||
|
)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) CreateTrack(
|
||||||
|
title string,
|
||||||
|
description string,
|
||||||
|
lyrics string,
|
||||||
|
previewURL string,
|
||||||
|
) (string, error) {
|
||||||
|
var trackID string
|
||||||
|
err := repo.db.QueryRow(
|
||||||
|
"INSERT INTO musictrack (title, description, lyrics, preview_url) "+
|
||||||
|
"VALUES ($1, $2, $3, $4) "+
|
||||||
|
"RETURNING id",
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
lyrics,
|
||||||
|
previewURL,
|
||||||
|
).Scan(&trackID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateTrack(track *model.Track) error {
|
||||||
|
_, err := repo.db.Exec(
|
||||||
|
"UPDATE musictrack "+
|
||||||
|
"SET title=$2, description=$3, lyrics=$4, preview_url=$5 "+
|
||||||
|
"WHERE id=$1",
|
||||||
|
track.ID,
|
||||||
|
track.Title,
|
||||||
|
track.Description,
|
||||||
|
track.Lyrics,
|
||||||
|
track.PreviewURL,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateTrackTitle(id string, title string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musictrack SET title=$2 WHERE id=$1", id, title)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateTrackDescription(id string, description string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musictrack SET description=$2 WHERE id=$1", id, description)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateTrackLyrics(id string, lyrics string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musictrack SET lyrics=$2 WHERE id=$1", id, lyrics)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
func (repo *MusicRepositoryPostgres) UpdateTrackPreviewURL(id string, previewURL string) error {
|
||||||
|
_, err := repo.db.Exec("UPDATE musictrack SET preview_url=$2 WHERE id=$1", id, previewURL)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *MusicRepositoryPostgres) DeleteTrack(id string) (string, error) {
|
||||||
|
var deletedID string
|
||||||
|
err := repo.db.Get(&deletedID, "DELETE FROM musictrack WHERE id=$1 RETURNING id", id)
|
||||||
|
return deletedID, err
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
repository "arimelody-web/repository/account"
|
repository "arimelody-web/repository/account"
|
||||||
"errors"
|
"arimelody-web/service/validator"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -29,33 +29,24 @@ func (s *AccountService) GetCount() (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccountService) GetByID(id string) (*model.Account, error) {
|
func (s *AccountService) GetByID(id string) (*model.Account, error) {
|
||||||
if account, err := s.repo.GetByID(id); err != nil {
|
account, err := s.repo.GetByID(id)
|
||||||
return nil, err
|
if err != nil { return nil, err }
|
||||||
} else if account == nil {
|
if account == nil { return nil, errors.NewNotExistError("Account does not exist") }
|
||||||
return nil, fmt.Errorf("Account does not exist: %s", id)
|
|
||||||
} else {
|
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccountService) GetByUsername(username string) (*model.Account, error) {
|
func (s *AccountService) GetByUsername(username string) (*model.Account, error) {
|
||||||
if account, err := s.repo.GetByUsername(username); err != nil {
|
account, err := s.repo.GetByUsername(username)
|
||||||
return nil, err
|
if err != nil { return nil, err }
|
||||||
} else if account == nil {
|
if account == nil { return nil, errors.NewNotExistError("Account does not exist") }
|
||||||
return nil, fmt.Errorf("Account does not exist: %s", username)
|
|
||||||
} else {
|
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccountService) GetByEmail(email string) (*model.Account, error) {
|
func (s *AccountService) GetByEmail(email string) (*model.Account, error) {
|
||||||
if account, err := s.repo.GetByEmail(email); err != nil {
|
account, err := s.repo.GetByEmail(email)
|
||||||
return nil, err
|
if err != nil { return nil, err }
|
||||||
} else if account == nil {
|
if account == nil { return nil, errors.NewNotExistError("Account does not exist") }
|
||||||
return nil, fmt.Errorf("Account does not exist with email: %s", email)
|
|
||||||
} else {
|
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccountService) Create(
|
func (s *AccountService) Create(
|
||||||
|
|
@ -64,13 +55,13 @@ func (s *AccountService) Create(
|
||||||
email *string,
|
email *string,
|
||||||
avatarURL *string,
|
avatarURL *string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
if len(username) == 0 { return "", errors.NewValidationError("Username cannot be empty") }
|
||||||
|
if !validator.ValidateID(username) { return "", errors.NewValidationError("Username contains invalid characters") }
|
||||||
|
if len(password) == 0 { return "", errors.NewValidationError("Password cannot be empty") }
|
||||||
|
if email != nil && len(*email) == 0 { return "", errors.NewValidationError("Email cannot be empty") }
|
||||||
|
|
||||||
var id string
|
var id string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if len(username) == 0 { return id, errors.New("Username cannot be empty") }
|
|
||||||
if len(password) == 0 { return id, errors.New("Password cannot be empty") }
|
|
||||||
if email != nil && len(*email) == 0 { return id, errors.New("Email cannot be empty") }
|
|
||||||
|
|
||||||
if id, err = s.repo.Create(username, password, email, avatarURL); err != nil {
|
if id, err = s.repo.Create(username, password, email, avatarURL); err != nil {
|
||||||
return id, err
|
return id, err
|
||||||
}
|
}
|
||||||
|
|
@ -80,48 +71,31 @@ func (s *AccountService) Create(
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccountService) ChangeUsername(id string, username string) error {
|
func (s *AccountService) ChangeUsername(id string, username string) error {
|
||||||
if len(username) == 0 { return errors.New("Username cannot be empty") }
|
if len(username) == 0 { return errors.NewValidationError("Username cannot be empty") }
|
||||||
if err := s.repo.UpdateAvatarURL(id, username); err != nil {
|
if !validator.ValidateID(username) { return errors.NewValidationError("Username contains invalid characters") }
|
||||||
return err
|
if err := s.repo.UpdateUsername(id, username); err != nil { return err }
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Printf("Changed username for %s to '%s'", id, username)
|
s.log.Printf("Changed username for %s to '%s'", id, username)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (s *AccountService) ChangePassword(id string, password string) error {
|
func (s *AccountService) ChangePassword(id string, password string) error {
|
||||||
if len(password) == 0 { return errors.New("Password cannot be empty") }
|
if len(password) == 0 { return errors.NewValidationError("Password cannot be empty") }
|
||||||
if err := s.repo.UpdateAvatarURL(id, password); err != nil {
|
if err := s.repo.UpdatePassword(id, password); err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Printf("Changed password for %s", id)
|
s.log.Printf("Changed password for %s", id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (s *AccountService) ChangeEmail(id string, email string) error {
|
func (s *AccountService) ChangeEmail(id string, email string) error {
|
||||||
if len(email) == 0 { return s.repo.RemoveEmail(id) }
|
if len(email) == 0 { return s.repo.RemoveEmail(id) }
|
||||||
if err := s.repo.UpdateAvatarURL(id, email); err != nil {
|
if err := s.repo.UpdateEmail(id, email); err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Printf("Changed email for %s to '%s'", id, email)
|
s.log.Printf("Changed email for %s to '%s'", id, email)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (s *AccountService) ChangeAvatarURL(id string, avatarURL string) error {
|
func (s *AccountService) ChangeAvatarURL(id string, avatarURL string) error {
|
||||||
if len(avatarURL) == 0 { return s.repo.RemoveAvatar(id) }
|
if len(avatarURL) == 0 { return s.repo.RemoveAvatar(id) }
|
||||||
if err := s.repo.UpdateAvatarURL(id, avatarURL); err != nil {
|
if err := s.repo.UpdateAvatarURL(id, avatarURL); err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Printf("Changed avatar URL for %s to '%s'", id, avatarURL)
|
s.log.Printf("Changed avatar URL for %s to '%s'", id, avatarURL)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccountService) Delete(id string) error {
|
|
||||||
if err := s.repo.Delete(id); err != nil { return err }
|
|
||||||
s.log.Printf("Deleted account %s", id)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AccountService) IncrementFails(id string) (int, error) {
|
func (s *AccountService) IncrementFails(id string) (int, error) {
|
||||||
num, err := s.repo.IncrementFails(id)
|
num, err := s.repo.IncrementFails(id)
|
||||||
if err != nil { return 0, err }
|
if err != nil { return 0, err }
|
||||||
|
|
@ -146,3 +120,11 @@ func (s *AccountService) Unlock(id string) error {
|
||||||
s.log.Printf("Unlocked account %s", id)
|
s.log.Printf("Unlocked account %s", id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AccountService) Delete(id string) error {
|
||||||
|
deletedID, err := s.repo.Delete(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if deletedID == "" { return errors.NewNotExistError("Account does not exist") }
|
||||||
|
s.log.Printf("Deleted account %s", id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
package account
|
package account_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
repository "arimelody-web/repository/account"
|
repository "arimelody-web/repository/account"
|
||||||
|
service "arimelody-web/service/account"
|
||||||
|
"arimelody-web/errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
service *AccountService
|
s *service.AccountService
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -19,7 +22,7 @@ func init() {
|
||||||
defer devNullFile.Close()
|
defer devNullFile.Close()
|
||||||
|
|
||||||
repo := repository.NewAccountRepositoryMemory(make([]*model.Account, 0))
|
repo := repository.NewAccountRepositoryMemory(make([]*model.Account, 0))
|
||||||
service = NewAccountService(
|
s = service.NewAccountService(
|
||||||
repo,
|
repo,
|
||||||
log.New(devNullFile, "", model.DEFAULT_LOG_FLAGS),
|
log.New(devNullFile, "", model.DEFAULT_LOG_FLAGS),
|
||||||
)
|
)
|
||||||
|
|
@ -41,14 +44,14 @@ func Test_Account(t *testing.T) {
|
||||||
username := "testificate"
|
username := "testificate"
|
||||||
password := "the amazing digital data breach"
|
password := "the amazing digital data breach"
|
||||||
email := "goober@arimelody.space"
|
email := "goober@arimelody.space"
|
||||||
avatarURL := "/img/default-avatar.webp"
|
avatarURL := "/img/account-avatar.webp"
|
||||||
|
|
||||||
var id string
|
var id string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
t.Run("accounts should start empty", func(t *testing.T) {
|
t.Run("accounts should start empty", func(t *testing.T) {
|
||||||
t.Run("count is zero", func(t *testing.T) {
|
t.Run("count is zero", func(t *testing.T) {
|
||||||
if num, err := service.GetCount(); err != nil {
|
if num, err := s.GetCount(); err != nil {
|
||||||
t.Errorf("Failed to get number of accounts: %v", err)
|
t.Errorf("Failed to get number of accounts: %v", err)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, num, 0)
|
assert.Equal(t, num, 0)
|
||||||
|
|
@ -56,8 +59,8 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("service returns empty array", func(t *testing.T) {
|
t.Run("service returns empty array", func(t *testing.T) {
|
||||||
if accounts, err := service.GetAll(); err != nil {
|
if accounts, err := s.GetAll(); err != nil {
|
||||||
t.Errorf("Failed to get number of accounts: %v", err)
|
t.Errorf("Failed to get accounts: %v", err)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, len(accounts), 0)
|
assert.Equal(t, len(accounts), 0)
|
||||||
}
|
}
|
||||||
|
|
@ -65,13 +68,28 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can create account", func(t *testing.T) {
|
t.Run("can create account", func(t *testing.T) {
|
||||||
id, err = service.Create(username, password, &email, &avatarURL)
|
id, err = s.Create(username, password, &email, &avatarURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to create account: %v", err)
|
t.Errorf("Failed to create account: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("but not with invalid username", func(t *testing.T) {
|
||||||
|
if _, err := s.Create("", password, &email, &avatarURL); err == nil {
|
||||||
|
t.Error("Could create account with invalid username")
|
||||||
|
} else if !errors.IsValidationError(err) {
|
||||||
|
t.Error("Error is not validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("but not with invalid password", func(t *testing.T) {
|
||||||
|
if _, err := s.Create("test-username", "", &email, &avatarURL); err == nil {
|
||||||
|
t.Error("Could create account with invalid password")
|
||||||
|
} else if !errors.IsValidationError(err) {
|
||||||
|
t.Error("Error is not validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("and fetch by ID", func(t *testing.T) {
|
t.Run("and fetch by ID", func(t *testing.T) {
|
||||||
account, err := service.GetByID(id)
|
account, err := s.GetByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to get account after creation: %v", err)
|
t.Errorf("Failed to get account after creation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +102,7 @@ func Test_Account(t *testing.T) {
|
||||||
assert.Equal(t, account.Locked, false)
|
assert.Equal(t, account.Locked, false)
|
||||||
})
|
})
|
||||||
t.Run("and fetch by username", func(t *testing.T) {
|
t.Run("and fetch by username", func(t *testing.T) {
|
||||||
account, err := service.GetByUsername(username)
|
account, err := s.GetByUsername(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to get account after creation: %v", err)
|
t.Errorf("Failed to get account after creation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +115,7 @@ func Test_Account(t *testing.T) {
|
||||||
assert.Equal(t, account.Locked, false)
|
assert.Equal(t, account.Locked, false)
|
||||||
})
|
})
|
||||||
t.Run("and fetch by email", func(t *testing.T) {
|
t.Run("and fetch by email", func(t *testing.T) {
|
||||||
account, err := service.GetByEmail(email)
|
account, err := s.GetByEmail(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to get account after creation: %v", err)
|
t.Errorf("Failed to get account after creation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +131,7 @@ func Test_Account(t *testing.T) {
|
||||||
|
|
||||||
t.Run("number of accounts should increment", func(t *testing.T) {
|
t.Run("number of accounts should increment", func(t *testing.T) {
|
||||||
t.Run("count is one", func(t *testing.T) {
|
t.Run("count is one", func(t *testing.T) {
|
||||||
if num, err := service.GetCount(); err != nil {
|
if num, err := s.GetCount(); err != nil {
|
||||||
t.Errorf("Failed to get number of accounts: %v", err)
|
t.Errorf("Failed to get number of accounts: %v", err)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, num, 1)
|
assert.Equal(t, num, 1)
|
||||||
|
|
@ -121,8 +139,8 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("service returns array with one account", func(t *testing.T) {
|
t.Run("service returns array with one account", func(t *testing.T) {
|
||||||
if accounts, err := service.GetAll(); err != nil {
|
if accounts, err := s.GetAll(); err != nil {
|
||||||
t.Errorf("Failed to get number of accounts: %v", err)
|
t.Errorf("Failed to get accounts: %v", err)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, len(accounts), 1)
|
assert.Equal(t, len(accounts), 1)
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +148,7 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can't create duplicate account", func(t *testing.T) {
|
t.Run("can't create duplicate account", func(t *testing.T) {
|
||||||
_, err := service.Create(username, password, &email, &avatarURL)
|
_, err := s.Create(username, password, &email, &avatarURL)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Duplicate account was created")
|
t.Error("Duplicate account was created")
|
||||||
}
|
}
|
||||||
|
|
@ -138,11 +156,11 @@ func Test_Account(t *testing.T) {
|
||||||
|
|
||||||
t.Run("can change username", func(t *testing.T) {
|
t.Run("can change username", func(t *testing.T) {
|
||||||
testUsername := "some_other_name"
|
testUsername := "some_other_name"
|
||||||
if err := service.ChangeUsername(id, testUsername); err != nil {
|
if err := s.ChangeUsername(id, testUsername); err != nil {
|
||||||
t.Errorf("Failed to change username: %v", err)
|
t.Errorf("Failed to change username: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -151,19 +169,21 @@ func Test_Account(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("but not to an invalid value", func(t *testing.T) {
|
t.Run("but not to an invalid value", func(t *testing.T) {
|
||||||
if err := service.ChangeUsername(id, ""); err == nil {
|
if err := s.ChangeUsername(id, ""); err == nil {
|
||||||
t.Error("Could change username to invalid value")
|
t.Error("Could change username to invalid value")
|
||||||
|
} else if !errors.IsValidationError(err) {
|
||||||
|
t.Error("Error is not validation error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can change password", func(t *testing.T) {
|
t.Run("can change password", func(t *testing.T) {
|
||||||
testPassword := "other more different password"
|
testPassword := "other more different password"
|
||||||
if err := service.ChangePassword(id, testPassword); err != nil {
|
if err := s.ChangePassword(id, testPassword); err != nil {
|
||||||
t.Errorf("Failed to change password: %v", err)
|
t.Errorf("Failed to change password: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -172,19 +192,21 @@ func Test_Account(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("but not to an invalid value", func(t *testing.T) {
|
t.Run("but not to an invalid value", func(t *testing.T) {
|
||||||
if err := service.ChangePassword(id, ""); err == nil {
|
if err := s.ChangePassword(id, ""); err == nil {
|
||||||
t.Error("Could change password to invalid value")
|
t.Error("Could change password to invalid value")
|
||||||
|
} else if !errors.IsValidationError(err) {
|
||||||
|
t.Error("Error is not validation error")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can change email", func(t *testing.T) {
|
t.Run("can change email", func(t *testing.T) {
|
||||||
testEmail := "brandnewemail@for.me"
|
testEmail := "brandnewemail@for.me"
|
||||||
if err := service.ChangeEmail(id, testEmail); err != nil {
|
if err := s.ChangeEmail(id, testEmail); err != nil {
|
||||||
t.Errorf("Failed to change email: %v", err)
|
t.Errorf("Failed to change email: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -194,11 +216,11 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can remove email", func(t *testing.T) {
|
t.Run("can remove email", func(t *testing.T) {
|
||||||
if err := service.ChangeEmail(id, ""); err != nil {
|
if err := s.ChangeEmail(id, ""); err != nil {
|
||||||
t.Errorf("Failed to change email: %v", err)
|
t.Errorf("Failed to change email: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -209,11 +231,11 @@ func Test_Account(t *testing.T) {
|
||||||
|
|
||||||
t.Run("can change avatar URL", func(t *testing.T) {
|
t.Run("can change avatar URL", func(t *testing.T) {
|
||||||
testAvatarURL := "/img/some-other-avatar.webp"
|
testAvatarURL := "/img/some-other-avatar.webp"
|
||||||
if err := service.ChangeAvatarURL(id, testAvatarURL); err != nil {
|
if err := s.ChangeAvatarURL(id, testAvatarURL); err != nil {
|
||||||
t.Errorf("Failed to change avatar URL: %v", err)
|
t.Errorf("Failed to change avatar URL: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -223,11 +245,11 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can remove avatar URL", func(t *testing.T) {
|
t.Run("can remove avatar URL", func(t *testing.T) {
|
||||||
if err := service.ChangeAvatarURL(id, ""); err != nil {
|
if err := s.ChangeAvatarURL(id, ""); err != nil {
|
||||||
t.Errorf("Failed to change avatar URL: %v", err)
|
t.Errorf("Failed to change avatar URL: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -237,13 +259,13 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can increment auth failures", func(t *testing.T) {
|
t.Run("can increment auth failures", func(t *testing.T) {
|
||||||
if num, err := service.IncrementFails(id); err != nil {
|
if num, err := s.IncrementFails(id); err != nil {
|
||||||
t.Errorf("Failed to increment account auth failures: %v", err)
|
t.Errorf("Failed to increment account auth failures: %v", err)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, num, 1)
|
assert.Equal(t, num, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -253,11 +275,11 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can reset auth failures", func(t *testing.T) {
|
t.Run("can reset auth failures", func(t *testing.T) {
|
||||||
if err := service.ResetFails(id); err != nil {
|
if err := s.ResetFails(id); err != nil {
|
||||||
t.Errorf("Failed to reset account auth failures: %v", err)
|
t.Errorf("Failed to reset account auth failures: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -267,11 +289,11 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can lock account", func(t *testing.T) {
|
t.Run("can lock account", func(t *testing.T) {
|
||||||
if err := service.Lock(id); err != nil {
|
if err := s.Lock(id); err != nil {
|
||||||
t.Errorf("Failed to lock account: %v", err)
|
t.Errorf("Failed to lock account: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -281,11 +303,11 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can unlock account", func(t *testing.T) {
|
t.Run("can unlock account", func(t *testing.T) {
|
||||||
if err := service.Unlock(id); err != nil {
|
if err := s.Unlock(id); err != nil {
|
||||||
t.Errorf("Failed to unlock account: %v", err)
|
t.Errorf("Failed to unlock account: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
t.Errorf("Failed to get account: %v", err)
|
t.Errorf("Failed to get account: %v", err)
|
||||||
} else if account == nil {
|
} else if account == nil {
|
||||||
t.Error("Account is nil after update")
|
t.Error("Account is nil after update")
|
||||||
|
|
@ -295,12 +317,14 @@ func Test_Account(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can delete account", func(t *testing.T) {
|
t.Run("can delete account", func(t *testing.T) {
|
||||||
if err = service.Delete(id); err != nil {
|
if err = s.Delete(id); err != nil {
|
||||||
t.Errorf("Failed to delete account: %v", err)
|
t.Errorf("Failed to delete account: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account, err := service.GetByID(id); err != nil {
|
if account, err := s.GetByID(id); err != nil {
|
||||||
|
if !errors.IsNotExistError(err) {
|
||||||
t.Errorf("Failed to get account after deletion: %v", err)
|
t.Errorf("Failed to get account after deletion: %v", err)
|
||||||
|
}
|
||||||
} else if account != nil {
|
} else if account != nil {
|
||||||
t.Error("Account still exists after deletion")
|
t.Error("Account still exists after deletion")
|
||||||
}
|
}
|
||||||
|
|
@ -308,56 +332,76 @@ func Test_Account(t *testing.T) {
|
||||||
|
|
||||||
t.Run("can't create an account with invalid", func(t *testing.T) {
|
t.Run("can't create an account with invalid", func(t *testing.T) {
|
||||||
t.Run("username", func(t *testing.T) {
|
t.Run("username", func(t *testing.T) {
|
||||||
if _, err := service.Create("", password, &email, &avatarURL); err == nil {
|
if _, err := s.Create("", password, &email, &avatarURL); err == nil {
|
||||||
t.Error("Could create account with empty username")
|
t.Error("Could create account with empty username")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("password", func(t *testing.T) {
|
t.Run("password", func(t *testing.T) {
|
||||||
if _, err := service.Create(username, "", &email, &avatarURL); err == nil {
|
if _, err := s.Create(username, "", &email, &avatarURL); err == nil {
|
||||||
t.Error("Could create account with empty password")
|
t.Error("Could create account with empty password")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("email", func(t *testing.T) {
|
t.Run("email", func(t *testing.T) {
|
||||||
testEmail := ""
|
testEmail := ""
|
||||||
if _, err := service.Create(username, password, &testEmail, &avatarURL); err == nil {
|
if _, err := s.Create(username, password, &testEmail, &avatarURL); err == nil {
|
||||||
t.Error("Could create account with empty (non-nil) email")
|
t.Error("Could create account with empty (non-nil) email")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("can't fetch account that doesn't exist", func(t *testing.T) {
|
||||||
|
t.Run("by ID", func(t *testing.T) {
|
||||||
|
if account, err := s.GetByID("adsginh534g9405gmb40i9bm"); err != nil {
|
||||||
|
if !errors.IsNotExistError(err) { t.Errorf("Failed to get account: %v", err) }
|
||||||
|
} else if account != nil {
|
||||||
|
t.Error("Could fetch non-existent account")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("by username", func(t *testing.T) {
|
||||||
|
if account, err := s.GetByUsername("adsginh534g9405gmb40i9bm"); err != nil {
|
||||||
|
if !errors.IsNotExistError(err) { t.Errorf("Failed to get account: %v", err) }
|
||||||
|
} else if account != nil {
|
||||||
|
t.Error("Could fetch non-existent account")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("email", func(t *testing.T) {
|
||||||
|
if account, err := s.GetByEmail("adsginh534g9405gmb40i9bm"); err != nil {
|
||||||
|
if !errors.IsNotExistError(err) { t.Errorf("Failed to get account: %v", err) }
|
||||||
|
} else if account != nil {
|
||||||
|
t.Error("Could fetch non-existent account")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("can't update account that doesn't exist", func(t *testing.T) {
|
t.Run("can't update account that doesn't exist", func(t *testing.T) {
|
||||||
garbageAccountID := "adsginh534g9405gmb40i9bm"
|
garbageAccountID := "adsginh534g9405gmb40i9bm"
|
||||||
|
|
||||||
t.Run("username", func(t *testing.T) {
|
t.Run("username", func(t *testing.T) {
|
||||||
if err := service.ChangeUsername(garbageAccountID, "some-username"); err == nil {
|
if err := s.ChangeUsername(garbageAccountID, "some-username"); err == nil {
|
||||||
t.Error("Could update non-existent account's username")
|
t.Error("Could update non-existent account's username")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("password", func(t *testing.T) {
|
t.Run("password", func(t *testing.T) {
|
||||||
if err := service.ChangePassword(garbageAccountID, "some-password"); err == nil {
|
if err := s.ChangePassword(garbageAccountID, "some-password"); err == nil {
|
||||||
t.Error("Could update non-existent account's password")
|
t.Error("Could update non-existent account's password")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("email", func(t *testing.T) {
|
t.Run("email", func(t *testing.T) {
|
||||||
if err := service.ChangeEmail(garbageAccountID, "some-email@real.gov"); err == nil {
|
if err := s.ChangeEmail(garbageAccountID, "some-email@real.gov"); err == nil {
|
||||||
t.Error("Could update non-existent account's email")
|
t.Error("Could update non-existent account's email")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("avatar URL", func(t *testing.T) {
|
t.Run("avatar URL", func(t *testing.T) {
|
||||||
if err := service.ChangeAvatarURL(garbageAccountID, "/img/null.webp"); err == nil {
|
if err := s.ChangeAvatarURL(garbageAccountID, "/img/null.webp"); err == nil {
|
||||||
t.Error("Could update non-existent account's avatar URL")
|
t.Error("Could update non-existent account's avatar URL")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
if err := service.Delete(id); err != nil {
|
|
||||||
t.Errorf("Failed to clean up test case: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package log
|
||||||
import (
|
import (
|
||||||
"arimelody-web/model"
|
"arimelody-web/model"
|
||||||
repository "arimelody-web/repository/log"
|
repository "arimelody-web/repository/log"
|
||||||
|
"arimelody-web/errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -43,7 +44,10 @@ func (s *LogService) Warn(logType string, format string, args ...any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LogService) Fetch(id string) (*model.Log, error) {
|
func (s *LogService) Fetch(id string) (*model.Log, error) {
|
||||||
return s.repo.Get(id)
|
log, err := s.repo.Get(id)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
if log == nil { return nil, errors.NewNotExistError("Log does not exist") }
|
||||||
|
return log, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LogService) Search(
|
func (s *LogService) Search(
|
||||||
|
|
|
||||||
82
service/music/artist.go
Normal file
82
service/music/artist.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
|
"arimelody-web/model"
|
||||||
|
"arimelody-web/service/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *MusicService) GetAllArtists() ([]*model.Artist, error) {
|
||||||
|
return s.repo.GetAllArtists()
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetArtistCount() (int, error) {
|
||||||
|
return s.repo.GetArtistCount()
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetArtistByID(id string) (*model.Artist, error) {
|
||||||
|
artist, err := s.repo.GetArtistByID(id)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
if artist == nil { return nil, errors.NewNotExistError("Artist does not exist") }
|
||||||
|
return artist, nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetArtistsNotOnRelease(releaseID string) ([]*model.Artist, error) {
|
||||||
|
return s.repo.GetArtistsNotOnRelease(releaseID)
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetArtistCredits(artistID string, showHidden bool) ([]*model.Credit, error) {
|
||||||
|
return s.repo.GetArtistCredits(artistID, showHidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) CreateArtist(
|
||||||
|
id string,
|
||||||
|
name string,
|
||||||
|
website string,
|
||||||
|
avatar string,
|
||||||
|
) error {
|
||||||
|
if len(id) == 0 { return errors.NewValidationError("Artist ID cannot be empty") }
|
||||||
|
if !validator.ValidateID(id) { return errors.NewValidationError("Artist ID contains invalid characters") }
|
||||||
|
if len(name) == 0 { return errors.NewValidationError("Artist name cannot be empty") }
|
||||||
|
|
||||||
|
if err := s.repo.CreateArtist(id, name, website, avatar); err != nil { return err }
|
||||||
|
s.log.Printf("Created new artist '%s' (%s)", name, id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) UpdateArtist(artist *model.Artist) error {
|
||||||
|
if len(artist.ID) == 0 { return errors.NewValidationError("Artist ID cannot be empty") }
|
||||||
|
if !validator.ValidateID(artist.ID) { return errors.NewValidationError("Artist ID contains invalid characters") }
|
||||||
|
if len(artist.Name) == 0 { return errors.NewValidationError("Artist name cannot be empty") }
|
||||||
|
if err := s.repo.UpdateArtist(artist); err != nil { return err }
|
||||||
|
s.log.Printf("Updated artist %s", artist.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) UpdateArtistID(oldID string, newID string) error {
|
||||||
|
if len(newID) == 0 { return errors.NewValidationError("Artist ID cannot be empty") }
|
||||||
|
if !validator.ValidateID(newID) { return errors.NewValidationError("Artist ID contains invalid characters") }
|
||||||
|
if err := s.repo.UpdateArtistID(oldID, newID); err != nil { return err }
|
||||||
|
s.log.Printf("Updated artist ID %s to %s", oldID, newID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateArtistName(id string, name string) error {
|
||||||
|
if len(name) == 0 { return errors.NewValidationError("Artist name cannot be empty") }
|
||||||
|
if err := s.repo.UpdateArtistName(id, name); err != nil { return err }
|
||||||
|
s.log.Printf("Updated artist %s name to %s", id, name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateArtistWebsite(id string, website string) error {
|
||||||
|
if err := s.repo.UpdateArtistWebsite(id, website); err != nil { return err }
|
||||||
|
s.log.Printf("Updated artist %s website to %s", id, website)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateArtistAvatar(id string, avatar string) error {
|
||||||
|
if err := s.repo.UpdateArtistAvatar(id, avatar); err != nil { return err }
|
||||||
|
s.log.Printf("Updated artist %s avatar to %s", id, avatar)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) DeleteArtist(id string) error {
|
||||||
|
deletedID, err := s.repo.DeleteArtist(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if deletedID == "" { return errors.NewNotExistError("Artist does not exist") }
|
||||||
|
s.log.Printf("Deleted artist %s", id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
225
service/music/artist_test.go
Normal file
225
service/music/artist_test.go
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
package music_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
|
"arimelody-web/model"
|
||||||
|
repository "arimelody-web/repository/music"
|
||||||
|
service "arimelody-web/service/music"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Artist(t *testing.T) {
|
||||||
|
devNullFile, err := os.OpenFile(os.DevNull, os.O_RDWR, 0666)
|
||||||
|
if err != nil { panic(err) }
|
||||||
|
defer devNullFile.Close()
|
||||||
|
|
||||||
|
repo := repository.NewMusicRepositoryMemory(
|
||||||
|
make([]*model.Artist, 0),
|
||||||
|
make([]*model.Release, 0),
|
||||||
|
make([]*model.Track, 0),
|
||||||
|
)
|
||||||
|
s = service.NewMusicService(
|
||||||
|
repo,
|
||||||
|
log.New(devNullFile, "", model.DEFAULT_LOG_FLAGS),
|
||||||
|
)
|
||||||
|
|
||||||
|
id := "cool-artist"
|
||||||
|
name := "Cool Artist"
|
||||||
|
website := "artist.arimelody.space"
|
||||||
|
avatarURL := "/img/cool-artist.webp"
|
||||||
|
|
||||||
|
t.Run("artists should start empty", func(t *testing.T) {
|
||||||
|
t.Run("count is zero", func(t *testing.T) {
|
||||||
|
if num, err := s.GetArtistCount(); err != nil {
|
||||||
|
t.Errorf("Failed to get number of artists: %v", err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, num, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("service returns empty array", func(t *testing.T) {
|
||||||
|
if artists, err := s.GetAllArtists(); err != nil {
|
||||||
|
t.Errorf("Failed to get artists: %v", err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, len(artists), 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can create artist", func(t *testing.T) {
|
||||||
|
if err := s.CreateArtist(id, name, website, avatarURL); err != nil {
|
||||||
|
t.Errorf("Failed to create artist: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("but not with an invalid ID", func(t *testing.T) {
|
||||||
|
if err := s.CreateArtist("", name, website, avatarURL); err == nil {
|
||||||
|
t.Error("Created artist with invalid ID")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("but not with an invalid name", func(t *testing.T) {
|
||||||
|
if err := s.CreateArtist("test-artist", "", website, avatarURL); err == nil {
|
||||||
|
t.Error("Created artist with invalid name")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("and retrieve it", func(t *testing.T) {
|
||||||
|
repoArtist, err := s.GetArtistByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get artist: %v", err) }
|
||||||
|
|
||||||
|
assert.Equal(t, repoArtist.ID, id)
|
||||||
|
assert.Equal(t, repoArtist.Name, name)
|
||||||
|
assert.Equal(t, repoArtist.Website, website)
|
||||||
|
assert.Equal(t, repoArtist.Avatar, avatarURL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("number of artists should increment", func(t *testing.T) {
|
||||||
|
t.Run("count is one", func(t *testing.T) {
|
||||||
|
if num, err := s.GetArtistCount(); err != nil {
|
||||||
|
t.Errorf("Failed to get number of artists: %v", err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, num, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("service returns array with one artist", func(t *testing.T) {
|
||||||
|
if artists, err := s.GetAllArtists(); err != nil {
|
||||||
|
t.Errorf("Failed to get artists: %v", err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, len(artists), 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
releaseID := "test-release"
|
||||||
|
t.Run("can get artists not on a release", func(t *testing.T) {
|
||||||
|
if err := s.CreateRelease(releaseID, "test", string(model.Single), time.Now(), ""); err != nil {
|
||||||
|
t.Errorf("Failed to create release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
artists, err := s.GetArtistsNotOnRelease(releaseID)
|
||||||
|
if err != nil { t.Errorf("Failed to get artists: %v", err) }
|
||||||
|
|
||||||
|
assert.Assert(t, slices.ContainsFunc(artists, func(artist *model.Artist) bool {
|
||||||
|
return artist.ID == id
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
credit := &model.Credit{
|
||||||
|
Artist: &model.Artist{ ID: id, Name: name, Website: website, Avatar: avatarURL },
|
||||||
|
Role: "did a lot of stuff",
|
||||||
|
Primary: true,
|
||||||
|
}
|
||||||
|
t.Run("can get artist credits", func(t *testing.T) {
|
||||||
|
if err := s.UpdateReleaseCredits(releaseID, []*model.Credit{ credit }); err != nil {
|
||||||
|
t.Errorf("Failed to update release credits: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
credits, err := s.GetArtistCredits(id, true)
|
||||||
|
if err != nil { t.Errorf("Failed to get credits: %v", err) }
|
||||||
|
|
||||||
|
index := slices.IndexFunc(credits, func(credit *model.Credit) bool {
|
||||||
|
return credit.Artist.ID == id
|
||||||
|
})
|
||||||
|
assert.Assert(t, index != -1)
|
||||||
|
assert.Equal(t, credits[index].Artist.ID, id)
|
||||||
|
assert.Equal(t, credits[index].Artist.Name, name)
|
||||||
|
assert.Equal(t, credits[index].Artist.Website, website)
|
||||||
|
assert.Equal(t, credits[index].Artist.Avatar, avatarURL)
|
||||||
|
assert.Equal(t, credits[index].Role, credit.Role)
|
||||||
|
assert.Equal(t, credits[index].Primary, credit.Primary)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update artist", func(t *testing.T) {
|
||||||
|
testName := "this name is only temporary"
|
||||||
|
testWebsite := "https://test.example.org"
|
||||||
|
testAvatar := "/img/test-avatar.webp"
|
||||||
|
if err := s.UpdateArtist(&model.Artist{
|
||||||
|
ID: id,
|
||||||
|
Name: testName,
|
||||||
|
Website: testWebsite,
|
||||||
|
Avatar: testAvatar,
|
||||||
|
}); err != nil {
|
||||||
|
t.Errorf("Failed to update artist: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
artist, err := s.GetArtistByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get artist: %v", err) }
|
||||||
|
assert.Equal(t, artist.Name, testName)
|
||||||
|
assert.Equal(t, artist.Website, testWebsite)
|
||||||
|
assert.Equal(t, artist.GetAvatar(), testAvatar)
|
||||||
|
|
||||||
|
t.Run("but not with invalid name", func(t *testing.T) {
|
||||||
|
if err := s.UpdateArtist(&model.Artist{
|
||||||
|
ID: id,
|
||||||
|
Name: "",
|
||||||
|
}); err == nil {
|
||||||
|
t.Errorf("Updated artist name to invalid value")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update artist name", func(t *testing.T) {
|
||||||
|
if err := s.UpdateArtistName(id, name); err != nil {
|
||||||
|
t.Errorf("Failed to update artist: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
artist, err := s.GetArtistByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get artist: %v", err) }
|
||||||
|
assert.Equal(t, artist.Name, name)
|
||||||
|
|
||||||
|
t.Run("but not with invalid value", func(t *testing.T) {
|
||||||
|
if err := s.UpdateArtistName(id, ""); err == nil {
|
||||||
|
t.Errorf("Updated artist name to invalid value")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update artist website", func(t *testing.T) {
|
||||||
|
if err := s.UpdateArtistWebsite(id, website); err != nil {
|
||||||
|
t.Errorf("Failed to update artist: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
artist, err := s.GetArtistByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get artist: %v", err) }
|
||||||
|
assert.Equal(t, artist.Website, website)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update artist avatar", func(t *testing.T) {
|
||||||
|
if err := s.UpdateArtistAvatar(id, ""); err != nil {
|
||||||
|
t.Errorf("Failed to update artist: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
artist, err := s.GetArtistByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get artist: %v", err) }
|
||||||
|
assert.Equal(t, artist.GetAvatar(), model.DEFAULT_AVATAR_URL)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can delete artist", func(t *testing.T) {
|
||||||
|
if err := s.DeleteArtist(id); err != nil {
|
||||||
|
t.Errorf("Failed to delete artist: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("no longer exists", func(t *testing.T) {
|
||||||
|
if artist, err := s.GetArtistByID(id); err == nil {
|
||||||
|
t.Error("No error getting artist")
|
||||||
|
} else if artist != nil {
|
||||||
|
t.Error("Artist with this ID still exists")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("but not one that doesn't exist", func(t *testing.T) {
|
||||||
|
if err := s.DeleteArtist("some-garbage-id"); err == nil {
|
||||||
|
if !errors.IsNotExistError(err) {
|
||||||
|
t.Errorf("Failed to delete artist: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
18
service/music/music.go
Normal file
18
service/music/music.go
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
repository "arimelody-web/repository/music"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MusicService struct {
|
||||||
|
repo repository.MusicRepository
|
||||||
|
log *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMusicService(repo repository.MusicRepository, logger *log.Logger) *MusicService {
|
||||||
|
return &MusicService{
|
||||||
|
repo: repo,
|
||||||
|
log: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
9
service/music/music_test.go
Normal file
9
service/music/music_test.go
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
package music_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
service "arimelody-web/service/music"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
s *service.MusicService
|
||||||
|
)
|
||||||
180
service/music/release.go
Normal file
180
service/music/release.go
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
|
"arimelody-web/model"
|
||||||
|
"arimelody-web/service/validator"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hydrates a release with its additional data (credits, tracks, links)
|
||||||
|
func (s *MusicService) fillRelease(release *model.Release) error {
|
||||||
|
credits, err := s.GetReleaseCredits(release.ID)
|
||||||
|
if err != nil { return fmt.Errorf("credits: %s", err) }
|
||||||
|
release.Credits = append(release.Credits, credits...)
|
||||||
|
|
||||||
|
tracks, err := s.GetReleaseTracks(release.ID)
|
||||||
|
if err != nil { return fmt.Errorf("tracks: %s", err) }
|
||||||
|
release.Tracks = append(release.Tracks, tracks...)
|
||||||
|
|
||||||
|
links, err := s.GetReleaseLinks(release.ID)
|
||||||
|
if err != nil { return fmt.Errorf("links: %s", err) }
|
||||||
|
release.Links = append(release.Links, links...)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) GetAllReleases(onlyVisible bool, limit int) ([]*model.Release, error) {
|
||||||
|
return s.repo.GetAllReleases(onlyVisible, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) GetAllFullReleases(onlyVisible bool, limit int) ([]*model.Release, error) {
|
||||||
|
releases, err := s.repo.GetAllReleases(onlyVisible, limit)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
for _, release := range releases {
|
||||||
|
if err := s.fillRelease(release); err != nil { return nil, err }
|
||||||
|
}
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) GetReleaseCount(onlyVisible bool) (int, error) {
|
||||||
|
return s.repo.GetReleaseCount(onlyVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) GetReleaseByID(id string) (*model.Release, error) {
|
||||||
|
release, err := s.repo.GetReleaseByID(id)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
if release == nil { return nil, errors.NewNotExistError("Release does not exist") }
|
||||||
|
return release, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) GetFullReleaseByID(id string) (*model.Release, error) {
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
if err := s.fillRelease(release); err != nil { return nil, err }
|
||||||
|
return release, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) GetReleaseTracks(id string) ([]*model.Track, error) {
|
||||||
|
return s.repo.GetReleaseTracks(id)
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetReleaseCredits(id string) ([]*model.Credit, error) {
|
||||||
|
return s.repo.GetReleaseCredits(id)
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetReleaseLinks(id string) ([]*model.Link, error) {
|
||||||
|
return s.repo.GetReleaseLinks(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) CreateRelease(
|
||||||
|
id string,
|
||||||
|
title string,
|
||||||
|
releaseType string,
|
||||||
|
releaseDate time.Time,
|
||||||
|
artworkURL string,
|
||||||
|
) error {
|
||||||
|
if len(id) == 0 { return errors.NewValidationError("Release ID cannot be empty") }
|
||||||
|
if !validator.ValidateID(id) { return errors.NewValidationError("Release ID contains invalid characters") }
|
||||||
|
if len(title) == 0 { return errors.NewValidationError("Release title cannot be empty") }
|
||||||
|
validReleaseType, ok := model.ValidReleaseType(releaseType)
|
||||||
|
if !ok { return errors.NewValidationError("Invalid release type") }
|
||||||
|
|
||||||
|
if err := s.repo.CreateRelease(id, title, validReleaseType, releaseDate, artworkURL); err != nil { return err }
|
||||||
|
s.log.Printf("Created new release '%s' (%s)", title, id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) UpdateRelease(release *model.Release) error {
|
||||||
|
if len(release.ID) == 0 { return errors.NewValidationError("Release ID cannot be empty") }
|
||||||
|
if !validator.ValidateID(release.ID) { return errors.NewValidationError("Release ID contains invalid characters") }
|
||||||
|
if len(release.Title) == 0 { return errors.NewValidationError("Release title cannot be empty") }
|
||||||
|
|
||||||
|
if err := s.repo.UpdateRelease(release); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' (%s)", release.Title, release.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateReleaseID(oldID string, newID string) error {
|
||||||
|
if len(newID) == 0 { return errors.NewValidationError("Release ID cannot be empty") }
|
||||||
|
if !validator.ValidateID(newID) { return errors.NewValidationError("Release ID contains invalid characters") }
|
||||||
|
if err := s.repo.UpdateReleaseID(oldID, newID); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release ID %s to %s", oldID, newID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateReleaseVisibility(id string, visible bool) error {
|
||||||
|
if err := s.repo.UpdateReleaseVisibility(id, visible); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' visibility to %t", id, visible)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateReleaseTitle(id string, title string) error {
|
||||||
|
if len(title) == 0 { return errors.NewValidationError("Release title cannot be empty") }
|
||||||
|
if err := s.repo.UpdateReleaseTitle(id, title); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' title to %s", id, title)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateReleaseDescription(id string, description string) error {
|
||||||
|
if err := s.repo.UpdateReleaseDescription(id, description); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' description to %s", id, description)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateReleaseType(id string, releaseType string) error {
|
||||||
|
validReleaseType, ok := model.ValidReleaseType(releaseType)
|
||||||
|
if !ok { return errors.NewValidationError("Invalid release type") }
|
||||||
|
if err := s.repo.UpdateReleaseType(id, validReleaseType); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' type to %s", id, releaseType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateReleaseDate(id string, releaseDate time.Time) error {
|
||||||
|
if err := s.repo.UpdateReleaseDate(id, releaseDate); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' date to %s", id, releaseDate.Format(time.RFC3339))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateReleaseArtwork(id string, artwork string) error {
|
||||||
|
if err := s.repo.UpdateReleaseArtwork(id, artwork); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' artwork to %s", id, artwork)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateReleaseBuyInfo(id string, buyName string, buyLink string) error {
|
||||||
|
if err := s.repo.UpdateReleaseBuyInfo(id, buyName, buyLink); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' buy info (name='%s', link='%s')", id, buyName, buyLink)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateReleaseCopyright(id string, copyright string, url string) error {
|
||||||
|
if err := s.repo.UpdateReleaseCopyright(id, copyright, url); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' copyright (copyright='%s', url='%s')", id, copyright, url)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) UpdateReleaseTracks(id string, newTrackIDs []string) error {
|
||||||
|
for _, id := range newTrackIDs {
|
||||||
|
if len(id) == 0 { return errors.NewValidationError("Track IDs cannot be empty") }
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.UpdateReleaseTracks(id, newTrackIDs); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' tracks (%d tracks)", id, len(newTrackIDs))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) UpdateReleaseCredits(id string, newCredits []*model.Credit) error {
|
||||||
|
if err := s.repo.UpdateReleaseCredits(id, newCredits); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' credits (%d credits)", id, len(newCredits))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) UpdateReleaseLinks(id string, newLinks []*model.Link) error {
|
||||||
|
for _, link := range newLinks {
|
||||||
|
if len(link.Name) == 0 { return errors.NewValidationError("Link names cannot be empty") }
|
||||||
|
if len(link.URL) == 0 { return errors.NewValidationError("Link URLs cannot be empty") }
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.UpdateReleaseLinks(id, newLinks); err != nil { return err }
|
||||||
|
s.log.Printf("Updated release '%s' links (%d links)", id, len(newLinks))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) DeleteRelease(id string) error {
|
||||||
|
deletedID, err := s.repo.DeleteRelease(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if deletedID == "" { return errors.NewNotExistError("Release does not exist") }
|
||||||
|
s.log.Printf("Deleted release '%s'", id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
374
service/music/release_test.go
Normal file
374
service/music/release_test.go
Normal file
|
|
@ -0,0 +1,374 @@
|
||||||
|
package music_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/errors"
|
||||||
|
"arimelody-web/model"
|
||||||
|
repository "arimelody-web/repository/music"
|
||||||
|
service "arimelody-web/service/music"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Release(t *testing.T) {
|
||||||
|
devNullFile, err := os.OpenFile(os.DevNull, os.O_RDWR, 0666)
|
||||||
|
if err != nil { panic(err) }
|
||||||
|
defer devNullFile.Close()
|
||||||
|
|
||||||
|
repo := repository.NewMusicRepositoryMemory(
|
||||||
|
make([]*model.Artist, 0),
|
||||||
|
make([]*model.Release, 0),
|
||||||
|
make([]*model.Track, 0),
|
||||||
|
)
|
||||||
|
s = service.NewMusicService(
|
||||||
|
repo,
|
||||||
|
log.New(devNullFile, "", model.DEFAULT_LOG_FLAGS),
|
||||||
|
)
|
||||||
|
|
||||||
|
id := "cool-release"
|
||||||
|
title := "Cool Release"
|
||||||
|
releaseType := model.Album
|
||||||
|
releaseDate := time.Now()
|
||||||
|
artworkURL := "/img/some-cool-artwork.webp"
|
||||||
|
|
||||||
|
t.Run("releases should start empty", func(t *testing.T) {
|
||||||
|
t.Run("count is zero", func(t *testing.T) {
|
||||||
|
if num, err := s.GetReleaseCount(false); err != nil {
|
||||||
|
t.Errorf("Failed to get number of releases: %v", err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, num, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("service returns empty array", func(t *testing.T) {
|
||||||
|
if releases, err := s.GetAllReleases(false, 0); err != nil {
|
||||||
|
t.Errorf("Failed to get releases: %v", err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, len(releases), 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can create release", func(t *testing.T) {
|
||||||
|
if err := s.CreateRelease(id, title, string(releaseType), releaseDate, artworkURL); err != nil {
|
||||||
|
t.Errorf("Failed to create release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("but not with an invalid ID", func(t *testing.T) {
|
||||||
|
if err := s.CreateRelease("", title, string(releaseType), releaseDate, artworkURL); err == nil {
|
||||||
|
t.Error("Created release with invalid ID")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("but not with an invalid title", func(t *testing.T) {
|
||||||
|
if err := s.CreateRelease("test-release", "", string(releaseType), releaseDate, artworkURL); err == nil {
|
||||||
|
t.Error("Created release with invalid title")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("but not with an invalid type", func(t *testing.T) {
|
||||||
|
if err := s.CreateRelease("test-release", "", "garbage-type", releaseDate, artworkURL); err == nil {
|
||||||
|
t.Error("Created release with invalid type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("and retrieve it", func(t *testing.T) {
|
||||||
|
repoRelease, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if repoRelease == nil { t.Error("Release does not exist") }
|
||||||
|
|
||||||
|
assert.Equal(t, repoRelease.ID, id)
|
||||||
|
assert.Equal(t, repoRelease.Title, title)
|
||||||
|
assert.Equal(t, repoRelease.ReleaseType, releaseType)
|
||||||
|
assert.Equal(t, repoRelease.ReleaseDate, releaseDate)
|
||||||
|
assert.Equal(t, repoRelease.GetArtwork(), artworkURL)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should not be visible by default", func(t *testing.T) {
|
||||||
|
releases, err := s.GetAllReleases(true, 0)
|
||||||
|
if err != nil { t.Errorf("Failed to get releases: %v", err) }
|
||||||
|
|
||||||
|
assert.Equal(t, slices.ContainsFunc(releases, func(release *model.Release) bool {
|
||||||
|
return release.ID == id
|
||||||
|
}), false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("number of releases should increment", func(t *testing.T) {
|
||||||
|
t.Run("count is one", func(t *testing.T) {
|
||||||
|
if num, err := s.GetReleaseCount(false); err != nil {
|
||||||
|
t.Errorf("Failed to get number of releases: %v", err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, num, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("service returns array with one release", func(t *testing.T) {
|
||||||
|
if releases, err := s.GetAllReleases(false, 0); err != nil {
|
||||||
|
t.Errorf("Failed to get releases: %v", err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, len(releases), 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update release", func(t *testing.T) {
|
||||||
|
testTitle := "this title is only temporary"
|
||||||
|
testReleaseType := model.Compilation
|
||||||
|
testReleaseDate := time.Now().AddDate(0, 0, 10)
|
||||||
|
testArtworkURL := "/img/test-artwork.webp"
|
||||||
|
if err := s.UpdateRelease(&model.Release{
|
||||||
|
ID: id,
|
||||||
|
Title: testTitle,
|
||||||
|
ReleaseType: testReleaseType,
|
||||||
|
ReleaseDate: testReleaseDate,
|
||||||
|
Artwork: testArtworkURL,
|
||||||
|
}); err != nil {
|
||||||
|
t.Errorf("Failed to update release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if release == nil { t.Error("Release does not exist after update") }
|
||||||
|
assert.Equal(t, release.Title, testTitle)
|
||||||
|
assert.Equal(t, release.ReleaseType, testReleaseType)
|
||||||
|
assert.Equal(t, release.ReleaseDate, testReleaseDate)
|
||||||
|
assert.Equal(t, release.GetArtwork(), testArtworkURL)
|
||||||
|
|
||||||
|
t.Run("but not with invalid title", func(t *testing.T) {
|
||||||
|
if err := s.UpdateRelease(&model.Release{
|
||||||
|
ID: id,
|
||||||
|
Title: "",
|
||||||
|
}); err == nil {
|
||||||
|
t.Errorf("Updated release title to invalid value")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update visibility", func(t *testing.T) {
|
||||||
|
if err := s.UpdateReleaseVisibility(id, true); err != nil {
|
||||||
|
t.Errorf("Failed to update release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if release == nil { t.Error("Release does not exist after update") }
|
||||||
|
assert.Equal(t, release.Visible, true)
|
||||||
|
|
||||||
|
t.Run("should be visible after updating", func(t *testing.T) {
|
||||||
|
releases, err := s.GetAllReleases(true, 0)
|
||||||
|
if err != nil { t.Errorf("Failed to get releases: %v", err) }
|
||||||
|
|
||||||
|
assert.Equal(t, slices.ContainsFunc(releases, func(release *model.Release) bool {
|
||||||
|
return release.ID == id
|
||||||
|
}), true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update title", func(t *testing.T) {
|
||||||
|
if err := s.UpdateReleaseTitle(id, title); err != nil {
|
||||||
|
t.Errorf("Failed to update release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if release == nil { t.Error("Release does not exist after update") }
|
||||||
|
assert.Equal(t, release.Title, title)
|
||||||
|
|
||||||
|
t.Run("but not with invalid value", func(t *testing.T) {
|
||||||
|
if err := s.UpdateReleaseTitle(id, ""); err == nil {
|
||||||
|
t.Errorf("Updated release title to invalid value")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update description", func(t *testing.T) {
|
||||||
|
testDescription := "an incredible and thought-provoking description"
|
||||||
|
if err := s.UpdateReleaseDescription(id, testDescription); err != nil {
|
||||||
|
t.Errorf("Failed to update release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if release == nil { t.Error("Release does not exist after update") }
|
||||||
|
assert.Equal(t, release.Description, testDescription)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update type", func(t *testing.T) {
|
||||||
|
testType := model.EP
|
||||||
|
if err := s.UpdateReleaseType(id, string(testType)); err != nil {
|
||||||
|
t.Errorf("Failed to update release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if release == nil { t.Error("Release does not exist after update") }
|
||||||
|
assert.Equal(t, release.ReleaseType, testType)
|
||||||
|
releaseType = testType
|
||||||
|
|
||||||
|
t.Run("but not with invalid value", func(t *testing.T) {
|
||||||
|
if err := s.UpdateReleaseType(id, "garbage-type"); err == nil {
|
||||||
|
t.Errorf("Updated release type to invalid value")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update date", func(t *testing.T) {
|
||||||
|
testDate := time.Now().Add(time.Hour * 24)
|
||||||
|
if err := s.UpdateReleaseDate(id, testDate); err != nil {
|
||||||
|
t.Errorf("Failed to update release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if release == nil { t.Error("Release does not exist after update") }
|
||||||
|
assert.Equal(t, release.ReleaseDate, testDate)
|
||||||
|
releaseDate = testDate
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update artwork", func(t *testing.T) {
|
||||||
|
if err := s.UpdateReleaseArtwork(id, ""); err != nil {
|
||||||
|
t.Errorf("Failed to update release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if release == nil { t.Error("Release does not exist after update") }
|
||||||
|
assert.Equal(t, release.GetArtwork(), model.DEFAULT_RELEASE_ARTWORK_URL)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update buy info", func(t *testing.T) {
|
||||||
|
testBuyName := "get it now!!!"
|
||||||
|
testBuyLink := "https://arimelody.space/music"
|
||||||
|
if err := s.UpdateReleaseBuyInfo(id, testBuyName, testBuyLink); err != nil {
|
||||||
|
t.Errorf("Failed to update release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if release == nil { t.Error("Release does not exist after update") }
|
||||||
|
assert.Equal(t, release.Buyname, testBuyName)
|
||||||
|
assert.Equal(t, release.Buylink, testBuyLink)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update copyright info", func(t *testing.T) {
|
||||||
|
testCopyright := "CC BY-SA 4.0"
|
||||||
|
testCopyrightURL := "https://creativecommons.org/licenses/by-sa/4.0/"
|
||||||
|
if err := s.UpdateReleaseCopyright(id, testCopyright, testCopyrightURL); err != nil {
|
||||||
|
t.Errorf("Failed to update release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := s.GetReleaseByID(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get release: %v", err) }
|
||||||
|
if release == nil { t.Error("Release does not exist after update") }
|
||||||
|
assert.Equal(t, release.Copyright, testCopyright)
|
||||||
|
assert.Equal(t, release.CopyrightURL, testCopyrightURL)
|
||||||
|
})
|
||||||
|
|
||||||
|
track := &model.Track{
|
||||||
|
Title: "test track",
|
||||||
|
Description: "average description",
|
||||||
|
Lyrics: "some lyrics",
|
||||||
|
}
|
||||||
|
t.Run("can update tracks", func(t *testing.T) {
|
||||||
|
if trackID, err := s.CreateTrack(track.Title, track.Description, track.Lyrics, ""); err != nil {
|
||||||
|
t.Errorf("Failed to create track: %v", err)
|
||||||
|
} else { track.ID = trackID }
|
||||||
|
|
||||||
|
if err := s.UpdateReleaseTracks(id, []string{ track.ID }); err != nil {
|
||||||
|
t.Errorf("Failed to update release tracks: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tracks, err := s.GetReleaseTracks(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get tracks: %v", err) }
|
||||||
|
|
||||||
|
index := slices.IndexFunc(tracks, func(repoTrack *model.Track) bool {
|
||||||
|
return repoTrack.ID == track.ID
|
||||||
|
})
|
||||||
|
assert.Assert(t, index != -1)
|
||||||
|
assert.Equal(t, tracks[index].Title, track.Title)
|
||||||
|
assert.Equal(t, tracks[index].Description, track.Description)
|
||||||
|
assert.Equal(t, tracks[index].Lyrics, track.Lyrics)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update credits", func(t *testing.T) {
|
||||||
|
artist := &model.Artist{ ID: id, Name: title, Website: "", Avatar: "" }
|
||||||
|
if err := s.CreateArtist(artist.ID, artist.Name, artist.Website, artist.Avatar); err != nil {
|
||||||
|
t.Errorf("Failed to create artist: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
credit := &model.Credit{
|
||||||
|
Artist: artist,
|
||||||
|
Role: "did a lot of stuff",
|
||||||
|
Primary: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.UpdateReleaseCredits(id, []*model.Credit{ credit }); err != nil {
|
||||||
|
t.Errorf("Failed to update release credits: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
credits, err := s.GetReleaseCredits(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get credits: %v", err) }
|
||||||
|
assert.Equal(t, len(credits), 1)
|
||||||
|
|
||||||
|
index := slices.IndexFunc(credits, func(repoCredit *model.Credit) bool {
|
||||||
|
return repoCredit.Artist.ID == credit.Artist.ID
|
||||||
|
})
|
||||||
|
assert.Assert(t, index != -1)
|
||||||
|
assert.Equal(t, credits[index].Release.ID, id)
|
||||||
|
assert.Equal(t, credits[index].Release.Title, title)
|
||||||
|
assert.Equal(t, credits[index].Release.ReleaseType, releaseType)
|
||||||
|
assert.Equal(t, credits[index].Release.ReleaseDate, releaseDate)
|
||||||
|
assert.Equal(t, credits[index].Release.GetArtwork(), model.DEFAULT_RELEASE_ARTWORK_URL)
|
||||||
|
assert.Equal(t, credits[index].Artist.ID, credit.Artist.ID)
|
||||||
|
assert.Equal(t, credits[index].Artist.Name, credit.Artist.Name)
|
||||||
|
assert.Equal(t, credits[index].Artist.Website, credit.Artist.Website)
|
||||||
|
assert.Equal(t, credits[index].Artist.GetAvatar(), credit.Artist.GetAvatar())
|
||||||
|
assert.Equal(t, credits[index].Role, credit.Role)
|
||||||
|
assert.Equal(t, credits[index].Primary, credit.Primary)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can update links", func(t *testing.T) {
|
||||||
|
link := &model.Link{
|
||||||
|
Name: "awesome link you should totally go here",
|
||||||
|
URL: "https://arimelody.space",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.UpdateReleaseLinks(id, []*model.Link{ link }); err != nil {
|
||||||
|
t.Errorf("Failed to update release tracks: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
links, err := s.GetReleaseLinks(id)
|
||||||
|
if err != nil { t.Errorf("Failed to get tracks: %v", err) }
|
||||||
|
|
||||||
|
index := slices.IndexFunc(links, func(repoLink *model.Link) bool {
|
||||||
|
return repoLink.Name == link.Name
|
||||||
|
})
|
||||||
|
assert.Assert(t, index != -1)
|
||||||
|
assert.Equal(t, links[index].URL, link.URL)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can delete release", func(t *testing.T) {
|
||||||
|
if err := s.DeleteRelease(id); err != nil {
|
||||||
|
t.Errorf("Failed to delete release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("no longer exists", func(t *testing.T) {
|
||||||
|
if release, err := s.GetReleaseByID(id); err == nil {
|
||||||
|
t.Error("No error getting release")
|
||||||
|
} else if release != nil {
|
||||||
|
t.Error("Release with this ID still exists")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("but not one that doesn't exist", func(t *testing.T) {
|
||||||
|
if err := s.DeleteRelease("some-garbage-id"); err == nil {
|
||||||
|
if !errors.IsNotExistError(err) {
|
||||||
|
t.Errorf("Failed to delete release: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
81
service/music/track.go
Normal file
81
service/music/track.go
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
package music
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/model"
|
||||||
|
"arimelody-web/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func (s *MusicService) GetAllTracks() ([]*model.Track, error) {
|
||||||
|
return s.repo.GetAllTracks()
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetTrackCount() (int, error) {
|
||||||
|
return s.repo.GetTrackCount()
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetTrackByID(id string) (*model.Track, error) {
|
||||||
|
track, err := s.repo.GetTrackByID(id)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
if track == nil { return nil, errors.NewNotExistError("Track does not exist") }
|
||||||
|
return track, nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetOrphanTracks() ([]*model.Track, error) {
|
||||||
|
return s.repo.GetOrphanTracks()
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetTracksNotOnRelease(releaseID string) ([]*model.Track, error) {
|
||||||
|
return s.repo.GetTracksNotOnRelease(releaseID)
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetTrackReleases(trackID string) ([]*model.Release, error) {
|
||||||
|
return s.repo.GetTrackReleases(trackID)
|
||||||
|
}
|
||||||
|
func (s *MusicService) GetTrackFullReleases(trackID string) ([]*model.Release, error) {
|
||||||
|
releases, err := s.GetTrackReleases(trackID)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
for _, release := range releases {
|
||||||
|
if err := s.fillRelease(release); err != nil { return nil, err }
|
||||||
|
}
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) CreateTrack(title string, description string, lyrics string, previewURL string) (string, error) {
|
||||||
|
if len(title) == 0 { return "", errors.NewValidationError("Track title cannot be empty") }
|
||||||
|
id, err := s.repo.CreateTrack(title, description, lyrics, previewURL)
|
||||||
|
if err != nil { return "", err }
|
||||||
|
s.log.Printf("Created track '%s' (%s)", title, id)
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) UpdateTrack(track *model.Track) error {
|
||||||
|
if len(track.Title) == 0 { return errors.NewValidationError("Track title cannot be empty") }
|
||||||
|
if err := s.repo.UpdateTrack(track); err != nil { return err }
|
||||||
|
s.log.Printf("Updated track '%s' (%s)", track.Title, track.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateTrackTitle(id string, title string) error {
|
||||||
|
if len(title) == 0 { return errors.NewValidationError("Track title cannot be empty") }
|
||||||
|
if err := s.repo.UpdateTrackTitle(id, title); err != nil { return err }
|
||||||
|
s.log.Printf("Updated track %s title to '%s'", id, title)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateTrackDescription(id string, description string) error {
|
||||||
|
if err := s.repo.UpdateTrackDescription(id, description); err != nil { return err }
|
||||||
|
s.log.Printf("Updated track %s description to '%s'", id, description)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateTrackLyrics(id string, lyrics string) error {
|
||||||
|
if err := s.repo.UpdateTrackLyrics(id, lyrics); err != nil { return err }
|
||||||
|
s.log.Printf("Updated track %s lyrics to '%s'", id, lyrics)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *MusicService) UpdateTrackPreviewURL(id string, previewURL string) error {
|
||||||
|
if err := s.repo.UpdateTrackPreviewURL(id, previewURL); err != nil { return err }
|
||||||
|
s.log.Printf("Updated track %s preview URL to '%s'", id, previewURL)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MusicService) DeleteTrack(id string) error {
|
||||||
|
deletedID, err := s.repo.DeleteTrack(id)
|
||||||
|
if err != nil { return err }
|
||||||
|
if deletedID == "" { return errors.NewNotExistError("Track does not exist") }
|
||||||
|
s.log.Printf("Deleted track %s", id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
8
service/validator/validator.go
Normal file
8
service/validator/validator.go
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
var idRegexp = regexp.MustCompile(`^[a-zA-Z0-9\-_\.]+$`)
|
||||||
|
func ValidateID(id string) bool {
|
||||||
|
return idRegexp.MatchString(id)
|
||||||
|
}
|
||||||
39
service/validator/validator_test.go
Normal file
39
service/validator/validator_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package validator_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"arimelody-web/service/validator"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ValidateID(t *testing.T) {
|
||||||
|
t.Run("accepts alphanumberic ID", func(t *testing.T) {
|
||||||
|
assert.Equal(t, validator.ValidateID("abcDEF123"), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("accepts hyphens", func(t *testing.T) {
|
||||||
|
assert.Equal(t, validator.ValidateID("a0-1b"), true)
|
||||||
|
})
|
||||||
|
t.Run("accepts underscores", func(t *testing.T) {
|
||||||
|
assert.Equal(t, validator.ValidateID("a0_1b"), true)
|
||||||
|
})
|
||||||
|
t.Run("accepts periods", func(t *testing.T) {
|
||||||
|
assert.Equal(t, validator.ValidateID("a0.1b"), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("rejects other characters", func(t *testing.T) {
|
||||||
|
t.Run("emoji", func(t *testing.T) {
|
||||||
|
assert.Equal(t, validator.ValidateID("🗣️🔥‼️"), false)
|
||||||
|
})
|
||||||
|
t.Run("slashes", func(t *testing.T) {
|
||||||
|
assert.Equal(t, validator.ValidateID("this/could/be/really/bad/for/the/router"), false)
|
||||||
|
})
|
||||||
|
t.Run("question marks", func(t *testing.T) {
|
||||||
|
assert.Equal(t, validator.ValidateID("query?these=nuts"), false)
|
||||||
|
})
|
||||||
|
t.Run("hashes", func(t *testing.T) {
|
||||||
|
assert.Equal(t, validator.ValidateID("unnecessary#tagging"), false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ func MusicHandler(app *app.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
release, err := controller.GetRelease(app.DB, r.URL.Path[1:], true)
|
release, err := app.MusicService.GetFullReleaseByID(r.URL.Path[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
|
@ -36,7 +36,7 @@ func MusicHandler(app *app.AppState) http.Handler {
|
||||||
|
|
||||||
func ServeCatalog(app *app.AppState) http.Handler {
|
func ServeCatalog(app *app.AppState) 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(app.DB, true, 0, true)
|
releases, err := app.MusicService.GetAllFullReleases(true, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to pull releases for catalog: %s\n", err)
|
fmt.Printf("WARN: 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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue