* fix(database): correct name entry for SingBox in initialization script

* fix(purchase): update gift amount deduction logic and handle zero-amount order status

* feat: add type and default fields to rule group requests and update related logic

* feat(rule): implement logic to set a default rule group during creation and update

* fix(rule): add type and default fields to rule group model and update related logic

* feat(proxy): enhance proxy group handling and sorting logic

* refactor(proxy): replace hardcoded group names with constants for better maintainability

* fix(proxy): update group selection logic to skip empty and default names

* feat(proxy): enhance proxy and group handling with new configuration options

* feat(surge): add Surge adapter support and enhance subscription URL handling

* feat(traffic): implement traffic reset logic for subscription cycles

* feat(auth): improve email and mobile config unmarshalling with default values

* fix(auth) upbind email not update

* fix(order) discount set default 1

* fix(order) discount set default 1

* fix: refactor surfboard proxy handling and enhance configuration template

* fix(renewal) discount set default 1

* feat(loon): add Loon configuration template and enhance proxy handling

* feat(subscription): update user subscription status based on expiration time

* fix(renewal): update subscription retrieval method to use token instead of order ID

* feat(order): enhance order processing logic with improved error handling and user subscription management

* fix(order): improve code quality and fix critical bugs in order processing logic

- Fix inconsistent logging calls across all order logic files
- Fix critical gift amount deduction logic bug in renewal process
- Fix variable shadowing errors in database transactions
- Add comprehensive Go-standard documentation comments
- Improve log prefix consistency for better debugging
- Remove redundant discount validation code

* fix(docker): add build argument for version in Docker image build process

* feat(version): add endpoint to retrieve application version information

* fix(auth): improve user authentication method logic and update user cache

* feat(user): add ordering functionality to user list retrieval

* fix(RevenueStatistics) fill list

* fix(UserStatistics) fill list

* fix(user): implement user cache clearing after auth method operations

* fix(auth): enhance OAuth login logic with improved request handling and user registration flow

* fix(user): implement sorting for authentication methods based on priority

* fix(user): correct ordering clause for user retrieval based on filter

* refactor(user): streamline cache management and enhance cache clearing logic

* feat(logs) set logs volume in develop

* fix(handler): implement browser interception to deny access for specific user agents

* fix(resetTraffic) reset daily server

* refactor(trojan): remove unused parameter and clean up logging in slice

* fix(middleware): add domain length check and improve user-agent handling

* fix(middleware): reorder domain processing and enhance user-agent handling

* fix(resetTraffic): update subscription reset logic to use expire_time for monthly and yearly checks

* fix(scheduler): update reset traffic task schedule to run daily at 00:30

* fix(traffic): enhance traffic reset logic for subscriptions and adjust status checks

* fix(activateOrder): update traffic reset logic to include reset day check

* feat(marketing): add batch email task management API and logic

* feat(application): implement CRUD operations for subscribe applications

* feat(types): add user agent limit and list to subscription configuration

* feat(application): update subscription application requests to include structured download links

* feat(application): add scheme field and download link handling to subscribe application

* feat(application): add endpoint to retrieve client information

* feat(application): move DownloadLink and SubscribeApplication types to types.api

* feat(application): add DownloadLink and SubscribeClient types, update client response structure

* feat(application): remove ProxyTemplate field from application API

* feat(application): implement adapter for client configuration and add preview template functionality

* feat(application): move DownloadLink type to types.api and remove from common.api

* feat(application): update PreviewSubscribeTemplate to return structured response

* feat(application): remove ProxyTemplate field from application API

* feat(application): enhance cache key generation for user list and server data

* feat(subscribe): add ClearCache method to manage subscription cache invalidation

* feat(payment): add Description field to PaymentMethodDetail response

* feat(subscribe): update next reset time calculation to use ExpireTime

* feat(purchase): include handling fee in total amount calculation

* feat(subscribe): add V2SubscribeHandler and logic for enhanced subscription management

* feat(subscribe): add output format configuration to subscription adapter

* feat(application): default data

---------

