feat(server/m7-352): AssertEditable + ErrSystemPlaylistReadonly on playlists service
This commit is contained in:
@@ -30,10 +30,11 @@ import (
|
|||||||
// Typed errors. The API layer maps each to its wire status code; tests
|
// Typed errors. The API layer maps each to its wire status code; tests
|
||||||
// use errors.Is to assert the right path was taken.
|
// use errors.Is to assert the right path was taken.
|
||||||
var (
|
var (
|
||||||
ErrNotFound = errors.New("playlists: playlist not found")
|
ErrNotFound = errors.New("playlists: playlist not found")
|
||||||
ErrForbidden = errors.New("playlists: forbidden")
|
ErrForbidden = errors.New("playlists: forbidden")
|
||||||
ErrInvalidInput = errors.New("playlists: invalid input")
|
ErrInvalidInput = errors.New("playlists: invalid input")
|
||||||
ErrTrackNotFound = errors.New("playlists: track not found")
|
ErrTrackNotFound = errors.New("playlists: track not found")
|
||||||
|
ErrSystemPlaylistReadonly = errors.New("playlists: system playlist is read-only")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service wires the sqlc queries + on-disk cover-art directory together.
|
// Service wires the sqlc queries + on-disk cover-art directory together.
|
||||||
@@ -292,6 +293,32 @@ func (s *Service) Delete(ctx context.Context, callerID, playlistID pgtype.UUID)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertEditable returns nil if the playlist exists, the caller owns it,
|
||||||
|
// AND the playlist is user-created (kind='user'). Returns:
|
||||||
|
// - ErrNotFound — playlist doesn't exist
|
||||||
|
// - ErrForbidden — caller is not the owner
|
||||||
|
// - ErrSystemPlaylistReadonly — playlist exists but kind='system'
|
||||||
|
//
|
||||||
|
// Edit handlers call this first; on error, the API layer maps the typed
|
||||||
|
// error to the appropriate status code.
|
||||||
|
func (s *Service) AssertEditable(ctx context.Context, callerID, playlistID pgtype.UUID) error {
|
||||||
|
q := dbq.New(s.pool)
|
||||||
|
p, err := q.GetPlaylist(ctx, playlistID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
return fmt.Errorf("playlists: load for editability check: %w", err)
|
||||||
|
}
|
||||||
|
if !pgtypeUUIDEqual(p.UserID, callerID) {
|
||||||
|
return ErrForbidden
|
||||||
|
}
|
||||||
|
if p.Kind == "system" {
|
||||||
|
return ErrSystemPlaylistReadonly
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AppendTracks adds the given track ids at the end of the playlist,
|
// AppendTracks adds the given track ids at the end of the playlist,
|
||||||
// preserving order. Snapshot fields (title, artist, album, duration_sec)
|
// preserving order. Snapshot fields (title, artist, album, duration_sec)
|
||||||
// are populated from the tracks/albums/artists join at insert time.
|
// are populated from the tracks/albums/artists join at insert time.
|
||||||
|
|||||||
Reference in New Issue
Block a user