feat: add superadmin assets and notifications

This commit is contained in:
2026-01-15 15:28:41 +08:00
parent c683fa5cf3
commit b896d0fa00
22 changed files with 4852 additions and 260 deletions

View File

@@ -0,0 +1,63 @@
package v1
import (
dto "quyun/v2/app/http/super/v1/dto"
"quyun/v2/app/requests"
"quyun/v2/app/services"
"github.com/gofiber/fiber/v3"
)
// @provider
type assets struct{}
// List assets
//
// @Router /super/v1/assets [get]
// @Summary List assets
// @Description List assets across tenants
// @Tags Asset
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param limit query int false "Page size"
// @Success 200 {object} requests.Pager{items=[]dto.SuperAssetItem}
// @Bind filter query
func (c *assets) List(ctx fiber.Ctx, filter *dto.SuperAssetListFilter) (*requests.Pager, error) {
return services.Super.ListAssets(ctx, filter)
}
// Asset usage
//
// @Router /super/v1/assets/usage [get]
// @Summary Asset usage
// @Description Asset usage statistics
// @Tags Asset
// @Accept json
// @Produce json
// @Success 200 {object} dto.SuperAssetUsageResponse
// @Bind filter query
func (c *assets) Usage(ctx fiber.Ctx, filter *dto.SuperAssetUsageFilter) (*dto.SuperAssetUsageResponse, error) {
return services.Super.AssetUsage(ctx, filter)
}
// Delete asset
//
// @Router /super/v1/assets/:id<int> [delete]
// @Summary Delete asset
// @Description Delete asset
// @Tags Asset
// @Accept json
// @Produce json
// @Param id path int64 true "Asset ID"
// @Param force query bool false "Force delete"
// @Success 200 {string} string "Deleted"
// @Bind id path
// @Bind query query
func (c *assets) Delete(ctx fiber.Ctx, id int64, query *dto.SuperAssetDeleteQuery) error {
force := false
if query != nil && query.Force != nil {
force = *query.Force
}
return services.Super.DeleteAsset(ctx, id, force)
}

View File

@@ -0,0 +1,119 @@
package dto
import (
"quyun/v2/app/requests"
"quyun/v2/pkg/consts"
)
// SuperAssetListFilter 超管资产列表查询条件。
type SuperAssetListFilter struct {
requests.Pagination
// ID 资产ID精确匹配。
ID *int64 `query:"id"`
// TenantID 租户ID精确匹配。
TenantID *int64 `query:"tenant_id"`
// TenantCode 租户编码,模糊匹配。
TenantCode *string `query:"tenant_code"`
// TenantName 租户名称,模糊匹配。
TenantName *string `query:"tenant_name"`
// UserID 上传用户ID精确匹配。
UserID *int64 `query:"user_id"`
// Username 上传用户名/昵称,模糊匹配。
Username *string `query:"username"`
// Type 媒体类型过滤。
Type *consts.MediaAssetType `query:"type"`
// Status 处理状态过滤。
Status *consts.MediaAssetStatus `query:"status"`
// Provider 存储提供方过滤。
Provider *string `query:"provider"`
// ObjectKey 对象Key关键字模糊匹配。
ObjectKey *string `query:"object_key"`
// CreatedAtFrom 上传时间起始RFC3339
CreatedAtFrom *string `query:"created_at_from"`
// CreatedAtTo 上传时间结束RFC3339
CreatedAtTo *string `query:"created_at_to"`
// SizeMin 文件大小下限(字节)。
SizeMin *int64 `query:"size_min"`
// SizeMax 文件大小上限(字节)。
SizeMax *int64 `query:"size_max"`
// Asc 升序字段id/created_at
Asc *string `query:"asc"`
// Desc 降序字段id/created_at
Desc *string `query:"desc"`
}
// SuperAssetUsageFilter 超管资产用量统计查询条件。
type SuperAssetUsageFilter struct {
// TenantID 租户ID不传代表全平台
TenantID *int64 `query:"tenant_id"`
}
// SuperAssetDeleteQuery 超管资产删除参数。
type SuperAssetDeleteQuery struct {
// Force 是否强制删除(忽略被内容引用的限制)。
Force *bool `query:"force"`
}
// SuperAssetItem 超管资产条目。
type SuperAssetItem struct {
// ID 资产ID。
ID int64 `json:"id"`
// TenantID 租户ID。
TenantID int64 `json:"tenant_id"`
// TenantCode 租户编码。
TenantCode string `json:"tenant_code"`
// TenantName 租户名称。
TenantName string `json:"tenant_name"`
// UserID 上传用户ID。
UserID int64 `json:"user_id"`
// Username 上传用户名/昵称。
Username string `json:"username"`
// Type 媒体类型。
Type consts.MediaAssetType `json:"type"`
// Status 处理状态。
Status consts.MediaAssetStatus `json:"status"`
// Provider 存储提供方。
Provider string `json:"provider"`
// Bucket 存储桶名称。
Bucket string `json:"bucket"`
// ObjectKey 对象Key。
ObjectKey string `json:"object_key"`
// URL 访问URL若可用
URL string `json:"url"`
// Filename 原始文件名。
Filename string `json:"filename"`
// Size 文件大小(字节)。
Size int64 `json:"size"`
// Hash 文件哈希MD5
Hash string `json:"hash"`
// Variant 媒体变体main/preview/cover 等)。
Variant consts.MediaAssetVariant `json:"variant"`
// SourceAssetID 源资产ID用于变体关联
SourceAssetID int64 `json:"source_asset_id"`
// UsedCount 被内容引用次数。
UsedCount int64 `json:"used_count"`
// CreatedAt 创建时间RFC3339
CreatedAt string `json:"created_at"`
// UpdatedAt 更新时间RFC3339
UpdatedAt string `json:"updated_at"`
}
// SuperAssetUsageResponse 超管资产用量统计响应。
type SuperAssetUsageResponse struct {
// TotalCount 资产总量。
TotalCount int64 `json:"total_count"`
// TotalSize 资产总大小(字节)。
TotalSize int64 `json:"total_size"`
// ByType 按媒体类型汇总的用量统计。
ByType []SuperAssetUsageItem `json:"by_type"`
}
// SuperAssetUsageItem 资产类型用量统计条目。
type SuperAssetUsageItem struct {
// Type 媒体类型。
Type consts.MediaAssetType `json:"type"`
// Count 该类型资产数量。
Count int64 `json:"count"`
// TotalSize 该类型资产大小总和(字节)。
TotalSize int64 `json:"total_size"`
}

