Develop (#76)
* refactor: rename queryannouncementhandler.go to queryAnnouncementLogic.go for clarity
* feat(panDomain): update subscription logic to use V2 handler for improved functionality
* refactor(subscribe): replace V2 handler with a unified Handler method for subscription logic
* feat(subscribe): implement user agent limit feature with configurable list
* fix(subscribe): improve error handling and logging for subscription requests
* feat(subscribe): add user agent limit configuration to system settings
* refactor(api): remove deprecated application-related endpoints and types
* refactor(swagger): remove deprecated app.json generation from swagger configuration
* refactor(swagger): remove deprecated app.json check from swagger configuration
* fix(subscribe): update delete method to use Where clause for improved query accuracy
* fix(subscribe): update Id field tag to use primaryKey and improve save method query
* fix(subscribe): update Id field tag to use primaryKey and improve model queries
* fix(subscribe): rename variable for clarity and add special handling for Stash user agent
* fix(email): convert RegisterStartTime and RegisterEndTime to time.Time for accurate query filtering
* refactor(log): consolidate logging models and update related logic for improved clarity and functionality
* fix(types): change Content field type in MessageLog to interface{} for improved flexibility
* fix(log): change MessageLog list to use value type for improved performance and memory efficiency
* fix(email): set EmailTypeVerify in task payload and update content type conversion for verification email
* fix(log): remove unused Id field from SystemLog during login log insertion
* fix(login): remove debug logs and error logging during user login process
* fix(log): add traffic reset logging for subscription resets
* fix(log): insert reset traffic log during subscription activation
* feat(log): add endpoints for retrieving and resetting subscribe traffic logs
* refactor(log): remove Reset Subscribe Traffic Log endpoint and related types
* feat(traffic): add traffic statistics logging and scheduling
* fix(subscribe): ensure active status and reset timestamps during traffic resets
* feat(api): enhance server and node management with new request/response structures
* refactor(api): rename OnlineUser to ServerOnlineUser for clarity
* feat(api): define OnlineUser type with SID and IP fields
* feat(server): implement server management handlers and database schema
* feat(api): add traffic log details filtering and enhance traffic log structures
* feat(api): migrate server and node data handling, update related structures and logic
* feat(server): implement server deletion logic with error handling
* feat(api): update log filtering to use ResetSubscribe type for subscription logs
* feat(api): standardize timestamp field across log structures
* feat(api): refactor cache key handling for server and user lists
* feat(api): enhance server status handling with protocol support and refactor related logic
* fix(traffic): adjust start date for traffic statistics and improve log deletion comment
* feat(api): implement daily traffic ranking for users and servers with error handling
* feat(api): update server total data response to use 'OnlineUsers' and implement daily traffic statistics logging
* feat(api): add log settings management with auto-clear and clear days configuration
* fix(log): correct category in log settings update query
* feat(routes): add handler for scheduled traffic statistics
* feat(model): add user counts struct and update queries for new and renewal users
* feat(api): add referral percentage and only first purchase fields to user model and requests
* feat(database): update user table to add referral percentage and only first purchase fields
* feat(api): add reset sort endpoints for server and node
* feat(api): add sort field to server model
* feat(api): implement sorting functionality for nodes and servers
* fix(database): add sort column to nodes table
* fix(model): enhance user statistics queries with new order and renewal order counts
* fix(log): update timestamp handling in login and registration logs
* fix(log): update sorting logic for server and user subscribe traffic logs
* fix(server): add server status handling based on last reported time
* fix(model): correct filter condition to use 'date' instead of 'data'
* fix(migration): add index for traffic log on timestamp, user_id, and subscribe_id
* fix(log): optimize user traffic rank data handling by using append instead of index assignment
* fix(filter): refactor node list creation to use append and remove duplicates from tags
* fix(node): add ServerId and Enabled fields to node update logic
* feat(tags): add endpoint to query all node tags
* fix(preview): add Preload parameter to FilterNodeList for improved data retrieval
* fix(log): date is empty
* feat(subscribe): add Language field to subscription models and update query logic
* feat(subscription): add Language parameter to GetSubscription request and update query logic
* fix(server): encode ServerKey in base64 and update last reported time for nodes
* feat: delete common GetSubscription
* feat(subscription): implement FilterList method for subscription queries and update related logic
* fix(subscribe): remove duplicate user agents in SubscribeHandler
* fix(push): initialize onlineUsers as a map in pushOnlineUsersLogic
* fix(reset): initialize subs as a map in clearCache method
* refactor(query): simplify node and tag filtering using InSet function
* feat(userlist): enhance GetServerUserListLogic with improved node and tag handling
* fix(userlist): correct node ID assignment and update query logic for tag filtering
* fix(userlist): correct node ID assignment in getServerUserListLogic
* refactor(query): streamline query construction for tag filtering
* fix(statistics): optimize server ranking data handling in QueryServerTotalDataLogic
* refactor(statistics): simplify server ranking data construction in QueryServerTotalDataLogic
* fix(statistics): correct server traffic data assignment in QueryServerTotalDataLogic
* fix(statistics): optimize yesterday's top 10 server traffic data assignment in QueryServerTotalDataLogic
* fix(middleware): remove duplicate elements from user agent list in PanDomainMiddleware
* feat(middleware): enhance user agent handling by querying client list in PanDomainMiddleware
* feat(client): subscribe_template
* feat(oauth): add user agent and IP logging to registration and login processes
* fix(balance): add timestamp to balance logs for payment, refund, and recharge transactions
* fix(log): correct comment for CommissionTypeRefund to improve clarity
* fix(log): replace magic number with constant for gift type in purchase checkout logic
* fix(log): rename OrderId to OrderNo for consistency in balance logging
* feat(log): add logging for balance, gift amount, and commission adjustments
* fix(user): correct placement of DeepCopy for user info update logic
* feat(log): add UserSubscribeId to FilterSubscribeLogRequest for enhanced filtering
* fix(purchase): streamline error handling and improve JSON marshaling for temporary orders
* fix(order): simplify commission handling and improve payload parsing logic
* fix(order): update commission calculation to actual payment amount minus gateway handling fee
* feat(payment): add support for CryptoSaaS payment platform and enhance configuration handling
* fix(balance): update QueryUserBalanceLog response structure to include balance log list
* fix(email): update task progress handling to use specific task ID for updates
* feat(quota): add quota task creation and querying endpoints with updated data structures
* fix(email): update task handling to use generic task model and improve error logging
* fix(order): improve error logging for database transaction and user cache updates
* feat(quota): enhance quota task management with new request structures and processing logic
* fix(quota): remove redundant quota task status endpoint from admin marketing routes
* fix(worker): update task completion status handling in worker logic
* fix(quota): update taskInfo to include current subscription count in quota logic
* doc(log): rename function for clarity and add cache cleanup comment
* fix(quota): update time handling in quota logic and correct subscriber ID query
* fix(quota): update time handling to use UnixMilli for start time in quota logic
* feat(protocol): add server protocol configuration query and enhance protocol options
* fix(quota): correct time range queries for start and expire times in quota logic
* fix(types): update plugin options to include 'none' in the plugin field
---------
Co-authored-by: Chang lue Tsen <tension@ppanel.dev>
This commit is contained in:
@@ -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),
|
||||
@@ -45,34 +45,37 @@ func (l *CreateBatchSendEmailTaskLogic) CreateBatchSendEmailTask(req *types.Crea
|
||||
Where("auth_type = ?", "email")
|
||||
|
||||
if req.RegisterStartTime != 0 {
|
||||
query = query.Where("user.created_at >= ?", req.RegisterStartTime)
|
||||
query = query.Where("user.created_at >= ?", time.UnixMilli(req.RegisterStartTime))
|
||||
}
|
||||
if req.RegisterEndTime != 0 {
|
||||
query = query.Where("user.created_at <= ?", req.RegisterEndTime)
|
||||
query = query.Where("user.created_at <= ?", time.UnixMilli(req.RegisterEndTime))
|
||||
}
|
||||
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")
|
||||
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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
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 {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewCreateQuotaTaskLogic 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 {
|
||||
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("`expire_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,
|
||||
Objects: subIds,
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -28,15 +28,15 @@ func NewGetBatchSendEmailTaskStatusLogic(ctx context.Context, svcCtx *svc.Servic
|
||||
func (l *GetBatchSendEmailTaskStatusLogic) GetBatchSendEmailTaskStatus(req *types.GetBatchSendEmailTaskStatusRequest) (resp *types.GetBatchSendEmailTaskStatusResponse, err error) {
|
||||
tx := l.svcCtx.DB
|
||||
|
||||
var taskInfo *task.EmailTask
|
||||
err = tx.Model(&task.EmailTask{}).Where("id = ?", req.Id).First(&taskInfo).Error
|
||||
var taskInfo *task.Task
|
||||
err = tx.Model(&task.Task{}).Where("id = ?", req.Id).First(&taskInfo).Error
|
||||
if err != nil {
|
||||
l.Errorf("failed to get email task status, error: %v", err)
|
||||
return nil, xerr.NewErrCode(xerr.DatabaseQueryError)
|
||||
}
|
||||
|
||||
return &types.GetBatchSendEmailTaskStatusResponse{
|
||||
Status: taskInfo.Status,
|
||||
Status: uint8(taskInfo.Status),
|
||||
Total: int64(taskInfo.Total),
|
||||
Current: int64(taskInfo.Current),
|
||||
Errors: taskInfo.Errors,
|
||||
|
||||
@@ -2,7 +2,9 @@ package marketing
|
||||
|
||||
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"
|
||||
@@ -37,36 +39,41 @@ func (l *GetPreSendEmailCountLogic) GetPreSendEmailCount(req *types.GetPreSendEm
|
||||
Where("auth_type = ?", "email")
|
||||
|
||||
if req.RegisterStartTime != 0 {
|
||||
query = query.Where("user.created_at >= ?", req.RegisterStartTime)
|
||||
|
||||
registerStartTime := time.UnixMilli(req.RegisterStartTime)
|
||||
|
||||
query = query.Where("user.created_at >= ?", registerStartTime)
|
||||
}
|
||||
if req.RegisterEndTime != 0 {
|
||||
query = query.Where("user.created_at <= ?", req.RegisterEndTime)
|
||||
registerEndTime := time.UnixMilli(req.RegisterEndTime)
|
||||
query = query.Where("user.created_at <= ?", registerEndTime)
|
||||
}
|
||||
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")
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type QueryQuotaTaskListLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewQueryQuotaTaskListLogic 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) {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type QueryQuotaTaskPreCountLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewQueryQuotaTaskPreCountLogic 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) {
|
||||
tx := l.svcCtx.DB.WithContext(l.ctx).Model(&user.Subscribe{})
|
||||
var count int64
|
||||
|
||||
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("`expire_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
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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 {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewQueryQuotaTaskStatusLogic 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) {
|
||||
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
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func (l *StopBatchSendEmailTaskLogic) StopBatchSendEmailTask(req *types.StopBatc
|
||||
} else {
|
||||
logger.Error("[StopBatchSendEmailTaskLogic] email.Manager is nil, cannot stop task")
|
||||
}
|
||||
err = l.svcCtx.DB.Model(&task.EmailTask{}).Where("id = ?", req.Id).Update("status", 2).Error
|
||||
err = l.svcCtx.DB.Model(&task.Task{}).Where("id = ?", req.Id).Update("status", 2).Error
|
||||
|
||||
if err != nil {
|
||||
l.Errorf("failed to stop email task, error: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user