feat(server/m7-352): 403 system_playlist_readonly on playlist edit endpoints

Push kind='system' guard into each of the five Service edit methods
(Update, Delete, AppendTracks, RemoveTrack, Reorder) immediately after
the ownership check; map the typed error to 403 in writePlaylistErr;
add table-driven test covering all five verbs against a seeded system
playlist.
This commit is contained in:
2026-05-04 09:29:37 -04:00
parent a90ff11f0f
commit 0a1e76564d
3 changed files with 63 additions and 0 deletions
+15
View File
@@ -233,6 +233,9 @@ func (s *Service) Update(ctx context.Context, callerID, playlistID pgtype.UUID,
if !pgtypeUUIDEqual(existing.UserID, callerID) {
return nil, ErrForbidden
}
if existing.Kind == "system" {
return nil, ErrSystemPlaylistReadonly
}
params := dbq.UpdatePlaylistParams{
ID: playlistID,
@@ -281,6 +284,9 @@ func (s *Service) Delete(ctx context.Context, callerID, playlistID pgtype.UUID)
if !pgtypeUUIDEqual(existing.UserID, callerID) {
return ErrForbidden
}
if existing.Kind == "system" {
return ErrSystemPlaylistReadonly
}
deleted, err := q.DeletePlaylist(ctx, playlistID)
if err != nil {
@@ -341,6 +347,9 @@ func (s *Service) AppendTracks(ctx context.Context, callerID, playlistID pgtype.
if !pgtypeUUIDEqual(pl.UserID, callerID) {
return ErrForbidden
}
if pl.Kind == "system" {
return ErrSystemPlaylistReadonly
}
tx, err := s.pool.Begin(ctx)
if err != nil {
@@ -393,6 +402,9 @@ func (s *Service) RemoveTrack(ctx context.Context, callerID, playlistID pgtype.U
if !pgtypeUUIDEqual(pl.UserID, callerID) {
return ErrForbidden
}
if pl.Kind == "system" {
return ErrSystemPlaylistReadonly
}
tx, err := s.pool.Begin(ctx)
if err != nil {
@@ -442,6 +454,9 @@ func (s *Service) Reorder(ctx context.Context, callerID, playlistID pgtype.UUID,
if !pgtypeUUIDEqual(pl.UserID, callerID) {
return ErrForbidden
}
if pl.Kind == "system" {
return ErrSystemPlaylistReadonly
}
if int32(len(orderedPositions)) != pl.TrackCount {
return fmt.Errorf("%w: expected %d positions, got %d", ErrInvalidInput, pl.TrackCount, len(orderedPositions))