From 23ef9dbff132706c309467a030e9f6421d4df429 Mon Sep 17 00:00:00 2001 From: EUForest Date: Tue, 6 Jan 2026 16:15:47 +0800 Subject: [PATCH] feat: bind device limit --- apis/types.api | 2 + internal/config/config.go | 1 + internal/logic/auth/bindDeviceLogic.go | 61 ++++++++++++++++++++++++++ internal/types/types.go | 8 ++++ 4 files changed, 72 insertions(+) diff --git a/apis/types.api b/apis/types.api index ee5f56c..1a59839 100644 --- a/apis/types.api +++ b/apis/types.api @@ -152,6 +152,7 @@ type ( EnableIpRegisterLimit bool `json:"enable_ip_register_limit"` IpRegisterLimit int64 `json:"ip_register_limit"` IpRegisterLimitDuration int64 `json:"ip_register_limit_duration"` + DeviceLimit int64 `json:"device_limit"` } VerifyConfig { TurnstileSiteKey string `json:"turnstile_site_key"` @@ -456,6 +457,7 @@ type ( SubscribePlan int64 `json:"subscribe_plan"` UnitTime string `json:"unit_time"` Quantity int64 `json:"quantity"` + Status int64 `json:"status"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } diff --git a/internal/config/config.go b/internal/config/config.go index fc93da5..07e1e50 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -73,6 +73,7 @@ type RegisterConfig struct { IpRegisterLimit int64 `yaml:"IpRegisterLimit" default:"0"` IpRegisterLimitDuration int64 `yaml:"IpRegisterLimitDuration" default:"0"` EnableIpRegisterLimit bool `yaml:"EnableIpRegisterLimit" default:"false"` + DeviceLimit int64 `yaml:"DeviceLimit" default:"5"` } type EmailConfig struct { diff --git a/internal/logic/auth/bindDeviceLogic.go b/internal/logic/auth/bindDeviceLogic.go index 34b35d3..3aedd6f 100644 --- a/internal/logic/auth/bindDeviceLogic.go +++ b/internal/logic/auth/bindDeviceLogic.go @@ -89,6 +89,36 @@ func (l *BindDeviceLogic) createDeviceForUser(identifier, ip, userAgent string, logger.Field("user_id", userId), ) + // Check device limit + deviceLimit := l.svcCtx.Config.Register.DeviceLimit + if deviceLimit > 0 { + // Count current user's devices + var deviceCount int64 + if err := l.svcCtx.DB.Model(&user.Device{}).Where("user_id = ?", userId).Count(&deviceCount).Error; err != nil { + l.Errorw("failed to count user devices", + logger.Field("user_id", userId), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count devices failed: %v", err.Error()) + } + + // Check if limit reached + if deviceCount >= deviceLimit { + l.Errorw("device limit reached", + logger.Field("user_id", userId), + logger.Field("device_count", deviceCount), + logger.Field("device_limit", deviceLimit), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "device limit reached: maximum %d devices allowed", deviceLimit) + } + + l.Infow("device limit check passed", + logger.Field("user_id", userId), + logger.Field("device_count", deviceCount), + logger.Field("device_limit", deviceLimit), + ) + } + err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { // Create device auth method authMethod := &user.AuthMethods{ @@ -147,6 +177,37 @@ func (l *BindDeviceLogic) createDeviceForUser(identifier, ip, userAgent string, func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, userAgent string, newUserId int64) error { oldUserId := deviceInfo.UserId + + // Check device limit for new user + deviceLimit := l.svcCtx.Config.Register.DeviceLimit + if deviceLimit > 0 { + // Count new user's current devices (excluding the one being rebound) + var deviceCount int64 + if err := l.svcCtx.DB.Model(&user.Device{}).Where("user_id = ? AND id != ?", newUserId, deviceInfo.Id).Count(&deviceCount).Error; err != nil { + l.Errorw("failed to count new user devices", + logger.Field("user_id", newUserId), + logger.Field("error", err.Error()), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count devices failed: %v", err.Error()) + } + + // Check if limit reached + if deviceCount >= deviceLimit { + l.Errorw("device limit reached for new user", + logger.Field("user_id", newUserId), + logger.Field("device_count", deviceCount), + logger.Field("device_limit", deviceLimit), + ) + return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "device limit reached: maximum %d devices allowed", deviceLimit) + } + + l.Infow("device limit check passed for rebinding", + logger.Field("user_id", newUserId), + logger.Field("device_count", deviceCount), + logger.Field("device_limit", deviceLimit), + ) + } + var users []*user.User err := l.svcCtx.DB.Where("id in (?)", []int64{oldUserId, newUserId}).Find(&users).Error if err != nil { diff --git a/internal/types/types.go b/internal/types/types.go index 51f883a..b75589f 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -1836,6 +1836,7 @@ type RedemptionCode struct { SubscribePlan int64 `json:"subscribe_plan"` UnitTime string `json:"unit_time"` Quantity int64 `json:"quantity"` + Status int64 `json:"status"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } @@ -1860,6 +1861,7 @@ type RegisterConfig struct { EnableIpRegisterLimit bool `json:"enable_ip_register_limit"` IpRegisterLimit int64 `json:"ip_register_limit"` IpRegisterLimitDuration int64 `json:"ip_register_limit_duration"` + DeviceLimit int64 `json:"device_limit"` } type RegisterLog struct { @@ -2318,6 +2320,11 @@ type ToggleNodeStatusRequest struct { Enable *bool `json:"enable"` } +type ToggleRedemptionCodeStatusRequest struct { + Id int64 `json:"id" validate:"required"` + Status int64 `json:"status" validate:"oneof=0 1"` +} + type ToggleUserSubscribeStatusRequest struct { UserSubscribeId int64 `json:"user_subscribe_id"` } @@ -2494,6 +2501,7 @@ type UpdateRedemptionCodeRequest struct { 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"` + Status int64 `json:"status,omitempty" validate:"omitempty,oneof=0 1"` } type UpdateServerRequest struct {