feat: Redemption Code
This commit is contained in:
parent
5beff61e91
commit
518595b058
86
apis/admin/redemption.api
Normal file
86
apis/admin/redemption.api
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "redemption API"
|
||||||
|
desc: "API for redemption code management"
|
||||||
|
author: "Tension"
|
||||||
|
email: "tension@ppanel.com"
|
||||||
|
version: "0.0.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
import "../types.api"
|
||||||
|
|
||||||
|
type (
|
||||||
|
CreateRedemptionCodeRequest {
|
||||||
|
TotalCount int64 `json:"total_count" validate:"required"`
|
||||||
|
SubscribePlan int64 `json:"subscribe_plan" validate:"required"`
|
||||||
|
UnitTime string `json:"unit_time" validate:"required,oneof=day month quarter half_year year"`
|
||||||
|
Quantity int64 `json:"quantity" validate:"required"`
|
||||||
|
BatchCount int64 `json:"batch_count" validate:"required,min=1"`
|
||||||
|
}
|
||||||
|
UpdateRedemptionCodeRequest {
|
||||||
|
Id int64 `json:"id" validate:"required"`
|
||||||
|
TotalCount int64 `json:"total_count,omitempty"`
|
||||||
|
SubscribePlan int64 `json:"subscribe_plan,omitempty"`
|
||||||
|
UnitTime string `json:"unit_time,omitempty" validate:"omitempty,oneof=day month quarter half_year year"`
|
||||||
|
Quantity int64 `json:"quantity,omitempty"`
|
||||||
|
}
|
||||||
|
DeleteRedemptionCodeRequest {
|
||||||
|
Id int64 `json:"id" validate:"required"`
|
||||||
|
}
|
||||||
|
BatchDeleteRedemptionCodeRequest {
|
||||||
|
Ids []int64 `json:"ids" validate:"required"`
|
||||||
|
}
|
||||||
|
GetRedemptionCodeListRequest {
|
||||||
|
Page int64 `form:"page" validate:"required"`
|
||||||
|
Size int64 `form:"size" validate:"required"`
|
||||||
|
SubscribePlan int64 `form:"subscribe_plan,omitempty"`
|
||||||
|
UnitTime string `form:"unit_time,omitempty"`
|
||||||
|
Code string `form:"code,omitempty"`
|
||||||
|
}
|
||||||
|
GetRedemptionCodeListResponse {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
List []RedemptionCode `json:"list"`
|
||||||
|
}
|
||||||
|
GetRedemptionRecordListRequest {
|
||||||
|
Page int64 `form:"page" validate:"required"`
|
||||||
|
Size int64 `form:"size" validate:"required"`
|
||||||
|
UserId int64 `form:"user_id,omitempty"`
|
||||||
|
CodeId int64 `form:"code_id,omitempty"`
|
||||||
|
}
|
||||||
|
GetRedemptionRecordListResponse {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
List []RedemptionRecord `json:"list"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server (
|
||||||
|
prefix: v1/admin/redemption
|
||||||
|
group: admin/redemption
|
||||||
|
middleware: AuthMiddleware
|
||||||
|
)
|
||||||
|
service ppanel {
|
||||||
|
@doc "Create redemption code"
|
||||||
|
@handler CreateRedemptionCode
|
||||||
|
post /code (CreateRedemptionCodeRequest)
|
||||||
|
|
||||||
|
@doc "Update redemption code"
|
||||||
|
@handler UpdateRedemptionCode
|
||||||
|
put /code (UpdateRedemptionCodeRequest)
|
||||||
|
|
||||||
|
@doc "Delete redemption code"
|
||||||
|
@handler DeleteRedemptionCode
|
||||||
|
delete /code (DeleteRedemptionCodeRequest)
|
||||||
|
|
||||||
|
@doc "Batch delete redemption code"
|
||||||
|
@handler BatchDeleteRedemptionCode
|
||||||
|
delete /code/batch (BatchDeleteRedemptionCodeRequest)
|
||||||
|
|
||||||
|
@doc "Get redemption code list"
|
||||||
|
@handler GetRedemptionCodeList
|
||||||
|
get /code/list (GetRedemptionCodeListRequest) returns (GetRedemptionCodeListResponse)
|
||||||
|
|
||||||
|
@doc "Get redemption record list"
|
||||||
|
@handler GetRedemptionRecordList
|
||||||
|
get /record/list (GetRedemptionRecordListRequest) returns (GetRedemptionRecordListResponse)
|
||||||
|
}
|
||||||
32
apis/public/redemption.api
Normal file
32
apis/public/redemption.api
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
syntax = "v1"
|
||||||
|
|
||||||
|
info (
|
||||||
|
title: "redemption API"
|
||||||
|
desc: "API for redemption"
|
||||||
|
author: "Tension"
|
||||||
|
email: "tension@ppanel.com"
|
||||||
|
version: "0.0.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
import "../types.api"
|
||||||
|
|
||||||
|
type (
|
||||||
|
RedeemCodeRequest {
|
||||||
|
Code string `json:"code" validate:"required"`
|
||||||
|
}
|
||||||
|
RedeemCodeResponse {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@server (
|
||||||
|
prefix: v1/public/redemption
|
||||||
|
group: public/redemption
|
||||||
|
jwt: JwtAuth
|
||||||
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
|
)
|
||||||
|
service ppanel {
|
||||||
|
@doc "Redeem code"
|
||||||
|
@handler RedeemCode
|
||||||
|
post / (RedeemCodeRequest) returns (RedeemCodeResponse)
|
||||||
|
}
|
||||||
@ -448,6 +448,27 @@ type (
|
|||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
RedemptionCode {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
UsedCount int64 `json:"used_count"`
|
||||||
|
SubscribePlan int64 `json:"subscribe_plan"`
|
||||||
|
UnitTime string `json:"unit_time"`
|
||||||
|
Quantity int64 `json:"quantity"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
}
|
||||||
|
RedemptionRecord {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
RedemptionCodeId int64 `json:"redemption_code_id"`
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
|
UnitTime string `json:"unit_time"`
|
||||||
|
Quantity int64 `json:"quantity"`
|
||||||
|
RedeemedAt int64 `json:"redeemed_at"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
}
|
||||||
Announcement {
|
Announcement {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
|||||||
5
initialize/migrate/database/02126_redemption.down.sql
Normal file
5
initialize/migrate/database/02126_redemption.down.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
-- Drop redemption_record table
|
||||||
|
DROP TABLE IF EXISTS `redemption_record`;
|
||||||
|
|
||||||
|
-- Drop redemption_code table
|
||||||
|
DROP TABLE IF EXISTS `redemption_code`;
|
||||||
31
initialize/migrate/database/02126_redemption.up.sql
Normal file
31
initialize/migrate/database/02126_redemption.up.sql
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
-- Create redemption_code table
|
||||||
|
CREATE TABLE IF NOT EXISTS `redemption_code` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
|
||||||
|
`code` VARCHAR(255) NOT NULL COMMENT 'Redemption Code',
|
||||||
|
`total_count` BIGINT NOT NULL DEFAULT 0 COMMENT 'Total Redemption Count',
|
||||||
|
`used_count` BIGINT NOT NULL DEFAULT 0 COMMENT 'Used Redemption Count',
|
||||||
|
`subscribe_plan` BIGINT NOT NULL DEFAULT 0 COMMENT 'Subscribe Plan',
|
||||||
|
`unit_time` VARCHAR(50) NOT NULL DEFAULT 'month' COMMENT 'Unit Time: day, month, quarter, half_year, year',
|
||||||
|
`quantity` BIGINT NOT NULL DEFAULT 1 COMMENT 'Quantity',
|
||||||
|
`created_at` DATETIME NOT NULL COMMENT 'Creation Time',
|
||||||
|
`updated_at` DATETIME NOT NULL COMMENT 'Update Time',
|
||||||
|
`deleted_at` DATETIME DEFAULT NULL COMMENT 'Deletion Time',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_code` (`code`),
|
||||||
|
KEY `idx_deleted_at` (`deleted_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Redemption Code Table';
|
||||||
|
|
||||||
|
-- Create redemption_record table
|
||||||
|
CREATE TABLE IF NOT EXISTS `redemption_record` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
|
||||||
|
`redemption_code_id` BIGINT NOT NULL DEFAULT 0 COMMENT 'Redemption Code Id',
|
||||||
|
`user_id` BIGINT NOT NULL DEFAULT 0 COMMENT 'User Id',
|
||||||
|
`subscribe_id` BIGINT NOT NULL DEFAULT 0 COMMENT 'Subscribe Id',
|
||||||
|
`unit_time` VARCHAR(50) NOT NULL DEFAULT 'month' COMMENT 'Unit Time',
|
||||||
|
`quantity` BIGINT NOT NULL DEFAULT 1 COMMENT 'Quantity',
|
||||||
|
`redeemed_at` DATETIME NOT NULL COMMENT 'Redeemed Time',
|
||||||
|
`created_at` DATETIME NOT NULL COMMENT 'Creation Time',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_redemption_code_id` (`redemption_code_id`),
|
||||||
|
KEY `idx_user_id` (`user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Redemption Record Table';
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/admin/redemption"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Batch delete redemption code
|
||||||
|
func BatchDeleteRedemptionCodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.BatchDeleteRedemptionCodeRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := redemption.NewBatchDeleteRedemptionCodeLogic(c.Request.Context(), svcCtx)
|
||||||
|
err := l.BatchDeleteRedemptionCode(&req)
|
||||||
|
result.HttpResult(c, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/admin/redemption"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create redemption code
|
||||||
|
func CreateRedemptionCodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.CreateRedemptionCodeRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := redemption.NewCreateRedemptionCodeLogic(c.Request.Context(), svcCtx)
|
||||||
|
err := l.CreateRedemptionCode(&req)
|
||||||
|
result.HttpResult(c, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/admin/redemption"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete redemption code
|
||||||
|
func DeleteRedemptionCodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.DeleteRedemptionCodeRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := redemption.NewDeleteRedemptionCodeLogic(c.Request.Context(), svcCtx)
|
||||||
|
err := l.DeleteRedemptionCode(&req)
|
||||||
|
result.HttpResult(c, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/admin/redemption"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get redemption code list
|
||||||
|
func GetRedemptionCodeListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.GetRedemptionCodeListRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := redemption.NewGetRedemptionCodeListLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.GetRedemptionCodeList(&req)
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/admin/redemption"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get redemption record list
|
||||||
|
func GetRedemptionRecordListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.GetRedemptionRecordListRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := redemption.NewGetRedemptionRecordListLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.GetRedemptionRecordList(&req)
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/admin/redemption"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update redemption code
|
||||||
|
func UpdateRedemptionCodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.UpdateRedemptionCodeRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := redemption.NewUpdateRedemptionCodeLogic(c.Request.Context(), svcCtx)
|
||||||
|
err := l.UpdateRedemptionCode(&req)
|
||||||
|
result.HttpResult(c, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/handler/public/redemption/redeemCodeHandler.go
Normal file
26
internal/handler/public/redemption/redeemCodeHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/redemption"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redeem code
|
||||||
|
func RedeemCodeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.RedeemCodeRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := redemption.NewRedeemCodeLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.RedeemCode(&req)
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,7 @@ import (
|
|||||||
adminMarketing "github.com/perfect-panel/server/internal/handler/admin/marketing"
|
adminMarketing "github.com/perfect-panel/server/internal/handler/admin/marketing"
|
||||||
adminOrder "github.com/perfect-panel/server/internal/handler/admin/order"
|
adminOrder "github.com/perfect-panel/server/internal/handler/admin/order"
|
||||||
adminPayment "github.com/perfect-panel/server/internal/handler/admin/payment"
|
adminPayment "github.com/perfect-panel/server/internal/handler/admin/payment"
|
||||||
|
adminRedemption "github.com/perfect-panel/server/internal/handler/admin/redemption"
|
||||||
adminServer "github.com/perfect-panel/server/internal/handler/admin/server"
|
adminServer "github.com/perfect-panel/server/internal/handler/admin/server"
|
||||||
adminSubscribe "github.com/perfect-panel/server/internal/handler/admin/subscribe"
|
adminSubscribe "github.com/perfect-panel/server/internal/handler/admin/subscribe"
|
||||||
adminSystem "github.com/perfect-panel/server/internal/handler/admin/system"
|
adminSystem "github.com/perfect-panel/server/internal/handler/admin/system"
|
||||||
@ -30,6 +31,7 @@ import (
|
|||||||
publicOrder "github.com/perfect-panel/server/internal/handler/public/order"
|
publicOrder "github.com/perfect-panel/server/internal/handler/public/order"
|
||||||
publicPayment "github.com/perfect-panel/server/internal/handler/public/payment"
|
publicPayment "github.com/perfect-panel/server/internal/handler/public/payment"
|
||||||
publicPortal "github.com/perfect-panel/server/internal/handler/public/portal"
|
publicPortal "github.com/perfect-panel/server/internal/handler/public/portal"
|
||||||
|
publicRedemption "github.com/perfect-panel/server/internal/handler/public/redemption"
|
||||||
publicSubscribe "github.com/perfect-panel/server/internal/handler/public/subscribe"
|
publicSubscribe "github.com/perfect-panel/server/internal/handler/public/subscribe"
|
||||||
publicTicket "github.com/perfect-panel/server/internal/handler/public/ticket"
|
publicTicket "github.com/perfect-panel/server/internal/handler/public/ticket"
|
||||||
publicUser "github.com/perfect-panel/server/internal/handler/public/user"
|
publicUser "github.com/perfect-panel/server/internal/handler/public/user"
|
||||||
@ -298,6 +300,29 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
adminPaymentGroupRouter.GET("/platform", adminPayment.GetPaymentPlatformHandler(serverCtx))
|
adminPaymentGroupRouter.GET("/platform", adminPayment.GetPaymentPlatformHandler(serverCtx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adminRedemptionGroupRouter := router.Group("/v1/admin/redemption")
|
||||||
|
adminRedemptionGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
||||||
|
|
||||||
|
{
|
||||||
|
// Create redemption code
|
||||||
|
adminRedemptionGroupRouter.POST("/code", adminRedemption.CreateRedemptionCodeHandler(serverCtx))
|
||||||
|
|
||||||
|
// Update redemption code
|
||||||
|
adminRedemptionGroupRouter.PUT("/code", adminRedemption.UpdateRedemptionCodeHandler(serverCtx))
|
||||||
|
|
||||||
|
// Delete redemption code
|
||||||
|
adminRedemptionGroupRouter.DELETE("/code", adminRedemption.DeleteRedemptionCodeHandler(serverCtx))
|
||||||
|
|
||||||
|
// Batch delete redemption code
|
||||||
|
adminRedemptionGroupRouter.DELETE("/code/batch", adminRedemption.BatchDeleteRedemptionCodeHandler(serverCtx))
|
||||||
|
|
||||||
|
// Get redemption code list
|
||||||
|
adminRedemptionGroupRouter.GET("/code/list", adminRedemption.GetRedemptionCodeListHandler(serverCtx))
|
||||||
|
|
||||||
|
// Get redemption record list
|
||||||
|
adminRedemptionGroupRouter.GET("/record/list", adminRedemption.GetRedemptionRecordListHandler(serverCtx))
|
||||||
|
}
|
||||||
|
|
||||||
adminServerGroupRouter := router.Group("/v1/admin/server")
|
adminServerGroupRouter := router.Group("/v1/admin/server")
|
||||||
adminServerGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
adminServerGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
||||||
|
|
||||||
@ -748,6 +773,14 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
publicPortalGroupRouter.GET("/subscribe", publicPortal.GetSubscriptionHandler(serverCtx))
|
publicPortalGroupRouter.GET("/subscribe", publicPortal.GetSubscriptionHandler(serverCtx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publicRedemptionGroupRouter := router.Group("/v1/public/redemption")
|
||||||
|
publicRedemptionGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
|
{
|
||||||
|
// Redeem code
|
||||||
|
publicRedemptionGroupRouter.POST("/", publicRedemption.RedeemCodeHandler(serverCtx))
|
||||||
|
}
|
||||||
|
|
||||||
publicSubscribeGroupRouter := router.Group("/v1/public/subscribe")
|
publicSubscribeGroupRouter := router.Group("/v1/public/subscribe")
|
||||||
publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
119
internal/logic/admin/redemption/createRedemptionCodeLogic.go
Normal file
119
internal/logic/admin/redemption/createRedemptionCodeLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
36
internal/logic/admin/redemption/deleteRedemptionCodeLogic.go
Normal file
36
internal/logic/admin/redemption/deleteRedemptionCodeLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
65
internal/logic/admin/redemption/updateRedemptionCodeLogic.go
Normal file
65
internal/logic/admin/redemption/updateRedemptionCodeLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
254
internal/logic/public/redemption/redeemCodeLogic.go
Normal file
254
internal/logic/public/redemption/redeemCodeLogic.go
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/perfect-panel/server/internal/model/redemption"
|
||||||
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
|
"github.com/perfect-panel/server/pkg/snowflake"
|
||||||
|
"github.com/perfect-panel/server/pkg/uuidx"
|
||||||
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RedeemCodeLogic struct {
|
||||||
|
logger.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redeem code
|
||||||
|
func NewRedeemCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RedeemCodeLogic {
|
||||||
|
return &RedeemCodeLogic{
|
||||||
|
Logger: logger.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *RedeemCodeLogic) RedeemCode(req *types.RedeemCodeRequest) (resp *types.RedeemCodeResponse, err error) {
|
||||||
|
// Get user from context
|
||||||
|
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||||
|
if !ok {
|
||||||
|
logger.Error("current user is not found in context")
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find redemption code by code
|
||||||
|
redemptionCode, err := l.svcCtx.RedemptionCodeModel.FindOneByCode(l.ctx, req.Code)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
l.Errorw("[RedeemCode] Redemption code not found", logger.Field("code", req.Code))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "redemption code not found")
|
||||||
|
}
|
||||||
|
l.Errorw("[RedeemCode] Database Error", logger.Field("error", err.Error()))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find redemption code error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if redemption code has remaining count
|
||||||
|
if redemptionCode.TotalCount > 0 && redemptionCode.UsedCount >= redemptionCode.TotalCount {
|
||||||
|
l.Errorw("[RedeemCode] Redemption code has been fully used",
|
||||||
|
logger.Field("code", req.Code),
|
||||||
|
logger.Field("total_count", redemptionCode.TotalCount),
|
||||||
|
logger.Field("used_count", redemptionCode.UsedCount))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "redemption code has been fully used")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has already redeemed this code
|
||||||
|
userRecords, err := l.svcCtx.RedemptionRecordModel.FindByUserId(l.ctx, u.Id)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[RedeemCode] Database Error", logger.Field("error", err.Error()))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find redemption records error: %v", err.Error())
|
||||||
|
}
|
||||||
|
for _, record := range userRecords {
|
||||||
|
if record.RedemptionCodeId == redemptionCode.Id {
|
||||||
|
l.Errorw("[RedeemCode] User has already redeemed this code",
|
||||||
|
logger.Field("user_id", u.Id),
|
||||||
|
logger.Field("code", req.Code))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "you have already redeemed this code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find subscribe plan from redemption code
|
||||||
|
subscribePlan, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, redemptionCode.SubscribePlan)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[RedeemCode] Subscribe plan not found",
|
||||||
|
logger.Field("subscribe_plan", redemptionCode.SubscribePlan),
|
||||||
|
logger.Field("error", err.Error()))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "subscribe plan not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if subscribe plan is available
|
||||||
|
if !*subscribePlan.Sell {
|
||||||
|
l.Errorw("[RedeemCode] Subscribe plan is not available",
|
||||||
|
logger.Field("subscribe_plan", redemptionCode.SubscribePlan))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNotAvailable), "subscribe plan is not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start transaction
|
||||||
|
err = l.svcCtx.RedemptionCodeModel.Transaction(l.ctx, func(tx *gorm.DB) error {
|
||||||
|
// Find user's existing subscribe for this plan
|
||||||
|
var existingSubscribe *user.SubscribeDetails
|
||||||
|
userSubscribes, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id, 0, 1)
|
||||||
|
if err == nil {
|
||||||
|
for _, us := range userSubscribes {
|
||||||
|
if us.SubscribeId == redemptionCode.SubscribePlan {
|
||||||
|
existingSubscribe = us
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
if existingSubscribe != nil {
|
||||||
|
// Extend existing subscribe
|
||||||
|
var newExpireTime time.Time
|
||||||
|
if existingSubscribe.ExpireTime.After(now) {
|
||||||
|
newExpireTime = existingSubscribe.ExpireTime
|
||||||
|
} else {
|
||||||
|
newExpireTime = now
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate duration based on redemption code
|
||||||
|
duration, err := calculateDuration(redemptionCode.UnitTime, redemptionCode.Quantity)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[RedeemCode] Calculate duration error", logger.Field("error", err.Error()))
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "calculate duration error: %v", err.Error())
|
||||||
|
}
|
||||||
|
newExpireTime = newExpireTime.Add(duration)
|
||||||
|
|
||||||
|
// Update subscribe
|
||||||
|
existingSubscribe.ExpireTime = newExpireTime
|
||||||
|
existingSubscribe.Status = 1
|
||||||
|
|
||||||
|
// Add traffic if needed
|
||||||
|
if subscribePlan.Traffic > 0 {
|
||||||
|
existingSubscribe.Traffic = subscribePlan.Traffic * 1024 * 1024 * 1024
|
||||||
|
existingSubscribe.Download = 0
|
||||||
|
existingSubscribe.Upload = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.svcCtx.UserModel.UpdateSubscribe(l.ctx, &user.Subscribe{
|
||||||
|
Id: existingSubscribe.Id,
|
||||||
|
UserId: existingSubscribe.UserId,
|
||||||
|
ExpireTime: existingSubscribe.ExpireTime,
|
||||||
|
Status: existingSubscribe.Status,
|
||||||
|
Traffic: existingSubscribe.Traffic,
|
||||||
|
Download: existingSubscribe.Download,
|
||||||
|
Upload: existingSubscribe.Upload,
|
||||||
|
}, tx)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[RedeemCode] Update subscribe error", logger.Field("error", err.Error()))
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update subscribe error: %v", err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create new subscribe
|
||||||
|
expireTime, traffic, err := calculateSubscribeTimeAndTraffic(redemptionCode.UnitTime, redemptionCode.Quantity, subscribePlan.Traffic)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[RedeemCode] Calculate subscribe time and traffic error", logger.Field("error", err.Error()))
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "calculate subscribe time and traffic error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
newSubscribe := &user.Subscribe{
|
||||||
|
Id: snowflake.GetID(),
|
||||||
|
UserId: u.Id,
|
||||||
|
OrderId: 0,
|
||||||
|
SubscribeId: redemptionCode.SubscribePlan,
|
||||||
|
StartTime: now,
|
||||||
|
ExpireTime: expireTime,
|
||||||
|
FinishedAt: nil,
|
||||||
|
Traffic: traffic,
|
||||||
|
Download: 0,
|
||||||
|
Upload: 0,
|
||||||
|
Token: uuidx.SubscribeToken(fmt.Sprintf("redemption:%d:%d", u.Id, time.Now().UnixMilli())),
|
||||||
|
UUID: uuid.New().String(),
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.svcCtx.UserModel.InsertSubscribe(l.ctx, newSubscribe, tx)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[RedeemCode] Insert subscribe error", logger.Field("error", err.Error()))
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert subscribe error: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment redemption code used count
|
||||||
|
err = l.svcCtx.RedemptionCodeModel.IncrementUsedCount(l.ctx, redemptionCode.Id)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[RedeemCode] Increment used count error", logger.Field("error", err.Error()))
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "increment used count error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create redemption record
|
||||||
|
redemptionRecord := &redemption.RedemptionRecord{
|
||||||
|
Id: snowflake.GetID(),
|
||||||
|
RedemptionCodeId: redemptionCode.Id,
|
||||||
|
UserId: u.Id,
|
||||||
|
SubscribeId: redemptionCode.SubscribePlan,
|
||||||
|
UnitTime: redemptionCode.UnitTime,
|
||||||
|
Quantity: redemptionCode.Quantity,
|
||||||
|
RedeemedAt: now,
|
||||||
|
CreatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.svcCtx.RedemptionRecordModel.Insert(l.ctx, redemptionRecord)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("[RedeemCode] Insert redemption record error", logger.Field("error", err.Error()))
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert redemption record error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.RedeemCodeResponse{
|
||||||
|
Message: "Redemption successful",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateDuration calculates time duration based on unit time
|
||||||
|
func calculateDuration(unitTime string, quantity int64) (time.Duration, error) {
|
||||||
|
switch unitTime {
|
||||||
|
case "month":
|
||||||
|
return time.Duration(quantity*30*24) * time.Hour, nil
|
||||||
|
case "quarter":
|
||||||
|
return time.Duration(quantity*90*24) * time.Hour, nil
|
||||||
|
case "half_year":
|
||||||
|
return time.Duration(quantity*180*24) * time.Hour, nil
|
||||||
|
case "year":
|
||||||
|
return time.Duration(quantity*365*24) * time.Hour, nil
|
||||||
|
case "day":
|
||||||
|
return time.Duration(quantity*24) * time.Hour, nil
|
||||||
|
default:
|
||||||
|
return time.Duration(quantity*30*24) * time.Hour, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateSubscribeTimeAndTraffic calculates expire time and traffic based on subscribe plan
|
||||||
|
func calculateSubscribeTimeAndTraffic(unitTime string, quantity int64, traffic int64) (time.Time, int64, error) {
|
||||||
|
duration, err := calculateDuration(unitTime, quantity)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expireTime := time.Now().Add(duration)
|
||||||
|
trafficBytes := int64(0)
|
||||||
|
if traffic > 0 {
|
||||||
|
trafficBytes = traffic * 1024 * 1024 * 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
return expireTime, trafficBytes, nil
|
||||||
|
}
|
||||||
279
internal/model/redemption/default.go
Normal file
279
internal/model/redemption/default.go
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/pkg/cache"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RedemptionCodeModel = (*customRedemptionCodeModel)(nil)
|
||||||
|
var _ RedemptionRecordModel = (*customRedemptionRecordModel)(nil)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cacheRedemptionCodeIdPrefix = "cache:redemption_code:id:"
|
||||||
|
cacheRedemptionCodeCodePrefix = "cache:redemption_code:code:"
|
||||||
|
cacheRedemptionRecordIdPrefix = "cache:redemption_record:id:"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
RedemptionCodeModel interface {
|
||||||
|
Insert(ctx context.Context, data *RedemptionCode) error
|
||||||
|
FindOne(ctx context.Context, id int64) (*RedemptionCode, error)
|
||||||
|
FindOneByCode(ctx context.Context, code string) (*RedemptionCode, error)
|
||||||
|
Update(ctx context.Context, data *RedemptionCode) error
|
||||||
|
Delete(ctx context.Context, id int64) error
|
||||||
|
Transaction(ctx context.Context, fn func(db *gorm.DB) error) error
|
||||||
|
customRedemptionCodeLogicModel
|
||||||
|
}
|
||||||
|
|
||||||
|
RedemptionRecordModel interface {
|
||||||
|
Insert(ctx context.Context, data *RedemptionRecord) error
|
||||||
|
FindOne(ctx context.Context, id int64) (*RedemptionRecord, error)
|
||||||
|
Update(ctx context.Context, data *RedemptionRecord) error
|
||||||
|
Delete(ctx context.Context, id int64) error
|
||||||
|
customRedemptionRecordLogicModel
|
||||||
|
}
|
||||||
|
|
||||||
|
customRedemptionCodeLogicModel interface {
|
||||||
|
QueryRedemptionCodeListByPage(ctx context.Context, page, size int, subscribePlan int64, unitTime string, code string) (total int64, list []*RedemptionCode, err error)
|
||||||
|
BatchDelete(ctx context.Context, ids []int64) error
|
||||||
|
IncrementUsedCount(ctx context.Context, id int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
customRedemptionRecordLogicModel interface {
|
||||||
|
QueryRedemptionRecordListByPage(ctx context.Context, page, size int, userId int64, codeId int64) (total int64, list []*RedemptionRecord, err error)
|
||||||
|
FindByUserId(ctx context.Context, userId int64) ([]*RedemptionRecord, error)
|
||||||
|
FindByCodeId(ctx context.Context, codeId int64) ([]*RedemptionRecord, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
customRedemptionCodeModel struct {
|
||||||
|
*defaultRedemptionCodeModel
|
||||||
|
}
|
||||||
|
defaultRedemptionCodeModel struct {
|
||||||
|
cache.CachedConn
|
||||||
|
table string
|
||||||
|
}
|
||||||
|
|
||||||
|
customRedemptionRecordModel struct {
|
||||||
|
*defaultRedemptionRecordModel
|
||||||
|
}
|
||||||
|
defaultRedemptionRecordModel struct {
|
||||||
|
cache.CachedConn
|
||||||
|
table string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRedemptionCodeModel(db *gorm.DB, c *redis.Client) *defaultRedemptionCodeModel {
|
||||||
|
return &defaultRedemptionCodeModel{
|
||||||
|
CachedConn: cache.NewConn(db, c),
|
||||||
|
table: "`redemption_code`",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRedemptionRecordModel(db *gorm.DB, c *redis.Client) *defaultRedemptionRecordModel {
|
||||||
|
return &defaultRedemptionRecordModel{
|
||||||
|
CachedConn: cache.NewConn(db, c),
|
||||||
|
table: "`redemption_record`",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedemptionCode cache methods
|
||||||
|
func (m *defaultRedemptionCodeModel) getCacheKeys(data *RedemptionCode) []string {
|
||||||
|
if data == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
codeIdKey := fmt.Sprintf("%s%v", cacheRedemptionCodeIdPrefix, data.Id)
|
||||||
|
codeCodeKey := fmt.Sprintf("%s%v", cacheRedemptionCodeCodePrefix, data.Code)
|
||||||
|
cacheKeys := []string{
|
||||||
|
codeIdKey,
|
||||||
|
codeCodeKey,
|
||||||
|
}
|
||||||
|
return cacheKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionCodeModel) Insert(ctx context.Context, data *RedemptionCode) error {
|
||||||
|
err := m.ExecCtx(ctx, func(conn *gorm.DB) error {
|
||||||
|
return conn.Create(data).Error
|
||||||
|
}, m.getCacheKeys(data)...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionCodeModel) FindOne(ctx context.Context, id int64) (*RedemptionCode, error) {
|
||||||
|
codeIdKey := fmt.Sprintf("%s%v", cacheRedemptionCodeIdPrefix, id)
|
||||||
|
var resp RedemptionCode
|
||||||
|
err := m.QueryCtx(ctx, &resp, codeIdKey, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
return conn.Model(&RedemptionCode{}).Where("`id` = ?", id).First(&resp).Error
|
||||||
|
})
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return &resp, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionCodeModel) FindOneByCode(ctx context.Context, code string) (*RedemptionCode, error) {
|
||||||
|
codeCodeKey := fmt.Sprintf("%s%v", cacheRedemptionCodeCodePrefix, code)
|
||||||
|
var resp RedemptionCode
|
||||||
|
err := m.QueryCtx(ctx, &resp, codeCodeKey, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
return conn.Model(&RedemptionCode{}).Where("`code` = ?", code).First(&resp).Error
|
||||||
|
})
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return &resp, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionCodeModel) Update(ctx context.Context, data *RedemptionCode) error {
|
||||||
|
old, err := m.FindOne(ctx, data.Id)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = m.ExecCtx(ctx, func(conn *gorm.DB) error {
|
||||||
|
db := conn
|
||||||
|
return db.Save(data).Error
|
||||||
|
}, m.getCacheKeys(old)...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionCodeModel) Delete(ctx context.Context, id int64) error {
|
||||||
|
data, err := m.FindOne(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = m.ExecCtx(ctx, func(conn *gorm.DB) error {
|
||||||
|
db := conn
|
||||||
|
return db.Delete(&RedemptionCode{}, id).Error
|
||||||
|
}, m.getCacheKeys(data)...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionCodeModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error {
|
||||||
|
return m.TransactCtx(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedemptionCode custom logic methods
|
||||||
|
func (m *customRedemptionCodeModel) QueryRedemptionCodeListByPage(ctx context.Context, page, size int, subscribePlan int64, unitTime string, code string) (total int64, list []*RedemptionCode, err error) {
|
||||||
|
err = m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
db := conn.Model(&RedemptionCode{})
|
||||||
|
if subscribePlan != 0 {
|
||||||
|
db = db.Where("subscribe_plan = ?", subscribePlan)
|
||||||
|
}
|
||||||
|
if unitTime != "" {
|
||||||
|
db = db.Where("unit_time = ?", unitTime)
|
||||||
|
}
|
||||||
|
if code != "" {
|
||||||
|
db = db.Where("code like ?", "%"+code+"%")
|
||||||
|
}
|
||||||
|
return db.Count(&total).Limit(size).Offset((page - 1) * size).Order("created_at DESC").Find(v).Error
|
||||||
|
})
|
||||||
|
return total, list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *customRedemptionCodeModel) BatchDelete(ctx context.Context, ids []int64) error {
|
||||||
|
var err error
|
||||||
|
for _, id := range ids {
|
||||||
|
if err = m.Delete(ctx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *customRedemptionCodeModel) IncrementUsedCount(ctx context.Context, id int64) error {
|
||||||
|
data, err := m.FindOne(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data.UsedCount++
|
||||||
|
return m.Update(ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedemptionRecord cache methods
|
||||||
|
func (m *defaultRedemptionRecordModel) getCacheKeys(data *RedemptionRecord) []string {
|
||||||
|
if data == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
recordIdKey := fmt.Sprintf("%s%v", cacheRedemptionRecordIdPrefix, data.Id)
|
||||||
|
cacheKeys := []string{
|
||||||
|
recordIdKey,
|
||||||
|
}
|
||||||
|
return cacheKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionRecordModel) Insert(ctx context.Context, data *RedemptionRecord) error {
|
||||||
|
err := m.ExecCtx(ctx, func(conn *gorm.DB) error {
|
||||||
|
return conn.Create(data).Error
|
||||||
|
}, m.getCacheKeys(data)...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionRecordModel) FindOne(ctx context.Context, id int64) (*RedemptionRecord, error) {
|
||||||
|
recordIdKey := fmt.Sprintf("%s%v", cacheRedemptionRecordIdPrefix, id)
|
||||||
|
var resp RedemptionRecord
|
||||||
|
err := m.QueryCtx(ctx, &resp, recordIdKey, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
return conn.Model(&RedemptionRecord{}).Where("`id` = ?", id).First(&resp).Error
|
||||||
|
})
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return &resp, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionRecordModel) Update(ctx context.Context, data *RedemptionRecord) error {
|
||||||
|
err := m.ExecCtx(ctx, func(conn *gorm.DB) error {
|
||||||
|
db := conn
|
||||||
|
return db.Save(data).Error
|
||||||
|
}, m.getCacheKeys(data)...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *defaultRedemptionRecordModel) Delete(ctx context.Context, id int64) error {
|
||||||
|
err := m.ExecCtx(ctx, func(conn *gorm.DB) error {
|
||||||
|
db := conn
|
||||||
|
return db.Delete(&RedemptionRecord{}, id).Error
|
||||||
|
}, m.getCacheKeys(nil)...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedemptionRecord custom logic methods
|
||||||
|
func (m *customRedemptionRecordModel) QueryRedemptionRecordListByPage(ctx context.Context, page, size int, userId int64, codeId int64) (total int64, list []*RedemptionRecord, err error) {
|
||||||
|
err = m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
db := conn.Model(&RedemptionRecord{})
|
||||||
|
if userId != 0 {
|
||||||
|
db = db.Where("user_id = ?", userId)
|
||||||
|
}
|
||||||
|
if codeId != 0 {
|
||||||
|
db = db.Where("redemption_code_id = ?", codeId)
|
||||||
|
}
|
||||||
|
return db.Count(&total).Limit(size).Offset((page - 1) * size).Order("created_at DESC").Find(v).Error
|
||||||
|
})
|
||||||
|
return total, list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *customRedemptionRecordModel) FindByUserId(ctx context.Context, userId int64) ([]*RedemptionRecord, error) {
|
||||||
|
var list []*RedemptionRecord
|
||||||
|
err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
return conn.Model(&RedemptionRecord{}).Where("user_id = ?", userId).Order("created_at DESC").Find(v).Error
|
||||||
|
})
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *customRedemptionRecordModel) FindByCodeId(ctx context.Context, codeId int64) ([]*RedemptionRecord, error) {
|
||||||
|
var list []*RedemptionRecord
|
||||||
|
err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
return conn.Model(&RedemptionRecord{}).Where("redemption_code_id = ?", codeId).Order("created_at DESC").Find(v).Error
|
||||||
|
})
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
20
internal/model/redemption/model.go
Normal file
20
internal/model/redemption/model.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRedemptionCodeModel returns a model for the redemption_code table.
|
||||||
|
func NewRedemptionCodeModel(conn *gorm.DB, c *redis.Client) RedemptionCodeModel {
|
||||||
|
return &customRedemptionCodeModel{
|
||||||
|
defaultRedemptionCodeModel: newRedemptionCodeModel(conn, c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRedemptionRecordModel returns a model for the redemption_record table.
|
||||||
|
func NewRedemptionRecordModel(conn *gorm.DB, c *redis.Client) RedemptionRecordModel {
|
||||||
|
return &customRedemptionRecordModel{
|
||||||
|
defaultRedemptionRecordModel: newRedemptionRecordModel(conn, c),
|
||||||
|
}
|
||||||
|
}
|
||||||
39
internal/model/redemption/redemption.go
Normal file
39
internal/model/redemption/redemption.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package redemption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RedemptionCode struct {
|
||||||
|
Id int64 `gorm:"primaryKey"`
|
||||||
|
Code string `gorm:"type:varchar(255);not null;unique;comment:Redemption Code"`
|
||||||
|
TotalCount int64 `gorm:"type:int;not null;default:0;comment:Total Redemption Count"`
|
||||||
|
UsedCount int64 `gorm:"type:int;not null;default:0;comment:Used Redemption Count"`
|
||||||
|
SubscribePlan int64 `gorm:"type:bigint;not null;default:0;comment:Subscribe Plan"`
|
||||||
|
UnitTime string `gorm:"type:varchar(50);not null;default:'month';comment:Unit Time: day, month, quarter, half_year, year"`
|
||||||
|
Quantity int64 `gorm:"type:int;not null;default:1;comment:Quantity"`
|
||||||
|
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
|
||||||
|
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index;comment:Delete Time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedemptionRecord struct {
|
||||||
|
Id int64 `gorm:"primaryKey"`
|
||||||
|
RedemptionCodeId int64 `gorm:"type:bigint;not null;default:0;comment:Redemption Code Id;index"`
|
||||||
|
UserId int64 `gorm:"type:bigint;not null;default:0;comment:User Id;index"`
|
||||||
|
SubscribeId int64 `gorm:"type:bigint;not null;default:0;comment:Subscribe Id"`
|
||||||
|
UnitTime string `gorm:"type:varchar(50);not null;default:'month';comment:Unit Time"`
|
||||||
|
Quantity int64 `gorm:"type:int;not null;default:1;comment:Quantity"`
|
||||||
|
RedeemedAt time.Time `gorm:"<-:create;comment:Redeemed Time"`
|
||||||
|
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RedemptionCode) TableName() string {
|
||||||
|
return "redemption_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RedemptionRecord) TableName() string {
|
||||||
|
return "redemption_record"
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/client"
|
"github.com/perfect-panel/server/internal/model/client"
|
||||||
"github.com/perfect-panel/server/internal/model/node"
|
"github.com/perfect-panel/server/internal/model/node"
|
||||||
|
"github.com/perfect-panel/server/internal/model/redemption"
|
||||||
"github.com/perfect-panel/server/pkg/device"
|
"github.com/perfect-panel/server/pkg/device"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
"github.com/perfect-panel/server/internal/config"
|
||||||
@ -49,13 +50,15 @@ type ServiceContext struct {
|
|||||||
ClientModel client.Model
|
ClientModel client.Model
|
||||||
TicketModel ticket.Model
|
TicketModel ticket.Model
|
||||||
//ServerModel server.Model
|
//ServerModel server.Model
|
||||||
SystemModel system.Model
|
SystemModel system.Model
|
||||||
CouponModel coupon.Model
|
CouponModel coupon.Model
|
||||||
PaymentModel payment.Model
|
RedemptionCodeModel redemption.RedemptionCodeModel
|
||||||
DocumentModel document.Model
|
RedemptionRecordModel redemption.RedemptionRecordModel
|
||||||
SubscribeModel subscribe.Model
|
PaymentModel payment.Model
|
||||||
TrafficLogModel traffic.Model
|
DocumentModel document.Model
|
||||||
AnnouncementModel announcement.Model
|
SubscribeModel subscribe.Model
|
||||||
|
TrafficLogModel traffic.Model
|
||||||
|
AnnouncementModel announcement.Model
|
||||||
|
|
||||||
Restart func() error
|
Restart func() error
|
||||||
TelegramBot *tgbotapi.BotAPI
|
TelegramBot *tgbotapi.BotAPI
|
||||||
@ -110,13 +113,15 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||||||
ClientModel: client.NewSubscribeApplicationModel(db),
|
ClientModel: client.NewSubscribeApplicationModel(db),
|
||||||
TicketModel: ticket.NewModel(db, rds),
|
TicketModel: ticket.NewModel(db, rds),
|
||||||
//ServerModel: server.NewModel(db, rds),
|
//ServerModel: server.NewModel(db, rds),
|
||||||
SystemModel: system.NewModel(db, rds),
|
SystemModel: system.NewModel(db, rds),
|
||||||
CouponModel: coupon.NewModel(db, rds),
|
CouponModel: coupon.NewModel(db, rds),
|
||||||
PaymentModel: payment.NewModel(db, rds),
|
RedemptionCodeModel: redemption.NewRedemptionCodeModel(db, rds),
|
||||||
DocumentModel: document.NewModel(db, rds),
|
RedemptionRecordModel: redemption.NewRedemptionRecordModel(db, rds),
|
||||||
SubscribeModel: subscribe.NewModel(db, rds),
|
PaymentModel: payment.NewModel(db, rds),
|
||||||
TrafficLogModel: traffic.NewModel(db),
|
DocumentModel: document.NewModel(db, rds),
|
||||||
AnnouncementModel: announcement.NewModel(db, rds),
|
SubscribeModel: subscribe.NewModel(db, rds),
|
||||||
|
TrafficLogModel: traffic.NewModel(db),
|
||||||
|
AnnouncementModel: announcement.NewModel(db, rds),
|
||||||
}
|
}
|
||||||
srv.DeviceManager = NewDeviceManager(srv)
|
srv.DeviceManager = NewDeviceManager(srv)
|
||||||
return srv
|
return srv
|
||||||
|
|||||||
@ -146,6 +146,10 @@ type BatchDeleteDocumentRequest struct {
|
|||||||
Ids []int64 `json:"ids" validate:"required"`
|
Ids []int64 `json:"ids" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BatchDeleteRedemptionCodeRequest struct {
|
||||||
|
Ids []int64 `json:"ids" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type BatchDeleteSubscribeGroupRequest struct {
|
type BatchDeleteSubscribeGroupRequest struct {
|
||||||
Ids []int64 `json:"ids" validate:"required"`
|
Ids []int64 `json:"ids" validate:"required"`
|
||||||
}
|
}
|
||||||
@ -367,6 +371,14 @@ type CreateQuotaTaskRequest struct {
|
|||||||
GiftValue uint64 `json:"gift_value"`
|
GiftValue uint64 `json:"gift_value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateRedemptionCodeRequest struct {
|
||||||
|
TotalCount int64 `json:"total_count" validate:"required"`
|
||||||
|
SubscribePlan int64 `json:"subscribe_plan" validate:"required"`
|
||||||
|
UnitTime string `json:"unit_time" validate:"required,oneof=day month quarter half_year year"`
|
||||||
|
Quantity int64 `json:"quantity" validate:"required"`
|
||||||
|
BatchCount int64 `json:"batch_count" validate:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateServerRequest struct {
|
type CreateServerRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Country string `json:"country,omitempty"`
|
Country string `json:"country,omitempty"`
|
||||||
@ -501,6 +513,10 @@ type DeletePaymentMethodRequest struct {
|
|||||||
Id int64 `json:"id" validate:"required"`
|
Id int64 `json:"id" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteRedemptionCodeRequest struct {
|
||||||
|
Id int64 `json:"id" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteServerRequest struct {
|
type DeleteServerRequest struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
}
|
}
|
||||||
@ -934,6 +950,31 @@ type GetPreSendEmailCountResponse struct {
|
|||||||
Count int64 `json:"count"`
|
Count int64 `json:"count"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetRedemptionCodeListRequest struct {
|
||||||
|
Page int64 `form:"page" validate:"required"`
|
||||||
|
Size int64 `form:"size" validate:"required"`
|
||||||
|
SubscribePlan int64 `form:"subscribe_plan,omitempty"`
|
||||||
|
UnitTime string `form:"unit_time,omitempty"`
|
||||||
|
Code string `form:"code,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetRedemptionCodeListResponse struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
List []RedemptionCode `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetRedemptionRecordListRequest struct {
|
||||||
|
Page int64 `form:"page" validate:"required"`
|
||||||
|
Size int64 `form:"size" validate:"required"`
|
||||||
|
UserId int64 `form:"user_id,omitempty"`
|
||||||
|
CodeId int64 `form:"code_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetRedemptionRecordListResponse struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
List []RedemptionRecord `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
type GetServerConfigRequest struct {
|
type GetServerConfigRequest struct {
|
||||||
ServerCommon
|
ServerCommon
|
||||||
}
|
}
|
||||||
@ -1779,6 +1820,37 @@ type RechargeOrderResponse struct {
|
|||||||
OrderNo string `json:"order_no"`
|
OrderNo string `json:"order_no"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RedeemCodeRequest struct {
|
||||||
|
Code string `json:"code" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedeemCodeResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedemptionCode struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
UsedCount int64 `json:"used_count"`
|
||||||
|
SubscribePlan int64 `json:"subscribe_plan"`
|
||||||
|
UnitTime string `json:"unit_time"`
|
||||||
|
Quantity int64 `json:"quantity"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedemptionRecord struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
RedemptionCodeId int64 `json:"redemption_code_id"`
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
|
UnitTime string `json:"unit_time"`
|
||||||
|
Quantity int64 `json:"quantity"`
|
||||||
|
RedeemedAt int64 `json:"redeemed_at"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type RegisterConfig struct {
|
type RegisterConfig struct {
|
||||||
StopRegister bool `json:"stop_register"`
|
StopRegister bool `json:"stop_register"`
|
||||||
EnableTrial bool `json:"enable_trial"`
|
EnableTrial bool `json:"enable_trial"`
|
||||||
@ -2416,6 +2488,14 @@ type UpdatePaymentMethodRequest struct {
|
|||||||
Enable *bool `json:"enable" validate:"required"`
|
Enable *bool `json:"enable" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateRedemptionCodeRequest struct {
|
||||||
|
Id int64 `json:"id" validate:"required"`
|
||||||
|
TotalCount int64 `json:"total_count,omitempty"`
|
||||||
|
SubscribePlan int64 `json:"subscribe_plan,omitempty"`
|
||||||
|
UnitTime string `json:"unit_time,omitempty" validate:"omitempty,oneof=day month quarter half_year year"`
|
||||||
|
Quantity int64 `json:"quantity,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateServerRequest struct {
|
type UpdateServerRequest struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"apis/admin/subscribe.api"
|
"apis/admin/subscribe.api"
|
||||||
"apis/admin/payment.api"
|
"apis/admin/payment.api"
|
||||||
"apis/admin/coupon.api"
|
"apis/admin/coupon.api"
|
||||||
|
"apis/admin/redemption.api"
|
||||||
"apis/admin/order.api"
|
"apis/admin/order.api"
|
||||||
"apis/admin/ticket.api"
|
"apis/admin/ticket.api"
|
||||||
"apis/admin/announcement.api"
|
"apis/admin/announcement.api"
|
||||||
@ -31,6 +32,7 @@ import (
|
|||||||
"apis/admin/application.api"
|
"apis/admin/application.api"
|
||||||
"apis/public/user.api"
|
"apis/public/user.api"
|
||||||
"apis/public/subscribe.api"
|
"apis/public/subscribe.api"
|
||||||
|
"apis/public/redemption.api"
|
||||||
"apis/public/order.api"
|
"apis/public/order.api"
|
||||||
"apis/public/announcement.api"
|
"apis/public/announcement.api"
|
||||||
"apis/public/ticket.api"
|
"apis/public/ticket.api"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user