diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-07 15:26:17 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-07 15:26:17 +0300 |
| commit | 907187b16ebf44a883f28358c2f3728865f9b8d8 (patch) | |
| tree | 9aa1e76cd165e7367c558af59057db08e446c58a | |
| parent | d8daecca7de6352d2d1b0588151ad9005be90bdd (diff) | |
j1 fix admin media lifecycle QA defects
| -rw-r--r-- | internal/service/access.go | 29 | ||||
| -rw-r--r-- | internal/service/browse.go | 3 | ||||
| -rw-r--r-- | internal/service/media_test.go | 34 | ||||
| -rw-r--r-- | internal/service/write.go | 2 | ||||
| -rw-r--r-- | internal/service/write_test.go | 24 |
5 files changed, 86 insertions, 6 deletions
diff --git a/internal/service/access.go b/internal/service/access.go index 4d630a1..e8546d6 100644 --- a/internal/service/access.go +++ b/internal/service/access.go @@ -82,6 +82,35 @@ func (h *accessHelper) verifyModifyAccess(ctx context.Context, mediaID, userID i return media, nil } +// verifyRestoreAccess checks owner/admin access for active or soft-deleted media. +func (h *accessHelper) verifyRestoreAccess(ctx context.Context, mediaID, userID int64) (*model.Media, error) { + media, err := h.store.GetMediaByID(ctx, mediaID) + if err != nil { + return nil, fmt.Errorf("get media: %w", err) + } + if media == nil { + deleted, err := h.store.ListDeletedMedia(ctx) + if err != nil { + return nil, fmt.Errorf("list deleted media: %w", err) + } + for i := range deleted { + if deleted[i].ID == mediaID { + media = &deleted[i] + break + } + } + } + if media == nil { + return nil, ErrNotFound + } + + if err := h.checkSetPermission(ctx, media.SetID, userID, model.RoleOwner); err != nil { + return nil, err + } + + return media, nil +} + // verifySetModifyAccess checks that the user is an owner or admin for a set. func (h *accessHelper) verifySetModifyAccess(ctx context.Context, setID, userID int64) error { return h.checkSetPermission(ctx, setID, userID, model.RoleOwner) diff --git a/internal/service/browse.go b/internal/service/browse.go index 7425d35..9e23fa9 100644 --- a/internal/service/browse.go +++ b/internal/service/browse.go @@ -146,6 +146,9 @@ func (s *browseService) ListMedia(ctx context.Context, userID int64, filter Medi for _, p := range perms { allowed = append(allowed, p.SetID) } + if len(allowed) == 0 { + return []model.Media{}, nil + } repoFilter.AllowedSetIDs = allowed repoFilter.UserID = userID return s.store.ListMedia(ctx, repoFilter) diff --git a/internal/service/media_test.go b/internal/service/media_test.go index d9cbebd..3d550ce 100644 --- a/internal/service/media_test.go +++ b/internal/service/media_test.go @@ -1614,6 +1614,40 @@ func TestMediaService_ListMedia_AdminAndUserFiltering(t *testing.T) { t.Fatalf("unexpected result: %+v", res) } }) + + t.Run("user with no permissions sees no media", func(t *testing.T) { + listMediaCalled := false + store := &repository.MockStore{ + UserRepo: repository.MockUserRepo{ + GetUserByIDFunc: func(ctx context.Context, id int64) (*model.User, error) { + return &model.User{ID: id, IsAdmin: false}, nil + }, + }, + SetPermissionRepo: repository.MockSetPermissionRepo{ + ListPermissionsByUserFunc: func(ctx context.Context, userID int64) ([]model.SetPermission, error) { + return nil, nil + }, + }, + MediaRepo: repository.MockMediaRepo{ + ListMediaFunc: func(ctx context.Context, filter repository.MediaFilter) ([]model.Media, error) { + listMediaCalled = true + return []model.Media{{ID: 5}}, nil + }, + }, + } + svc := NewMediaService(store, newMockClock(), "/tmp/media", nil, nil) + setID := int64(1) + res, err := svc.ListMedia(ctx, 2, MediaQueryFilter{SetID: &setID}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(res) != 0 { + t.Fatalf("expected no media, got %+v", res) + } + if listMediaCalled { + t.Fatal("expected repository ListMedia not to be called") + } + }) } func TestMediaService_RegenerateThumbnail(t *testing.T) { diff --git a/internal/service/write.go b/internal/service/write.go index b67e644..b5cd181 100644 --- a/internal/service/write.go +++ b/internal/service/write.go @@ -49,7 +49,7 @@ func (s *writeService) SoftDeleteMedia(ctx context.Context, mediaID, userID int6 } func (s *writeService) RestoreMedia(ctx context.Context, mediaID, userID int64) error { - _, err := s.helper.verifyModifyAccess(ctx, mediaID, userID) + _, err := s.helper.verifyRestoreAccess(ctx, mediaID, userID) if err != nil { return err } diff --git a/internal/service/write_test.go b/internal/service/write_test.go index d303431..122ca78 100644 --- a/internal/service/write_test.go +++ b/internal/service/write_test.go @@ -14,17 +14,28 @@ func TestWriteService_RestoreMedia(t *testing.T) { ctx := context.Background() tests := []struct { - name string - media *model.Media - storeErr error - wantErr bool - wantCode error + name string + media *model.Media + deletedMedia []model.Media + deletedErr error + storeErr error + wantErr bool + wantCode error }{ { name: "ok", media: &model.Media{ID: 1, SetID: 1}, }, { + name: "soft deleted ok", + deletedMedia: []model.Media{{ID: 1, SetID: 1}}, + }, + { + name: "deleted lookup error", + deletedErr: errors.New("deleted lookup failed"), + wantErr: true, + }, + { name: "store error", media: &model.Media{ID: 1, SetID: 1}, storeErr: errors.New("boom"), @@ -45,6 +56,9 @@ func TestWriteService_RestoreMedia(t *testing.T) { GetMediaByIDFunc: func(ctx context.Context, id int64) (*model.Media, error) { return tt.media, nil }, + ListDeletedMediaFunc: func(ctx context.Context) ([]model.Media, error) { + return tt.deletedMedia, tt.deletedErr + }, RestoreMediaFunc: func(ctx context.Context, id int64) error { return tt.storeErr }, |
