diff --git a/internal/config/config.go b/internal/config/config.go index a8b141f..2d6718b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -82,15 +82,16 @@ type SubscribeConfig struct { } type RegisterConfig struct { - StopRegister bool `yaml:"StopRegister" default:"false"` - EnableTrial bool `yaml:"EnableTrial" default:"false"` - TrialSubscribe int64 `yaml:"TrialSubscribe" default:"0"` - TrialTime int64 `yaml:"TrialTime" default:"0"` - TrialTimeUnit string `yaml:"TrialTimeUnit" default:""` + StopRegister bool `yaml:"StopRegister" default:"false"` + EnableTrial bool `yaml:"EnableTrial" default:"false"` + EnableTrialEmailWhitelist bool `yaml:"EnableTrialEmailWhitelist" default:"true"` + TrialSubscribe int64 `yaml:"TrialSubscribe" default:"0"` + TrialTime int64 `yaml:"TrialTime" default:"0"` + TrialTimeUnit string `yaml:"TrialTimeUnit" default:""` TrialEmailDomainWhitelist string `yaml:"TrialEmailDomainWhitelist" default:""` - IpRegisterLimit int64 `yaml:"IpRegisterLimit" default:"0"` - IpRegisterLimitDuration int64 `yaml:"IpRegisterLimitDuration" default:"0"` - EnableIpRegisterLimit bool `yaml:"EnableIpRegisterLimit" default:"false"` + IpRegisterLimit int64 `yaml:"IpRegisterLimit" default:"0"` + IpRegisterLimitDuration int64 `yaml:"IpRegisterLimitDuration" default:"0"` + EnableIpRegisterLimit bool `yaml:"EnableIpRegisterLimit" default:"false"` DeviceLimit int64 `yaml:"DeviceLimit" default:"2"` } @@ -102,12 +103,12 @@ type EmailConfig struct { EnableNotify bool `yaml:"enable_notify"` EnableDomainSuffix bool `yaml:"enable_domain_suffix"` DomainSuffixList string `yaml:"domain_suffix_list"` - VerifyEmailTemplate string `yaml:"verify_email_template"` - VerifyEmailTemplates map[string]string `yaml:"verify_email_templates"` - ExpirationEmailTemplate string `yaml:"expiration_email_template"` - MaintenanceEmailTemplate string `yaml:"maintenance_email_template"` - TrafficExceedEmailTemplate string `yaml:"traffic_exceed_email_template"` - DeleteAccountEmailTemplate string `yaml:"delete_account_email_template"` + VerifyEmailTemplate string `yaml:"verify_email_template"` + VerifyEmailTemplates map[string]string `yaml:"verify_email_templates"` + ExpirationEmailTemplate string `yaml:"expiration_email_template"` + MaintenanceEmailTemplate string `yaml:"maintenance_email_template"` + TrafficExceedEmailTemplate string `yaml:"traffic_exceed_email_template"` + DeleteAccountEmailTemplate string `yaml:"delete_account_email_template"` } type MobileConfig struct { diff --git a/internal/logic/auth/emailLoginLogic.go b/internal/logic/auth/emailLoginLogic.go index a875709..e6034cd 100644 --- a/internal/logic/auth/emailLoginLogic.go +++ b/internal/logic/auth/emailLoginLogic.go @@ -126,7 +126,7 @@ func (l *EmailLoginLogic) EmailLogin(req *types.EmailLoginRequest) (resp *types. return err } rc := l.svcCtx.Config.Register - if rc.EnableTrial && rc.TrialEmailDomainWhitelist != "" && IsEmailDomainWhitelisted(req.Email, rc.TrialEmailDomainWhitelist) { + if ShouldGrantTrialForEmail(rc, req.Email) { if err = l.activeTrial(userInfo.Id); err != nil { return err } diff --git a/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go b/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go index f8182db..c05220d 100644 --- a/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go +++ b/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go @@ -8,6 +8,7 @@ import ( "time" "github.com/perfect-panel/server/internal/config" + authlogic "github.com/perfect-panel/server/internal/logic/auth" "github.com/perfect-panel/server/internal/model/auth" "github.com/perfect-panel/server/internal/model/log" "github.com/perfect-panel/server/internal/model/user" @@ -395,8 +396,7 @@ func (l *OAuthLoginGetTokenLogic) register(email, avatar, method, openid, reques } rc := l.svcCtx.Config.Register - // Only activate trial if email domain is in whitelist (whitelist cannot be empty) - shouldActivateTrial := rc.EnableTrial && rc.TrialEmailDomainWhitelist != "" && (email != "" && l.isEmailDomainWhitelisted(email, rc.TrialEmailDomainWhitelist)) + shouldActivateTrial := email != "" && authlogic.ShouldGrantTrialForEmail(rc, email) if shouldActivateTrial { l.Debugw("activating trial subscription", diff --git a/internal/logic/auth/trialEmailWhitelist.go b/internal/logic/auth/trialEmailWhitelist.go index 4decd98..90a2071 100644 --- a/internal/logic/auth/trialEmailWhitelist.go +++ b/internal/logic/auth/trialEmailWhitelist.go @@ -1,6 +1,10 @@ package auth -import "strings" +import ( + "strings" + + "github.com/perfect-panel/server/internal/config" +) // IsEmailDomainWhitelisted checks if the email's domain is in the comma-separated whitelist. // Returns false if the email format is invalid. @@ -20,3 +24,16 @@ func IsEmailDomainWhitelisted(email, whitelistCSV string) bool { } return false } + +func ShouldGrantTrialForEmail(register config.RegisterConfig, email string) bool { + if !register.EnableTrial { + return false + } + if !register.EnableTrialEmailWhitelist { + return true + } + if register.TrialEmailDomainWhitelist == "" { + return false + } + return IsEmailDomainWhitelisted(email, register.TrialEmailDomainWhitelist) +} diff --git a/internal/logic/auth/userRegisterLogic.go b/internal/logic/auth/userRegisterLogic.go index 90c06bf..a38cdde 100644 --- a/internal/logic/auth/userRegisterLogic.go +++ b/internal/logic/auth/userRegisterLogic.go @@ -148,7 +148,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * // Activate trial subscription after transaction success (moved outside transaction to reduce lock time) rc := l.svcCtx.Config.Register - if rc.EnableTrial && rc.TrialEmailDomainWhitelist != "" && IsEmailDomainWhitelisted(req.Email, rc.TrialEmailDomainWhitelist) { + if ShouldGrantTrialForEmail(rc, req.Email) { trialSubscribe, err = l.activeTrial(userInfo.Id) if err != nil { l.Errorw("Failed to activate trial subscription", logger.Field("error", err.Error())) diff --git a/internal/logic/public/user/bindEmailWithVerificationLogic.go b/internal/logic/public/user/bindEmailWithVerificationLogic.go index d2f5924..a44c584 100644 --- a/internal/logic/public/user/bindEmailWithVerificationLogic.go +++ b/internal/logic/public/user/bindEmailWithVerificationLogic.go @@ -8,14 +8,12 @@ import ( "time" "github.com/perfect-panel/server/internal/config" - "github.com/perfect-panel/server/internal/logic/auth" "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/pkg/constant" "github.com/perfect-panel/server/pkg/jwt" "github.com/perfect-panel/server/pkg/logger" - "github.com/perfect-panel/server/pkg/tool" "github.com/perfect-panel/server/pkg/uuidx" "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" @@ -129,8 +127,7 @@ func (l *BindEmailWithVerificationLogic) BindEmailWithVerification(req *types.Bi if err != nil { return nil, err } - // Grant trial subscription if email domain is whitelisted - l.tryGrantTrialOnEmailBind(emailUser.Id, req.Email) + tryGrantTrialOnEmailBind(l.ctx, l.svcCtx, l.Logger, emailUser.Id, req.Email) return &types.BindEmailWithVerificationResponse{ Success: true, Message: "email user created and joined family", @@ -158,8 +155,7 @@ func (l *BindEmailWithVerificationLogic) BindEmailWithVerification(req *types.Bi return nil, err } - // Grant trial subscription if email domain is whitelisted - l.tryGrantTrialOnEmailBind(existingMethod.UserId, req.Email) + tryGrantTrialOnEmailBind(l.ctx, l.svcCtx, l.Logger, existingMethod.UserId, req.Email) return &types.BindEmailWithVerificationResponse{ Success: true, @@ -207,74 +203,3 @@ func (l *BindEmailWithVerificationLogic) refreshBindSessionToken(userId int64) ( return token, nil } - -// tryGrantTrialOnEmailBind grants trial subscription to the email user (family owner) -// if email domain is in the configured whitelist (or if whitelist is empty, no trial is granted). -func (l *BindEmailWithVerificationLogic) tryGrantTrialOnEmailBind(ownerUserId int64, email string) { - rc := l.svcCtx.Config.Register - if !rc.EnableTrial || rc.TrialEmailDomainWhitelist == "" { - return - } - if !auth.IsEmailDomainWhitelisted(email, rc.TrialEmailDomainWhitelist) { - l.Infow("email domain not in trial whitelist, skip", - logger.Field("email", email), - logger.Field("owner_user_id", ownerUserId), - ) - return - } - - // Anti-duplicate: check if owner already has trial subscription - var count int64 - if err := l.svcCtx.DB.WithContext(l.ctx). - Model(&user.Subscribe{}). - Where("user_id = ? AND subscribe_id = ?", ownerUserId, rc.TrialSubscribe). - Count(&count).Error; err != nil { - l.Errorw("failed to check existing trial", logger.Field("error", err.Error())) - return - } - if count > 0 { - l.Infow("trial already granted, skip", - logger.Field("owner_user_id", ownerUserId), - ) - return - } - - sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, rc.TrialSubscribe) - if err != nil { - l.Errorw("failed to find trial subscribe template", logger.Field("error", err.Error())) - return - } - - userSub := &user.Subscribe{ - UserId: ownerUserId, - OrderId: 0, - SubscribeId: sub.Id, - StartTime: time.Now(), - ExpireTime: tool.AddTime(rc.TrialTimeUnit, rc.TrialTime, time.Now()), - Traffic: sub.Traffic, - Download: 0, - Upload: 0, - Token: uuidx.NewUUID().String(), - UUID: uuidx.NewUUID().String(), - Status: 1, - } - if err = l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub); err != nil { - l.Errorw("failed to insert trial subscribe", - logger.Field("error", err.Error()), - logger.Field("owner_user_id", ownerUserId), - ) - return - } - - // InsertSubscribe auto-clears user subscribe cache via execSubscribeMutation. - // Clear server cache so nodes pick up the new subscription. - if err = l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); err != nil { - l.Errorw("ClearServerAllCache error", logger.Field("error", err.Error())) - } - - l.Infow("trial granted on email bind", - logger.Field("owner_user_id", ownerUserId), - logger.Field("email", email), - logger.Field("subscribe_id", sub.Id), - ) -} diff --git a/internal/logic/public/user/emailTrialGrant.go b/internal/logic/public/user/emailTrialGrant.go new file mode 100644 index 0000000..c59ec53 --- /dev/null +++ b/internal/logic/public/user/emailTrialGrant.go @@ -0,0 +1,80 @@ +package user + +import ( + "context" + "time" + + "github.com/perfect-panel/server/internal/logic/auth" + "github.com/perfect-panel/server/internal/model/user" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/uuidx" +) + +func tryGrantTrialOnEmailBind(ctx context.Context, svcCtx *svc.ServiceContext, log logger.Logger, ownerUserId int64, email string) { + rc := svcCtx.Config.Register + if !auth.ShouldGrantTrialForEmail(rc, email) { + if rc.EnableTrial && rc.EnableTrialEmailWhitelist { + log.Infow("email domain not in trial whitelist, skip", + logger.Field("email", email), + logger.Field("owner_user_id", ownerUserId), + ) + } + return + } + + var count int64 + if err := svcCtx.DB.WithContext(ctx). + Model(&user.Subscribe{}). + Where("user_id = ? AND subscribe_id = ?", ownerUserId, rc.TrialSubscribe). + Count(&count).Error; err != nil { + log.Errorw("failed to check existing trial", logger.Field("error", err.Error())) + return + } + if count > 0 { + log.Infow("trial already granted, skip", + logger.Field("owner_user_id", ownerUserId), + ) + return + } + + sub, err := svcCtx.SubscribeModel.FindOne(ctx, rc.TrialSubscribe) + if err != nil { + log.Errorw("failed to find trial subscribe template", logger.Field("error", err.Error())) + return + } + + userSub := &user.Subscribe{ + UserId: ownerUserId, + OrderId: 0, + SubscribeId: sub.Id, + StartTime: time.Now(), + ExpireTime: tool.AddTime(rc.TrialTimeUnit, rc.TrialTime, time.Now()), + Traffic: sub.Traffic, + Download: 0, + Upload: 0, + Token: uuidx.NewUUID().String(), + UUID: uuidx.NewUUID().String(), + Status: 1, + } + if err = svcCtx.UserModel.InsertSubscribe(ctx, userSub); err != nil { + log.Errorw("failed to insert trial subscribe", + logger.Field("error", err.Error()), + logger.Field("owner_user_id", ownerUserId), + ) + return + } + + if svcCtx.NodeModel != nil { + if err = svcCtx.NodeModel.ClearServerAllCache(ctx); err != nil { + log.Errorw("ClearServerAllCache error", logger.Field("error", err.Error())) + } + } + + log.Infow("trial granted on email bind", + logger.Field("owner_user_id", ownerUserId), + logger.Field("email", email), + logger.Field("subscribe_id", sub.Id), + ) +} diff --git a/internal/logic/public/user/verifyEmailLogic.go b/internal/logic/public/user/verifyEmailLogic.go index d9c59de..36b46c2 100644 --- a/internal/logic/public/user/verifyEmailLogic.go +++ b/internal/logic/public/user/verifyEmailLogic.go @@ -77,5 +77,6 @@ func (l *VerifyEmailLogic) VerifyEmail(req *types.VerifyEmailRequest) error { if err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "UpdateUserAuthMethods error") } + tryGrantTrialOnEmailBind(l.ctx, l.svcCtx, l.Logger, method.UserId, req.Email) return nil }