Co-authored-by: Chang lue Tsen <tension@ppanel.dev>
Co-authored-by: NoWay <Bob455668@hotmail.com>
This commit is contained in:
Leif Draven
2025-08-16 01:30:21 +09:00
committed by GitHub
parent c8de30f78c
commit 41d660bb9e
89 changed files with 4542 additions and 523 deletions
@@ -0,0 +1,26 @@
package application
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/application"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Create subscribe application
func CreateSubscribeApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.CreateSubscribeApplicationRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := application.NewCreateSubscribeApplicationLogic(c.Request.Context(), svcCtx)
resp, err := l.CreateSubscribeApplication(&req)
result.HttpResult(c, resp, err)
}
}
@@ -0,0 +1,26 @@
package application
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/application"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Delete subscribe application
func DeleteSubscribeApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.DeleteSubscribeApplicationRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := application.NewDeleteSubscribeApplicationLogic(c.Request.Context(), svcCtx)
err := l.DeleteSubscribeApplication(&req)
result.HttpResult(c, nil, err)
}
}
@@ -0,0 +1,26 @@
package application
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/application"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Get subscribe application list
func GetSubscribeApplicationListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.GetSubscribeApplicationListRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := application.NewGetSubscribeApplicationListLogic(c.Request.Context(), svcCtx)
resp, err := l.GetSubscribeApplicationList(&req)
result.HttpResult(c, resp, err)
}
}
@@ -0,0 +1,27 @@
package application
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/application"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Preview Template
func PreviewSubscribeTemplateHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.PreviewSubscribeTemplateRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := application.NewPreviewSubscribeTemplateLogic(c.Request.Context(), svcCtx)
resp, err := l.PreviewSubscribeTemplate(&req)
result.HttpResult(c, resp, err)
}
}
@@ -0,0 +1,26 @@
package application
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/application"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Update subscribe application
func UpdateSubscribeApplicationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.UpdateSubscribeApplicationRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := application.NewUpdateSubscribeApplicationLogic(c.Request.Context(), svcCtx)
resp, err := l.UpdateSubscribeApplication(&req)
result.HttpResult(c, resp, err)
}
}
@@ -0,0 +1,26 @@
package marketing
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/marketing"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Create a batch send email task
func CreateBatchSendEmailTaskHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.CreateBatchSendEmailTaskRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := marketing.NewCreateBatchSendEmailTaskLogic(c.Request.Context(), svcCtx)
err := l.CreateBatchSendEmailTask(&req)
result.HttpResult(c, nil, err)
}
}
@@ -0,0 +1,26 @@
package marketing
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/marketing"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Get batch send email task list
func GetBatchSendEmailTaskListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.GetBatchSendEmailTaskListRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := marketing.NewGetBatchSendEmailTaskListLogic(c.Request.Context(), svcCtx)
resp, err := l.GetBatchSendEmailTaskList(&req)
result.HttpResult(c, resp, err)
}
}
@@ -0,0 +1,26 @@
package marketing
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/marketing"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Get batch send email task status
func GetBatchSendEmailTaskStatusHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.GetBatchSendEmailTaskStatusRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := marketing.NewGetBatchSendEmailTaskStatusLogic(c.Request.Context(), svcCtx)
resp, err := l.GetBatchSendEmailTaskStatus(&req)
result.HttpResult(c, resp, err)
}
}
@@ -0,0 +1,26 @@
package marketing
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/marketing"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// Get pre-send email count
func GetPreSendEmailCountHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.GetPreSendEmailCountRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := marketing.NewGetPreSendEmailCountLogic(c.Request.Context(), svcCtx)
resp, err := l.GetPreSendEmailCount(&req)
result.HttpResult(c, resp, err)
}
}
@@ -0,0 +1,26 @@
package marketing
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/marketing"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/result"
)
// StopBatchSendEmailTaskHandler Stop a batch send email task
func StopBatchSendEmailTaskHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.StopBatchSendEmailTaskRequest
_ = c.ShouldBind(&req)
validateErr := svcCtx.Validate(&req)
if validateErr != nil {
result.ParamErrorResult(c, validateErr)
return
}
l := marketing.NewStopBatchSendEmailTaskLogic(c.Request.Context(), svcCtx)
err := l.StopBatchSendEmailTask(&req)
result.HttpResult(c, nil, err)
}
}
@@ -0,0 +1,18 @@
package tool
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/admin/tool"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/result"
)
// GetVersionHandler Get Version
func GetVersionHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
l := tool.NewGetVersionLogic(c.Request.Context(), svcCtx)
resp, err := l.GetVersion()
result.HttpResult(c, resp, err)
}
}
@@ -0,0 +1,18 @@
package common
import (
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/common"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/result"
)
// Get Client
func GetClientHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
l := common.NewGetClientLogic(c.Request.Context(), svcCtx)
resp, err := l.GetClient()
result.HttpResult(c, resp, err)
}
}
+48
View File
@@ -7,11 +7,13 @@ import (
"github.com/gin-gonic/gin"
adminAds "github.com/perfect-panel/server/internal/handler/admin/ads"
adminAnnouncement "github.com/perfect-panel/server/internal/handler/admin/announcement"
adminApplication "github.com/perfect-panel/server/internal/handler/admin/application"
adminAuthMethod "github.com/perfect-panel/server/internal/handler/admin/authMethod"
adminConsole "github.com/perfect-panel/server/internal/handler/admin/console"
adminCoupon "github.com/perfect-panel/server/internal/handler/admin/coupon"
adminDocument "github.com/perfect-panel/server/internal/handler/admin/document"
adminLog "github.com/perfect-panel/server/internal/handler/admin/log"
adminMarketing "github.com/perfect-panel/server/internal/handler/admin/marketing"
adminOrder "github.com/perfect-panel/server/internal/handler/admin/order"
adminPayment "github.com/perfect-panel/server/internal/handler/admin/payment"
adminServer "github.com/perfect-panel/server/internal/handler/admin/server"
@@ -86,6 +88,26 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
adminAnnouncementGroupRouter.GET("/list", adminAnnouncement.GetAnnouncementListHandler(serverCtx))
}
adminApplicationGroupRouter := router.Group("/v1/admin/application")
adminApplicationGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{
// Create subscribe application
adminApplicationGroupRouter.POST("/", adminApplication.CreateSubscribeApplicationHandler(serverCtx))
// Preview Template
adminApplicationGroupRouter.GET("/preview", adminApplication.PreviewSubscribeTemplateHandler(serverCtx))
// Update subscribe application
adminApplicationGroupRouter.PUT("/subscribe_application", adminApplication.UpdateSubscribeApplicationHandler(serverCtx))
// Delete subscribe application
adminApplicationGroupRouter.DELETE("/subscribe_application", adminApplication.DeleteSubscribeApplicationHandler(serverCtx))
// Get subscribe application list
adminApplicationGroupRouter.GET("/subscribe_application_list", adminApplication.GetSubscribeApplicationListHandler(serverCtx))
}
adminAuthMethodGroupRouter := router.Group("/v1/admin/auth-method")
adminAuthMethodGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
@@ -180,6 +202,26 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
adminLogGroupRouter.GET("/message/list", adminLog.GetMessageLogListHandler(serverCtx))
}
adminMarketingGroupRouter := router.Group("/v1/admin/marketing")
adminMarketingGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
{
// Get batch send email task list
adminMarketingGroupRouter.GET("/email/batch/list", adminMarketing.GetBatchSendEmailTaskListHandler(serverCtx))
// Get pre-send email count
adminMarketingGroupRouter.POST("/email/batch/pre-send-count", adminMarketing.GetPreSendEmailCountHandler(serverCtx))
// Create a batch send email task
adminMarketingGroupRouter.POST("/email/batch/send", adminMarketing.CreateBatchSendEmailTaskHandler(serverCtx))
// Get batch send email task status
adminMarketingGroupRouter.POST("/email/batch/status", adminMarketing.GetBatchSendEmailTaskStatusHandler(serverCtx))
// Stop a batch send email task
adminMarketingGroupRouter.POST("/email/batch/stop", adminMarketing.StopBatchSendEmailTaskHandler(serverCtx))
}
adminOrderGroupRouter := router.Group("/v1/admin/order")
adminOrderGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
@@ -441,6 +483,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Restart System
adminToolGroupRouter.GET("/restart", adminTool.RestartSystemHandler(serverCtx))
// Get Version
adminToolGroupRouter.GET("/version", adminTool.GetVersionHandler(serverCtx))
}
adminUserGroupRouter := router.Group("/v1/admin/user")
@@ -720,6 +765,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
// Check verification code
commonGroupRouter.POST("/check_verification_code", common.CheckVerificationCodeHandler(serverCtx))
// Get Client
commonGroupRouter.GET("/client", common.GetClientHandler(serverCtx))
// Get verification code
commonGroupRouter.POST("/send_code", common.SendEmailCodeHandler(serverCtx))
+57
View File
@@ -1,6 +1,9 @@
package handler
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/perfect-panel/server/internal/logic/subscribe"
"github.com/perfect-panel/server/internal/svc"
@@ -17,6 +20,22 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
}
req.UA = c.Request.Header.Get("User-Agent")
req.Flag = c.Query("flag")
// intercept browser
ua := c.GetHeader("User-Agent")
if ua == "" {
c.String(http.StatusForbidden, "Access denied")
return
}
browserKeywords := []string{"chrome", "firefox", "safari", "edge", "opera", "micromessenger"}
for _, keyword := range browserKeywords {
lcUA := strings.ToLower(ua)
if strings.Contains(lcUA, keyword) {
c.String(http.StatusForbidden, "Access denied")
return
}
}
l := subscribe.NewSubscribeLogic(c, svcCtx)
resp, err := l.Generate(&req)
if err != nil {
@@ -27,10 +46,48 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
}
}
func V2SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
return func(c *gin.Context) {
var req types.SubscribeRequest
if c.Request.Header.Get("token") != "" {
req.Token = c.Request.Header.Get("token")
} else {
req.Token = c.Query("token")
}
req.UA = c.Request.Header.Get("User-Agent")
req.Flag = c.Query("flag")
// intercept browser
ua := c.GetHeader("User-Agent")
if ua == "" {
c.String(http.StatusForbidden, "Access denied")
return
}
browserKeywords := []string{"chrome", "firefox", "safari", "edge", "opera", "micromessenger"}
for _, keyword := range browserKeywords {
lcUA := strings.ToLower(ua)
if strings.Contains(lcUA, keyword) {
c.String(http.StatusForbidden, "Access denied")
return
}
}
l := subscribe.NewSubscribeLogic(c, svcCtx)
resp, err := l.V2(&req)
if err != nil {
return
}
c.Header("subscription-userinfo", resp.Header)
c.String(200, "%s", string(resp.Config))
}
}
func RegisterSubscribeHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
path := serverCtx.Config.Subscribe.SubscribePath
if path == "" {
path = "/api/subscribe"
}
router.GET(path, SubscribeHandler(serverCtx))
router.GET(path+"/v2", V2SubscribeHandler(serverCtx))
}