feat: refine content media access rules
This commit is contained in:
@@ -204,16 +204,39 @@ func (s *content) Get(ctx context.Context, tenantID, userID, id int64) (*content
|
||||
if item.UserID == uid {
|
||||
hasAccess = true // Owner
|
||||
} else {
|
||||
isAdmin, err := s.isTenantAdmin(ctx, item.TenantID, uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isAdmin {
|
||||
hasAccess = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasAccess {
|
||||
// Check Purchase
|
||||
exists, _ := models.ContentAccessQuery.WithContext(ctx).
|
||||
exists, err := models.ContentAccessQuery.WithContext(ctx).
|
||||
Where(models.ContentAccessQuery.UserID.Eq(uid),
|
||||
models.ContentAccessQuery.ContentID.Eq(id),
|
||||
models.ContentAccessQuery.Status.Eq(consts.ContentAccessStatusActive)).
|
||||
Exists()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
if exists {
|
||||
hasAccess = true
|
||||
}
|
||||
}
|
||||
|
||||
if !hasAccess && item.Visibility == consts.ContentVisibilityTenantOnly {
|
||||
isMember, err := s.isTenantMember(ctx, item.TenantID, uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isMember {
|
||||
hasAccess = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter Assets based on Access
|
||||
@@ -250,7 +273,7 @@ func (s *content) Get(ctx context.Context, tenantID, userID, id int64) (*content
|
||||
IsFavorited: isFavorited,
|
||||
}
|
||||
// Pass IsPurchased/HasAccess info to frontend?
|
||||
detail.ContentItem.IsPurchased = hasAccess // Update DTO field logic if needed. IsPurchased usually means "Bought". Owner implies access but not necessarily purchased. But for UI "Play" button, IsPurchased=true is fine.
|
||||
detail.ContentItem.IsPurchased = hasAccess // 购买或具备可访问权限时统一展示为已解锁。
|
||||
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
@@ -116,14 +116,21 @@ func (s *ContentTestSuite) Test_Get() {
|
||||
models.ContentQuery.WithContext(ctx).Create(content)
|
||||
|
||||
member := &models.User{Nickname: "Member", Username: "member1", Phone: "13800000003"}
|
||||
admin := &models.User{Nickname: "Admin", Username: "admin1", Phone: "13800000005"}
|
||||
guest := &models.User{Nickname: "Guest", Username: "guest1", Phone: "13800000004"}
|
||||
models.UserQuery.WithContext(ctx).Create(member, guest)
|
||||
models.UserQuery.WithContext(ctx).Create(member, admin, guest)
|
||||
models.TenantUserQuery.WithContext(ctx).Create(&models.TenantUser{
|
||||
TenantID: 1,
|
||||
UserID: member.ID,
|
||||
Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember},
|
||||
Status: consts.UserStatusVerified,
|
||||
})
|
||||
models.TenantUserQuery.WithContext(ctx).Create(&models.TenantUser{
|
||||
TenantID: 1,
|
||||
UserID: admin.ID,
|
||||
Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleTenantAdmin},
|
||||
Status: consts.UserStatusVerified,
|
||||
})
|
||||
|
||||
tenantOnly := &models.Content{
|
||||
TenantID: 1,
|
||||
@@ -152,6 +159,38 @@ func (s *ContentTestSuite) Test_Get() {
|
||||
}
|
||||
models.ContentAssetQuery.WithContext(ctx).Create(ca)
|
||||
|
||||
tenantMain := &models.MediaAsset{
|
||||
TenantID: 1,
|
||||
UserID: author.ID,
|
||||
ObjectKey: "tenant_main.mp4",
|
||||
Type: consts.MediaAssetTypeVideo,
|
||||
}
|
||||
tenantPrev := &models.MediaAsset{
|
||||
TenantID: 1,
|
||||
UserID: author.ID,
|
||||
ObjectKey: "tenant_preview.mp4",
|
||||
Type: consts.MediaAssetTypeVideo,
|
||||
}
|
||||
models.MediaAssetQuery.WithContext(ctx).Create(tenantMain, tenantPrev)
|
||||
models.ContentAssetQuery.WithContext(ctx).Create(
|
||||
&models.ContentAsset{
|
||||
TenantID: 1,
|
||||
UserID: author.ID,
|
||||
ContentID: tenantOnly.ID,
|
||||
AssetID: tenantMain.ID,
|
||||
Sort: 1,
|
||||
Role: consts.ContentAssetRoleMain,
|
||||
},
|
||||
&models.ContentAsset{
|
||||
TenantID: 1,
|
||||
UserID: author.ID,
|
||||
ContentID: tenantOnly.ID,
|
||||
AssetID: tenantPrev.ID,
|
||||
Sort: 2,
|
||||
Role: consts.ContentAssetRolePreview,
|
||||
},
|
||||
)
|
||||
|
||||
// Set context to author
|
||||
ctx = context.WithValue(ctx, consts.CtxKeyUser, author.ID)
|
||||
|
||||
@@ -168,6 +207,14 @@ func (s *ContentTestSuite) Test_Get() {
|
||||
detail, err := Content.Get(ctx, tenantID, member.ID, tenantOnly.ID)
|
||||
So(err, ShouldBeNil)
|
||||
So(detail.Title, ShouldEqual, "Member Only")
|
||||
So(len(detail.MediaUrls), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("should allow tenant_only content for admin", func() {
|
||||
detail, err := Content.Get(ctx, tenantID, admin.ID, tenantOnly.ID)
|
||||
So(err, ShouldBeNil)
|
||||
So(detail.Title, ShouldEqual, "Member Only")
|
||||
So(len(detail.MediaUrls), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("should reject tenant_only content for non-member", func() {
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
- 内容可见性与 tenant_only 访问控制。
|
||||
- OTP 登录流程与租户成员校验(未加入拒绝登录)。
|
||||
- ID 类型已统一为 int64(仅保留 upload_id/external_id/uuid 等非数字标识)。
|
||||
- 内容资源权限与预览差异化(未购预览、已购/管理员/成员全量)。
|
||||
|
||||
## 里程碑建议
|
||||
- M1:完成 P0
|
||||
|
||||
Reference in New Issue
Block a user