View File

@@ -0,0 +1,140 @@
package dto
import (
"quyun/v2/app/requests"
"quyun/v2/pkg/consts"
)
// SuperNotificationListFilter 超管通知列表查询条件。
type SuperNotificationListFilter struct {
requests.Pagination
// ID 通知ID精确匹配。
ID *int64 `query:"id"`
// TenantID 租户ID精确匹配。
TenantID *int64 `query:"tenant_id"`
// TenantCode 租户编码,模糊匹配。
TenantCode *string `query:"tenant_code"`
// TenantName 租户名称,模糊匹配。
TenantName *string `query:"tenant_name"`
// UserID 用户ID精确匹配。
UserID *int64 `query:"user_id"`
// Username 用户名/昵称,模糊匹配。
Username *string `query:"username"`
// Type 通知类型过滤。
Type *consts.NotificationType `query:"type"`
// IsRead 是否已读过滤。
IsRead *bool `query:"is_read"`
// Keyword 标题或内容关键词,模糊匹配。
Keyword *string `query:"keyword"`
// CreatedAtFrom 创建时间起始RFC3339
CreatedAtFrom *string `query:"created_at_from"`
// CreatedAtTo 创建时间结束RFC3339
CreatedAtTo *string `query:"created_at_to"`
// Asc 升序字段id/created_at
Asc *string `query:"asc"`
// Desc 降序字段id/created_at
Desc *string `query:"desc"`
}
// SuperNotificationBroadcastForm 超管通知群发参数。
type SuperNotificationBroadcastForm struct {
// TenantID 租户ID选填用于指定租户成员
TenantID *int64 `json:"tenant_id"`
// UserIDs 指定接收用户ID列表优先级高于 TenantID
UserIDs []int64 `json:"user_ids"`
// Type 通知类型system/order/audit/interaction
Type consts.NotificationType `json:"type"`
// Title 通知标题。
Title string `json:"title"`
// Content 通知内容。
Content string `json:"content"`
}
// SuperNotificationItem 超管通知条目。
type SuperNotificationItem struct {
// ID 通知ID。
ID int64 `json:"id"`
// TenantID 租户ID。
TenantID int64 `json:"tenant_id"`
// TenantCode 租户编码。
TenantCode string `json:"tenant_code"`
// TenantName 租户名称。
TenantName string `json:"tenant_name"`
// UserID 用户ID。
UserID int64 `json:"user_id"`
// Username 用户名/昵称。
Username string `json:"username"`
// Type 通知类型。
Type consts.NotificationType `json:"type"`
// Title 通知标题。
Title string `json:"title"`
// Content 通知内容。
Content string `json:"content"`
// IsRead 是否已读。
IsRead bool `json:"is_read"`
// CreatedAt 创建时间RFC3339
CreatedAt string `json:"created_at"`
}
// SuperNotificationTemplateListFilter 超管通知模板列表查询条件。
type SuperNotificationTemplateListFilter struct {
requests.Pagination
// TenantID 租户ID不传代表全平台模板
TenantID *int64 `query:"tenant_id"`
// Keyword 模板名称或标题关键字,模糊匹配。
Keyword *string `query:"keyword"`
// Type 通知类型过滤。
Type *consts.NotificationType `query:"type"`
// IsActive 是否启用过滤。
IsActive *bool `query:"is_active"`
// CreatedAtFrom 创建时间起始RFC3339
CreatedAtFrom *string `query:"created_at_from"`
// CreatedAtTo 创建时间结束RFC3339
CreatedAtTo *string `query:"created_at_to"`
// Asc 升序字段id/created_at
Asc *string `query:"asc"`
// Desc 降序字段id/created_at
Desc *string `query:"desc"`
}
// SuperNotificationTemplateCreateForm 超管通知模板创建参数。
type SuperNotificationTemplateCreateForm struct {
// TenantID 租户ID不传代表全平台模板
TenantID *int64 `json:"tenant_id"`
// Name 模板名称(用于识别用途)。
Name string `json:"name"`
// Type 通知类型system/order/audit/interaction
Type consts.NotificationType `json:"type"`
// Title 通知标题。
Title string `json:"title"`
// Content 通知内容。
Content string `json:"content"`
// IsActive 是否启用(不传默认启用)。
IsActive *bool `json:"is_active"`
}
// SuperNotificationTemplateItem 超管通知模板条目。
type SuperNotificationTemplateItem struct {
// ID 模板ID。
ID int64 `json:"id"`
// TenantID 租户ID。
TenantID int64 `json:"tenant_id"`
// TenantCode 租户编码。
TenantCode string `json:"tenant_code"`
// TenantName 租户名称。
TenantName string `json:"tenant_name"`
// Name 模板名称。
Name string `json:"name"`
// Type 通知类型。
Type consts.NotificationType `json:"type"`
// Title 模板标题。
Title string `json:"title"`
// Content 模板内容。
Content string `json:"content"`
// IsActive 是否启用。
IsActive bool `json:"is_active"`
// CreatedAt 创建时间RFC3339
CreatedAt string `json:"created_at"`
// UpdatedAt 更新时间RFC3339
UpdatedAt string `json:"updated_at"`
}

