Files
quyun-v2/backend/app/http/tenant_public/content.go
Rogee 39454458f1 feat: Implement public access for tenant content
- Add TenantOptionalAuth middleware to allow access to public content without requiring authentication.
- Introduce ListPublicPublished and PublicDetail methods in the content service to retrieve publicly accessible content.
- Create tenant_public HTTP routes for listing and showing public content, including preview and main asset retrieval.
- Enhance content tests to cover scenarios for public content access and permissions.
- Update specifications to reflect the new public content access features and rules.
2025-12-22 16:29:44 +08:00

182 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package tenant_public
import (
"quyun/v2/app/errorx"
tenant_dto "quyun/v2/app/http/tenant/dto"
"quyun/v2/app/requests"
"quyun/v2/app/services"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
"quyun/v2/providers/jwt"
"github.com/gofiber/fiber/v3"
log "github.com/sirupsen/logrus"
)
// content 提供“租户维度的公开只读接口”(不要求租户成员)。
//
// @provider
type content struct{}
func viewerUserID(ctx fiber.Ctx) int64 {
claims, ok := ctx.Locals(consts.CtxKeyClaims).(*jwt.Claims)
if !ok || claims == nil {
return 0
}
return claims.UserID
}
// list
//
// @Summary 公开内容列表(已发布 + public
// @Tags TenantPublic
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Param filter query tenant_dto.ContentListFilter true "Filter"
// @Success 200 {object} requests.Pager{items=tenant_dto.ContentItem}
//
// @Router /t/:tenantCode/v1/public/contents [get]
// @Bind tenant local key(tenant)
// @Bind filter query
func (*content) list(
ctx fiber.Ctx,
tenant *models.Tenant,
filter *tenant_dto.ContentListFilter,
) (*requests.Pager, error) {
uid := viewerUserID(ctx)
log.WithFields(log.Fields{
"tenant_id": tenant.ID,
"user_id": uid,
}).Info("tenant_public.contents.list")
if filter == nil {
filter = &tenant_dto.ContentListFilter{}
}
filter.Pagination.Format()
return services.Content.ListPublicPublished(ctx, tenant.ID, uid, filter)
}
// show
//
// @Summary 公开内容详情(已发布 + public
// @Tags TenantPublic
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Param contentID path int64 true "ContentID"
// @Success 200 {object} tenant_dto.ContentDetail
//
// @Router /t/:tenantCode/v1/public/contents/:contentID [get]
// @Bind tenant local key(tenant)
// @Bind contentID path
func (*content) show(ctx fiber.Ctx, tenant *models.Tenant, contentID int64) (*tenant_dto.ContentDetail, error) {
uid := viewerUserID(ctx)
log.WithFields(log.Fields{
"tenant_id": tenant.ID,
"user_id": uid,
"content_id": contentID,
}).Info("tenant_public.contents.show")
item, err := services.Content.PublicDetail(ctx, tenant.ID, uid, contentID)
if err != nil {
return nil, err
}
return &tenant_dto.ContentDetail{
Content: item.Content,
Price: item.Price,
HasAccess: item.HasAccess,
}, nil
}
// previewAssets
//
// @Summary 获取公开试看资源preview role
// @Tags TenantPublic
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Param contentID path int64 true "ContentID"
// @Success 200 {object} tenant_dto.ContentAssetsResponse
//
// @Router /t/:tenantCode/v1/public/contents/:contentID/preview [get]
// @Bind tenant local key(tenant)
// @Bind contentID path
func (*content) previewAssets(
ctx fiber.Ctx,
tenant *models.Tenant,
contentID int64,
) (*tenant_dto.ContentAssetsResponse, error) {
uid := viewerUserID(ctx)
log.WithFields(log.Fields{
"tenant_id": tenant.ID,
"user_id": uid,
"content_id": contentID,
}).Info("tenant_public.contents.preview_assets")
detail, err := services.Content.PublicDetail(ctx, tenant.ID, uid, contentID)
if err != nil {
return nil, err
}
assets, err := services.Content.AssetsByRole(ctx, tenant.ID, contentID, consts.ContentAssetRolePreview)
if err != nil {
return nil, err
}
previewSeconds := int32(detail.Content.PreviewSeconds)
if previewSeconds <= 0 {
previewSeconds = consts.DefaultContentPreviewSeconds
}
return &tenant_dto.ContentAssetsResponse{
Content: detail.Content,
Assets: assets,
PreviewSeconds: previewSeconds,
}, nil
}
// mainAssets
//
// @Summary 获取公开正片资源main role免费/作者/已购)
// @Tags TenantPublic
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Param contentID path int64 true "ContentID"
// @Success 200 {object} tenant_dto.ContentAssetsResponse
//
// @Router /t/:tenantCode/v1/public/contents/:contentID/assets [get]
// @Bind tenant local key(tenant)
// @Bind contentID path
func (*content) mainAssets(
ctx fiber.Ctx,
tenant *models.Tenant,
contentID int64,
) (*tenant_dto.ContentAssetsResponse, error) {
uid := viewerUserID(ctx)
log.WithFields(log.Fields{
"tenant_id": tenant.ID,
"user_id": uid,
"content_id": contentID,
}).Info("tenantpublic.contents.main_assets")
detail, err := services.Content.PublicDetail(ctx, tenant.ID, uid, contentID)
if err != nil {
return nil, err
}
if !detail.HasAccess {
return nil, errorx.ErrPermissionDenied.WithMsg("未购买或无权限访问")
}
assets, err := services.Content.AssetsByRole(ctx, tenant.ID, contentID, consts.ContentAssetRoleMain)
if err != nil {
return nil, err
}
return &tenant_dto.ContentAssetsResponse{
Content: detail.Content,
Assets: assets,
}, nil
}