package backend import ( "context" "errors" "time" "music-server/internal/db/repository" "github.com/google/uuid" "github.com/jackc/pgx/v5" ) // Soundtrack is a soundtrack as returned by the API. type Soundtrack struct { Uuid uuid.UUID `json:"uuid"` Name string `json:"name"` TimesPlayed int32 `json:"times_played"` LastPlayed *time.Time `json:"last_played,omitempty"` Added time.Time `json:"added"` } // SoundtrackWithSongList is a soundtrack with all its songs. type SoundtrackWithSongList struct { Soundtrack Songs []Song `json:"songs"` } // Song is a song as returned by the API. type Song struct { Uuid uuid.UUID `json:"uuid"` Name string `json:"name"` FileName *string `json:"file_name,omitempty"` TimesPlayed int32 `json:"times_played"` SoundtrackUuid uuid.UUID `json:"soundtrack_uuid"` SoundtrackName string `json:"soundtrack_name"` } // LibraryService looks up soundtracks and songs by UUID. type LibraryService struct { q repository.Querier } // NewLibraryService creates a LibraryService. func NewLibraryService(q repository.Querier) *LibraryService { return &LibraryService{q: q} } func toSoundtrack(s repository.Soundtrack) Soundtrack { return Soundtrack{ Uuid: s.Uuid, Name: s.SoundtrackName, TimesPlayed: s.TimesPlayed, LastPlayed: s.LastPlayed, Added: s.Added, } } func notFound(err error) error { if errors.Is(err, pgx.ErrNoRows) { return ErrNotFound } return err } // Soundtracks returns all active soundtracks, sorted by name. func (l *LibraryService) Soundtracks(ctx context.Context) ([]Soundtrack, error) { rows, err := l.q.FindAllSoundtracks(ctx) if err != nil { return nil, err } soundtracks := make([]Soundtrack, len(rows)) for i, row := range rows { soundtracks[i] = toSoundtrack(row) } return soundtracks, nil } // Soundtrack returns an active soundtrack with its songs. func (l *LibraryService) Soundtrack(ctx context.Context, id uuid.UUID) (SoundtrackWithSongList, error) { row, err := l.q.GetSoundtrackByUUID(ctx, id) if err != nil { return SoundtrackWithSongList{}, notFound(err) } songRows, err := l.q.GetSongsBySoundtrackUUID(ctx, id) if err != nil { return SoundtrackWithSongList{}, err } songs := make([]Song, len(songRows)) for i, song := range songRows { songs[i] = toSong(song, row.SoundtrackName) } return SoundtrackWithSongList{Soundtrack: toSoundtrack(row), Songs: songs}, nil } func toSong(s repository.Song, soundtrackName string) Song { return Song{ Uuid: s.Uuid, Name: s.SongName, FileName: s.FileName, TimesPlayed: s.TimesPlayed, SoundtrackUuid: s.SoundtrackUuid, SoundtrackName: soundtrackName, } } // Song returns a song of an active soundtrack. func (l *LibraryService) Song(ctx context.Context, id uuid.UUID) (Song, error) { row, err := l.q.GetActiveSongByUUID(ctx, id) if err != nil { return Song{}, notFound(err) } return toSong(row.Song, row.SoundtrackName), nil } // SongPath returns the file path of a song of an active soundtrack, if the file can be played. func (l *LibraryService) SongPath(ctx context.Context, id uuid.UUID) (string, error) { row, err := l.q.GetActiveSongByUUID(ctx, id) if err != nil { return "", notFound(err) } if !songIsPlayable(row.Song, row.SoundtrackPath) { return "", ErrNotFound } return row.Song.Path, nil }