package music import ( "encoding/json" "errors" "fmt" "net/http" "strings" "time" "arimelody.me/arimelody.me/api/v1/admin" "github.com/jmoiron/sqlx" ) type ReleaseType string const ( Single ReleaseType = "Single" Album ReleaseType = "Album" EP ReleaseType = "EP" Compilation ReleaseType = "Compilation" ) type Release struct { ID string `json:"id"` Title string `json:"title"` Description string `json:"description"` ReleaseType ReleaseType `json:"type"` ReleaseDate time.Time `json:"releaseDate"` Artwork string `json:"artwork"` Buyname string `json:"buyname"` Buylink string `json:"buylink"` Links []Link `json:"links"` Credits []Credit `json:"credits"` Tracks []Track `json:"tracks"` } var Releases []Release; // GETTERS func (release Release) GetID() string { return release.ID } func (release Release) GetTitle() string { return release.Title } func (release Release) GetDescription() string { return release.Description } func (release Release) GetType() ReleaseType { return release.ReleaseType } func (release Release) GetReleaseDate() time.Time { return release.ReleaseDate } func (release Release) GetArtwork() string { if release.Artwork == "" { return "/img/default-cover-art.png" } return release.Artwork } func (release Release) GetBuyName() string { return release.Buyname } func (release Release) GetBuyLink() string { return release.Buylink } func (release Release) GetLinks() []Link { return release.Links } func (release Release) GetCredits() []Credit { return release.Credits } func (release Release) GetTracks() []Track { return release.Tracks } // SETTERS func (release Release) SetID(id string) error { // TODO: update DB release.ID = id return nil } func (release Release) SetTitle(title string) error { // TODO: update DB release.Title = title return nil } func (release Release) SetDescription(description string) error { // TODO: update DB release.Description = description return nil } func (release Release) SetType(releaseType ReleaseType) error { // TODO: update DB release.ReleaseType = releaseType return nil } func (release Release) SetReleaseDate(releaseDate time.Time) error { // TODO: update DB release.ReleaseDate = releaseDate return nil } func (release Release) SetArtwork(artwork string) error { // TODO: update DB release.Artwork = artwork return nil } func (release Release) SetBuyName(buyname string) error { // TODO: update DB release.Buyname = buyname return nil } func (release Release) SetBuyLink(buylink string) error { // TODO: update DB release.Buylink = buylink return nil } func (release Release) SetLinks(links []Link) error { // TODO: update DB release.Links = links return nil } func (release Release) SetCredits(credits []Credit) error { // TODO: update DB release.Credits = credits return nil } func (release Release) SetTracks(tracks []Track) error { // TODO: update DB release.Tracks = tracks return nil } // MISC func GetRelease(id string) *Release { for _, release := range Releases { if release.GetID() == id { return &release } } return nil } func (release Release) PrintReleaseDate() string { return release.ReleaseDate.Format("02 January 2006") } func (release Release) GetReleaseYear() int { return release.ReleaseDate.Year() } func (release Release) IsSingle() bool { return len(release.Tracks) == 1; } func (release Release) IsReleased() bool { return release.ReleaseDate.Before(time.Now()) } func (release Release) GetUniqueArtists(only_primary bool) []Artist { var artists = []Artist{} for _, credit := range release.Credits { if only_primary && !credit.Primary { continue } exists := false for _, a := range artists { if a.ID == credit.Artist.ID { exists = true break } } if exists { continue } artists = append(artists, *credit.Artist) } return artists } func (release Release) GetUniqueArtistNames(only_primary bool) []string { var names = []string{} for _, artist := range release.GetUniqueArtists(only_primary) { names = append(names, artist.GetName()) } return names } func (release Release) PrintArtists(only_primary bool, ampersand bool) string { names := release.GetUniqueArtistNames(only_primary) if len(names) == 0 { return "Unknown Artist" } else if len(names) == 1 { return names[0] } if ampersand { res := strings.Join(names[:len(names)-1], ", ") res += " & " + names[len(names)-1] return res } else { return strings.Join(names[:], ", ") } } // DATABASE func (release Release) PushToDB(db *sqlx.DB) error { // fmt.Printf("Pushing release [%s] to database...", release.ID) tx, err := db.Begin() if err != nil { return errors.New(fmt.Sprintf("Failed to initiate transaction: %s", err)) } _, err = tx.Exec( "INSERT INTO musicreleases (id, title, description, type, release_date, artwork, buyname, buylink) "+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "+ "ON CONFLICT (id) "+ "DO UPDATE SET title=$2, description=$3, type=$4, release_date=$5, artwork=$6, buyname=$7, buylink=$8", release.ID, release.Title, release.Description, release.ReleaseType, release.ReleaseDate.Format("2-Jan-2006"), release.Artwork, release.Buyname, release.Buylink, ) for _, link := range release.Links { _, err = tx.Exec( "INSERT INTO musiclinks (release, name, url) "+ "VALUES ($1, $2, $3) "+ "ON CONFLICT (release, name) "+ "DO UPDATE SET url=$3 ", release.ID, link.Name, link.URL, ) if err != nil { return errors.New(fmt.Sprintf("Failed to add music link to transaction: %s", err)) } } for _, credit := range release.Credits { _, err = tx.Exec( "INSERT INTO musiccredits (release, artist, role, is_primary) "+ "VALUES ($1, $2, $3, $4) "+ "ON CONFLICT (release, artist) "+ "DO UPDATE SET role=$3, is_primary=$4", release.ID, credit.Artist.ID, credit.Role, credit.Primary, ) if err != nil { return errors.New(fmt.Sprintf("Failed to add music credit to transaction: %s", err)) } } for _, track := range release.Tracks { _, err = tx.Exec( "INSERT INTO musictracks (release, number, title, description, lyrics, preview_url) "+ "VALUES ($1, $2, $3, $4, $5, $6) "+ "ON CONFLICT (release, number) "+ "DO UPDATE SET title=$3, description=$4, lyrics=$5, preview_url=$6", release.ID, track.Number, track.Title, track.Description, track.Lyrics, track.PreviewURL, ) if err != nil { return errors.New(fmt.Sprintf("Failed to add music track to transaction: %s", err)) } } err = tx.Commit() if err != nil { return errors.New(fmt.Sprintf("Failed to commit transaction: %s", err)) } // fmt.Printf("done!\n") return nil } func (release Release) DeleteFromDB(db *sqlx.DB) error { // this probably doesn't need to be a transaction; // i just felt like making it one tx, err := db.Begin() if err != nil { return errors.New(fmt.Sprintf("Failed to initiate transaction: %s", err)) } _, err = tx.Exec("DELETE FROM musicreleases WHERE id=$1", release.ID) err = tx.Commit() if err != nil { return errors.New(fmt.Sprintf("Failed to commit transaction: %s", err)) } return nil } func PullAllReleases(db *sqlx.DB) ([]Release, error) { releases := []Release{} rows, err := db.Query("SELECT id, title, description, type, release_date, artwork, buyname, buylink FROM musicreleases") if err != nil { return nil, err } for rows.Next() { var release = Release{} err = rows.Scan( &release.ID, &release.Title, &release.Description, &release.ReleaseType, &release.ReleaseDate, &release.Artwork, &release.Buyname, &release.Buylink, ) if err != nil { fmt.Printf("Error while pulling a release: %s\n", err) continue } release.Credits, err = PullReleaseCredits(db, release.ID) if err != nil { fmt.Printf("Failed to pull credits for %s: %v\n", release.ID, err) } release.Links, err = PullReleaseLinks(db, release.ID) if err != nil { fmt.Printf("Failed to pull links for %s: %v\n", release.ID, err) } release.Tracks, err = PullReleaseTracks(db, release.ID) if err != nil { return nil, errors.New(fmt.Sprintf("error pulling tracks for %s: %v\n", release.ID, err)) } releases = append(releases, release) } return releases, nil } // HTTP HANDLERS func ServeRelease() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { http.NotFound(w, r) return } releaseID := r.URL.Path[1:] var release = GetRelease(releaseID) if release == nil { http.NotFound(w, r) return } // only allow authorised users to view unreleased releases authorised := r.Context().Value("role") != nil && r.Context().Value("role") == "admin" if !release.IsReleased() && !authorised { admin.MustAuthorise(ServeRelease()).ServeHTTP(w, r) return } jsonBytes, err := json.Marshal(release) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(jsonBytes) }) } func PostRelease() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.NotFound(w, r) return } var release Release err := json.NewDecoder(r.Body).Decode(&release) if err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if GetRelease(release.ID) != nil { http.Error(w, fmt.Sprintf("Release %s already exists", release.ID), http.StatusBadRequest) return } Releases = append(Releases, release) jsonBytes, err := json.Marshal(release) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) w.Write(jsonBytes) }) }