HEAVY: finish music service migration, tidy up services, more tests

This commit is contained in:
ari melody 2026-08-01 00:29:31 +01:00
parent 9e311df462
commit 90a671982c
Signed by: ari
GPG key ID: CF99829C92678188
47 changed files with 2698 additions and 902 deletions

View file

@ -27,11 +27,11 @@ type AccountRepository interface {
UpdateAvatarURL(id string, avatarURL string) error
RemoveAvatar(id string) error
Delete(id string) error
// Increment the number of account login failure attempts,
// returning the current fail count.
IncrementFails(id string) (int, error)
ResetFails(id string) error
SetLocked(id string, lock bool) error
Delete(id string) (string, error)
}

View file

@ -1,9 +1,10 @@
package account
import (
"arimelody-web/errors"
"arimelody-web/model"
"database/sql"
"errors"
"slices"
"strconv"
)
@ -24,10 +25,11 @@ func (repo *AccountRepositoryMemory) GetCount() (int, error) {
return len(repo.accounts), nil
}
func (repo *AccountRepositoryMemory) GetByID(id string) (*model.Account, error) {
for _, account := range repo.accounts {
if account.ID == id { return account, nil }
}
return nil, nil
index := slices.IndexFunc(repo.accounts, func(account *model.Account) bool {
return account.ID == id
})
if index == -1 { return nil, nil }
return repo.accounts[index], nil
}
func (repo *AccountRepositoryMemory) GetByUsername(username string) (*model.Account, error) {
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.
func (repo *AccountRepositoryMemory) Create(username string, password string, email *string, avatarURL *string) (string, error) {
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 {
return "", errors.New("Account with this username already exists")
return "", errors.NewNotExistError("Account with this username already exists")
}
emailRef := ""
@ -74,9 +76,9 @@ func (repo *AccountRepositoryMemory) Create(username string, password string, em
// more specialised Update* and Remove* functions should be used.
func (repo *AccountRepositoryMemory) Update(id string, username string, password string, email *string, avatarUrl *string) error {
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 {
return errors.New("Account with this username already exists")
return errors.NewNotExistError("Account with this username already exists")
}
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 {
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 {
return errors.New("Account with this username already exists")
return errors.NewNotExistError("Account with this username already exists")
}
account, err := repo.GetByID(id)
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
return nil
@ -108,7 +110,7 @@ func (repo *AccountRepositoryMemory) UpdateUsername(id string, username string)
func (repo *AccountRepositoryMemory) UpdatePassword(id string, password string) error {
account, err := repo.GetByID(id)
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
return nil
@ -116,7 +118,7 @@ func (repo *AccountRepositoryMemory) UpdatePassword(id string, password string)
func (repo *AccountRepositoryMemory) UpdateEmail(id string, email string) error {
account, err := repo.GetByID(id)
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.String = email
@ -125,7 +127,7 @@ func (repo *AccountRepositoryMemory) UpdateEmail(id string, email string) error
func (repo *AccountRepositoryMemory) RemoveEmail(id string) error {
account, err := repo.GetByID(id)
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.String = ""
@ -134,7 +136,7 @@ func (repo *AccountRepositoryMemory) RemoveEmail(id string) error {
func (repo *AccountRepositoryMemory) UpdateAvatarURL(id string, avatarURL string) error {
account, err := repo.GetByID(id)
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.String = avatarURL
@ -143,31 +145,13 @@ func (repo *AccountRepositoryMemory) UpdateAvatarURL(id string, avatarURL string
func (repo *AccountRepositoryMemory) RemoveAvatar(id string) error {
account, err := repo.GetByID(id)
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.String = ""
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,
// returning the current fail count.
func (repo *AccountRepositoryMemory) IncrementFails(id string) (int, error) {
@ -188,3 +172,17 @@ func (repo *AccountRepositoryMemory) SetLocked(id string, locked bool) error {
account.Locked = locked
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
}

View file

@ -157,11 +157,6 @@ func (repo *AccountRepositoryPostgres) RemoveAvatar(id string) error {
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,
// returning the current fail count.
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)
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
}

View 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
}

View file

@ -0,0 +1,154 @@
package music
import (
"arimelody-web/model"
)
func (repo *MusicRepositoryPostgres) GetAllArtists() ([]*model.Artist, error) {
var artists = []*model.Artist{}
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{}
err := repo.db.Get(&artist, "SELECT * FROM artist WHERE id=$1", id)
if err != nil {
return nil, err
}
return &artist, nil
}
func (repo *MusicRepositoryPostgres) GetArtistsNotOnRelease(releaseID string) ([]*model.Artist, error) {
var artists = []*model.Artist{}
err := repo.db.Select(&artists,
"SELECT * FROM artist "+
"WHERE id NOT IN "+
"(SELECT artist FROM musiccredit WHERE release=$1)",
releaseID)
if err != nil {
return nil, err
}
return artists, nil
}
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 "+
"FROM musiccredit "+
"JOIN musicrelease AS release ON release=release.id "+
"JOIN artist ON artist=artist.id "+
"WHERE artist=$1 "
if !showHidden { query += "AND visible=true " }
query += "ORDER BY release_date DESC"
rows, err := repo.db.Query(query, artistID)
if err != nil {
return nil, err
}
defer rows.Close()
type NamePrimary struct {
Name string `json:"name"`
Primary bool `json:"primary" db:"is_primary"`
}
var credits []*model.Credit
for rows.Next() {
credit := &model.Credit{
Release: &model.Release{},
Artist: &model.Artist{},
}
err = rows.Scan(
&credit.Release.ID,
&credit.Release.Title,
&credit.Release.Artwork,
&credit.Release.ReleaseDate,
&credit.Artist.ID,
&credit.Artist.Name,
&credit.Artist.Website,
&credit.Artist.Avatar,
&credit.Role,
&credit.Primary,
)
otherArtists := []NamePrimary{}
err = repo.db.Select(&otherArtists,
"SELECT name,is_primary FROM artist "+
"JOIN musiccredit ON artist=id "+
"WHERE release=$1",
credit.Release.ID)
for _, otherCredit := range otherArtists {
credit.Release.Credits = append(credit.Release.Credits, &model.Credit{
Artist: &model.Artist{
Name: otherCredit.Name,
},
Primary: otherCredit.Primary,
})
}
credits = append(credits, credit)
}
return credits, nil
}
func (repo *MusicRepositoryPostgres) CreateArtist(
id string,
name string,
website string,
avatar string,
) error {
_, err := repo.db.Exec(
"INSERT INTO artist (id, name, website, avatar) "+
"VALUES ($1, $2, $3, $4)",
id, name, website, avatar,
)
return err
}
func (repo *MusicRepositoryPostgres) UpdateArtist(artist *model.Artist) error {
_, err := repo.db.Exec(
"UPDATE artist "+
"SET name=$2, website=$3, avatar=$4 "+
"WHERE id=$1",
artist.ID,
artist.Name,
artist.Website,
artist.Avatar,
)
return err
}
func (repo *MusicRepositoryPostgres) UpdateArtistID(oldID string, newID string) error {
_, 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 (repo *MusicRepositoryPostgres) DeleteArtist(id string) (string, error) {
var deletedID string
err := repo.db.Get(&deletedID, "DELETE FROM artist WHERE id=$1", id)
return deletedID, err
}

View 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,
}
}

View 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
}

View 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
}

View 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
}

View 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
}