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(

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"path/filepath"
"strconv"
"strings"
"time"
@@ -2127,6 +2128,784 @@ func (s *super) ContentStatistics(ctx context.Context, filter *super_dto.SuperCo
}, nil
}
func (s *super) ListAssets(ctx context.Context, filter *super_dto.SuperAssetListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.SuperAssetListFilter{}
}
tbl, q := models.MediaAssetQuery.QueryContext(ctx)
if filter.ID != nil && *filter.ID > 0 {
q = q.Where(tbl.ID.Eq(*filter.ID))
}
if filter.TenantID != nil && *filter.TenantID > 0 {
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
}
if filter.UserID != nil && *filter.UserID > 0 {
q = q.Where(tbl.UserID.Eq(*filter.UserID))
}
if filter.Type != nil && *filter.Type != "" {
q = q.Where(tbl.Type.Eq(*filter.Type))
}
if filter.Status != nil && *filter.Status != "" {
q = q.Where(tbl.Status.Eq(*filter.Status))
}
if filter.Provider != nil && strings.TrimSpace(*filter.Provider) != "" {
q = q.Where(tbl.Provider.Eq(strings.TrimSpace(*filter.Provider)))
}
if filter.ObjectKey != nil && strings.TrimSpace(*filter.ObjectKey) != "" {
keyword := "%" + strings.TrimSpace(*filter.ObjectKey) + "%"
q = q.Where(tbl.ObjectKey.Like(keyword))
}
tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
if err != nil {
return nil, err
}
if tenantFilter {
if len(tenantIDs) == 0 {
q = q.Where(tbl.ID.Eq(-1))
} else {
q = q.Where(tbl.TenantID.In(tenantIDs...))
}
}
userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
if err != nil {
return nil, err
}
if userFilter {
if len(userIDs) == 0 {
q = q.Where(tbl.ID.Eq(-1))
} else {
q = q.Where(tbl.UserID.In(userIDs...))
}
}
if filter.CreatedAtFrom != nil {
from, err := s.parseFilterTime(filter.CreatedAtFrom)
if err != nil {
return nil, err
}
if from != nil {
q = q.Where(tbl.CreatedAt.Gte(*from))
}
}
if filter.CreatedAtTo != nil {
to, err := s.parseFilterTime(filter.CreatedAtTo)
if err != nil {
return nil, err
}
if to != nil {
q = q.Where(tbl.CreatedAt.Lte(*to))
}
}
if filter.SizeMin != nil {
// JSONB 元信息内的 size 需要使用原生表达式过滤。
q = q.Where(field.NewUnsafeFieldRaw("coalesce((meta->>'size')::bigint,0) >= ?", *filter.SizeMin))
}
if filter.SizeMax != nil {
// JSONB 元信息内的 size 需要使用原生表达式过滤。
q = q.Where(field.NewUnsafeFieldRaw("coalesce((meta->>'size')::bigint,0) <= ?", *filter.SizeMax))
}
orderApplied := false
if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
switch strings.TrimSpace(*filter.Desc) {
case "id":
q = q.Order(tbl.ID.Desc())
case "created_at":
q = q.Order(tbl.CreatedAt.Desc())
}
orderApplied = true
} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
switch strings.TrimSpace(*filter.Asc) {
case "id":
q = q.Order(tbl.ID)
case "created_at":
q = q.Order(tbl.CreatedAt)
}
orderApplied = true
}
if !orderApplied {
q = q.Order(tbl.CreatedAt.Desc())
}
filter.Pagination.Format()
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
if len(list) == 0 {
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: []super_dto.SuperAssetItem{},
}, nil
}
tenantSet := make(map[int64]struct{})
userSet := make(map[int64]struct{})
assetIDs := make([]int64, 0, len(list))
for _, asset := range list {
assetIDs = append(assetIDs, asset.ID)
if asset.TenantID > 0 {
tenantSet[asset.TenantID] = struct{}{}
}
if asset.UserID > 0 {
userSet[asset.UserID] = struct{}{}
}
}
tenantMap := make(map[int64]*models.Tenant, len(tenantSet))
if len(tenantSet) > 0 {
ids := make([]int64, 0, len(tenantSet))
for id := range tenantSet {
ids = append(ids, id)
}
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
tenants, err := tenantQuery.Where(tenantTbl.ID.In(ids...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, tenant := range tenants {
tenantMap[tenant.ID] = tenant
}
}
userMap := make(map[int64]*models.User, len(userSet))
if len(userSet) > 0 {
ids := make([]int64, 0, len(userSet))
for id := range userSet {
ids = append(ids, id)
}
userTbl, userQuery := models.UserQuery.QueryContext(ctx)
users, err := userQuery.Where(userTbl.ID.In(ids...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, user := range users {
userMap[user.ID] = user
}
}
usedCountMap := make(map[int64]int64)
if len(assetIDs) > 0 {
type assetUseRow struct {
AssetID int64 `gorm:"column:asset_id"`
Count int64 `gorm:"column:count"`
}
rows := make([]assetUseRow, 0)
query := models.ContentAssetQuery.WithContext(ctx).
UnderlyingDB().
Model(&models.ContentAsset{}).
Select("asset_id, count(*) as count").
Where("asset_id in ?", assetIDs).
Group("asset_id")
if err := query.Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, row := range rows {
usedCountMap[row.AssetID] = row.Count
}
}
items := make([]super_dto.SuperAssetItem, 0, len(list))
for _, asset := range list {
meta := asset.Meta.Data()
filename := strings.TrimSpace(meta.Filename)
if filename == "" {
filename = filepath.Base(asset.ObjectKey)
}
url := ""
if Common != nil {
url = Common.GetAssetURL(asset.ObjectKey)
}
item := super_dto.SuperAssetItem{
ID: asset.ID,
TenantID: asset.TenantID,
UserID: asset.UserID,
Type: asset.Type,
Status: asset.Status,
Provider: asset.Provider,
Bucket: asset.Bucket,
ObjectKey: asset.ObjectKey,
URL: url,
Filename: filename,
Size: meta.Size,
Hash: asset.Hash,
Variant: asset.Variant,
SourceAssetID: asset.SourceAssetID,
UsedCount: usedCountMap[asset.ID],
CreatedAt: s.formatTime(asset.CreatedAt),
UpdatedAt: s.formatTime(asset.UpdatedAt),
}
if tenant := tenantMap[asset.TenantID]; tenant != nil {
item.TenantCode = tenant.Code
item.TenantName = tenant.Name
}
if user := userMap[asset.UserID]; user != nil {
item.Username = user.Username
} else if asset.UserID > 0 {
item.Username = "ID:" + strconv.FormatInt(asset.UserID, 10)
}
items = append(items, item)
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: items,
}, nil
}
func (s *super) AssetUsage(ctx context.Context, filter *super_dto.SuperAssetUsageFilter) (*super_dto.SuperAssetUsageResponse, error) {
tenantID := int64(0)
if filter != nil && filter.TenantID != nil {
tenantID = *filter.TenantID
}
tbl, q := models.MediaAssetQuery.QueryContext(ctx)
if tenantID > 0 {
q = q.Where(tbl.TenantID.Eq(tenantID))
}
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
var totalSize int64
sizeQuery := models.MediaAssetQuery.WithContext(ctx).
UnderlyingDB().
Model(&models.MediaAsset{}).
Select("coalesce(sum((meta->>'size')::bigint), 0) as size")
if tenantID > 0 {
sizeQuery = sizeQuery.Where("tenant_id = ?", tenantID)
}
if err := sizeQuery.Scan(&totalSize).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
type usageRow struct {
Type string `gorm:"column:type"`
Count int64 `gorm:"column:count"`
Size int64 `gorm:"column:size"`
}
rows := make([]usageRow, 0)
typeQuery := models.MediaAssetQuery.WithContext(ctx).
UnderlyingDB().
Model(&models.MediaAsset{}).
Select("type, count(*) as count, coalesce(sum((meta->>'size')::bigint), 0) as size").
Group("type")
if tenantID > 0 {
typeQuery = typeQuery.Where("tenant_id = ?", tenantID)
}
if err := typeQuery.Scan(&rows).Error; err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
byType := make([]super_dto.SuperAssetUsageItem, 0, len(rows))
for _, row := range rows {
byType = append(byType, super_dto.SuperAssetUsageItem{
Type: consts.MediaAssetType(row.Type),
Count: row.Count,
TotalSize: row.Size,
})
}
return &super_dto.SuperAssetUsageResponse{
TotalCount: total,
TotalSize: totalSize,
ByType: byType,
}, nil
}
func (s *super) DeleteAsset(ctx context.Context, assetID int64, force bool) error {
if assetID <= 0 {
return errorx.ErrBadRequest.WithMsg("资产ID不能为空")
}
tbl, q := models.MediaAssetQuery.QueryContext(ctx)
asset, err := q.Where(tbl.ID.Eq(assetID)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errorx.ErrRecordNotFound.WithMsg("资产不存在")
}
return errorx.ErrDatabaseError.WithCause(err)
}
useTbl, useQuery := models.ContentAssetQuery.QueryContext(ctx)
usedCount, err := useQuery.Where(useTbl.AssetID.Eq(assetID)).Count()
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if usedCount > 0 && !force {
return errorx.ErrStatusConflict.WithMsg("资产已被内容引用,无法删除")
}
if usedCount > 0 && force {
// 强制删除时先清理引用关系,避免残留无效关联。
if _, err := useQuery.Where(useTbl.AssetID.Eq(assetID)).Delete(); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
}
if _, err := q.Where(tbl.ID.Eq(assetID)).Delete(); err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if Common != nil && asset.ObjectKey != "" {
count, err := models.MediaAssetQuery.WithContext(ctx).
Where(models.MediaAssetQuery.ObjectKey.Eq(asset.ObjectKey)).
Count()
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
if count == 0 && Common.storage != nil {
_ = Common.storage.Delete(asset.ObjectKey)
}
}
return nil
}
func (s *super) ListNotifications(ctx context.Context, filter *super_dto.SuperNotificationListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.SuperNotificationListFilter{}
}
tbl, q := models.NotificationQuery.QueryContext(ctx)
if filter.ID != nil && *filter.ID > 0 {
q = q.Where(tbl.ID.Eq(*filter.ID))
}
if filter.TenantID != nil && *filter.TenantID > 0 {
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
}
if filter.UserID != nil && *filter.UserID > 0 {
q = q.Where(tbl.UserID.Eq(*filter.UserID))
}
if filter.Type != nil && *filter.Type != "" {
q = q.Where(tbl.Type.Eq(filter.Type.String()))
}
if filter.IsRead != nil {
q = q.Where(tbl.IsRead.Is(*filter.IsRead))
}
if filter.Keyword != nil && strings.TrimSpace(*filter.Keyword) != "" {
keyword := "%" + strings.TrimSpace(*filter.Keyword) + "%"
q = q.Where(field.Or(tbl.Title.Like(keyword), tbl.Content.Like(keyword)))
}
tenantIDs, tenantFilter, err := s.lookupTenantIDs(ctx, filter.TenantCode, filter.TenantName)
if err != nil {
return nil, err
}
if tenantFilter {
if len(tenantIDs) == 0 {
q = q.Where(tbl.ID.Eq(-1))
} else {
q = q.Where(tbl.TenantID.In(tenantIDs...))
}
}
userIDs, userFilter, err := s.lookupUserIDs(ctx, filter.Username)
if err != nil {
return nil, err
}
if userFilter {
if len(userIDs) == 0 {
q = q.Where(tbl.ID.Eq(-1))
} else {
q = q.Where(tbl.UserID.In(userIDs...))
}
}
if filter.CreatedAtFrom != nil {
from, err := s.parseFilterTime(filter.CreatedAtFrom)
if err != nil {
return nil, err
}
if from != nil {
q = q.Where(tbl.CreatedAt.Gte(*from))
}
}
if filter.CreatedAtTo != nil {
to, err := s.parseFilterTime(filter.CreatedAtTo)
if err != nil {
return nil, err
}
if to != nil {
q = q.Where(tbl.CreatedAt.Lte(*to))
}
}
orderApplied := false
if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
switch strings.TrimSpace(*filter.Desc) {
case "id":
q = q.Order(tbl.ID.Desc())
case "created_at":
q = q.Order(tbl.CreatedAt.Desc())
}
orderApplied = true
} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
switch strings.TrimSpace(*filter.Asc) {
case "id":
q = q.Order(tbl.ID)
case "created_at":
q = q.Order(tbl.CreatedAt)
}
orderApplied = true
}
if !orderApplied {
q = q.Order(tbl.CreatedAt.Desc())
}
filter.Pagination.Format()
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
if len(list) == 0 {
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: []super_dto.SuperNotificationItem{},
}, nil
}
tenantSet := make(map[int64]struct{})
userSet := make(map[int64]struct{})
for _, n := range list {
if n.TenantID > 0 {
tenantSet[n.TenantID] = struct{}{}
}
if n.UserID > 0 {
userSet[n.UserID] = struct{}{}
}
}
tenantMap := make(map[int64]*models.Tenant, len(tenantSet))
if len(tenantSet) > 0 {
ids := make([]int64, 0, len(tenantSet))
for id := range tenantSet {
ids = append(ids, id)
}
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
tenants, err := tenantQuery.Where(tenantTbl.ID.In(ids...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, tenant := range tenants {
tenantMap[tenant.ID] = tenant
}
}
userMap := make(map[int64]*models.User, len(userSet))
if len(userSet) > 0 {
ids := make([]int64, 0, len(userSet))
for id := range userSet {
ids = append(ids, id)
}
userTbl, userQuery := models.UserQuery.QueryContext(ctx)
users, err := userQuery.Where(userTbl.ID.In(ids...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, user := range users {
userMap[user.ID] = user
}
}
items := make([]super_dto.SuperNotificationItem, 0, len(list))
for _, n := range list {
item := super_dto.SuperNotificationItem{
ID: n.ID,
TenantID: n.TenantID,
UserID: n.UserID,
Type: consts.NotificationType(n.Type),
Title: n.Title,
Content: n.Content,
IsRead: n.IsRead,
CreatedAt: s.formatTime(n.CreatedAt),
}
if tenant := tenantMap[n.TenantID]; tenant != nil {
item.TenantCode = tenant.Code
item.TenantName = tenant.Name
}
if user := userMap[n.UserID]; user != nil {
item.Username = user.Username
} else if n.UserID > 0 {
item.Username = "ID:" + strconv.FormatInt(n.UserID, 10)
}
items = append(items, item)
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: items,
}, nil
}
func (s *super) BroadcastNotifications(ctx context.Context, form *super_dto.SuperNotificationBroadcastForm) error {
if form == nil {
return errorx.ErrBadRequest.WithMsg("群发参数不能为空")
}
title := strings.TrimSpace(form.Title)
content := strings.TrimSpace(form.Content)
if title == "" || content == "" {
return errorx.ErrBadRequest.WithMsg("通知标题和内容不能为空")
}
if !form.Type.IsValid() {
return errorx.ErrBadRequest.WithMsg("通知类型非法")
}
tenantID := int64(0)
if form.TenantID != nil {
tenantID = *form.TenantID
}
userSet := make(map[int64]struct{})
for _, id := range form.UserIDs {
if id <= 0 {
continue
}
userSet[id] = struct{}{}
}
if len(userSet) == 0 && tenantID <= 0 {
return errorx.ErrBadRequest.WithMsg("请指定租户或用户")
}
if len(userSet) == 0 && tenantID > 0 {
// 仅向该租户的已验证成员发送。
tbl, q := models.TenantUserQuery.QueryContext(ctx)
list, err := q.Where(tbl.TenantID.Eq(tenantID), tbl.Status.Eq(consts.UserStatusVerified)).Find()
if err != nil {
return errorx.ErrDatabaseError.WithCause(err)
}
for _, tu := range list {
if tu.UserID > 0 {
userSet[tu.UserID] = struct{}{}
}
}
}
if len(userSet) == 0 {
return errorx.ErrRecordNotFound.WithMsg("未找到可通知的用户")
}
if Notification == nil {
return errorx.ErrInternalError.WithMsg("通知服务不可用")
}
typ := form.Type.String()
for userID := range userSet {
if err := Notification.Send(ctx, tenantID, userID, typ, title, content); err != nil {
return err
}
}
return nil
}
func (s *super) ListNotificationTemplates(ctx context.Context, filter *super_dto.SuperNotificationTemplateListFilter) (*requests.Pager, error) {
if filter == nil {
filter = &super_dto.SuperNotificationTemplateListFilter{}
}
tbl, q := models.NotificationTemplateQuery.QueryContext(ctx)
if filter.TenantID != nil && *filter.TenantID >= 0 {
q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
}
if filter.Type != nil && *filter.Type != "" {
q = q.Where(tbl.Type.Eq(*filter.Type))
}
if filter.IsActive != nil {
q = q.Where(tbl.IsActive.Is(*filter.IsActive))
}
if filter.Keyword != nil && strings.TrimSpace(*filter.Keyword) != "" {
keyword := "%" + strings.TrimSpace(*filter.Keyword) + "%"
q = q.Where(field.Or(tbl.Name.Like(keyword), tbl.Title.Like(keyword)))
}
if filter.CreatedAtFrom != nil {
from, err := s.parseFilterTime(filter.CreatedAtFrom)
if err != nil {
return nil, err
}
if from != nil {
q = q.Where(tbl.CreatedAt.Gte(*from))
}
}
if filter.CreatedAtTo != nil {
to, err := s.parseFilterTime(filter.CreatedAtTo)
if err != nil {
return nil, err
}
if to != nil {
q = q.Where(tbl.CreatedAt.Lte(*to))
}
}
orderApplied := false
if filter.Desc != nil && strings.TrimSpace(*filter.Desc) != "" {
switch strings.TrimSpace(*filter.Desc) {
case "id":
q = q.Order(tbl.ID.Desc())
case "created_at":
q = q.Order(tbl.CreatedAt.Desc())
}
orderApplied = true
} else if filter.Asc != nil && strings.TrimSpace(*filter.Asc) != "" {
switch strings.TrimSpace(*filter.Asc) {
case "id":
q = q.Order(tbl.ID)
case "created_at":
q = q.Order(tbl.CreatedAt)
}
orderApplied = true
}
if !orderApplied {
q = q.Order(tbl.CreatedAt.Desc())
}
filter.Pagination.Format()
total, err := q.Count()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
if len(list) == 0 {
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: []super_dto.SuperNotificationTemplateItem{},
}, nil
}
tenantSet := make(map[int64]struct{})
for _, tmpl := range list {
if tmpl.TenantID > 0 {
tenantSet[tmpl.TenantID] = struct{}{}
}
}
tenantMap := make(map[int64]*models.Tenant, len(tenantSet))
if len(tenantSet) > 0 {
ids := make([]int64, 0, len(tenantSet))
for id := range tenantSet {
ids = append(ids, id)
}
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
tenants, err := tenantQuery.Where(tenantTbl.ID.In(ids...)).Find()
if err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
for _, tenant := range tenants {
tenantMap[tenant.ID] = tenant
}
}
items := make([]super_dto.SuperNotificationTemplateItem, 0, len(list))
for _, tmpl := range list {
item := super_dto.SuperNotificationTemplateItem{
ID: tmpl.ID,
TenantID: tmpl.TenantID,
Name: tmpl.Name,
Type: tmpl.Type,
Title: tmpl.Title,
Content: tmpl.Content,
IsActive: tmpl.IsActive,
CreatedAt: s.formatTime(tmpl.CreatedAt),
UpdatedAt: s.formatTime(tmpl.UpdatedAt),
}
if tenant := tenantMap[tmpl.TenantID]; tenant != nil {
item.TenantCode = tenant.Code
item.TenantName = tenant.Name
}
items = append(items, item)
}
return &requests.Pager{
Pagination: filter.Pagination,
Total: total,
Items: items,
}, nil
}
func (s *super) CreateNotificationTemplate(ctx context.Context, form *super_dto.SuperNotificationTemplateCreateForm) (*super_dto.SuperNotificationTemplateItem, error) {
if form == nil {
return nil, errorx.ErrBadRequest.WithMsg("模板参数不能为空")
}
name := strings.TrimSpace(form.Name)
title := strings.TrimSpace(form.Title)
content := strings.TrimSpace(form.Content)
if name == "" || title == "" || content == "" {
return nil, errorx.ErrBadRequest.WithMsg("模板名称、标题和内容不能为空")
}
if !form.Type.IsValid() {
return nil, errorx.ErrBadRequest.WithMsg("通知类型非法")
}
tenantID := int64(0)
if form.TenantID != nil {
tenantID = *form.TenantID
}
isActive := true
if form.IsActive != nil {
isActive = *form.IsActive
}
tmpl := &models.NotificationTemplate{
TenantID: tenantID,
Name: name,
Type: form.Type,
Title: title,
Content: content,
IsActive: isActive,
}
if err := models.NotificationTemplateQuery.WithContext(ctx).Create(tmpl); err != nil {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
item := &super_dto.SuperNotificationTemplateItem{
ID: tmpl.ID,
TenantID: tmpl.TenantID,
Name: tmpl.Name,
Type: tmpl.Type,
Title: tmpl.Title,
Content: tmpl.Content,
IsActive: tmpl.IsActive,
CreatedAt: s.formatTime(tmpl.CreatedAt),
UpdatedAt: s.formatTime(tmpl.UpdatedAt),
}
if tmpl.TenantID > 0 {
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
tenant, err := tenantQuery.Where(tenantTbl.ID.Eq(tmpl.TenantID)).First()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.ErrDatabaseError.WithCause(err)
}
if tenant != nil {
item.TenantCode = tenant.Code
item.TenantName = tenant.Name
}
}
return item, nil
}
func (s *super) ListOrders(ctx context.Context, filter *super_dto.SuperOrderListFilter) (*requests.Pager, error) {
tbl, q := models.OrderQuery.QueryContext(ctx)

View File

@@ -52,6 +52,8 @@ field_type:
type: consts.CouponType
user_coupons:
status: consts.UserCouponStatus
notification_templates:
type: consts.NotificationType
field_relate:
contents:
Author:

View File

@@ -0,0 +1,26 @@
-- +goose Up
CREATE TABLE IF NOT EXISTS notification_templates (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
name VARCHAR(128) NOT NULL,
type VARCHAR(32) NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE notification_templates IS '通知模板:用于超管/运营预置通知内容,便于批量触达与统一管理。';
COMMENT ON COLUMN notification_templates.id IS '模板主键ID用于后台检索与引用。';
COMMENT ON COLUMN notification_templates.tenant_id IS '归属租户ID0 表示平台级模板);用于限制模板适用范围。';
COMMENT ON COLUMN notification_templates.name IS '模板名称,用于后台识别用途(如“提现提醒”“内容审核通过”),不直接下发给用户。';
COMMENT ON COLUMN notification_templates.type IS '通知类型system/order/audit/interaction需与前端枚举一致用于分类与筛选。';
COMMENT ON COLUMN notification_templates.title IS '通知标题,直接展示给用户的标题文本。';
COMMENT ON COLUMN notification_templates.content IS '通知内容,直接展示给用户的正文,可包含简要说明与行动提示。';
COMMENT ON COLUMN notification_templates.is_active IS '是否启用;禁用模板不可用于发送,便于临时下架或停用。';
COMMENT ON COLUMN notification_templates.created_at IS '创建时间,按时间排序与审计使用。';
COMMENT ON COLUMN notification_templates.updated_at IS '更新时间,记录最近一次编辑时间。';
-- +goose Down
DROP TABLE IF EXISTS notification_templates;

View File

@@ -40,9 +40,9 @@ type Content struct {
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp with time zone" json:"deleted_at"`
Key string `gorm:"column:key;type:character varying(32);comment:Musical key/tone" json:"key"` // Musical key/tone
IsPinned bool `gorm:"column:is_pinned;type:boolean;comment:Whether content is pinned/featured" json:"is_pinned"` // Whether content is pinned/featured
Comments []*Comment `gorm:"foreignKey:ContentID;references:ID" json:"comments,omitempty"`
Author *User `gorm:"foreignKey:UserID;references:ID" json:"author,omitempty"`
ContentAssets []*ContentAsset `gorm:"foreignKey:ContentID;references:ID" json:"content_assets,omitempty"`
Comments []*Comment `gorm:"foreignKey:ContentID;references:ID" json:"comments,omitempty"`
}
// Quick operations without importing query package

View File

@@ -46,12 +46,6 @@ func newContent(db *gorm.DB, opts ...gen.DOOption) contentQuery {
_contentQuery.DeletedAt = field.NewField(tableName, "deleted_at")
_contentQuery.Key = field.NewString(tableName, "key")
_contentQuery.IsPinned = field.NewBool(tableName, "is_pinned")
_contentQuery.Comments = contentQueryHasManyComments{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Comments", "Comment"),
}
_contentQuery.Author = contentQueryBelongsToAuthor{
db: db.Session(&gorm.Session{}),
@@ -64,6 +58,12 @@ func newContent(db *gorm.DB, opts ...gen.DOOption) contentQuery {
RelationField: field.NewRelation("ContentAssets", "ContentAsset"),
}
_contentQuery.Comments = contentQueryHasManyComments{
db: db.Session(&gorm.Session{}),
RelationField: field.NewRelation("Comments", "Comment"),
}
_contentQuery.fillFieldMap()
return _contentQuery
@@ -94,12 +94,12 @@ type contentQuery struct {
DeletedAt field.Field
Key field.String // Musical key/tone
IsPinned field.Bool // Whether content is pinned/featured
Comments contentQueryHasManyComments
Author contentQueryBelongsToAuthor
Author contentQueryBelongsToAuthor
ContentAssets contentQueryHasManyContentAssets
Comments contentQueryHasManyComments
fieldMap map[string]field.Expr
}
@@ -195,104 +195,23 @@ func (c *contentQuery) fillFieldMap() {
func (c contentQuery) clone(db *gorm.DB) contentQuery {
c.contentQueryDo.ReplaceConnPool(db.Statement.ConnPool)
c.Comments.db = db.Session(&gorm.Session{Initialized: true})
c.Comments.db.Statement.ConnPool = db.Statement.ConnPool
c.Author.db = db.Session(&gorm.Session{Initialized: true})
c.Author.db.Statement.ConnPool = db.Statement.ConnPool
c.ContentAssets.db = db.Session(&gorm.Session{Initialized: true})
c.ContentAssets.db.Statement.ConnPool = db.Statement.ConnPool
c.Comments.db = db.Session(&gorm.Session{Initialized: true})
c.Comments.db.Statement.ConnPool = db.Statement.ConnPool
return c
}
func (c contentQuery) replaceDB(db *gorm.DB) contentQuery {
c.contentQueryDo.ReplaceDB(db)
c.Comments.db = db.Session(&gorm.Session{})
c.Author.db = db.Session(&gorm.Session{})
c.ContentAssets.db = db.Session(&gorm.Session{})
c.Comments.db = db.Session(&gorm.Session{})
return c
}
type contentQueryHasManyComments struct {
db *gorm.DB
field.RelationField
}
func (a contentQueryHasManyComments) Where(conds ...field.Expr) *contentQueryHasManyComments {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a contentQueryHasManyComments) WithContext(ctx context.Context) *contentQueryHasManyComments {
a.db = a.db.WithContext(ctx)
return &a
}
func (a contentQueryHasManyComments) Session(session *gorm.Session) *contentQueryHasManyComments {
a.db = a.db.Session(session)
return &a
}
func (a contentQueryHasManyComments) Model(m *Content) *contentQueryHasManyCommentsTx {
return &contentQueryHasManyCommentsTx{a.db.Model(m).Association(a.Name())}
}
func (a contentQueryHasManyComments) Unscoped() *contentQueryHasManyComments {
a.db = a.db.Unscoped()
return &a
}
type contentQueryHasManyCommentsTx struct{ tx *gorm.Association }
func (a contentQueryHasManyCommentsTx) Find() (result []*Comment, err error) {
return result, a.tx.Find(&result)
}
func (a contentQueryHasManyCommentsTx) Append(values ...*Comment) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a contentQueryHasManyCommentsTx) Replace(values ...*Comment) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a contentQueryHasManyCommentsTx) Delete(values ...*Comment) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a contentQueryHasManyCommentsTx) Clear() error {
return a.tx.Clear()
}
func (a contentQueryHasManyCommentsTx) Count() int64 {
return a.tx.Count()
}
func (a contentQueryHasManyCommentsTx) Unscoped() *contentQueryHasManyCommentsTx {
a.tx = a.tx.Unscoped()
return &a
}
type contentQueryBelongsToAuthor struct {
db *gorm.DB
@@ -455,6 +374,87 @@ func (a contentQueryHasManyContentAssetsTx) Unscoped() *contentQueryHasManyConte
return &a
}
type contentQueryHasManyComments struct {
db *gorm.DB
field.RelationField
}
func (a contentQueryHasManyComments) Where(conds ...field.Expr) *contentQueryHasManyComments {
if len(conds) == 0 {
return &a
}
exprs := make([]clause.Expression, 0, len(conds))
for _, cond := range conds {
exprs = append(exprs, cond.BeCond().(clause.Expression))
}
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
return &a
}
func (a contentQueryHasManyComments) WithContext(ctx context.Context) *contentQueryHasManyComments {
a.db = a.db.WithContext(ctx)
return &a
}
func (a contentQueryHasManyComments) Session(session *gorm.Session) *contentQueryHasManyComments {
a.db = a.db.Session(session)
return &a
}
func (a contentQueryHasManyComments) Model(m *Content) *contentQueryHasManyCommentsTx {
return &contentQueryHasManyCommentsTx{a.db.Model(m).Association(a.Name())}
}
func (a contentQueryHasManyComments) Unscoped() *contentQueryHasManyComments {
a.db = a.db.Unscoped()
return &a
}
type contentQueryHasManyCommentsTx struct{ tx *gorm.Association }
func (a contentQueryHasManyCommentsTx) Find() (result []*Comment, err error) {
return result, a.tx.Find(&result)
}
func (a contentQueryHasManyCommentsTx) Append(values ...*Comment) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Append(targetValues...)
}
func (a contentQueryHasManyCommentsTx) Replace(values ...*Comment) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Replace(targetValues...)
}
func (a contentQueryHasManyCommentsTx) Delete(values ...*Comment) (err error) {
targetValues := make([]interface{}, len(values))
for i, v := range values {
targetValues[i] = v
}
return a.tx.Delete(targetValues...)
}
func (a contentQueryHasManyCommentsTx) Clear() error {
return a.tx.Clear()
}
func (a contentQueryHasManyCommentsTx) Count() int64 {
return a.tx.Count()
}
func (a contentQueryHasManyCommentsTx) Unscoped() *contentQueryHasManyCommentsTx {
a.tx = a.tx.Unscoped()
return &a
}
type contentQueryDo struct{ gen.DO }
func (c contentQueryDo) Debug() *contentQueryDo {

View File

@@ -0,0 +1,65 @@
// Code generated by go.ipao.vip/gen. DO NOT EDIT.
// Code generated by go.ipao.vip/gen. DO NOT EDIT.
// Code generated by go.ipao.vip/gen. DO NOT EDIT.
package models
import (
"context"
"time"
"quyun/v2/pkg/consts"
"go.ipao.vip/gen"
)
const TableNameNotificationTemplate = "notification_templates"
// NotificationTemplate mapped from table <notification_templates>
type NotificationTemplate struct {
ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true;comment:模板主键ID用于后台检索与引用。" json:"id"` // 模板主键ID用于后台检索与引用。
TenantID int64 `gorm:"column:tenant_id;type:bigint;not null;comment:归属租户ID0 表示平台级模板);用于限制模板适用范围。" json:"tenant_id"` // 归属租户ID0 表示平台级模板);用于限制模板适用范围。
Name string `gorm:"column:name;type:character varying(128);not null;comment:模板名称,用于后台识别用途(如“提现提醒”“内容审核通过”),不直接下发给用户。" json:"name"` // 模板名称,用于后台识别用途(如“提现提醒”“内容审核通过”),不直接下发给用户。
Type consts.NotificationType `gorm:"column:type;type:character varying(32);not null;comment:通知类型system/order/audit/interaction需与前端枚举一致用于分类与筛选。" json:"type"` // 通知类型system/order/audit/interaction需与前端枚举一致用于分类与筛选。
Title string `gorm:"column:title;type:character varying(255);not null;comment:通知标题,直接展示给用户的标题文本。" json:"title"` // 通知标题,直接展示给用户的标题文本。
Content string `gorm:"column:content;type:text;not null;comment:通知内容,直接展示给用户的正文,可包含简要说明与行动提示。" json:"content"` // 通知内容,直接展示给用户的正文,可包含简要说明与行动提示。
IsActive bool `gorm:"column:is_active;type:boolean;not null;default:true;comment:是否启用;禁用模板不可用于发送,便于临时下架或停用。" json:"is_active"` // 是否启用;禁用模板不可用于发送,便于临时下架或停用。
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;not null;default:now();comment:创建时间,按时间排序与审计使用。" json:"created_at"` // 创建时间,按时间排序与审计使用。
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;not null;default:now();comment:更新时间,记录最近一次编辑时间。" json:"updated_at"` // 更新时间,记录最近一次编辑时间。
}
// Quick operations without importing query package
// Update applies changed fields to the database using the default DB.
func (m *NotificationTemplate) Update(ctx context.Context) (gen.ResultInfo, error) {
return Q.NotificationTemplate.WithContext(ctx).Updates(m)
}
// Save upserts the model using the default DB.
func (m *NotificationTemplate) Save(ctx context.Context) error {
return Q.NotificationTemplate.WithContext(ctx).Save(m)
}
// Create inserts the model using the default DB.
func (m *NotificationTemplate) Create(ctx context.Context) error {
return Q.NotificationTemplate.WithContext(ctx).Create(m)
}
// Delete removes the row represented by the model using the default DB.
func (m *NotificationTemplate) Delete(ctx context.Context) (gen.ResultInfo, error) {
return Q.NotificationTemplate.WithContext(ctx).Delete(m)
}
// ForceDelete permanently deletes the row (ignores soft delete) using the default DB.
func (m *NotificationTemplate) ForceDelete(ctx context.Context) (gen.ResultInfo, error) {
return Q.NotificationTemplate.WithContext(ctx).Unscoped().Delete(m)
}
// Reload reloads the model from database by its primary key and overwrites current fields.
func (m *NotificationTemplate) Reload(ctx context.Context) error {
fresh, err := Q.NotificationTemplate.WithContext(ctx).GetByID(m.ID)
if err != nil {
return err
}
*m = *fresh
return nil
}

View File

@@ -0,0 +1,495 @@
// Code generated by go.ipao.vip/gen. DO NOT EDIT.
// Code generated by go.ipao.vip/gen. DO NOT EDIT.
// Code generated by go.ipao.vip/gen. DO NOT EDIT.
package models
import (
"context"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"go.ipao.vip/gen"
"go.ipao.vip/gen/field"
"gorm.io/plugin/dbresolver"
)
func newNotificationTemplate(db *gorm.DB, opts ...gen.DOOption) notificationTemplateQuery {
_notificationTemplateQuery := notificationTemplateQuery{}
_notificationTemplateQuery.notificationTemplateQueryDo.UseDB(db, opts...)
_notificationTemplateQuery.notificationTemplateQueryDo.UseModel(&NotificationTemplate{})
tableName := _notificationTemplateQuery.notificationTemplateQueryDo.TableName()
_notificationTemplateQuery.ALL = field.NewAsterisk(tableName)
_notificationTemplateQuery.ID = field.NewInt64(tableName, "id")
_notificationTemplateQuery.TenantID = field.NewInt64(tableName, "tenant_id")
_notificationTemplateQuery.Name = field.NewString(tableName, "name")
_notificationTemplateQuery.Type = field.NewField(tableName, "type")
_notificationTemplateQuery.Title = field.NewString(tableName, "title")
_notificationTemplateQuery.Content = field.NewString(tableName, "content")
_notificationTemplateQuery.IsActive = field.NewBool(tableName, "is_active")
_notificationTemplateQuery.CreatedAt = field.NewTime(tableName, "created_at")
_notificationTemplateQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
_notificationTemplateQuery.fillFieldMap()
return _notificationTemplateQuery
}
type notificationTemplateQuery struct {
notificationTemplateQueryDo notificationTemplateQueryDo
ALL field.Asterisk
ID field.Int64 // 模板主键ID用于后台检索与引用。
TenantID field.Int64 // 归属租户ID0 表示平台级模板);用于限制模板适用范围。
Name field.String // 模板名称,用于后台识别用途(如“提现提醒”“内容审核通过”),不直接下发给用户。
Type field.Field // 通知类型system/order/audit/interaction需与前端枚举一致用于分类与筛选。
Title field.String // 通知标题,直接展示给用户的标题文本。
Content field.String // 通知内容,直接展示给用户的正文,可包含简要说明与行动提示。
IsActive field.Bool // 是否启用;禁用模板不可用于发送,便于临时下架或停用。
CreatedAt field.Time // 创建时间,按时间排序与审计使用。
UpdatedAt field.Time // 更新时间,记录最近一次编辑时间。
fieldMap map[string]field.Expr
}
func (n notificationTemplateQuery) Table(newTableName string) *notificationTemplateQuery {
n.notificationTemplateQueryDo.UseTable(newTableName)
return n.updateTableName(newTableName)
}
func (n notificationTemplateQuery) As(alias string) *notificationTemplateQuery {
n.notificationTemplateQueryDo.DO = *(n.notificationTemplateQueryDo.As(alias).(*gen.DO))
return n.updateTableName(alias)
}
func (n *notificationTemplateQuery) updateTableName(table string) *notificationTemplateQuery {
n.ALL = field.NewAsterisk(table)
n.ID = field.NewInt64(table, "id")
n.TenantID = field.NewInt64(table, "tenant_id")
n.Name = field.NewString(table, "name")
n.Type = field.NewField(table, "type")
n.Title = field.NewString(table, "title")
n.Content = field.NewString(table, "content")
n.IsActive = field.NewBool(table, "is_active")
n.CreatedAt = field.NewTime(table, "created_at")
n.UpdatedAt = field.NewTime(table, "updated_at")
n.fillFieldMap()
return n
}
func (n *notificationTemplateQuery) QueryContext(ctx context.Context) (*notificationTemplateQuery, *notificationTemplateQueryDo) {
return n, n.notificationTemplateQueryDo.WithContext(ctx)
}
func (n *notificationTemplateQuery) WithContext(ctx context.Context) *notificationTemplateQueryDo {
return n.notificationTemplateQueryDo.WithContext(ctx)
}
func (n notificationTemplateQuery) TableName() string {
return n.notificationTemplateQueryDo.TableName()
}
func (n notificationTemplateQuery) Alias() string { return n.notificationTemplateQueryDo.Alias() }
func (n notificationTemplateQuery) Columns(cols ...field.Expr) gen.Columns {
return n.notificationTemplateQueryDo.Columns(cols...)
}
func (n *notificationTemplateQuery) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
_f, ok := n.fieldMap[fieldName]
if !ok || _f == nil {
return nil, false
}
_oe, ok := _f.(field.OrderExpr)
return _oe, ok
}
func (n *notificationTemplateQuery) fillFieldMap() {
n.fieldMap = make(map[string]field.Expr, 9)
n.fieldMap["id"] = n.ID
n.fieldMap["tenant_id"] = n.TenantID
n.fieldMap["name"] = n.Name
n.fieldMap["type"] = n.Type
n.fieldMap["title"] = n.Title
n.fieldMap["content"] = n.Content
n.fieldMap["is_active"] = n.IsActive
n.fieldMap["created_at"] = n.CreatedAt
n.fieldMap["updated_at"] = n.UpdatedAt
}
func (n notificationTemplateQuery) clone(db *gorm.DB) notificationTemplateQuery {
n.notificationTemplateQueryDo.ReplaceConnPool(db.Statement.ConnPool)
return n
}
func (n notificationTemplateQuery) replaceDB(db *gorm.DB) notificationTemplateQuery {
n.notificationTemplateQueryDo.ReplaceDB(db)
return n
}
type notificationTemplateQueryDo struct{ gen.DO }
func (n notificationTemplateQueryDo) Debug() *notificationTemplateQueryDo {
return n.withDO(n.DO.Debug())
}
func (n notificationTemplateQueryDo) WithContext(ctx context.Context) *notificationTemplateQueryDo {
return n.withDO(n.DO.WithContext(ctx))
}
func (n notificationTemplateQueryDo) ReadDB() *notificationTemplateQueryDo {
return n.Clauses(dbresolver.Read)
}
func (n notificationTemplateQueryDo) WriteDB() *notificationTemplateQueryDo {
return n.Clauses(dbresolver.Write)
}
func (n notificationTemplateQueryDo) Session(config *gorm.Session) *notificationTemplateQueryDo {
return n.withDO(n.DO.Session(config))
}
func (n notificationTemplateQueryDo) Clauses(conds ...clause.Expression) *notificationTemplateQueryDo {
return n.withDO(n.DO.Clauses(conds...))
}
func (n notificationTemplateQueryDo) Returning(value interface{}, columns ...string) *notificationTemplateQueryDo {
return n.withDO(n.DO.Returning(value, columns...))
}
func (n notificationTemplateQueryDo) Not(conds ...gen.Condition) *notificationTemplateQueryDo {
return n.withDO(n.DO.Not(conds...))
}
func (n notificationTemplateQueryDo) Or(conds ...gen.Condition) *notificationTemplateQueryDo {
return n.withDO(n.DO.Or(conds...))
}
func (n notificationTemplateQueryDo) Select(conds ...field.Expr) *notificationTemplateQueryDo {
return n.withDO(n.DO.Select(conds...))
}
func (n notificationTemplateQueryDo) Where(conds ...gen.Condition) *notificationTemplateQueryDo {
return n.withDO(n.DO.Where(conds...))
}
func (n notificationTemplateQueryDo) Order(conds ...field.Expr) *notificationTemplateQueryDo {
return n.withDO(n.DO.Order(conds...))
}
func (n notificationTemplateQueryDo) Distinct(cols ...field.Expr) *notificationTemplateQueryDo {
return n.withDO(n.DO.Distinct(cols...))
}
func (n notificationTemplateQueryDo) Omit(cols ...field.Expr) *notificationTemplateQueryDo {
return n.withDO(n.DO.Omit(cols...))
}
func (n notificationTemplateQueryDo) Join(table schema.Tabler, on ...field.Expr) *notificationTemplateQueryDo {
return n.withDO(n.DO.Join(table, on...))
}
func (n notificationTemplateQueryDo) LeftJoin(table schema.Tabler, on ...field.Expr) *notificationTemplateQueryDo {
return n.withDO(n.DO.LeftJoin(table, on...))
}
func (n notificationTemplateQueryDo) RightJoin(table schema.Tabler, on ...field.Expr) *notificationTemplateQueryDo {
return n.withDO(n.DO.RightJoin(table, on...))
}
func (n notificationTemplateQueryDo) Group(cols ...field.Expr) *notificationTemplateQueryDo {
return n.withDO(n.DO.Group(cols...))
}
func (n notificationTemplateQueryDo) Having(conds ...gen.Condition) *notificationTemplateQueryDo {
return n.withDO(n.DO.Having(conds...))
}
func (n notificationTemplateQueryDo) Limit(limit int) *notificationTemplateQueryDo {
return n.withDO(n.DO.Limit(limit))
}
func (n notificationTemplateQueryDo) Offset(offset int) *notificationTemplateQueryDo {
return n.withDO(n.DO.Offset(offset))
}
func (n notificationTemplateQueryDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *notificationTemplateQueryDo {
return n.withDO(n.DO.Scopes(funcs...))
}
func (n notificationTemplateQueryDo) Unscoped() *notificationTemplateQueryDo {
return n.withDO(n.DO.Unscoped())
}
func (n notificationTemplateQueryDo) Create(values ...*NotificationTemplate) error {
if len(values) == 0 {
return nil
}
return n.DO.Create(values)
}
func (n notificationTemplateQueryDo) CreateInBatches(values []*NotificationTemplate, batchSize int) error {
return n.DO.CreateInBatches(values, batchSize)
}
// Save : !!! underlying implementation is different with GORM
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
func (n notificationTemplateQueryDo) Save(values ...*NotificationTemplate) error {
if len(values) == 0 {
return nil
}
return n.DO.Save(values)
}
func (n notificationTemplateQueryDo) First() (*NotificationTemplate, error) {
if result, err := n.DO.First(); err != nil {
return nil, err
} else {
return result.(*NotificationTemplate), nil
}
}
func (n notificationTemplateQueryDo) Take() (*NotificationTemplate, error) {
if result, err := n.DO.Take(); err != nil {
return nil, err
} else {
return result.(*NotificationTemplate), nil
}
}
func (n notificationTemplateQueryDo) Last() (*NotificationTemplate, error) {
if result, err := n.DO.Last(); err != nil {
return nil, err
} else {
return result.(*NotificationTemplate), nil
}
}
func (n notificationTemplateQueryDo) Find() ([]*NotificationTemplate, error) {
result, err := n.DO.Find()
return result.([]*NotificationTemplate), err
}
func (n notificationTemplateQueryDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*NotificationTemplate, err error) {
buf := make([]*NotificationTemplate, 0, batchSize)
err = n.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
defer func() { results = append(results, buf...) }()
return fc(tx, batch)
})
return results, err
}
func (n notificationTemplateQueryDo) FindInBatches(result *[]*NotificationTemplate, batchSize int, fc func(tx gen.Dao, batch int) error) error {
return n.DO.FindInBatches(result, batchSize, fc)
}
func (n notificationTemplateQueryDo) Attrs(attrs ...field.AssignExpr) *notificationTemplateQueryDo {
return n.withDO(n.DO.Attrs(attrs...))
}
func (n notificationTemplateQueryDo) Assign(attrs ...field.AssignExpr) *notificationTemplateQueryDo {
return n.withDO(n.DO.Assign(attrs...))
}
func (n notificationTemplateQueryDo) Joins(fields ...field.RelationField) *notificationTemplateQueryDo {
for _, _f := range fields {
n = *n.withDO(n.DO.Joins(_f))
}
return &n
}
func (n notificationTemplateQueryDo) Preload(fields ...field.RelationField) *notificationTemplateQueryDo {
for _, _f := range fields {
n = *n.withDO(n.DO.Preload(_f))
}
return &n
}
func (n notificationTemplateQueryDo) FirstOrInit() (*NotificationTemplate, error) {
if result, err := n.DO.FirstOrInit(); err != nil {
return nil, err
} else {
return result.(*NotificationTemplate), nil
}
}
func (n notificationTemplateQueryDo) FirstOrCreate() (*NotificationTemplate, error) {
if result, err := n.DO.FirstOrCreate(); err != nil {
return nil, err
} else {
return result.(*NotificationTemplate), nil
}
}
func (n notificationTemplateQueryDo) FindByPage(offset int, limit int) (result []*NotificationTemplate, count int64, err error) {
result, err = n.Offset(offset).Limit(limit).Find()
if err != nil {
return
}
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
count, err = n.Offset(-1).Limit(-1).Count()
return
}
func (n notificationTemplateQueryDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
count, err = n.Count()
if err != nil {
return
}
err = n.Offset(offset).Limit(limit).Scan(result)
return
}
func (n notificationTemplateQueryDo) Scan(result interface{}) (err error) {
return n.DO.Scan(result)
}
func (n notificationTemplateQueryDo) Delete(models ...*NotificationTemplate) (result gen.ResultInfo, err error) {
return n.DO.Delete(models)
}
// ForceDelete performs a permanent delete (ignores soft-delete) for current scope.
func (n notificationTemplateQueryDo) ForceDelete() (gen.ResultInfo, error) {
return n.Unscoped().Delete()
}
// Inc increases the given column by step for current scope.
func (n notificationTemplateQueryDo) Inc(column field.Expr, step int64) (gen.ResultInfo, error) {
// column = column + step
e := field.NewUnsafeFieldRaw("?+?", column.RawExpr(), step)
return n.DO.UpdateColumn(column, e)
}
// Dec decreases the given column by step for current scope.
func (n notificationTemplateQueryDo) Dec(column field.Expr, step int64) (gen.ResultInfo, error) {
// column = column - step
e := field.NewUnsafeFieldRaw("?-?", column.RawExpr(), step)
return n.DO.UpdateColumn(column, e)
}
// Sum returns SUM(column) for current scope.
func (n notificationTemplateQueryDo) Sum(column field.Expr) (float64, error) {
var _v float64
agg := field.NewUnsafeFieldRaw("SUM(?)", column.RawExpr())
if err := n.Select(agg).Scan(&_v); err != nil {
return 0, err
}
return _v, nil
}
// Avg returns AVG(column) for current scope.
func (n notificationTemplateQueryDo) Avg(column field.Expr) (float64, error) {
var _v float64
agg := field.NewUnsafeFieldRaw("AVG(?)", column.RawExpr())
if err := n.Select(agg).Scan(&_v); err != nil {
return 0, err
}
return _v, nil
}
// Min returns MIN(column) for current scope.
func (n notificationTemplateQueryDo) Min(column field.Expr) (float64, error) {
var _v float64
agg := field.NewUnsafeFieldRaw("MIN(?)", column.RawExpr())
if err := n.Select(agg).Scan(&_v); err != nil {
return 0, err
}
return _v, nil
}
// Max returns MAX(column) for current scope.
func (n notificationTemplateQueryDo) Max(column field.Expr) (float64, error) {
var _v float64
agg := field.NewUnsafeFieldRaw("MAX(?)", column.RawExpr())
if err := n.Select(agg).Scan(&_v); err != nil {
return 0, err
}
return _v, nil
}
// PluckMap returns a map[key]value for selected key/value expressions within current scope.
func (n notificationTemplateQueryDo) PluckMap(key, val field.Expr) (map[interface{}]interface{}, error) {
do := n.Select(key, val)
rows, err := do.DO.Rows()
if err != nil {
return nil, err
}
defer rows.Close()
mm := make(map[interface{}]interface{})
for rows.Next() {
var k interface{}
var v interface{}
if err := rows.Scan(&k, &v); err != nil {
return nil, err
}
mm[k] = v
}
return mm, rows.Err()
}
// Exists returns true if any record matches the given conditions.
func (n notificationTemplateQueryDo) Exists(conds ...gen.Condition) (bool, error) {
cnt, err := n.Where(conds...).Count()
if err != nil {
return false, err
}
return cnt > 0, nil
}
// PluckIDs returns all primary key values under current scope.
func (n notificationTemplateQueryDo) PluckIDs() ([]int64, error) {
ids := make([]int64, 0, 16)
pk := field.NewInt64(n.TableName(), "id")
if err := n.DO.Pluck(pk, &ids); err != nil {
return nil, err
}
return ids, nil
}
// GetByID finds a single record by primary key.
func (n notificationTemplateQueryDo) GetByID(id int64) (*NotificationTemplate, error) {
pk := field.NewInt64(n.TableName(), "id")
return n.Where(pk.Eq(id)).First()
}
// GetByIDs finds records by primary key list.
func (n notificationTemplateQueryDo) GetByIDs(ids ...int64) ([]*NotificationTemplate, error) {
if len(ids) == 0 {
return []*NotificationTemplate{}, nil
}
pk := field.NewInt64(n.TableName(), "id")
return n.Where(pk.In(ids...)).Find()
}
// DeleteByID deletes records by primary key.
func (n notificationTemplateQueryDo) DeleteByID(id int64) (gen.ResultInfo, error) {
pk := field.NewInt64(n.TableName(), "id")
return n.Where(pk.Eq(id)).Delete()
}
// DeleteByIDs deletes records by a list of primary keys.
func (n notificationTemplateQueryDo) DeleteByIDs(ids ...int64) (gen.ResultInfo, error) {
if len(ids) == 0 {
return gen.ResultInfo{RowsAffected: 0, Error: nil}, nil
}
pk := field.NewInt64(n.TableName(), "id")
return n.Where(pk.In(ids...)).Delete()
}
func (n *notificationTemplateQueryDo) withDO(do gen.Dao) *notificationTemplateQueryDo {
n.DO = *do.(*gen.DO)
return n
}

View File

@@ -16,27 +16,28 @@ import (
)
var (
Q = new(Query)
CommentQuery *commentQuery
ContentQuery *contentQuery
ContentAccessQuery *contentAccessQuery
ContentAssetQuery *contentAssetQuery
ContentPriceQuery *contentPriceQuery
CouponQuery *couponQuery
MediaAssetQuery *mediaAssetQuery
NotificationQuery *notificationQuery
OrderQuery *orderQuery
OrderItemQuery *orderItemQuery
PayoutAccountQuery *payoutAccountQuery
TenantQuery *tenantQuery
TenantInviteQuery *tenantInviteQuery
TenantJoinRequestQuery *tenantJoinRequestQuery
TenantLedgerQuery *tenantLedgerQuery
TenantUserQuery *tenantUserQuery
UserQuery *userQuery
UserCommentActionQuery *userCommentActionQuery
UserContentActionQuery *userContentActionQuery
UserCouponQuery *userCouponQuery
Q = new(Query)
CommentQuery *commentQuery
ContentQuery *contentQuery
ContentAccessQuery *contentAccessQuery
ContentAssetQuery *contentAssetQuery
ContentPriceQuery *contentPriceQuery
CouponQuery *couponQuery
MediaAssetQuery *mediaAssetQuery
NotificationQuery *notificationQuery
NotificationTemplateQuery *notificationTemplateQuery
OrderQuery *orderQuery
OrderItemQuery *orderItemQuery
PayoutAccountQuery *payoutAccountQuery
TenantQuery *tenantQuery
TenantInviteQuery *tenantInviteQuery
TenantJoinRequestQuery *tenantJoinRequestQuery
TenantLedgerQuery *tenantLedgerQuery
TenantUserQuery *tenantUserQuery
UserQuery *userQuery
UserCommentActionQuery *userCommentActionQuery
UserContentActionQuery *userContentActionQuery
UserCouponQuery *userCouponQuery
)
func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
@@ -49,6 +50,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
CouponQuery = &Q.Coupon
MediaAssetQuery = &Q.MediaAsset
NotificationQuery = &Q.Notification
NotificationTemplateQuery = &Q.NotificationTemplate
OrderQuery = &Q.Order
OrderItemQuery = &Q.OrderItem
PayoutAccountQuery = &Q.PayoutAccount
@@ -65,80 +67,83 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
return &Query{
db: db,
Comment: newComment(db, opts...),
Content: newContent(db, opts...),
ContentAccess: newContentAccess(db, opts...),
ContentAsset: newContentAsset(db, opts...),
ContentPrice: newContentPrice(db, opts...),
Coupon: newCoupon(db, opts...),
MediaAsset: newMediaAsset(db, opts...),
Notification: newNotification(db, opts...),
Order: newOrder(db, opts...),
OrderItem: newOrderItem(db, opts...),
PayoutAccount: newPayoutAccount(db, opts...),
Tenant: newTenant(db, opts...),
TenantInvite: newTenantInvite(db, opts...),
TenantJoinRequest: newTenantJoinRequest(db, opts...),
TenantLedger: newTenantLedger(db, opts...),
TenantUser: newTenantUser(db, opts...),
User: newUser(db, opts...),
UserCommentAction: newUserCommentAction(db, opts...),
UserContentAction: newUserContentAction(db, opts...),
UserCoupon: newUserCoupon(db, opts...),
db: db,
Comment: newComment(db, opts...),
Content: newContent(db, opts...),
ContentAccess: newContentAccess(db, opts...),
ContentAsset: newContentAsset(db, opts...),
ContentPrice: newContentPrice(db, opts...),
Coupon: newCoupon(db, opts...),
MediaAsset: newMediaAsset(db, opts...),
Notification: newNotification(db, opts...),
NotificationTemplate: newNotificationTemplate(db, opts...),
Order: newOrder(db, opts...),
OrderItem: newOrderItem(db, opts...),
PayoutAccount: newPayoutAccount(db, opts...),
Tenant: newTenant(db, opts...),
TenantInvite: newTenantInvite(db, opts...),
TenantJoinRequest: newTenantJoinRequest(db, opts...),
TenantLedger: newTenantLedger(db, opts...),
TenantUser: newTenantUser(db, opts...),
User: newUser(db, opts...),
UserCommentAction: newUserCommentAction(db, opts...),
UserContentAction: newUserContentAction(db, opts...),
UserCoupon: newUserCoupon(db, opts...),
}
}
type Query struct {
db *gorm.DB
Comment commentQuery
Content contentQuery
ContentAccess contentAccessQuery
ContentAsset contentAssetQuery
ContentPrice contentPriceQuery
Coupon couponQuery
MediaAsset mediaAssetQuery
Notification notificationQuery
Order orderQuery
OrderItem orderItemQuery
PayoutAccount payoutAccountQuery
Tenant tenantQuery
TenantInvite tenantInviteQuery
TenantJoinRequest tenantJoinRequestQuery
TenantLedger tenantLedgerQuery
TenantUser tenantUserQuery
User userQuery
UserCommentAction userCommentActionQuery
UserContentAction userContentActionQuery
UserCoupon userCouponQuery
Comment commentQuery
Content contentQuery
ContentAccess contentAccessQuery
ContentAsset contentAssetQuery
ContentPrice contentPriceQuery
Coupon couponQuery
MediaAsset mediaAssetQuery
Notification notificationQuery
NotificationTemplate notificationTemplateQuery
Order orderQuery
OrderItem orderItemQuery
PayoutAccount payoutAccountQuery
Tenant tenantQuery
TenantInvite tenantInviteQuery
TenantJoinRequest tenantJoinRequestQuery
TenantLedger tenantLedgerQuery
TenantUser tenantUserQuery
User userQuery
UserCommentAction userCommentActionQuery
UserContentAction userContentActionQuery
UserCoupon userCouponQuery
}
func (q *Query) Available() bool { return q.db != nil }
func (q *Query) clone(db *gorm.DB) *Query {
return &Query{
db: db,
Comment: q.Comment.clone(db),
Content: q.Content.clone(db),
ContentAccess: q.ContentAccess.clone(db),
ContentAsset: q.ContentAsset.clone(db),
ContentPrice: q.ContentPrice.clone(db),
Coupon: q.Coupon.clone(db),
MediaAsset: q.MediaAsset.clone(db),
Notification: q.Notification.clone(db),
Order: q.Order.clone(db),
OrderItem: q.OrderItem.clone(db),
PayoutAccount: q.PayoutAccount.clone(db),
Tenant: q.Tenant.clone(db),
TenantInvite: q.TenantInvite.clone(db),
TenantJoinRequest: q.TenantJoinRequest.clone(db),
TenantLedger: q.TenantLedger.clone(db),
TenantUser: q.TenantUser.clone(db),
User: q.User.clone(db),
UserCommentAction: q.UserCommentAction.clone(db),
UserContentAction: q.UserContentAction.clone(db),
UserCoupon: q.UserCoupon.clone(db),
db: db,
Comment: q.Comment.clone(db),
Content: q.Content.clone(db),
ContentAccess: q.ContentAccess.clone(db),
ContentAsset: q.ContentAsset.clone(db),
ContentPrice: q.ContentPrice.clone(db),
Coupon: q.Coupon.clone(db),
MediaAsset: q.MediaAsset.clone(db),
Notification: q.Notification.clone(db),
NotificationTemplate: q.NotificationTemplate.clone(db),
Order: q.Order.clone(db),
OrderItem: q.OrderItem.clone(db),
PayoutAccount: q.PayoutAccount.clone(db),
Tenant: q.Tenant.clone(db),
TenantInvite: q.TenantInvite.clone(db),
TenantJoinRequest: q.TenantJoinRequest.clone(db),
TenantLedger: q.TenantLedger.clone(db),
TenantUser: q.TenantUser.clone(db),
User: q.User.clone(db),
UserCommentAction: q.UserCommentAction.clone(db),
UserContentAction: q.UserContentAction.clone(db),
UserCoupon: q.UserCoupon.clone(db),
}
}
@@ -152,75 +157,78 @@ func (q *Query) WriteDB() *Query {
func (q *Query) ReplaceDB(db *gorm.DB) *Query {
return &Query{
db: db,
Comment: q.Comment.replaceDB(db),
Content: q.Content.replaceDB(db),
ContentAccess: q.ContentAccess.replaceDB(db),
ContentAsset: q.ContentAsset.replaceDB(db),
ContentPrice: q.ContentPrice.replaceDB(db),
Coupon: q.Coupon.replaceDB(db),
MediaAsset: q.MediaAsset.replaceDB(db),
Notification: q.Notification.replaceDB(db),
Order: q.Order.replaceDB(db),
OrderItem: q.OrderItem.replaceDB(db),
PayoutAccount: q.PayoutAccount.replaceDB(db),
Tenant: q.Tenant.replaceDB(db),
TenantInvite: q.TenantInvite.replaceDB(db),
TenantJoinRequest: q.TenantJoinRequest.replaceDB(db),
TenantLedger: q.TenantLedger.replaceDB(db),
TenantUser: q.TenantUser.replaceDB(db),
User: q.User.replaceDB(db),
UserCommentAction: q.UserCommentAction.replaceDB(db),
UserContentAction: q.UserContentAction.replaceDB(db),
UserCoupon: q.UserCoupon.replaceDB(db),
db: db,
Comment: q.Comment.replaceDB(db),
Content: q.Content.replaceDB(db),
ContentAccess: q.ContentAccess.replaceDB(db),
ContentAsset: q.ContentAsset.replaceDB(db),
ContentPrice: q.ContentPrice.replaceDB(db),
Coupon: q.Coupon.replaceDB(db),
MediaAsset: q.MediaAsset.replaceDB(db),
Notification: q.Notification.replaceDB(db),
NotificationTemplate: q.NotificationTemplate.replaceDB(db),
Order: q.Order.replaceDB(db),
OrderItem: q.OrderItem.replaceDB(db),
PayoutAccount: q.PayoutAccount.replaceDB(db),
Tenant: q.Tenant.replaceDB(db),
TenantInvite: q.TenantInvite.replaceDB(db),
TenantJoinRequest: q.TenantJoinRequest.replaceDB(db),
TenantLedger: q.TenantLedger.replaceDB(db),
TenantUser: q.TenantUser.replaceDB(db),
User: q.User.replaceDB(db),
UserCommentAction: q.UserCommentAction.replaceDB(db),
UserContentAction: q.UserContentAction.replaceDB(db),
UserCoupon: q.UserCoupon.replaceDB(db),
}
}
type queryCtx struct {
Comment *commentQueryDo
Content *contentQueryDo
ContentAccess *contentAccessQueryDo
ContentAsset *contentAssetQueryDo
ContentPrice *contentPriceQueryDo
Coupon *couponQueryDo
MediaAsset *mediaAssetQueryDo
Notification *notificationQueryDo
Order *orderQueryDo
OrderItem *orderItemQueryDo
PayoutAccount *payoutAccountQueryDo
Tenant *tenantQueryDo
TenantInvite *tenantInviteQueryDo
TenantJoinRequest *tenantJoinRequestQueryDo
TenantLedger *tenantLedgerQueryDo
TenantUser *tenantUserQueryDo
User *userQueryDo
UserCommentAction *userCommentActionQueryDo
UserContentAction *userContentActionQueryDo
UserCoupon *userCouponQueryDo
Comment *commentQueryDo
Content *contentQueryDo
ContentAccess *contentAccessQueryDo
ContentAsset *contentAssetQueryDo
ContentPrice *contentPriceQueryDo
Coupon *couponQueryDo
MediaAsset *mediaAssetQueryDo
Notification *notificationQueryDo
NotificationTemplate *notificationTemplateQueryDo
Order *orderQueryDo
OrderItem *orderItemQueryDo
PayoutAccount *payoutAccountQueryDo
Tenant *tenantQueryDo
TenantInvite *tenantInviteQueryDo
TenantJoinRequest *tenantJoinRequestQueryDo
TenantLedger *tenantLedgerQueryDo
TenantUser *tenantUserQueryDo
User *userQueryDo
UserCommentAction *userCommentActionQueryDo
UserContentAction *userContentActionQueryDo
UserCoupon *userCouponQueryDo
}
func (q *Query) WithContext(ctx context.Context) *queryCtx {
return &queryCtx{
Comment: q.Comment.WithContext(ctx),
Content: q.Content.WithContext(ctx),
ContentAccess: q.ContentAccess.WithContext(ctx),
ContentAsset: q.ContentAsset.WithContext(ctx),
ContentPrice: q.ContentPrice.WithContext(ctx),
Coupon: q.Coupon.WithContext(ctx),
MediaAsset: q.MediaAsset.WithContext(ctx),
Notification: q.Notification.WithContext(ctx),
Order: q.Order.WithContext(ctx),
OrderItem: q.OrderItem.WithContext(ctx),
PayoutAccount: q.PayoutAccount.WithContext(ctx),
Tenant: q.Tenant.WithContext(ctx),
TenantInvite: q.TenantInvite.WithContext(ctx),
TenantJoinRequest: q.TenantJoinRequest.WithContext(ctx),
TenantLedger: q.TenantLedger.WithContext(ctx),
TenantUser: q.TenantUser.WithContext(ctx),
User: q.User.WithContext(ctx),
UserCommentAction: q.UserCommentAction.WithContext(ctx),
UserContentAction: q.UserContentAction.WithContext(ctx),
UserCoupon: q.UserCoupon.WithContext(ctx),
Comment: q.Comment.WithContext(ctx),
Content: q.Content.WithContext(ctx),
ContentAccess: q.ContentAccess.WithContext(ctx),
ContentAsset: q.ContentAsset.WithContext(ctx),
ContentPrice: q.ContentPrice.WithContext(ctx),
Coupon: q.Coupon.WithContext(ctx),
MediaAsset: q.MediaAsset.WithContext(ctx),
Notification: q.Notification.WithContext(ctx),
NotificationTemplate: q.NotificationTemplate.WithContext(ctx),
Order: q.Order.WithContext(ctx),
OrderItem: q.OrderItem.WithContext(ctx),
PayoutAccount: q.PayoutAccount.WithContext(ctx),
Tenant: q.Tenant.WithContext(ctx),
TenantInvite: q.TenantInvite.WithContext(ctx),
TenantJoinRequest: q.TenantJoinRequest.WithContext(ctx),
TenantLedger: q.TenantLedger.WithContext(ctx),
TenantUser: q.TenantUser.WithContext(ctx),
User: q.User.WithContext(ctx),
UserCommentAction: q.UserCommentAction.WithContext(ctx),
UserContentAction: q.UserContentAction.WithContext(ctx),
UserCoupon: q.UserCoupon.WithContext(ctx),
}
}

View File

@@ -24,6 +24,120 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/super/v1/assets": {
"get": {
"description": "List assets across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Asset"
],
"summary": "List assets",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperAssetItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/assets/usage": {
"get": {
"description": "Asset usage statistics",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Asset"
],
"summary": "Asset usage",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SuperAssetUsageResponse"
}
}
}
}
},
"/super/v1/assets/{id}": {
"delete": {
"description": "Delete asset",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Asset"
],
"summary": "Delete asset",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Asset ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Force delete",
"name": "force",
"in": "query"
}
],
"responses": {
"200": {
"description": "Deleted",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/auth/login": {
"post": {
"description": "Login",
@@ -524,6 +638,162 @@ const docTemplate = `{
}
}
},
"/super/v1/notifications": {
"get": {
"description": "List notifications across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "List notifications",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperNotificationItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/notifications/broadcast": {
"post": {
"description": "Broadcast notification to users or tenant members",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "Broadcast notification",
"parameters": [
{
"description": "Broadcast form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperNotificationBroadcastForm"
}
}
],
"responses": {
"200": {
"description": "Sent",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/notifications/templates": {
"get": {
"description": "List notification templates",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "List notification templates",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperNotificationTemplateItem"
}
}
}
}
]
}
}
}
},
"post": {
"description": "Create notification template",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "Create notification template",
"parameters": [
{
"description": "Template form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperNotificationTemplateCreateForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SuperNotificationTemplateItem"
}
}
}
}
},
"/super/v1/orders": {
"get": {
"description": "List orders",
@@ -4976,6 +5246,62 @@ const docTemplate = `{
"GenderSecret"
]
},
"consts.MediaAssetStatus": {
"type": "string",
"enum": [
"uploaded",
"processing",
"ready",
"failed",
"deleted"
],
"x-enum-varnames": [
"MediaAssetStatusUploaded",
"MediaAssetStatusProcessing",
"MediaAssetStatusReady",
"MediaAssetStatusFailed",
"MediaAssetStatusDeleted"
]
},
"consts.MediaAssetType": {
"type": "string",
"enum": [
"video",
"audio",
"image"
],
"x-enum-varnames": [
"MediaAssetTypeVideo",
"MediaAssetTypeAudio",
"MediaAssetTypeImage"
]
},
"consts.MediaAssetVariant": {
"type": "string",
"enum": [
"main",
"preview"
],
"x-enum-varnames": [
"MediaAssetVariantMain",
"MediaAssetVariantPreview"
]
},
"consts.NotificationType": {
"type": "string",
"enum": [
"system",
"order",
"audit",
"interaction"
],
"x-enum-varnames": [
"NotificationTypeSystem",
"NotificationTypeOrder",
"NotificationTypeAudit",
"NotificationTypeInteraction"
]
},
"consts.OrderStatus": {
"type": "string",
"enum": [
@@ -6348,6 +6674,144 @@ const docTemplate = `{
}
}
},
"dto.SuperAssetItem": {
"type": "object",
"properties": {
"bucket": {
"description": "Bucket 存储桶名称。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"filename": {
"description": "Filename 原始文件名。",
"type": "string"
},
"hash": {
"description": "Hash 文件哈希MD5。",
"type": "string"
},
"id": {
"description": "ID 资产ID。",
"type": "integer"
},
"object_key": {
"description": "ObjectKey 对象Key。",
"type": "string"
},
"provider": {
"description": "Provider 存储提供方。",
"type": "string"
},
"size": {
"description": "Size 文件大小(字节)。",
"type": "integer"
},
"source_asset_id": {
"description": "SourceAssetID 源资产ID用于变体关联。",
"type": "integer"
},
"status": {
"description": "Status 处理状态。",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetStatus"
}
]
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"type": {
"description": "Type 媒体类型。",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetType"
}
]
},
"updated_at": {
"description": "UpdatedAt 更新时间RFC3339。",
"type": "string"
},
"url": {
"description": "URL 访问URL若可用。",
"type": "string"
},
"used_count": {
"description": "UsedCount 被内容引用次数。",
"type": "integer"
},
"user_id": {
"description": "UserID 上传用户ID。",
"type": "integer"
},
"username": {
"description": "Username 上传用户名/昵称。",
"type": "string"
},
"variant": {
"description": "Variant 媒体变体main/preview/cover 等)。",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetVariant"
}
]
}
}
},
"dto.SuperAssetUsageItem": {
"type": "object",
"properties": {
"count": {
"description": "Count 该类型资产数量。",
"type": "integer"
},
"total_size": {
"description": "TotalSize 该类型资产大小总和(字节)。",
"type": "integer"
},
"type": {
"description": "Type 媒体类型。",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetType"
}
]
}
}
},
"dto.SuperAssetUsageResponse": {
"type": "object",
"properties": {
"by_type": {
"description": "ByType 按媒体类型汇总的用量统计。",
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperAssetUsageItem"
}
},
"total_count": {
"description": "TotalCount 资产总量。",
"type": "integer"
},
"total_size": {
"description": "TotalSize 资产总大小(字节)。",
"type": "integer"
}
}
},
"dto.SuperContentBatchReviewForm": {
"type": "object",
"required": [
@@ -6633,6 +7097,177 @@ const docTemplate = `{
}
}
},
"dto.SuperNotificationBroadcastForm": {
"type": "object",
"properties": {
"content": {
"description": "Content 通知内容。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID选填用于指定租户成员。",
"type": "integer"
},
"title": {
"description": "Title 通知标题。",
"type": "string"
},
"type": {
"description": "Type 通知类型system/order/audit/interaction。",
"allOf": [
{
"$ref": "#/definitions/consts.NotificationType"
}
]
},
"user_ids": {
"description": "UserIDs 指定接收用户ID列表优先级高于 TenantID。",
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"dto.SuperNotificationItem": {
"type": "object",
"properties": {
"content": {
"description": "Content 通知内容。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 通知ID。",
"type": "integer"
},
"is_read": {
"description": "IsRead 是否已读。",
"type": "boolean"
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"title": {
"description": "Title 通知标题。",
"type": "string"
},
"type": {
"description": "Type 通知类型。",
"allOf": [
{
"$ref": "#/definitions/consts.NotificationType"
}
]
},
"user_id": {
"description": "UserID 用户ID。",
"type": "integer"
},
"username": {
"description": "Username 用户名/昵称。",
"type": "string"
}
}
},
"dto.SuperNotificationTemplateCreateForm": {
"type": "object",
"properties": {
"content": {
"description": "Content 通知内容。",
"type": "string"
},
"is_active": {
"description": "IsActive 是否启用(不传默认启用)。",
"type": "boolean"
},
"name": {
"description": "Name 模板名称(用于识别用途)。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID不传代表全平台模板。",
"type": "integer"
},
"title": {
"description": "Title 通知标题。",
"type": "string"
},
"type": {
"description": "Type 通知类型system/order/audit/interaction。",
"allOf": [
{
"$ref": "#/definitions/consts.NotificationType"
}
]
}
}
},
"dto.SuperNotificationTemplateItem": {
"type": "object",
"properties": {
"content": {
"description": "Content 模板内容。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 模板ID。",
"type": "integer"
},
"is_active": {
"description": "IsActive 是否启用。",
"type": "boolean"
},
"name": {
"description": "Name 模板名称。",
"type": "string"
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"title": {
"description": "Title 模板标题。",
"type": "string"
},
"type": {
"description": "Type 通知类型。",
"allOf": [
{
"$ref": "#/definitions/consts.NotificationType"
}
]
},
"updated_at": {
"description": "UpdatedAt 更新时间RFC3339。",
"type": "string"
}
}
},
"dto.SuperOrderDetail": {
"type": "object",
"properties": {

View File

@@ -18,6 +18,120 @@
"host": "localhost:8080",
"basePath": "/",
"paths": {
"/super/v1/assets": {
"get": {
"description": "List assets across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Asset"
],
"summary": "List assets",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperAssetItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/assets/usage": {
"get": {
"description": "Asset usage statistics",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Asset"
],
"summary": "Asset usage",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SuperAssetUsageResponse"
}
}
}
}
},
"/super/v1/assets/{id}": {
"delete": {
"description": "Delete asset",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Asset"
],
"summary": "Delete asset",
"parameters": [
{
"type": "integer",
"format": "int64",
"description": "Asset ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Force delete",
"name": "force",
"in": "query"
}
],
"responses": {
"200": {
"description": "Deleted",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/auth/login": {
"post": {
"description": "Login",
@@ -518,6 +632,162 @@
}
}
},
"/super/v1/notifications": {
"get": {
"description": "List notifications across tenants",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "List notifications",
"parameters": [
{
"type": "integer",
"description": "Page number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "Page size",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperNotificationItem"
}
}
}
}
]
}
}
}
}
},
"/super/v1/notifications/broadcast": {
"post": {
"description": "Broadcast notification to users or tenant members",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "Broadcast notification",
"parameters": [
{
"description": "Broadcast form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperNotificationBroadcastForm"
}
}
],
"responses": {
"200": {
"description": "Sent",
"schema": {
"type": "string"
}
}
}
}
},
"/super/v1/notifications/templates": {
"get": {
"description": "List notification templates",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "List notification templates",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/requests.Pager"
},
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperNotificationTemplateItem"
}
}
}
}
]
}
}
}
},
"post": {
"description": "Create notification template",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "Create notification template",
"parameters": [
{
"description": "Template form",
"name": "form",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.SuperNotificationTemplateCreateForm"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.SuperNotificationTemplateItem"
}
}
}
}
},
"/super/v1/orders": {
"get": {
"description": "List orders",
@@ -4970,6 +5240,62 @@
"GenderSecret"
]
},
"consts.MediaAssetStatus": {
"type": "string",
"enum": [
"uploaded",
"processing",
"ready",
"failed",
"deleted"
],
"x-enum-varnames": [
"MediaAssetStatusUploaded",
"MediaAssetStatusProcessing",
"MediaAssetStatusReady",
"MediaAssetStatusFailed",
"MediaAssetStatusDeleted"
]
},
"consts.MediaAssetType": {
"type": "string",
"enum": [
"video",
"audio",
"image"
],
"x-enum-varnames": [
"MediaAssetTypeVideo",
"MediaAssetTypeAudio",
"MediaAssetTypeImage"
]
},
"consts.MediaAssetVariant": {
"type": "string",
"enum": [
"main",
"preview"
],
"x-enum-varnames": [
"MediaAssetVariantMain",
"MediaAssetVariantPreview"
]
},
"consts.NotificationType": {
"type": "string",
"enum": [
"system",
"order",
"audit",
"interaction"
],
"x-enum-varnames": [
"NotificationTypeSystem",
"NotificationTypeOrder",
"NotificationTypeAudit",
"NotificationTypeInteraction"
]
},
"consts.OrderStatus": {
"type": "string",
"enum": [
@@ -6342,6 +6668,144 @@
}
}
},
"dto.SuperAssetItem": {
"type": "object",
"properties": {
"bucket": {
"description": "Bucket 存储桶名称。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"filename": {
"description": "Filename 原始文件名。",
"type": "string"
},
"hash": {
"description": "Hash 文件哈希MD5。",
"type": "string"
},
"id": {
"description": "ID 资产ID。",
"type": "integer"
},
"object_key": {
"description": "ObjectKey 对象Key。",
"type": "string"
},
"provider": {
"description": "Provider 存储提供方。",
"type": "string"
},
"size": {
"description": "Size 文件大小(字节)。",
"type": "integer"
},
"source_asset_id": {
"description": "SourceAssetID 源资产ID用于变体关联。",
"type": "integer"
},
"status": {
"description": "Status 处理状态。",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetStatus"
}
]
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"type": {
"description": "Type 媒体类型。",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetType"
}
]
},
"updated_at": {
"description": "UpdatedAt 更新时间RFC3339。",
"type": "string"
},
"url": {
"description": "URL 访问URL若可用。",
"type": "string"
},
"used_count": {
"description": "UsedCount 被内容引用次数。",
"type": "integer"
},
"user_id": {
"description": "UserID 上传用户ID。",
"type": "integer"
},
"username": {
"description": "Username 上传用户名/昵称。",
"type": "string"
},
"variant": {
"description": "Variant 媒体变体main/preview/cover 等)。",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetVariant"
}
]
}
}
},
"dto.SuperAssetUsageItem": {
"type": "object",
"properties": {
"count": {
"description": "Count 该类型资产数量。",
"type": "integer"
},
"total_size": {
"description": "TotalSize 该类型资产大小总和(字节)。",
"type": "integer"
},
"type": {
"description": "Type 媒体类型。",
"allOf": [
{
"$ref": "#/definitions/consts.MediaAssetType"
}
]
}
}
},
"dto.SuperAssetUsageResponse": {
"type": "object",
"properties": {
"by_type": {
"description": "ByType 按媒体类型汇总的用量统计。",
"type": "array",
"items": {
"$ref": "#/definitions/dto.SuperAssetUsageItem"
}
},
"total_count": {
"description": "TotalCount 资产总量。",
"type": "integer"
},
"total_size": {
"description": "TotalSize 资产总大小(字节)。",
"type": "integer"
}
}
},
"dto.SuperContentBatchReviewForm": {
"type": "object",
"required": [
@@ -6627,6 +7091,177 @@
}
}
},
"dto.SuperNotificationBroadcastForm": {
"type": "object",
"properties": {
"content": {
"description": "Content 通知内容。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID选填用于指定租户成员。",
"type": "integer"
},
"title": {
"description": "Title 通知标题。",
"type": "string"
},
"type": {
"description": "Type 通知类型system/order/audit/interaction。",
"allOf": [
{
"$ref": "#/definitions/consts.NotificationType"
}
]
},
"user_ids": {
"description": "UserIDs 指定接收用户ID列表优先级高于 TenantID。",
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"dto.SuperNotificationItem": {
"type": "object",
"properties": {
"content": {
"description": "Content 通知内容。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 通知ID。",
"type": "integer"
},
"is_read": {
"description": "IsRead 是否已读。",
"type": "boolean"
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"title": {
"description": "Title 通知标题。",
"type": "string"
},
"type": {
"description": "Type 通知类型。",
"allOf": [
{
"$ref": "#/definitions/consts.NotificationType"
}
]
},
"user_id": {
"description": "UserID 用户ID。",
"type": "integer"
},
"username": {
"description": "Username 用户名/昵称。",
"type": "string"
}
}
},
"dto.SuperNotificationTemplateCreateForm": {
"type": "object",
"properties": {
"content": {
"description": "Content 通知内容。",
"type": "string"
},
"is_active": {
"description": "IsActive 是否启用(不传默认启用)。",
"type": "boolean"
},
"name": {
"description": "Name 模板名称(用于识别用途)。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID不传代表全平台模板。",
"type": "integer"
},
"title": {
"description": "Title 通知标题。",
"type": "string"
},
"type": {
"description": "Type 通知类型system/order/audit/interaction。",
"allOf": [
{
"$ref": "#/definitions/consts.NotificationType"
}
]
}
}
},
"dto.SuperNotificationTemplateItem": {
"type": "object",
"properties": {
"content": {
"description": "Content 模板内容。",
"type": "string"
},
"created_at": {
"description": "CreatedAt 创建时间RFC3339。",
"type": "string"
},
"id": {
"description": "ID 模板ID。",
"type": "integer"
},
"is_active": {
"description": "IsActive 是否启用。",
"type": "boolean"
},
"name": {
"description": "Name 模板名称。",
"type": "string"
},
"tenant_code": {
"description": "TenantCode 租户编码。",
"type": "string"
},
"tenant_id": {
"description": "TenantID 租户ID。",
"type": "integer"
},
"tenant_name": {
"description": "TenantName 租户名称。",
"type": "string"
},
"title": {
"description": "Title 模板标题。",
"type": "string"
},
"type": {
"description": "Type 通知类型。",
"allOf": [
{
"$ref": "#/definitions/consts.NotificationType"
}
]
},
"updated_at": {
"description": "UpdatedAt 更新时间RFC3339。",
"type": "string"
}
}
},
"dto.SuperOrderDetail": {
"type": "object",
"properties": {

View File

@@ -38,6 +38,50 @@ definitions:
- GenderMale
- GenderFemale
- GenderSecret
consts.MediaAssetStatus:
enum:
- uploaded
- processing
- ready
- failed
- deleted
type: string
x-enum-varnames:
- MediaAssetStatusUploaded
- MediaAssetStatusProcessing
- MediaAssetStatusReady
- MediaAssetStatusFailed
- MediaAssetStatusDeleted
consts.MediaAssetType:
enum:
- video
- audio
- image
type: string
x-enum-varnames:
- MediaAssetTypeVideo
- MediaAssetTypeAudio
- MediaAssetTypeImage
consts.MediaAssetVariant:
enum:
- main
- preview
type: string
x-enum-varnames:
- MediaAssetVariantMain
- MediaAssetVariantPreview
consts.NotificationType:
enum:
- system
- order
- audit
- interaction
type: string
x-enum-varnames:
- NotificationTypeSystem
- NotificationTypeOrder
- NotificationTypeAudit
- NotificationTypeInteraction
consts.OrderStatus:
enum:
- created
@@ -1013,6 +1057,99 @@ definitions:
description: Likes 累计点赞数。
type: integer
type: object
dto.SuperAssetItem:
properties:
bucket:
description: Bucket 存储桶名称。
type: string
created_at:
description: CreatedAt 创建时间RFC3339
type: string
filename:
description: Filename 原始文件名。
type: string
hash:
description: Hash 文件哈希MD5
type: string
id:
description: ID 资产ID。
type: integer
object_key:
description: ObjectKey 对象Key。
type: string
provider:
description: Provider 存储提供方。
type: string
size:
description: Size 文件大小(字节)。
type: integer
source_asset_id:
description: SourceAssetID 源资产ID用于变体关联
type: integer
status:
allOf:
- $ref: '#/definitions/consts.MediaAssetStatus'
description: Status 处理状态。
tenant_code:
description: TenantCode 租户编码。
type: string
tenant_id:
description: TenantID 租户ID。
type: integer
tenant_name:
description: TenantName 租户名称。
type: string
type:
allOf:
- $ref: '#/definitions/consts.MediaAssetType'
description: Type 媒体类型。
updated_at:
description: UpdatedAt 更新时间RFC3339
type: string
url:
description: URL 访问URL若可用
type: string
used_count:
description: UsedCount 被内容引用次数。
type: integer
user_id:
description: UserID 上传用户ID。
type: integer
username:
description: Username 上传用户名/昵称。
type: string
variant:
allOf:
- $ref: '#/definitions/consts.MediaAssetVariant'
description: Variant 媒体变体main/preview/cover 等)。
type: object
dto.SuperAssetUsageItem:
properties:
count:
description: Count 该类型资产数量。
type: integer
total_size:
description: TotalSize 该类型资产大小总和(字节)。
type: integer
type:
allOf:
- $ref: '#/definitions/consts.MediaAssetType'
description: Type 媒体类型。
type: object
dto.SuperAssetUsageResponse:
properties:
by_type:
description: ByType 按媒体类型汇总的用量统计。
items:
$ref: '#/definitions/dto.SuperAssetUsageItem'
type: array
total_count:
description: TotalCount 资产总量。
type: integer
total_size:
description: TotalSize 资产总大小(字节)。
type: integer
type: object
dto.SuperContentBatchReviewForm:
properties:
action:
@@ -1214,6 +1351,123 @@ definitions:
required:
- action
type: object
dto.SuperNotificationBroadcastForm:
properties:
content:
description: Content 通知内容。
type: string
tenant_id:
description: TenantID 租户ID选填用于指定租户成员
type: integer
title:
description: Title 通知标题。
type: string
type:
allOf:
- $ref: '#/definitions/consts.NotificationType'
description: Type 通知类型system/order/audit/interaction
user_ids:
description: UserIDs 指定接收用户ID列表优先级高于 TenantID
items:
type: integer
type: array
type: object
dto.SuperNotificationItem:
properties:
content:
description: Content 通知内容。
type: string
created_at:
description: CreatedAt 创建时间RFC3339
type: string
id:
description: ID 通知ID。
type: integer
is_read:
description: IsRead 是否已读。
type: boolean
tenant_code:
description: TenantCode 租户编码。
type: string
tenant_id:
description: TenantID 租户ID。
type: integer
tenant_name:
description: TenantName 租户名称。
type: string
title:
description: Title 通知标题。
type: string
type:
allOf:
- $ref: '#/definitions/consts.NotificationType'
description: Type 通知类型。
user_id:
description: UserID 用户ID。
type: integer
username:
description: Username 用户名/昵称。
type: string
type: object
dto.SuperNotificationTemplateCreateForm:
properties:
content:
description: Content 通知内容。
type: string
is_active:
description: IsActive 是否启用(不传默认启用)。
type: boolean
name:
description: Name 模板名称(用于识别用途)。
type: string
tenant_id:
description: TenantID 租户ID不传代表全平台模板
type: integer
title:
description: Title 通知标题。
type: string
type:
allOf:
- $ref: '#/definitions/consts.NotificationType'
description: Type 通知类型system/order/audit/interaction
type: object
dto.SuperNotificationTemplateItem:
properties:
content:
description: Content 模板内容。
type: string
created_at:
description: CreatedAt 创建时间RFC3339
type: string
id:
description: ID 模板ID。
type: integer
is_active:
description: IsActive 是否启用。
type: boolean
name:
description: Name 模板名称。
type: string
tenant_code:
description: TenantCode 租户编码。
type: string
tenant_id:
description: TenantID 租户ID。
type: integer
tenant_name:
description: TenantName 租户名称。
type: string
title:
description: Title 模板标题。
type: string
type:
allOf:
- $ref: '#/definitions/consts.NotificationType'
description: Type 通知类型。
updated_at:
description: UpdatedAt 更新时间RFC3339
type: string
type: object
dto.SuperOrderDetail:
properties:
buyer:
@@ -2402,6 +2656,78 @@ info:
title: ApiDoc
version: "1.0"
paths:
/super/v1/assets:
get:
consumes:
- application/json
description: List assets across tenants
parameters:
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/requests.Pager'
- properties:
items:
items:
$ref: '#/definitions/dto.SuperAssetItem'
type: array
type: object
summary: List assets
tags:
- Asset
/super/v1/assets/{id}:
delete:
consumes:
- application/json
description: Delete asset
parameters:
- description: Asset ID
format: int64
in: path
name: id
required: true
type: integer
- description: Force delete
in: query
name: force
type: boolean
produces:
- application/json
responses:
"200":
description: Deleted
schema:
type: string
summary: Delete asset
tags:
- Asset
/super/v1/assets/usage:
get:
consumes:
- application/json
description: Asset usage statistics
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.SuperAssetUsageResponse'
summary: Asset usage
tags:
- Asset
/super/v1/auth/login:
post:
consumes:
@@ -2715,6 +3041,102 @@ paths:
summary: List creators
tags:
- Creator
/super/v1/notifications:
get:
consumes:
- application/json
description: List notifications across tenants
parameters:
- description: Page number
in: query
name: page
type: integer
- description: Page size
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/requests.Pager'
- properties:
items:
items:
$ref: '#/definitions/dto.SuperNotificationItem'
type: array
type: object
summary: List notifications
tags:
- Notification
/super/v1/notifications/broadcast:
post:
consumes:
- application/json
description: Broadcast notification to users or tenant members
parameters:
- description: Broadcast form
in: body
name: form
required: true
schema:
$ref: '#/definitions/dto.SuperNotificationBroadcastForm'
produces:
- application/json
responses:
"200":
description: Sent
schema:
type: string
summary: Broadcast notification
tags:
- Notification
/super/v1/notifications/templates:
get:
consumes:
- application/json
description: List notification templates
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/requests.Pager'
- properties:
items:
items:
$ref: '#/definitions/dto.SuperNotificationTemplateItem'
type: array
type: object
summary: List notification templates
tags:
- Notification
post:
consumes:
- application/json
description: Create notification template
parameters:
- description: Template form
in: body
name: form
required: true
schema:
$ref: '#/definitions/dto.SuperNotificationTemplateCreateForm'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.SuperNotificationTemplateItem'
summary: Create notification template
tags:
- Notification
/super/v1/orders:
get:
consumes: