feat(quota): enhance quota task management with new request structures and processing logic

This commit is contained in:
Chang lue Tsen
2025-09-10 14:53:48 -04:00
parent 83c1c14b01
commit 3f5aac239b
15 changed files with 740 additions and 95 deletions
@@ -2,10 +2,18 @@ package marketing
import (
"context"
"strconv"
"time"
"github.com/hibiken/asynq"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/model/user"
"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/xerr"
queueType "github.com/perfect-panel/server/queue/types"
"github.com/pkg/errors"
)
type CreateQuotaTaskLogic struct {
@@ -14,7 +22,7 @@ type CreateQuotaTaskLogic struct {
svcCtx *svc.ServiceContext
}
// Create a quota task
// NewCreateQuotaTaskLogic Create a quota task
func NewCreateQuotaTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateQuotaTaskLogic {
return &CreateQuotaTaskLogic{
Logger: logger.WithContext(ctx),
@@ -24,7 +32,72 @@ func NewCreateQuotaTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *C
}
func (l *CreateQuotaTaskLogic) CreateQuotaTask(req *types.CreateQuotaTaskRequest) error {
// todo: add your logic here and delete this line
var subs []*user.Subscribe
query := l.svcCtx.DB.WithContext(l.ctx).Model(&user.Subscribe{})
if len(req.Subscribers) > 0 {
query = query.Where("`subscribe_id` IN ?", req.Subscribers)
}
if req.IsActive != nil && *req.IsActive {
query = query.Where("`status` IN ?", []int64{0, 1, 2}) // 0: Pending 1: Active 2: Finished
}
if req.StartTime != 0 {
start := time.UnixMilli(req.StartTime)
query = query.Where("`start_time` >= ?", start)
}
if req.EndTime != 0 {
end := time.UnixMilli(req.EndTime)
query = query.Where("`start_time` <= ?", end)
}
if err := query.Find(&subs).Error; err != nil {
l.Errorf("[CreateQuotaTask] find subscribers error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribers error")
}
if len(subs) == 0 {
return errors.Wrapf(xerr.NewErrMsg("No subscribers found"), "no subscribers found")
}
var subIds []int64
for _, sub := range subs {
subIds = append(subIds, sub.Id)
}
scopeInfo := task.QuotaScope{
Subscribers: req.Subscribers,
IsActive: req.IsActive,
StartTime: req.StartTime,
EndTime: req.EndTime,
}
scopeBytes, _ := scopeInfo.Marshal()
contentInfo := task.QuotaContent{
ResetTraffic: req.ResetTraffic,
Days: req.Days,
GiftType: req.GiftType,
GiftValue: req.GiftValue,
}
contentBytes, _ := contentInfo.Marshal()
// create task
newTask := &task.Task{
Type: task.TypeQuota,
Status: 0,
Scope: string(scopeBytes),
Content: string(contentBytes),
Total: uint64(len(subIds)),
Current: 0,
Errors: "",
}
if err := l.svcCtx.DB.WithContext(l.ctx).Model(&task.Task{}).Create(newTask).Error; err != nil {
l.Errorf("[CreateQuotaTask] create task error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create task error")
}
// enqueue task
queueTask := asynq.NewTask(queueType.ForthwithQuotaTask, []byte(strconv.FormatInt(newTask.Id, 10)))
if _, err := l.svcCtx.Queue.EnqueueContext(l.ctx, queueTask); err != nil {
l.Errorf("[CreateQuotaTask] enqueue task error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.QueueEnqueueError), "enqueue task error")
}
logger.Infof("[CreateQuotaTask] Successfully created task with ID: %d", newTask.Id)
return nil
}
@@ -4,6 +4,7 @@ import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
@@ -50,28 +51,29 @@ func (l *GetPreSendEmailCountLogic) GetPreSendEmailCount(req *types.GetPreSendEm
return query
}
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")
case "skip":
case task.ScopeSkip:
// Skip scope does not require a count
query = nil
default:
l.Errorf("[CreateBatchSendEmailTask] Invalid scope: %v", req.Scope)
return nil, xerr.NewErrMsg("Invalid email scope")
@@ -3,6 +3,7 @@ package marketing
import (
"context"
"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"
@@ -14,7 +15,7 @@ type QueryQuotaTaskListLogic struct {
svcCtx *svc.ServiceContext
}
// Query quota task list
// NewQueryQuotaTaskListLogic Query quota task list
func NewQueryQuotaTaskListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskListLogic {
return &QueryQuotaTaskListLogic{
Logger: logger.WithContext(ctx),
@@ -24,7 +25,59 @@ func NewQueryQuotaTaskListLogic(ctx context.Context, svcCtx *svc.ServiceContext)
}
func (l *QueryQuotaTaskListLogic) QueryQuotaTaskList(req *types.QueryQuotaTaskListRequest) (resp *types.QueryQuotaTaskListResponse, err error) {
// todo: add your logic here and delete this line
var data []*task.Task
var count int64
query := l.svcCtx.DB.Model(&task.Task{}).Where("`type` = ?", task.TypeQuota)
if req.Page == 0 {
req.Page = 1
}
if req.Size == 0 {
req.Size = 20
}
return
if req.Status != nil {
query = query.Where("`status` = ?", *req.Status)
}
err = query.Count(&count).Offset((req.Page - 1) * req.Size).Limit(req.Size).Order("created_at DESC").Find(&data).Error
if err != nil {
l.Errorf("[QueryQuotaTaskList] failed to get quota tasks: %v", err)
return nil, err
}
var list []types.QuotaTask
for _, item := range data {
var scopeInfo task.QuotaScope
if err = scopeInfo.Unmarshal([]byte(item.Scope)); err != nil {
l.Errorf("[QueryQuotaTaskList] failed to unmarshal quota task scope: %v", err.Error())
continue
}
var contentInfo task.QuotaContent
if err = contentInfo.Unmarshal([]byte(item.Content)); err != nil {
l.Errorf("[QueryQuotaTaskList] failed to unmarshal quota task content: %v", err.Error())
continue
}
list = append(list, types.QuotaTask{
Id: item.Id,
Subscribers: scopeInfo.Subscribers,
IsActive: scopeInfo.IsActive,
StartTime: scopeInfo.StartTime,
EndTime: scopeInfo.EndTime,
ResetTraffic: contentInfo.ResetTraffic,
Days: contentInfo.Days,
GiftType: contentInfo.GiftType,
GiftValue: contentInfo.GiftValue,
Objects: scopeInfo.Objects,
Status: uint8(item.Status),
Total: int64(item.Total),
Current: int64(item.Current),
Errors: item.Errors,
CreatedAt: item.CreatedAt.UnixMilli(),
UpdatedAt: item.UpdatedAt.UnixMilli(),
})
}
return &types.QueryQuotaTaskListResponse{
Total: count,
List: list,
}, nil
}
@@ -2,7 +2,9 @@ package marketing
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
@@ -14,7 +16,7 @@ type QueryQuotaTaskPreCountLogic struct {
svcCtx *svc.ServiceContext
}
// Query quota task pre-count
// NewQueryQuotaTaskPreCountLogic Query quota task pre-count
func NewQueryQuotaTaskPreCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskPreCountLogic {
return &QueryQuotaTaskPreCountLogic{
Logger: logger.WithContext(ctx),
@@ -24,7 +26,30 @@ func NewQueryQuotaTaskPreCountLogic(ctx context.Context, svcCtx *svc.ServiceCont
}
func (l *QueryQuotaTaskPreCountLogic) QueryQuotaTaskPreCount(req *types.QueryQuotaTaskPreCountRequest) (resp *types.QueryQuotaTaskPreCountResponse, err error) {
// todo: add your logic here and delete this line
tx := l.svcCtx.DB.WithContext(l.ctx).Model(&user.Subscribe{})
var count int64
return
if len(req.Subscribers) > 0 {
tx = tx.Where("`subscribe_id` IN ?", req.Subscribers)
}
if req.IsActive != nil && *req.IsActive {
tx = tx.Where("`status` IN ?", []int64{0, 1, 2}) // 0: Pending 1: Active 2: Finished
}
if req.StartTime != 0 {
start := time.UnixMilli(req.StartTime)
tx = tx.Where("`start_time` >= ?", start)
}
if req.EndTime != 0 {
end := time.UnixMilli(req.EndTime)
tx = tx.Where("`start_time` <= ?", end)
}
if err = tx.Count(&count).Error; err != nil {
l.Errorf("[QueryQuotaTaskPreCount] count error: %v", err.Error())
return nil, err
}
return &types.QueryQuotaTaskPreCountResponse{
Count: count,
}, nil
}
@@ -3,9 +3,12 @@ package marketing
import (
"context"
"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/xerr"
"github.com/pkg/errors"
)
type QueryQuotaTaskStatusLogic struct {
@@ -14,7 +17,7 @@ type QueryQuotaTaskStatusLogic struct {
svcCtx *svc.ServiceContext
}
// Query quota task status
// NewQueryQuotaTaskStatusLogic Query quota task status
func NewQueryQuotaTaskStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskStatusLogic {
return &QueryQuotaTaskStatusLogic{
Logger: logger.WithContext(ctx),
@@ -24,7 +27,16 @@ func NewQueryQuotaTaskStatusLogic(ctx context.Context, svcCtx *svc.ServiceContex
}
func (l *QueryQuotaTaskStatusLogic) QueryQuotaTaskStatus(req *types.QueryQuotaTaskStatusRequest) (resp *types.QueryQuotaTaskStatusResponse, err error) {
// todo: add your logic here and delete this line
return
var data *task.Task
err = l.svcCtx.DB.Model(&task.Task{}).Where("id = ? AND `type` = ?", req.Id, task.TypeQuota).First(&data).Error
if err != nil {
l.Errorf("[QueryQuotaTaskStatus] failed to get quota task: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " failed to get quota task: %v", err.Error())
}
return &types.QueryQuotaTaskStatusResponse{
Status: uint8(data.Status),
Current: int64(data.Current),
Total: int64(data.Total),
Errors: data.Errors,
}, nil
}