View File

@@ -0,0 +1,72 @@
package v1
import (
dto "quyun/v2/app/http/super/v1/dto"
"quyun/v2/app/requests"
"quyun/v2/app/services"
"github.com/gofiber/fiber/v3"
)
// @provider
type notifications struct{}
// List notifications
//
// @Router /super/v1/notifications [get]
// @Summary List notifications
// @Description List notifications across tenants
// @Tags Notification
// @Accept json
// @Produce json
// @Param page query int false "Page number"
// @Param limit query int false "Page size"
// @Success 200 {object} requests.Pager{items=[]dto.SuperNotificationItem}
// @Bind filter query
func (c *notifications) List(ctx fiber.Ctx, filter *dto.SuperNotificationListFilter) (*requests.Pager, error) {
return services.Super.ListNotifications(ctx, filter)
}
// Broadcast notification
//
// @Router /super/v1/notifications/broadcast [post]
// @Summary Broadcast notification
// @Description Broadcast notification to users or tenant members
// @Tags Notification
// @Accept json
// @Produce json
// @Param form body dto.SuperNotificationBroadcastForm true "Broadcast form"
// @Success 200 {string} string "Sent"
// @Bind form body
func (c *notifications) Broadcast(ctx fiber.Ctx, form *dto.SuperNotificationBroadcastForm) error {
return services.Super.BroadcastNotifications(ctx, form)
}
// List notification templates
//
// @Router /super/v1/notifications/templates [get]
// @Summary List notification templates
// @Description List notification templates
// @Tags Notification
// @Accept json
// @Produce json
// @Success 200 {object} requests.Pager{items=[]dto.SuperNotificationTemplateItem}
// @Bind filter query
func (c *notifications) ListTemplates(ctx fiber.Ctx, filter *dto.SuperNotificationTemplateListFilter) (*requests.Pager, error) {
return services.Super.ListNotificationTemplates(ctx, filter)
}
// Create notification template
//
// @Router /super/v1/notifications/templates [post]
// @Summary Create notification template
// @Description Create notification template
// @Tags Notification
// @Accept json
// @Produce json
// @Param form body dto.SuperNotificationTemplateCreateForm true "Template form"
// @Success 200 {object} dto.SuperNotificationTemplateItem
// @Bind form body
func (c *notifications) CreateTemplate(ctx fiber.Ctx, form *dto.SuperNotificationTemplateCreateForm) (*dto.SuperNotificationTemplateItem, error) {
return services.Super.CreateNotificationTemplate(ctx, form)
}

