feat: Redemption Code
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
package redemption
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 BatchDeleteRedemptionCodeLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Batch delete redemption code
|
||||
func NewBatchDeleteRedemptionCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BatchDeleteRedemptionCodeLogic {
|
||||
return &BatchDeleteRedemptionCodeLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *BatchDeleteRedemptionCodeLogic) BatchDeleteRedemptionCode(req *types.BatchDeleteRedemptionCodeRequest) error {
|
||||
err := l.svcCtx.RedemptionCodeModel.BatchDelete(l.ctx, req.Ids)
|
||||
if err != nil {
|
||||
l.Errorw("[BatchDeleteRedemptionCode] Database Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "batch delete redemption code error: %v", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package redemption
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/redemption"
|
||||
"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"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CreateRedemptionCodeLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Create redemption code
|
||||
func NewCreateRedemptionCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateRedemptionCodeLogic {
|
||||
return &CreateRedemptionCodeLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// generateUniqueCode generates a unique redemption code
|
||||
func (l *CreateRedemptionCodeLogic) generateUniqueCode() (string, error) {
|
||||
const charset = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // Removed confusing characters like I, O, 0, 1
|
||||
const codeLength = 16
|
||||
|
||||
maxRetries := 10
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
code := make([]byte, codeLength)
|
||||
for j := 0; j < codeLength; j++ {
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
code[j] = charset[num.Int64()]
|
||||
}
|
||||
|
||||
codeStr := string(code)
|
||||
|
||||
// Check if code already exists
|
||||
_, err := l.svcCtx.RedemptionCodeModel.FindOneByCode(l.ctx, codeStr)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return codeStr, nil
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Code exists, try again
|
||||
}
|
||||
|
||||
return "", errors.New("failed to generate unique code after maximum retries")
|
||||
}
|
||||
|
||||
func (l *CreateRedemptionCodeLogic) CreateRedemptionCode(req *types.CreateRedemptionCodeRequest) error {
|
||||
// Check if subscribe plan is valid
|
||||
if req.SubscribePlan == 0 {
|
||||
l.Errorw("[CreateRedemptionCode] Subscribe plan cannot be empty")
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "subscribe plan cannot be empty")
|
||||
}
|
||||
|
||||
// Verify subscribe plan exists
|
||||
_, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, req.SubscribePlan)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
l.Errorw("[CreateRedemptionCode] Subscribe plan not found", logger.Field("subscribe_plan", req.SubscribePlan))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "subscribe plan not found")
|
||||
}
|
||||
l.Errorw("[CreateRedemptionCode] Database Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe plan error: %v", err.Error())
|
||||
}
|
||||
|
||||
// Validate batch count
|
||||
if req.BatchCount < 1 {
|
||||
l.Errorw("[CreateRedemptionCode] Batch count must be at least 1")
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "batch count must be at least 1")
|
||||
}
|
||||
|
||||
// Generate redemption codes in batch
|
||||
var createdCodes []string
|
||||
for i := int64(0); i < req.BatchCount; i++ {
|
||||
code, err := l.generateUniqueCode()
|
||||
if err != nil {
|
||||
l.Errorw("[CreateRedemptionCode] Failed to generate unique code", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "generate unique code error: %v", err.Error())
|
||||
}
|
||||
|
||||
redemptionCode := &redemption.RedemptionCode{
|
||||
Code: code,
|
||||
TotalCount: req.TotalCount,
|
||||
UsedCount: 0,
|
||||
SubscribePlan: req.SubscribePlan,
|
||||
UnitTime: req.UnitTime,
|
||||
Quantity: req.Quantity,
|
||||
}
|
||||
|
||||
err = l.svcCtx.RedemptionCodeModel.Insert(l.ctx, redemptionCode)
|
||||
if err != nil {
|
||||
l.Errorw("[CreateRedemptionCode] Database Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create redemption code error: %v", err.Error())
|
||||
}
|
||||
|
||||
createdCodes = append(createdCodes, code)
|
||||
}
|
||||
|
||||
l.Infow("[CreateRedemptionCode] Successfully created redemption codes",
|
||||
logger.Field("count", len(createdCodes)),
|
||||
logger.Field("codes", createdCodes))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package redemption
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 DeleteRedemptionCodeLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Delete redemption code
|
||||
func NewDeleteRedemptionCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteRedemptionCodeLogic {
|
||||
return &DeleteRedemptionCodeLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DeleteRedemptionCodeLogic) DeleteRedemptionCode(req *types.DeleteRedemptionCodeRequest) error {
|
||||
err := l.svcCtx.RedemptionCodeModel.Delete(l.ctx, req.Id)
|
||||
if err != nil {
|
||||
l.Errorw("[DeleteRedemptionCode] Database Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete redemption code error: %v", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package redemption
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 GetRedemptionCodeListLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Get redemption code list
|
||||
func NewGetRedemptionCodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRedemptionCodeListLogic {
|
||||
return &GetRedemptionCodeListLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetRedemptionCodeListLogic) GetRedemptionCodeList(req *types.GetRedemptionCodeListRequest) (resp *types.GetRedemptionCodeListResponse, err error) {
|
||||
total, list, err := l.svcCtx.RedemptionCodeModel.QueryRedemptionCodeListByPage(
|
||||
l.ctx,
|
||||
int(req.Page),
|
||||
int(req.Size),
|
||||
req.SubscribePlan,
|
||||
req.UnitTime,
|
||||
req.Code,
|
||||
)
|
||||
if err != nil {
|
||||
l.Errorw("[GetRedemptionCodeList] Database Error", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get redemption code list error: %v", err.Error())
|
||||
}
|
||||
|
||||
var redemptionCodes []types.RedemptionCode
|
||||
for _, item := range list {
|
||||
redemptionCodes = append(redemptionCodes, types.RedemptionCode{
|
||||
Id: item.Id,
|
||||
Code: item.Code,
|
||||
TotalCount: item.TotalCount,
|
||||
UsedCount: item.UsedCount,
|
||||
SubscribePlan: item.SubscribePlan,
|
||||
UnitTime: item.UnitTime,
|
||||
Quantity: item.Quantity,
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
return &types.GetRedemptionCodeListResponse{
|
||||
Total: total,
|
||||
List: redemptionCodes,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package redemption
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 GetRedemptionRecordListLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Get redemption record list
|
||||
func NewGetRedemptionRecordListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRedemptionRecordListLogic {
|
||||
return &GetRedemptionRecordListLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetRedemptionRecordListLogic) GetRedemptionRecordList(req *types.GetRedemptionRecordListRequest) (resp *types.GetRedemptionRecordListResponse, err error) {
|
||||
total, list, err := l.svcCtx.RedemptionRecordModel.QueryRedemptionRecordListByPage(
|
||||
l.ctx,
|
||||
int(req.Page),
|
||||
int(req.Size),
|
||||
req.UserId,
|
||||
req.CodeId,
|
||||
)
|
||||
if err != nil {
|
||||
l.Errorw("[GetRedemptionRecordList] Database Error", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get redemption record list error: %v", err.Error())
|
||||
}
|
||||
|
||||
var redemptionRecords []types.RedemptionRecord
|
||||
for _, item := range list {
|
||||
redemptionRecords = append(redemptionRecords, types.RedemptionRecord{
|
||||
Id: item.Id,
|
||||
RedemptionCodeId: item.RedemptionCodeId,
|
||||
UserId: item.UserId,
|
||||
SubscribeId: item.SubscribeId,
|
||||
UnitTime: item.UnitTime,
|
||||
Quantity: item.Quantity,
|
||||
RedeemedAt: item.RedeemedAt.Unix(),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
return &types.GetRedemptionRecordListResponse{
|
||||
Total: total,
|
||||
List: redemptionRecords,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package redemption
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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 UpdateRedemptionCodeLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Update redemption code
|
||||
func NewUpdateRedemptionCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateRedemptionCodeLogic {
|
||||
return &UpdateRedemptionCodeLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UpdateRedemptionCodeLogic) UpdateRedemptionCode(req *types.UpdateRedemptionCodeRequest) error {
|
||||
redemptionCode, err := l.svcCtx.RedemptionCodeModel.FindOne(l.ctx, req.Id)
|
||||
if err != nil {
|
||||
l.Errorw("[UpdateRedemptionCode] Find Redemption Code Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find redemption code error: %v", err.Error())
|
||||
}
|
||||
|
||||
// Code is not allowed to be modified
|
||||
if req.TotalCount != 0 {
|
||||
// Total count cannot be less than used count
|
||||
if req.TotalCount < redemptionCode.UsedCount {
|
||||
l.Errorw("[UpdateRedemptionCode] Total count cannot be less than used count",
|
||||
logger.Field("total_count", req.TotalCount),
|
||||
logger.Field("used_count", redemptionCode.UsedCount))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams),
|
||||
"total count cannot be less than used count: total_count=%d, used_count=%d",
|
||||
req.TotalCount, redemptionCode.UsedCount)
|
||||
}
|
||||
redemptionCode.TotalCount = req.TotalCount
|
||||
}
|
||||
if req.SubscribePlan != 0 {
|
||||
redemptionCode.SubscribePlan = req.SubscribePlan
|
||||
}
|
||||
if req.UnitTime != "" {
|
||||
redemptionCode.UnitTime = req.UnitTime
|
||||
}
|
||||
if req.Quantity != 0 {
|
||||
redemptionCode.Quantity = req.Quantity
|
||||
}
|
||||
|
||||
err = l.svcCtx.RedemptionCodeModel.Update(l.ctx, redemptionCode)
|
||||
if err != nil {
|
||||
l.Errorw("[UpdateRedemptionCode] Database Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update redemption code error: %v", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user