summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-07 15:26:17 +0300
committerPaul Buetow <paul@buetow.org>2026-05-07 15:26:17 +0300
commit907187b16ebf44a883f28358c2f3728865f9b8d8 (patch)
tree9aa1e76cd165e7367c558af59057db08e446c58a
parentd8daecca7de6352d2d1b0588151ad9005be90bdd (diff)
j1 fix admin media lifecycle QA defects
-rw-r--r--internal/service/access.go29
-rw-r--r--internal/service/browse.go3
-rw-r--r--internal/service/media_test.go34
-rw-r--r--internal/service/write.go2
-rw-r--r--internal/service/write_test.go24
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
},