View File

@@ -10,6 +10,13 @@ import (
)
func Provide(opts ...opt.Option) error {
if err := container.Container.Provide(func() (*assets, error) {
obj := &assets{}
return obj, nil
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*contents, error) {
obj := &contents{}
@@ -38,6 +45,13 @@ func Provide(opts ...opt.Option) error {
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*notifications, error) {
obj := &notifications{}
return obj, nil
}); err != nil {
return err
}
if err := container.Container.Provide(func() (*orders, error) {
obj := &orders{}
@@ -60,11 +74,13 @@ func Provide(opts ...opt.Option) error {
return err
}
if err := container.Container.Provide(func(
assets *assets,
contents *contents,
coupons *coupons,
creatorApplications *creatorApplications,
creators *creators,
middlewares *middlewares.Middlewares,
notifications *notifications,
orders *orders,
payoutAccounts *payoutAccounts,
reports *reports,
@@ -73,11 +89,13 @@ func Provide(opts ...opt.Option) error {
withdrawals *withdrawals,
) (contracts.HttpRoute, error) {
obj := &Routes{
assets: assets,
contents: contents,
coupons: coupons,
creatorApplications: creatorApplications,
creators: creators,
middlewares: middlewares,
notifications: notifications,
orders: orders,
payoutAccounts: payoutAccounts,
reports: reports,

View File

@@ -25,10 +25,12 @@ type Routes struct {
log *log.Entry `inject:"false"`
middlewares *middlewares.Middlewares
// Controller instances
assets *assets
contents *contents
coupons *coupons
creatorApplications *creatorApplications
creators *creators
notifications *notifications
orders *orders
payoutAccounts *payoutAccounts
reports *reports
@@ -52,6 +54,23 @@ func (r *Routes) Name() string {
// Register registers all HTTP routes with the provided fiber router.
// Each route is registered with its corresponding controller action and parameter bindings.
func (r *Routes) Register(router fiber.Router) {
// Register routes for controller: assets
r.log.Debugf("Registering route: Delete /super/v1/assets/:id<int> -> assets.Delete")
router.Delete("/super/v1/assets/:id<int>"[len(r.Path()):], Func2(
r.assets.Delete,
PathParam[int64]("id"),
Query[dto.SuperAssetDeleteQuery]("query"),
))
r.log.Debugf("Registering route: Get /super/v1/assets -> assets.List")
router.Get("/super/v1/assets"[len(r.Path()):], DataFunc1(
r.assets.List,
Query[dto.SuperAssetListFilter]("filter"),
))
r.log.Debugf("Registering route: Get /super/v1/assets/usage -> assets.Usage")
router.Get("/super/v1/assets/usage"[len(r.Path()):], DataFunc1(
r.assets.Usage,
Query[dto.SuperAssetUsageFilter]("filter"),
))
// Register routes for controller: contents
r.log.Debugf("Registering route: Get /super/v1/contents -> contents.List")
router.Get("/super/v1/contents"[len(r.Path()):], DataFunc1(
@@ -154,6 +173,27 @@ func (r *Routes) Register(router fiber.Router) {
r.creators.List,
Query[dto.TenantListFilter]("filter"),
))
// Register routes for controller: notifications
r.log.Debugf("Registering route: Get /super/v1/notifications -> notifications.List")
router.Get("/super/v1/notifications"[len(r.Path()):], DataFunc1(
r.notifications.List,
Query[dto.SuperNotificationListFilter]("filter"),
))
r.log.Debugf("Registering route: Get /super/v1/notifications/templates -> notifications.ListTemplates")
router.Get("/super/v1/notifications/templates"[len(r.Path()):], DataFunc1(
r.notifications.ListTemplates,
Query[dto.SuperNotificationTemplateListFilter]("filter"),
))
r.log.Debugf("Registering route: Post /super/v1/notifications/broadcast -> notifications.Broadcast")
router.Post("/super/v1/notifications/broadcast"[len(r.Path()):], Func1(
r.notifications.Broadcast,
Body[dto.SuperNotificationBroadcastForm]("form"),
))
r.log.Debugf("Registering route: Post /super/v1/notifications/templates -> notifications.CreateTemplate")
router.Post("/super/v1/notifications/templates"[len(r.Path()):], DataFunc1(
r.notifications.CreateTemplate,
Body[dto.SuperNotificationTemplateCreateForm]("form"),
))
// Register routes for controller: orders
r.log.Debugf("Registering route: Get /super/v1/orders -> orders.List")
router.Get("/super/v1/orders"[len(r.Path()):], DataFunc1(