feat(quota): add quota task creation and querying endpoints with updated data structures

This commit is contained in:
Chang lue Tsen 2025-09-09 13:39:05 -04:00
parent f4c6bd919b
commit d1be5febc3
18 changed files with 610 additions and 65 deletions

View File

@ -12,7 +12,7 @@ type (
CreateBatchSendEmailTaskRequest {
Subject string `json:"subject"`
Content string `json:"content"`
Scope string `json:"scope"`
Scope int8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time,omitempty"`
RegisterEndTime int64 `json:"register_end_time,omitempty"`
Additional string `json:"additional,omitempty"`
@ -25,7 +25,7 @@ type (
Subject string `json:"subject"`
Content string `json:"content"`
Recipients string `json:"recipients"`
Scope string `json:"scope"`
Scope int8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
Additional string `json:"additional"`
@ -42,7 +42,7 @@ type (
GetBatchSendEmailTaskListRequest {
Page int `form:"page"`
Size int `form:"size"`
Scope string `form:"scope,omitempty"`
Scope *int8 `form:"scope,omitempty"`
Status *uint8 `form:"status,omitempty"`
}
GetBatchSendEmailTaskListResponse {
@ -69,6 +69,57 @@ type (
Total int64 `json:"total"`
Errors string `json:"errors"`
}
CreateQuotaTaskRequest {
Scope int8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
QuotaType uint8 `json:"quota_type"`
Days uint64 `json:"days"` // Number of days for the quota
Gift uint8 `json:"gift"` // Invoice amount ratio(%) to gift amount for quota
}
QuotaTask {
Id int64 `json:"id"`
Scope int8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
QuotaType uint8 `json:"quota_type"`
Days uint64 `json:"days"` // Number of days for the quota
Gift uint8 `json:"gift"` // Invoice amount ratio(%) to gift
Recipients []int64 `json:"recipients"` // UserSubscribe IDs of recipients
Status uint8 `json:"status"`
Total int64 `json:"total"`
Current int64 `json:"current"`
Errors string `json:"errors"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
QueryQuotaTaskPreCountRequest {
Scope uint8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
}
QueryQuotaTaskPreCountResponse {
Count int64 `json:"count"`
}
QueryQuotaTaskListRequest {
Page int `form:"page"`
Size int `form:"size"`
Scope *uint8 `form:"scope,omitempty"`
Status *uint8 `form:"status,omitempty"`
}
QueryQuotaTaskListResponse {
Total int64 `json:"total"`
List []QuotaTask `json:"list"`
}
QueryQuotaTaskStatusRequest {
Id int64 `json:"id"`
}
QueryQuotaTaskStatusResponse {
Status uint8 `json:"status"`
Current int64 `json:"current"`
Total int64 `json:"total"`
Errors string `json:"errors"`
}
)
@server (
@ -96,5 +147,21 @@ service ppanel {
@doc "Get batch send email task status"
@handler GetBatchSendEmailTaskStatus
post /email/batch/status (GetBatchSendEmailTaskStatusRequest) returns (GetBatchSendEmailTaskStatusResponse)
@doc "Create a quota task"
@handler CreateQuotaTask
post /quota/create (CreateQuotaTaskRequest)
@doc "Query quota task pre-count"
@handler QueryQuotaTaskPreCount
post /quota/pre-count (QueryQuotaTaskPreCountRequest) returns (QueryQuotaTaskPreCountResponse)
@doc "Query quota task list"
@handler QueryQuotaTaskList
get /quota/list (QueryQuotaTaskListRequest) returns (QueryQuotaTaskListResponse)
@doc "Query quota task status"
@handler QueryQuotaTaskStatus
post /quota/status (QueryQuotaTaskStatusRequest) returns (QueryQuotaTaskStatusResponse)
}

View File

@ -0,0 +1,14 @@
DROP TABLE IF EXISTS `email_task`;
CREATE TABLE `task` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`type` tinyint NOT NULL COMMENT 'Task Type',
`scope` text COLLATE utf8mb4_general_ci COMMENT 'Task Scope',
`content` text COLLATE utf8mb4_general_ci COMMENT 'Task Content',
`status` tinyint NOT NULL DEFAULT '0' COMMENT 'Task Status: 0: Pending, 1: In Progress, 2: Completed, 3: Failed',
`errors` text COLLATE utf8mb4_general_ci COMMENT 'Task Errors',
`total` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Total Number',
`current` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Current Number',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@ -0,0 +1,26 @@
package marketing
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/marketing"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Create a quota task
func CreateQuotaTaskHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.CreateQuotaTaskRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := marketing.NewCreateQuotaTaskLogic(c.Request.Context(), svcCtx)
err := l.CreateQuotaTask(&req)
result.HttpResult(c, nil, err)
}
}

View File

@ -0,0 +1,26 @@
package marketing
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/marketing"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Query quota task list
func QueryQuotaTaskListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.QueryQuotaTaskListRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := marketing.NewQueryQuotaTaskListLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryQuotaTaskList(&req)
result.HttpResult(c, resp, err)
}
}

View File

@ -0,0 +1,26 @@
package marketing
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/marketing"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Query quota task pre-count
func QueryQuotaTaskPreCountHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.QueryQuotaTaskPreCountRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := marketing.NewQueryQuotaTaskPreCountLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryQuotaTaskPreCount(&req)
result.HttpResult(c, resp, err)
}
}

View File

@ -0,0 +1,26 @@
package marketing
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/marketing"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Query quota task status
func QueryQuotaTaskStatusHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.QueryQuotaTaskStatusRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := marketing.NewQueryQuotaTaskStatusLogic(c.Request.Context(), svcCtx)
resp, err := l.QueryQuotaTaskStatus(&req)
result.HttpResult(c, resp, err)
}
}

View File

@ -253,6 +253,18 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Stop a batch send email task
adminMarketingGroupRouter.POST("/email/batch/stop", adminMarketing.StopBatchSendEmailTaskHandler(serverCtx))
// Create a quota task
adminMarketingGroupRouter.POST("/quota/create", adminMarketing.CreateQuotaTaskHandler(serverCtx))
// Query quota task list
adminMarketingGroupRouter.GET("/quota/list", adminMarketing.QueryQuotaTaskListHandler(serverCtx))
// Query quota task pre-count
adminMarketingGroupRouter.POST("/quota/pre-count", adminMarketing.QueryQuotaTaskPreCountHandler(serverCtx))
// Query quota task status
adminMarketingGroupRouter.POST("/quota/status", adminMarketing.QueryQuotaTaskStatusHandler(serverCtx))
}
adminOrderGroupRouter := router.Group("/v1/admin/order")

View File

@ -24,7 +24,7 @@ type CreateBatchSendEmailTaskLogic struct {
svcCtx *svc.ServiceContext
}
// Create a batch send email task
// NewCreateBatchSendEmailTaskLogic Create a batch send email task
func NewCreateBatchSendEmailTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateBatchSendEmailTaskLogic {
return &CreateBatchSendEmailTaskLogic{
Logger: logger.WithContext(ctx),
@ -55,24 +55,27 @@ func (l *CreateBatchSendEmailTaskLogic) CreateBatchSendEmailTask(req *types.Crea
var query *gorm.DB
switch req.Scope {
case "all":
scope := task.ParseScopeType(req.Scope)
switch scope {
case task.ScopeAll:
query = baseQuery()
case "active":
case task.ScopeActive:
query = baseQuery().
Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id").
Where("user_subscribe.status IN ?", []int64{1, 2})
case "expired":
case task.ScopeExpired:
query = baseQuery().
Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id").
Where("user_subscribe.status = ?", 3)
case "none":
case task.ScopeNone:
query = baseQuery().
Joins("LEFT JOIN user_subscribe ON user.id = user_subscribe.user_id").
Where("user_subscribe.user_id IS NULL")
default:
}
if query != nil {
@ -85,7 +88,7 @@ func (l *CreateBatchSendEmailTaskLogic) CreateBatchSendEmailTask(req *types.Crea
}
// 邮箱列表为空,返回错误
if len(emails) == 0 && req.Scope != "skip" {
if len(emails) == 0 && scope != task.ScopeSkip {
l.Errorf("[CreateBatchSendEmailTask] No email addresses found for the specified scope")
return xerr.NewErrMsg("No email addresses found for the specified scope")
}
@ -96,41 +99,59 @@ func (l *CreateBatchSendEmailTaskLogic) CreateBatchSendEmailTask(req *types.Crea
var additionalEmails []string
// 追加额外的邮箱地址(不覆盖)
if req.Additional != "" {
additionalEmails = strings.Split(req.Additional, "\n")
additionalEmails = tool.RemoveDuplicateElements(strings.Split(req.Additional, "\n")...)
}
if len(additionalEmails) == 0 && req.Scope == "skip" {
if len(additionalEmails) == 0 && scope == task.ScopeSkip {
l.Errorf("[CreateBatchSendEmailTask] No additional email addresses provided for skip scope")
return xerr.NewErrMsg("No additional email addresses provided for skip scope")
}
var scheduledAt time.Time
if req.Scheduled == 0 {
scheduledAt = time.Now()
} else {
scheduledAt := time.Now().Add(10 * time.Second) // 默认延迟10秒执行,防止任务创建和执行时间过于接近
if req.Scheduled != 0 {
scheduledAt = time.Unix(req.Scheduled, 0)
if scheduledAt.Before(time.Now()) {
scheduledAt = time.Now()
}
}
taskInfo := &task.EmailTask{
Subject: req.Subject,
Content: req.Content,
Recipients: strings.Join(emails, "\n"),
Scope: req.Scope,
RegisterStartTime: time.Unix(req.RegisterStartTime, 0),
RegisterEndTime: time.Unix(req.RegisterEndTime, 0),
Additional: req.Additional,
Scheduled: scheduledAt,
scopeInfo := task.EmailScope{
Type: scope.Int8(),
RegisterStartTime: req.RegisterStartTime,
RegisterEndTime: req.RegisterEndTime,
Recipients: emails,
Additional: additionalEmails,
Scheduled: req.Scheduled,
Interval: req.Interval,
Limit: req.Limit,
Status: 0,
Errors: "",
Total: uint64(len(emails) + len(additionalEmails)),
Current: 0,
}
scopeBytes, _ := scopeInfo.Marshal()
taskContent := task.EmailContent{
Subject: req.Subject,
Content: req.Content,
}
if err = l.svcCtx.DB.Model(&task.EmailTask{}).Create(taskInfo).Error; err != nil {
contentBytes, _ := taskContent.Marshal()
var total uint64
if additionalEmails != nil {
list := append(emails, additionalEmails...)
total = uint64(len(tool.RemoveDuplicateElements(list...)))
} else {
total = uint64(len(emails))
}
taskInfo := &task.Task{
Type: task.TypeEmail,
Scope: string(scopeBytes),
Content: string(contentBytes),
Status: 0,
Errors: "",
Total: total,
Current: 0,
}
if err = l.svcCtx.DB.Model(&task.Task{}).Create(taskInfo).Error; err != nil {
l.Errorf("[CreateBatchSendEmailTask] Failed to create email task: %v", err.Error())
return xerr.NewErrCode(xerr.DatabaseInsertError)
}
@ -138,12 +159,12 @@ func (l *CreateBatchSendEmailTaskLogic) CreateBatchSendEmailTask(req *types.Crea
l.Infof("[CreateBatchSendEmailTask] Successfully created email task with ID: %d", taskInfo.Id)
t := asynq.NewTask(types2.ScheduledBatchSendEmail, []byte(strconv.FormatInt(taskInfo.Id, 10)))
info, err := l.svcCtx.Queue.EnqueueContext(l.ctx, t, asynq.ProcessAt(taskInfo.Scheduled))
info, err := l.svcCtx.Queue.EnqueueContext(l.ctx, t, asynq.ProcessAt(scheduledAt))
if err != nil {
l.Errorf("[CreateBatchSendEmailTask] Failed to enqueue email task: %v", err.Error())
return xerr.NewErrCode(xerr.QueueEnqueueError)
}
l.Infof("[CreateBatchSendEmailTask] Successfully enqueued email task with ID: %s, scheduled at: %s", info.ID, taskInfo.Scheduled)
l.Infof("[CreateBatchSendEmailTask] Successfully enqueued email task with ID: %s, scheduled at: %s", info.ID, scheduledAt.Format(time.DateTime))
return nil
}

View File

@ -0,0 +1,30 @@
package marketing
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type CreateQuotaTaskLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Create a quota task
func NewCreateQuotaTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateQuotaTaskLogic {
return &CreateQuotaTaskLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateQuotaTaskLogic) CreateQuotaTask(req *types.CreateQuotaTaskRequest) error {
// todo: add your logic here and delete this line
return nil
}

View File

@ -2,12 +2,12 @@ package marketing
import (
"context"
"strings"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
)
@ -28,12 +28,12 @@ func NewGetBatchSendEmailTaskListLogic(ctx context.Context, svcCtx *svc.ServiceC
func (l *GetBatchSendEmailTaskListLogic) GetBatchSendEmailTaskList(req *types.GetBatchSendEmailTaskListRequest) (resp *types.GetBatchSendEmailTaskListResponse, err error) {
var tasks []*task.EmailTask
tx := l.svcCtx.DB.Model(&task.EmailTask{})
var tasks []*task.Task
tx := l.svcCtx.DB.Model(&task.Task{}).Where("`type` = ?", task.TypeEmail)
if req.Status != nil {
tx = tx.Where("status = ?", *req.Status)
}
if req.Scope != "" {
if req.Scope != nil {
tx = tx.Where("scope = ?", req.Scope)
}
if req.Page == 0 {
@ -49,7 +49,40 @@ func (l *GetBatchSendEmailTaskListLogic) GetBatchSendEmailTaskList(req *types.Ge
}
list := make([]types.BatchSendEmailTask, 0)
tool.DeepCopy(&list, tasks)
for _, t := range tasks {
var scopeInfo task.EmailScope
if err = scopeInfo.Unmarshal([]byte(t.Scope)); err != nil {
l.Errorf("[GetBatchSendEmailTaskList] failed to unmarshal email task scope: %v", err.Error())
continue
}
var contentInfo task.EmailContent
if err = contentInfo.Unmarshal([]byte(t.Content)); err != nil {
l.Errorf("[GetBatchSendEmailTaskList] failed to unmarshal email task content: %v", err.Error())
continue
}
list = append(list, types.BatchSendEmailTask{
Id: t.Id,
Subject: contentInfo.Subject,
Content: contentInfo.Content,
Recipients: strings.Join(scopeInfo.Recipients, "\n"),
Scope: scopeInfo.Type,
RegisterStartTime: scopeInfo.RegisterStartTime,
RegisterEndTime: scopeInfo.RegisterEndTime,
Additional: strings.Join(scopeInfo.Additional, "\n"),
Scheduled: scopeInfo.Scheduled,
Interval: scopeInfo.Interval,
Limit: scopeInfo.Limit,
Status: uint8(t.Status),
Errors: t.Errors,
Total: t.Total,
Current: t.Current,
CreatedAt: t.CreatedAt.UnixMilli(),
UpdatedAt: t.UpdatedAt.UnixMilli(),
})
}
return &types.GetBatchSendEmailTaskListResponse{
List: list,
}, nil

View File

@ -0,0 +1,30 @@
package marketing
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type QueryQuotaTaskListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Query quota task list
func NewQueryQuotaTaskListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskListLogic {
return &QueryQuotaTaskListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryQuotaTaskListLogic) QueryQuotaTaskList(req *types.QueryQuotaTaskListRequest) (resp *types.QueryQuotaTaskListResponse, err error) {
// todo: add your logic here and delete this line
return
}

View File

@ -0,0 +1,30 @@
package marketing
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type QueryQuotaTaskPreCountLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Query quota task pre-count
func NewQueryQuotaTaskPreCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskPreCountLogic {
return &QueryQuotaTaskPreCountLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryQuotaTaskPreCountLogic) QueryQuotaTaskPreCount(req *types.QueryQuotaTaskPreCountRequest) (resp *types.QueryQuotaTaskPreCountResponse, err error) {
// todo: add your logic here and delete this line
return
}

View File

@ -0,0 +1,30 @@
package marketing
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type QueryQuotaTaskStatusLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Query quota task status
func NewQueryQuotaTaskStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskStatusLogic {
return &QueryQuotaTaskStatusLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryQuotaTaskStatusLogic) QueryQuotaTaskStatus(req *types.QueryQuotaTaskStatusRequest) (resp *types.QueryQuotaTaskStatusResponse, err error) {
// todo: add your logic here and delete this line
return
}

View File

@ -1,27 +1,142 @@
package task
import "time"
import (
"encoding/json"
"time"
)
type EmailTask struct {
Id int64 `gorm:"column:id;primaryKey;autoIncrement;comment:ID"`
Subject string `gorm:"column:subject;type:varchar(255);not null;comment:Email Subject"`
Content string `gorm:"column:content;type:text;not null;comment:Email Content"`
Recipients string `gorm:"column:recipient;type:text;not null;comment:Email Recipient"`
Scope string `gorm:"column:scope;type:varchar(50);not null;comment:Email Scope"`
RegisterStartTime time.Time `gorm:"column:register_start_time;default:null;comment:Register Start Time"`
RegisterEndTime time.Time `gorm:"column:register_end_time;default:null;comment:Register End Time"`
Additional string `gorm:"column:additional;type:text;default:null;comment:Additional Information"`
Scheduled time.Time `gorm:"column:scheduled;not null;comment:Scheduled Time"`
Interval uint8 `gorm:"column:interval;not null;comment:Interval in Seconds"`
Limit uint64 `gorm:"column:limit;not null;comment:Daily send limit"`
Status uint8 `gorm:"column:status;not null;comment:Daily Status"`
Errors string `gorm:"column:errors;type:text;not null;comment:Errors"`
Total uint64 `gorm:"column:total;not null;default:0;comment:Total Number"`
Current uint64 `gorm:"column:current;not null;default:0;comment:Current Number"`
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
UpdatedAt time.Time `gorm:"comment:Update Time"`
type Type int8
const (
Undefined Type = -1
TypeEmail = iota
TypeQuota
)
type Task struct {
Id int64 `gorm:"primaryKey;autoIncrement;comment:ID"`
Type int8 `gorm:"not null;comment:Task Type"`
Scope string `gorm:"type:text;comment:Task Scope"`
Content string `gorm:"type:text;comment:Task Content"`
Status int8 `gorm:"not null;default:0;comment:Task Status: 0: Pending, 1: In Progress, 2: Completed, 3: Failed"`
Errors string `gorm:"type:text;comment:Task Errors"`
Total uint64 `gorm:"column:total;not null;default:0;comment:Total Number"`
Current uint64 `gorm:"column:current;not null;default:0;comment:Current Number"`
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
UpdatedAt time.Time `gorm:"comment:Update Time"`
}
func (EmailTask) TableName() string {
return "email_task"
func (Task) TableName() string {
return "task"
}
type ScopeType int8
const (
ScopeAll ScopeType = iota + 1 // All users
ScopeActive // Active users
ScopeExpired // Expired users
ScopeNone // No Subscribe
ScopeSkip // Skip user filtering
)
func (t ScopeType) Int8() int8 {
return int8(t)
}
type EmailScope struct {
Type int8 `gorm:"not null;comment:Scope Type"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
Recipients []string `json:"recipients"` // list of email addresses
Additional []string `json:"additional"` // additional email addresses
Scheduled int64 `json:"scheduled"` // scheduled time (unix timestamp)
Interval uint8 `json:"interval"` // interval in seconds
Limit uint64 `json:"limit"` // daily send limit
}
func (s *EmailScope) Marshal() ([]byte, error) {
type Alias EmailScope
return json.Marshal(&struct {
*Alias
}{
Alias: (*Alias)(s),
})
}
func (s *EmailScope) Unmarshal(data []byte) error {
type Alias EmailScope
aux := (*Alias)(s)
return json.Unmarshal(data, &aux)
}
type EmailContent struct {
Subject string `json:"subject"`
Content string `json:"content"`
}
func (c *EmailContent) Marshal() ([]byte, error) {
type Alias EmailContent
return json.Marshal(&struct {
*Alias
}{
Alias: (*Alias)(c),
})
}
func (c *EmailContent) Unmarshal(data []byte) error {
type Alias EmailContent
aux := (*Alias)(c)
return json.Unmarshal(data, &aux)
}
type QuotaScope struct {
Type int8 `gorm:"not null;comment:Scope Type"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
Recipients []int64 `json:"recipients"` // list of user subs IDs
}
func (s *QuotaScope) Marshal() ([]byte, error) {
type Alias QuotaScope
return json.Marshal(&struct {
*Alias
}{
Alias: (*Alias)(s),
})
}
func (s *QuotaScope) Unmarshal(data []byte) error {
type Alias QuotaScope
aux := (*Alias)(s)
return json.Unmarshal(data, &aux)
}
type QuotaType int8
const (
QuotaTypeReset QuotaType = iota + 1 // Reset Subscribe Quota
QuotaTypeDays // Add Subscribe Days
QuotaTypeGift // Add Gift Amount
)
type QuotaContent struct {
Type int8 `json:"type"`
Days uint64 `json:"days,omitempty"` // days to add
Gift uint8 `json:"gift,omitempty"` // Invoice amount ratio(%) to gift amount
}
func ParseScopeType(t int8) ScopeType {
switch t {
case 1:
return ScopeAll
case 2:
return ScopeActive
case 3:
return ScopeExpired
case 4:
return ScopeNone
default:
return ScopeSkip
}
}

View File

@ -162,7 +162,7 @@ type BatchSendEmailTask struct {
Subject string `json:"subject"`
Content string `json:"content"`
Recipients string `json:"recipients"`
Scope string `json:"scope"`
Scope int8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
Additional string `json:"additional"`
@ -274,7 +274,7 @@ type CreateAnnouncementRequest struct {
type CreateBatchSendEmailTaskRequest struct {
Subject string `json:"subject"`
Content string `json:"content"`
Scope string `json:"scope"`
Scope int8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time,omitempty"`
RegisterEndTime int64 `json:"register_end_time,omitempty"`
Additional string `json:"additional,omitempty"`
@ -344,6 +344,15 @@ type CreatePaymentMethodRequest struct {
Enable *bool `json:"enable" validate:"required"`
}
type CreateQuotaTaskRequest struct {
Scope int8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
QuotaType uint8 `json:"quota_type"`
Days uint64 `json:"days"` // Number of days for the quota
Gift uint8 `json:"gift"` // Invoice amount ratio(%) to gift amount for quota
}
type CreateServerRequest struct {
Name string `json:"name"`
Country string `json:"country,omitempty"`
@ -756,7 +765,7 @@ type GetAvailablePaymentMethodsResponse struct {
type GetBatchSendEmailTaskListRequest struct {
Page int `form:"page"`
Size int `form:"size"`
Scope string `form:"scope,omitempty"`
Scope *int8 `form:"scope,omitempty"`
Status *uint8 `form:"status,omitempty"`
}
@ -1522,6 +1531,39 @@ type QueryPurchaseOrderResponse struct {
Token string `json:"token,omitempty"`
}
type QueryQuotaTaskListRequest struct {
Page int `form:"page"`
Size int `form:"size"`
Scope *uint8 `form:"scope,omitempty"`
Status *uint8 `form:"status,omitempty"`
}
type QueryQuotaTaskListResponse struct {
Total int64 `json:"total"`
List []QuotaTask `json:"list"`
}
type QueryQuotaTaskPreCountRequest struct {
Scope uint8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
}
type QueryQuotaTaskPreCountResponse struct {
Count int64 `json:"count"`
}
type QueryQuotaTaskStatusRequest struct {
Id int64 `json:"id"`
}
type QueryQuotaTaskStatusResponse struct {
Status uint8 `json:"status"`
Current int64 `json:"current"`
Total int64 `json:"total"`
Errors string `json:"errors"`
}
type QuerySubscribeGroupListResponse struct {
List []SubscribeGroup `json:"list"`
Total int64 `json:"total"`
@ -1571,6 +1613,23 @@ type QueryUserSubscribeListResponse struct {
Total int64 `json:"total"`
}
type QuotaTask struct {
Id int64 `json:"id"`
Scope int8 `json:"scope"`
RegisterStartTime int64 `json:"register_start_time"`
RegisterEndTime int64 `json:"register_end_time"`
QuotaType uint8 `json:"quota_type"`
Days uint64 `json:"days"` // Number of days for the quota
Gift uint8 `json:"gift"` // Invoice amount ratio(%) to gift
Recipients []int64 `json:"recipients"` // UserSubscribe IDs of recipients
Status uint8 `json:"status"`
Total int64 `json:"total"`
Current int64 `json:"current"`
Errors string `json:"errors"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
type RechargeOrderRequest struct {
Amount int64 `json:"amount"`
Payment int64 `json:"payment"`

View File

@ -3,7 +3,7 @@ package orm
import (
"testing"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/task"
"gorm.io/driver/mysql"
"gorm.io/gorm"
@ -31,7 +31,7 @@ func TestMysql(t *testing.T) {
if err != nil {
t.Fatalf("Failed to connect to MySQL: %v", err)
}
err = db.Migrator().AutoMigrate(&node.Server{})
err = db.Migrator().AutoMigrate(&task.Task{})
if err != nil {
t.Fatalf("Failed to auto migrate: %v", err)
return

View File

@ -44,8 +44,8 @@ func (l *BatchEmailLogic) ProcessTask(ctx context.Context, task *asynq.Task) err
return asynq.SkipRetry
}
tx := l.svcCtx.DB.WithContext(ctx)
var taskInfo taskModel.EmailTask
if err = tx.Model(&taskModel.EmailTask{}).Where("id = ?", taskID).First(&taskInfo).Error; err != nil {
var taskInfo taskModel.Task
if err = tx.Model(&taskModel.Task{}).Where("id = ?", taskID).First(&taskInfo).Error; err != nil {
logger.WithContext(ctx).Error("[BatchEmailLogic] ProcessTask failed",
logger.Field("error", err.Error()),
logger.Field("taskID", taskID),