This commit is contained in:
@@ -29,10 +29,12 @@ func NewPreviewSubscribeTemplateLogic(ctx context.Context, svcCtx *svc.ServiceCo
|
||||
}
|
||||
|
||||
func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) {
|
||||
enable := true
|
||||
_, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
||||
Page: 1,
|
||||
Size: 1000,
|
||||
Preload: true,
|
||||
Enabled: &enable,
|
||||
})
|
||||
if err != nil {
|
||||
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
|
||||
|
||||
@@ -92,6 +92,9 @@ func (l *UpdateAuthMethodConfigLogic) UpdateGlobal(method string) {
|
||||
if method == "mobile" {
|
||||
initialize.Mobile(l.svcCtx)
|
||||
}
|
||||
if method == "device" {
|
||||
initialize.Device(l.svcCtx)
|
||||
}
|
||||
}
|
||||
|
||||
func validatePlatformConfig(platform string, cfg map[string]interface{}) (interface{}, error) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/perfect-panel/server/internal/report"
|
||||
paymentPlatform "github.com/perfect-panel/server/pkg/payment"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/payment"
|
||||
@@ -43,15 +44,31 @@ func (l *GetPaymentMethodListLogic) GetPaymentMethodList(req *types.GetPaymentMe
|
||||
Total: total,
|
||||
List: make([]types.PaymentMethodDetail, len(list)),
|
||||
}
|
||||
|
||||
// gateway mod
|
||||
|
||||
isGatewayMod := report.IsGatewayMode()
|
||||
|
||||
for i, v := range list {
|
||||
config := make(map[string]interface{})
|
||||
_ = json.Unmarshal([]byte(v.Config), &config)
|
||||
notifyUrl := ""
|
||||
|
||||
if paymentPlatform.ParsePlatform(v.Platform) != paymentPlatform.Balance {
|
||||
notifyUrl = v.Domain
|
||||
if v.Domain != "" {
|
||||
notifyUrl = v.Domain + "/v1/notify/" + v.Platform + "/" + v.Token
|
||||
// if is gateway mod, use gateway domain
|
||||
if isGatewayMod {
|
||||
notifyUrl += "/api/"
|
||||
}
|
||||
notifyUrl += "/v1/notify/" + v.Platform + "/" + v.Token
|
||||
} else {
|
||||
notifyUrl = "https://" + l.svcCtx.Config.Host + "/v1/notify/" + v.Platform + "/" + v.Token
|
||||
notifyUrl += "https://" + l.svcCtx.Config.Host
|
||||
if isGatewayMod {
|
||||
notifyUrl += "/api/v1/notify/" + v.Platform + "/" + v.Token
|
||||
} else {
|
||||
notifyUrl += "/v1/notify/" + v.Platform + "/" + v.Token
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.List[i] = types.PaymentMethodDetail{
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package subscribe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/pkg/uuidx"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
)
|
||||
|
||||
type ResetAllSubscribeTokenLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Reset all subscribe tokens
|
||||
func NewResetAllSubscribeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetAllSubscribeTokenLogic {
|
||||
return &ResetAllSubscribeTokenLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ResetAllSubscribeTokenLogic) ResetAllSubscribeToken() (resp *types.ResetAllSubscribeTokenResponse, err error) {
|
||||
var list []*user.Subscribe
|
||||
tx := l.svcCtx.DB.WithContext(l.ctx).Begin()
|
||||
// select all active and Finished subscriptions
|
||||
if err = tx.Model(&user.Subscribe{}).Where("`status` IN ?", []int64{1, 2}).Find(&list).Error; err != nil {
|
||||
logger.Errorf("[ResetAllSubscribeToken] Failed to fetch subscribe list: %v", err.Error())
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Failed to fetch subscribe list: %v", err.Error())
|
||||
}
|
||||
|
||||
for _, sub := range list {
|
||||
sub.Token = uuidx.SubscribeToken(strconv.FormatInt(time.Now().UnixMilli(), 10) + strconv.FormatInt(sub.Id, 10))
|
||||
sub.UUID = uuidx.NewUUID().String()
|
||||
if err = tx.Model(&user.Subscribe{}).Where("id = ?", sub.Id).Save(sub).Error; err != nil {
|
||||
tx.Rollback()
|
||||
logger.Errorf("[ResetAllSubscribeToken] Failed to update subscribe token for ID %d: %v", sub.Id, err.Error())
|
||||
return &types.ResetAllSubscribeTokenResponse{
|
||||
Success: false,
|
||||
}, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Failed to update subscribe token for ID %d: %v", sub.Id, err.Error())
|
||||
}
|
||||
}
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
logger.Errorf("[ResetAllSubscribeToken] Failed to commit transaction: %v", err.Error())
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Failed to commit transaction: %v", err.Error())
|
||||
}
|
||||
|
||||
return &types.ResetAllSubscribeTokenResponse{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"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/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type GetModuleConfigLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Get Module Config
|
||||
func NewGetModuleConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetModuleConfigLogic {
|
||||
return &GetModuleConfigLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetModuleConfigLogic) GetModuleConfig() (resp *types.ModuleConfig, err error) {
|
||||
value, exists := os.LookupEnv("SECRET_KEY")
|
||||
if !exists {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), " SECRET_KEY not set in environment variables")
|
||||
}
|
||||
|
||||
return &types.ModuleConfig{
|
||||
Secret: value,
|
||||
ServiceName: constant.ServiceName,
|
||||
ServiceVersion: constant.Version,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"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 QueryIPLocationLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewQueryIPLocationLogic Query IP Location
|
||||
func NewQueryIPLocationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryIPLocationLogic {
|
||||
return &QueryIPLocationLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryIPLocationLogic) QueryIPLocation(req *types.QueryIPLocationRequest) (resp *types.QueryIPLocationResponse, err error) {
|
||||
if l.svcCtx.GeoIP == nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), " GeoIP database not configured")
|
||||
}
|
||||
|
||||
ip := net.ParseIP(req.IP)
|
||||
record, err := l.svcCtx.GeoIP.DB.City(ip)
|
||||
if err != nil {
|
||||
l.Errorf("Failed to query IP location: %v", err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Failed to query IP location")
|
||||
}
|
||||
|
||||
var country, region, city string
|
||||
if record.Country.Names != nil {
|
||||
country = record.Country.Names["en"]
|
||||
}
|
||||
if len(record.Subdivisions) > 0 && record.Subdivisions[0].Names != nil {
|
||||
region = record.Subdivisions[0].Names["en"]
|
||||
}
|
||||
if record.City.Names != nil {
|
||||
city = record.City.Names["en"]
|
||||
}
|
||||
|
||||
return &types.QueryIPLocationResponse{
|
||||
Country: country,
|
||||
Region: region,
|
||||
City: city,
|
||||
}, nil
|
||||
}
|
||||
@@ -40,6 +40,7 @@ func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) error {
|
||||
pwd := tool.EncodePassWord(req.Password)
|
||||
newUser := &user.User{
|
||||
Password: pwd,
|
||||
Algo: "default",
|
||||
ReferralPercentage: req.ReferralPercentage,
|
||||
OnlyFirstPurchase: &req.OnlyFirstPurchase,
|
||||
ReferCode: req.ReferCode,
|
||||
|
||||
@@ -129,6 +129,7 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
|
||||
return errors.Wrapf(xerr.NewErrCodeMsg(503, "Demo mode does not allow modification of the admin user password"), "UpdateUserBasicInfo failed: cannot update admin user password in demo mode")
|
||||
}
|
||||
userInfo.Password = tool.EncodePassWord(req.Password)
|
||||
userInfo.Algo = "default"
|
||||
}
|
||||
|
||||
err = l.svcCtx.UserModel.Update(l.ctx, userInfo)
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BindDeviceLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewBindDeviceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindDeviceLogic {
|
||||
return &BindDeviceLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
// BindDeviceToUser binds a device to a user
|
||||
// If the device is already bound to another user, it will disable that user and bind the device to the current user
|
||||
func (l *BindDeviceLogic) BindDeviceToUser(identifier, ip, userAgent string, currentUserId int64) error {
|
||||
if identifier == "" {
|
||||
// No device identifier provided, skip binding
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Infow("binding device to user",
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("user_id", currentUserId),
|
||||
logger.Field("ip", ip),
|
||||
)
|
||||
|
||||
// Check if device exists
|
||||
deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, identifier)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// Device not found, create new device record
|
||||
return l.createDeviceForUser(identifier, ip, userAgent, currentUserId)
|
||||
}
|
||||
l.Errorw("failed to query device",
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device failed: %v", err.Error())
|
||||
}
|
||||
|
||||
// Device exists, check if it's bound to current user
|
||||
if deviceInfo.UserId == currentUserId {
|
||||
// Already bound to current user, just update IP and UserAgent
|
||||
l.Infow("device already bound to current user, updating info",
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("user_id", currentUserId),
|
||||
)
|
||||
deviceInfo.Ip = ip
|
||||
deviceInfo.UserAgent = userAgent
|
||||
if err := l.svcCtx.UserModel.UpdateDevice(l.ctx, deviceInfo); err != nil {
|
||||
l.Errorw("failed to update device",
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Device is bound to another user, need to disable old user and rebind
|
||||
l.Infow("device bound to another user, rebinding",
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("old_user_id", deviceInfo.UserId),
|
||||
logger.Field("new_user_id", currentUserId),
|
||||
)
|
||||
|
||||
return l.rebindDeviceToNewUser(deviceInfo, ip, userAgent, currentUserId)
|
||||
}
|
||||
|
||||
func (l *BindDeviceLogic) createDeviceForUser(identifier, ip, userAgent string, userId int64) error {
|
||||
l.Infow("creating new device for user",
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("user_id", userId),
|
||||
)
|
||||
|
||||
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
|
||||
// Create device auth method
|
||||
authMethod := &user.AuthMethods{
|
||||
UserId: userId,
|
||||
AuthType: "device",
|
||||
AuthIdentifier: identifier,
|
||||
Verified: true,
|
||||
}
|
||||
if err := db.Create(authMethod).Error; err != nil {
|
||||
l.Errorw("failed to create device auth method",
|
||||
logger.Field("user_id", userId),
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device auth method failed: %v", err)
|
||||
}
|
||||
|
||||
// Create device record
|
||||
deviceInfo := &user.Device{
|
||||
Ip: ip,
|
||||
UserId: userId,
|
||||
UserAgent: userAgent,
|
||||
Identifier: identifier,
|
||||
Enabled: true,
|
||||
Online: false,
|
||||
}
|
||||
if err := db.Create(deviceInfo).Error; err != nil {
|
||||
l.Errorw("failed to create device",
|
||||
logger.Field("user_id", userId),
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
l.Errorw("device creation failed",
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("user_id", userId),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
l.Infow("device created successfully",
|
||||
logger.Field("identifier", identifier),
|
||||
logger.Field("user_id", userId),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, userAgent string, newUserId int64) error {
|
||||
oldUserId := deviceInfo.UserId
|
||||
|
||||
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
|
||||
// Check if old user has other auth methods besides device
|
||||
var authMethods []user.AuthMethods
|
||||
if err := db.Where("user_id = ?", oldUserId).Find(&authMethods).Error; err != nil {
|
||||
l.Errorw("failed to query auth methods for old user",
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query auth methods failed: %v", err)
|
||||
}
|
||||
|
||||
// Count non-device auth methods
|
||||
nonDeviceAuthCount := 0
|
||||
for _, auth := range authMethods {
|
||||
if auth.AuthType != "device" {
|
||||
nonDeviceAuthCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Only disable old user if they have no other auth methods
|
||||
if nonDeviceAuthCount == 0 {
|
||||
falseVal := false
|
||||
if err := db.Model(&user.User{}).Where("id = ?", oldUserId).Update("enable", &falseVal).Error; err != nil {
|
||||
l.Errorw("failed to disable old user",
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "disable old user failed: %v", err)
|
||||
}
|
||||
|
||||
l.Infow("disabled old user (no other auth methods)",
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
)
|
||||
} else {
|
||||
l.Infow("old user has other auth methods, not disabling",
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
logger.Field("non_device_auth_count", nonDeviceAuthCount),
|
||||
)
|
||||
}
|
||||
|
||||
// Update device auth method to new user
|
||||
if err := db.Model(&user.AuthMethods{}).
|
||||
Where("auth_type = ? AND auth_identifier = ?", "device", deviceInfo.Identifier).
|
||||
Update("user_id", newUserId).Error; err != nil {
|
||||
l.Errorw("failed to update device auth method",
|
||||
logger.Field("identifier", deviceInfo.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device auth method failed: %v", err)
|
||||
}
|
||||
|
||||
// Update device record
|
||||
deviceInfo.UserId = newUserId
|
||||
deviceInfo.Ip = ip
|
||||
deviceInfo.UserAgent = userAgent
|
||||
deviceInfo.Enabled = true
|
||||
|
||||
if err := db.Save(deviceInfo).Error; err != nil {
|
||||
l.Errorw("failed to update device",
|
||||
logger.Field("identifier", deviceInfo.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
l.Errorw("device rebinding failed",
|
||||
logger.Field("identifier", deviceInfo.Identifier),
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
logger.Field("new_user_id", newUserId),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
l.Infow("device rebound successfully",
|
||||
logger.Field("identifier", deviceInfo.Identifier),
|
||||
logger.Field("old_user_id", oldUserId),
|
||||
logger.Field("new_user_id", newUserId),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"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/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"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DeviceLoginLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Device Login
|
||||
func NewDeviceLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeviceLoginLogic {
|
||||
return &DeviceLoginLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *types.LoginResponse, err error) {
|
||||
if !l.svcCtx.Config.Device.Enable {
|
||||
return nil, xerr.NewErrMsg("Device login is disabled")
|
||||
}
|
||||
|
||||
loginStatus := false
|
||||
var userInfo *user.User
|
||||
// Record login status
|
||||
defer func() {
|
||||
if userInfo != nil && userInfo.Id != 0 {
|
||||
loginLog := log.Login{
|
||||
Method: "device",
|
||||
LoginIP: req.IP,
|
||||
UserAgent: req.UserAgent,
|
||||
Success: loginStatus,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
}
|
||||
content, _ := loginLog.Marshal()
|
||||
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
Type: log.TypeLogin.Uint8(),
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
ObjectID: userInfo.Id,
|
||||
Content: string(content),
|
||||
}); err != nil {
|
||||
l.Errorw("failed to insert login log",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("ip", req.IP),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Check if device exists by identifier
|
||||
deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// Device not found, create new user and device
|
||||
userInfo, err = l.registerUserAndDevice(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
l.Errorw("query device failed",
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device failed: %v", err.Error())
|
||||
}
|
||||
} else {
|
||||
// Device found, get user info
|
||||
userInfo, err = l.svcCtx.UserModel.FindOne(l.ctx, deviceInfo.UserId)
|
||||
if err != nil {
|
||||
l.Errorw("query user failed",
|
||||
logger.Field("user_id", deviceInfo.UserId),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user failed: %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Generate session id
|
||||
sessionId := uuidx.NewUUID().String()
|
||||
|
||||
// Generate token
|
||||
token, err := jwt.NewJwtToken(
|
||||
l.svcCtx.Config.JwtAuth.AccessSecret,
|
||||
time.Now().Unix(),
|
||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("LoginType", "device"),
|
||||
)
|
||||
if err != nil {
|
||||
l.Errorw("token generate error",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error())
|
||||
}
|
||||
|
||||
// Store session id in redis
|
||||
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
||||
if err = l.svcCtx.Redis.Set(l.ctx, sessionIdCacheKey, userInfo.Id, time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire)*time.Second).Err(); err != nil {
|
||||
l.Errorw("set session id error",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error())
|
||||
}
|
||||
|
||||
loginStatus = true
|
||||
return &types.LoginResponse{
|
||||
Token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *DeviceLoginLogic) registerUserAndDevice(req *types.DeviceLoginRequest) (*user.User, error) {
|
||||
l.Infow("device not found, creating new user and device",
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("ip", req.IP),
|
||||
)
|
||||
|
||||
var userInfo *user.User
|
||||
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
|
||||
// Create new user
|
||||
userInfo = &user.User{
|
||||
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
||||
}
|
||||
if err := db.Create(userInfo).Error; err != nil {
|
||||
l.Errorw("failed to create user",
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create user failed: %v", err)
|
||||
}
|
||||
|
||||
// Update refer code
|
||||
userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id)
|
||||
if err := db.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil {
|
||||
l.Errorw("failed to update refer code",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update refer code failed: %v", err)
|
||||
}
|
||||
|
||||
// Create device auth method
|
||||
authMethod := &user.AuthMethods{
|
||||
UserId: userInfo.Id,
|
||||
AuthType: "device",
|
||||
AuthIdentifier: req.Identifier,
|
||||
Verified: true,
|
||||
}
|
||||
if err := db.Create(authMethod).Error; err != nil {
|
||||
l.Errorw("failed to create device auth method",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device auth method failed: %v", err)
|
||||
}
|
||||
|
||||
// Insert device record
|
||||
deviceInfo := &user.Device{
|
||||
Ip: req.IP,
|
||||
UserId: userInfo.Id,
|
||||
UserAgent: req.UserAgent,
|
||||
Identifier: req.Identifier,
|
||||
Enabled: true,
|
||||
Online: false,
|
||||
}
|
||||
if err := db.Create(deviceInfo).Error; err != nil {
|
||||
l.Errorw("failed to insert device",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert device failed: %v", err)
|
||||
}
|
||||
|
||||
// Activate trial if enabled
|
||||
if l.svcCtx.Config.Register.EnableTrial {
|
||||
if err := l.activeTrial(userInfo.Id, db); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
l.Errorw("device registration failed",
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.Infow("device registration completed successfully",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("refer_code", userInfo.ReferCode),
|
||||
)
|
||||
|
||||
// Register log
|
||||
registerLog := log.Register{
|
||||
AuthMethod: "device",
|
||||
Identifier: req.Identifier,
|
||||
RegisterIP: req.IP,
|
||||
UserAgent: req.UserAgent,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
}
|
||||
content, _ := registerLog.Marshal()
|
||||
|
||||
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
|
||||
Type: log.TypeRegister.Uint8(),
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
ObjectID: userInfo.Id,
|
||||
Content: string(content),
|
||||
}); err != nil {
|
||||
l.Errorw("failed to insert register log",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("ip", req.IP),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
}
|
||||
|
||||
return userInfo, nil
|
||||
}
|
||||
|
||||
func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) error {
|
||||
sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe)
|
||||
if err != nil {
|
||||
l.Errorw("failed to find trial subscription template",
|
||||
logger.Field("user_id", userId),
|
||||
logger.Field("trial_subscribe_id", l.svcCtx.Config.Register.TrialSubscribe),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
expireTime := tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, startTime)
|
||||
subscribeToken := uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", userId))
|
||||
subscribeUUID := uuidx.NewUUID().String()
|
||||
|
||||
userSub := &user.Subscribe{
|
||||
UserId: userId,
|
||||
OrderId: 0,
|
||||
SubscribeId: sub.Id,
|
||||
StartTime: startTime,
|
||||
ExpireTime: expireTime,
|
||||
Traffic: sub.Traffic,
|
||||
Download: 0,
|
||||
Upload: 0,
|
||||
Token: subscribeToken,
|
||||
UUID: subscribeUUID,
|
||||
Status: 1,
|
||||
}
|
||||
|
||||
if err := db.Create(userSub).Error; err != nil {
|
||||
l.Errorw("failed to insert trial subscription",
|
||||
logger.Field("user_id", userId),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
l.Infow("trial subscription activated successfully",
|
||||
logger.Field("user_id", userId),
|
||||
logger.Field("subscribe_id", sub.Id),
|
||||
logger.Field("expire_time", expireTime),
|
||||
logger.Field("traffic", sub.Traffic),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -104,9 +104,26 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res
|
||||
|
||||
// Update password
|
||||
userInfo.Password = tool.EncodePassWord(req.Password)
|
||||
if err := l.svcCtx.UserModel.Update(l.ctx, userInfo); err != nil {
|
||||
userInfo.Algo = "default"
|
||||
if err = l.svcCtx.UserModel.Update(l.ctx, userInfo); err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user info failed: %v", err.Error())
|
||||
}
|
||||
|
||||
// Bind device to user if identifier is provided
|
||||
if req.Identifier != "" {
|
||||
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||
l.Errorw("failed to bind device to user",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
// Don't fail register if device binding fails, just log the error
|
||||
}
|
||||
}
|
||||
if l.ctx.Value(constant.LoginType) != nil {
|
||||
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||
}
|
||||
// Generate session id
|
||||
sessionId := uuidx.NewUUID().String()
|
||||
// Generate token
|
||||
@@ -116,6 +133,7 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res
|
||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("LoginType", req.LoginType),
|
||||
)
|
||||
if err != nil {
|
||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||
|
||||
@@ -98,7 +98,7 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
|
||||
|
||||
if req.TelephoneCode == "" {
|
||||
// Verify password
|
||||
if !tool.VerifyPassWord(req.Password, userInfo.Password) {
|
||||
if !tool.MultiPasswordVerify(userInfo.Algo, userInfo.Salt, req.Password, userInfo.Password) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
|
||||
}
|
||||
} else {
|
||||
@@ -124,6 +124,23 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
|
||||
l.svcCtx.Redis.Del(l.ctx, cacheKey)
|
||||
}
|
||||
|
||||
// Bind device to user if identifier is provided
|
||||
if req.Identifier != "" {
|
||||
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||
l.Errorw("failed to bind device to user",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
// Don't fail login if device binding fails, just log the error
|
||||
}
|
||||
}
|
||||
|
||||
if l.ctx.Value(constant.LoginType) != nil {
|
||||
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||
}
|
||||
|
||||
// Generate session id
|
||||
sessionId := uuidx.NewUUID().String()
|
||||
// Generate token
|
||||
@@ -133,6 +150,7 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
|
||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("LoginType", req.LoginType),
|
||||
)
|
||||
if err != nil {
|
||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||
|
||||
@@ -78,11 +78,27 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon
|
||||
// Generate password
|
||||
pwd := tool.EncodePassWord(req.Password)
|
||||
userInfo.Password = pwd
|
||||
userInfo.Algo = "default"
|
||||
err = l.svcCtx.UserModel.Update(l.ctx, userInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "update user password failed: %v", err.Error())
|
||||
}
|
||||
|
||||
// Bind device to user if identifier is provided
|
||||
if req.Identifier != "" {
|
||||
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||
l.Errorw("failed to bind device to user",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
// Don't fail register if device binding fails, just log the error
|
||||
}
|
||||
}
|
||||
if l.ctx.Value(constant.LoginType) != nil {
|
||||
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||
}
|
||||
// Generate session id
|
||||
sessionId := uuidx.NewUUID().String()
|
||||
// Generate token
|
||||
@@ -92,6 +108,7 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon
|
||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("LoginType", req.LoginType),
|
||||
)
|
||||
if err != nil {
|
||||
l.Errorw("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||
|
||||
@@ -107,6 +107,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
||||
pwd := tool.EncodePassWord(req.Password)
|
||||
userInfo := &user.User{
|
||||
Password: pwd,
|
||||
Algo: "default",
|
||||
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
||||
AuthMethods: []user.AuthMethods{
|
||||
{
|
||||
@@ -138,6 +139,22 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Bind device to user if identifier is provided
|
||||
if req.Identifier != "" {
|
||||
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||
l.Errorw("failed to bind device to user",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
// Don't fail register if device binding fails, just log the error
|
||||
}
|
||||
}
|
||||
if l.ctx.Value(constant.LoginType) != nil {
|
||||
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||
}
|
||||
// Generate session id
|
||||
sessionId := uuidx.NewUUID().String()
|
||||
// Generate token
|
||||
@@ -147,6 +164,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("LoginType", req.LoginType),
|
||||
)
|
||||
if err != nil {
|
||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
@@ -76,9 +77,25 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
||||
}
|
||||
|
||||
// Verify password
|
||||
if !tool.VerifyPassWord(req.Password, userInfo.Password) {
|
||||
if !tool.MultiPasswordVerify(userInfo.Algo, userInfo.Salt, req.Password, userInfo.Password) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
|
||||
}
|
||||
|
||||
// Bind device to user if identifier is provided
|
||||
if req.Identifier != "" {
|
||||
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||
l.Errorw("failed to bind device to user",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
// Don't fail login if device binding fails, just log the error
|
||||
}
|
||||
}
|
||||
if l.ctx.Value(constant.LoginType) != nil {
|
||||
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||
}
|
||||
// Generate session id
|
||||
sessionId := uuidx.NewUUID().String()
|
||||
// Generate token
|
||||
@@ -88,6 +105,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("LoginType", req.LoginType),
|
||||
)
|
||||
if err != nil {
|
||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||
|
||||
@@ -90,6 +90,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
||||
pwd := tool.EncodePassWord(req.Password)
|
||||
userInfo := &user.User{
|
||||
Password: pwd,
|
||||
Algo: "default",
|
||||
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
||||
}
|
||||
if referer != nil {
|
||||
@@ -125,6 +126,21 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
||||
}
|
||||
return nil
|
||||
})
|
||||
// Bind device to user if identifier is provided
|
||||
if req.Identifier != "" {
|
||||
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||
l.Errorw("failed to bind device to user",
|
||||
logger.Field("user_id", userInfo.Id),
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
// Don't fail register if device binding fails, just log the error
|
||||
}
|
||||
}
|
||||
if l.ctx.Value(constant.LoginType) != nil {
|
||||
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||
}
|
||||
// Generate session id
|
||||
sessionId := uuidx.NewUUID().String()
|
||||
// Generate token
|
||||
@@ -134,6 +150,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("LoginType", req.LoginType),
|
||||
)
|
||||
if err != nil {
|
||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||
|
||||
@@ -2,6 +2,7 @@ package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
@@ -67,6 +68,10 @@ func (l *GetGlobalConfigLogic) GetGlobalConfig() (resp *types.GetGlobalConfigRes
|
||||
for _, method := range authMethods {
|
||||
if *method.Enabled {
|
||||
methods = append(methods, method.Method)
|
||||
if method.Method == "device" {
|
||||
_ = json.Unmarshal([]byte(method.Config), &resp.Auth.Device)
|
||||
resp.Auth.Device.Enable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.OAuthMethods = methods
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
)
|
||||
|
||||
type HeartbeatLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewHeartbeatLogic Heartbeat
|
||||
func NewHeartbeatLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HeartbeatLogic {
|
||||
return &HeartbeatLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *HeartbeatLogic) Heartbeat() (resp *types.HeartbeatResponse, err error) {
|
||||
return &types.HeartbeatResponse{
|
||||
Status: true,
|
||||
Message: "service is alive",
|
||||
Timestamp: time.Now().Unix(),
|
||||
}, nil
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func (l *EPayNotifyLogic) EPayNotify(req *types.EPayNotifyRequest) error {
|
||||
return err
|
||||
}
|
||||
// Verify sign
|
||||
client := epay.NewClient(config.Pid, config.Url, config.Key)
|
||||
client := epay.NewClient(config.Pid, config.Url, config.Key, config.Type)
|
||||
if !client.VerifySign(urlParamsToMap(l.ctx.Request.URL.RawQuery)) && !l.svcCtx.Config.Debug {
|
||||
l.Logger.Error("[EPayNotify] Verify sign failed")
|
||||
return nil
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"github.com/perfect-panel/server/internal/report"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
|
||||
paymentPlatform "github.com/perfect-panel/server/pkg/payment"
|
||||
@@ -267,7 +268,7 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
|
||||
}
|
||||
// Initialize EPay client with merchant credentials
|
||||
client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key)
|
||||
client := epay.NewClient(epayConfig.Pid, epayConfig.Url, epayConfig.Key, epayConfig.Type)
|
||||
|
||||
// Convert order amount to CNY using current exchange rate
|
||||
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
||||
@@ -275,16 +276,29 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
||||
return "", err
|
||||
}
|
||||
|
||||
// gateway mod
|
||||
|
||||
isGatewayMod := report.IsGatewayMode()
|
||||
|
||||
// Build notification URL for payment status callbacks
|
||||
notifyUrl := ""
|
||||
if config.Domain != "" {
|
||||
notifyUrl = config.Domain + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||
notifyUrl = config.Domain
|
||||
if isGatewayMod {
|
||||
notifyUrl += "/api/"
|
||||
}
|
||||
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||
} else {
|
||||
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
|
||||
if !ok {
|
||||
host = l.svcCtx.Config.Host
|
||||
}
|
||||
notifyUrl = "https://" + host + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||
|
||||
notifyUrl = "https://" + host
|
||||
if isGatewayMod {
|
||||
notifyUrl += "/api"
|
||||
}
|
||||
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||
}
|
||||
|
||||
// Create payment URL for user redirection
|
||||
@@ -309,7 +323,7 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info
|
||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
|
||||
}
|
||||
// Initialize EPay client with merchant credentials
|
||||
client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey)
|
||||
client := epay.NewClient(epayConfig.AccountID, epayConfig.Endpoint, epayConfig.SecretKey, epayConfig.Type)
|
||||
|
||||
// Convert order amount to CNY using current exchange rate
|
||||
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
||||
@@ -317,18 +331,29 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info
|
||||
return "", err
|
||||
}
|
||||
|
||||
// gateway mod
|
||||
isGatewayMod := report.IsGatewayMode()
|
||||
|
||||
// Build notification URL for payment status callbacks
|
||||
notifyUrl := ""
|
||||
if config.Domain != "" {
|
||||
notifyUrl = config.Domain + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||
notifyUrl = config.Domain
|
||||
if isGatewayMod {
|
||||
notifyUrl += "/api/"
|
||||
}
|
||||
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||
} else {
|
||||
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
|
||||
if !ok {
|
||||
host = l.svcCtx.Config.Host
|
||||
}
|
||||
notifyUrl = "https://" + host + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||
}
|
||||
|
||||
notifyUrl = "https://" + host
|
||||
if isGatewayMod {
|
||||
notifyUrl += "/api"
|
||||
}
|
||||
notifyUrl = notifyUrl + "/v1/notify/" + config.Platform + "/" + config.Token
|
||||
}
|
||||
// Create payment URL for user redirection
|
||||
url := client.CreatePayUrl(epay.Order{
|
||||
Name: l.svcCtx.Config.Site.SiteName,
|
||||
@@ -347,6 +372,11 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount
|
||||
// Convert cents to decimal amount
|
||||
amount = float64(src) / float64(100)
|
||||
|
||||
if l.svcCtx.ExchangeRate != 0 && to == "CNY" {
|
||||
amount = amount * l.svcCtx.ExchangeRate
|
||||
return amount, nil
|
||||
}
|
||||
|
||||
// Retrieve system currency configuration
|
||||
currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -83,6 +83,12 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types.
|
||||
if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon used")
|
||||
}
|
||||
// Check expiration time
|
||||
expireTime := time.Unix(couponInfo.ExpireTime, 0)
|
||||
if time.Now().After(expireTime) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponExpired), "coupon expired")
|
||||
}
|
||||
|
||||
couponSub := tool.StringToInt64Slice(couponInfo.Subscribe)
|
||||
if len(couponSub) > 0 && !tool.Contains(couponSub, req.SubscribeId) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match")
|
||||
|
||||
@@ -59,8 +59,7 @@ func (l *QuerySubscribeListLogic) QuerySubscribeList(req *types.QuerySubscribeLi
|
||||
list[i] = sub
|
||||
// 通过服务组查询关联的节点数量
|
||||
if item.ServerGroup != "" {
|
||||
groupIds := tool.StringToInt64Slice(item.ServerGroup)
|
||||
servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, groupIds)
|
||||
servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, tool.StringToInt64Slice(item.ServerGroup))
|
||||
if err != nil {
|
||||
l.Errorw("[QuerySubscribeListLogic] FindServerListByGroupIds error", logger.Field("error", err.Error()))
|
||||
sub.ServerCount = 0
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package subscribe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/node"
|
||||
"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/logger"
|
||||
"github.com/perfect-panel/server/pkg/tool"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type QueryUserSubscribeNodeListLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Get user subscribe node info
|
||||
func NewQueryUserSubscribeNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryUserSubscribeNodeListLogic {
|
||||
return &QueryUserSubscribeNodeListLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryUserSubscribeNodeListLogic) QueryUserSubscribeNodeList() (resp *types.QueryUserSubscribeNodeListResponse, err error) {
|
||||
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")
|
||||
}
|
||||
|
||||
userSubscribes, err := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, u.Id, 1, 2)
|
||||
if err != nil {
|
||||
logger.Errorw("failed to query user subscribe", logger.Field("error", err.Error()), logger.Field("user_id", u.Id))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "DB_ERROR")
|
||||
}
|
||||
|
||||
resp = &types.QueryUserSubscribeNodeListResponse{}
|
||||
for _, us := range userSubscribes {
|
||||
userSubscribe, err := l.getUserSubscribe(us.Token)
|
||||
if err != nil {
|
||||
l.Errorw("[SubscribeLogic] Get user subscribe failed", logger.Field("error", err.Error()), logger.Field("token", userSubscribe.Token))
|
||||
return nil, err
|
||||
}
|
||||
nodes, err := l.getServers(userSubscribe)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userSubscribeInfo := types.UserSubscribeInfo{
|
||||
Id: userSubscribe.Id,
|
||||
Nodes: nodes,
|
||||
Traffic: userSubscribe.Traffic,
|
||||
Upload: userSubscribe.Upload,
|
||||
Download: userSubscribe.Download,
|
||||
Token: userSubscribe.Token,
|
||||
UserId: userSubscribe.UserId,
|
||||
OrderId: userSubscribe.OrderId,
|
||||
SubscribeId: userSubscribe.SubscribeId,
|
||||
StartTime: userSubscribe.StartTime.Unix(),
|
||||
ExpireTime: userSubscribe.ExpireTime.Unix(),
|
||||
Status: userSubscribe.Status,
|
||||
CreatedAt: userSubscribe.CreatedAt.Unix(),
|
||||
UpdatedAt: userSubscribe.UpdatedAt.Unix(),
|
||||
}
|
||||
|
||||
if userSubscribe.FinishedAt != nil {
|
||||
userSubscribeInfo.FinishedAt = userSubscribe.FinishedAt.Unix()
|
||||
}
|
||||
|
||||
if l.svcCtx.Config.Register.EnableTrial && l.svcCtx.Config.Register.TrialSubscribe == userSubscribe.SubscribeId {
|
||||
userSubscribeInfo.IsTryOut = true
|
||||
}
|
||||
|
||||
resp.List = append(resp.List, userSubscribeInfo)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *QueryUserSubscribeNodeListLogic) getServers(userSub *user.Subscribe) (userSubscribeNodes []*types.UserSubscribeNodeInfo, err error) {
|
||||
userSubscribeNodes = make([]*types.UserSubscribeNodeInfo, 0)
|
||||
if l.isSubscriptionExpired(userSub) {
|
||||
return l.createExpiredServers(), nil
|
||||
}
|
||||
|
||||
subDetails, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, userSub.SubscribeId)
|
||||
if err != nil {
|
||||
l.Errorw("[Generate Subscribe]find subscribe details error: %v", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe details error: %v", err.Error())
|
||||
}
|
||||
nodeIds := tool.StringToInt64Slice(subDetails.Nodes)
|
||||
tags := strings.Split(subDetails.NodeTags, ",")
|
||||
|
||||
l.Debugf("[Generate Subscribe]nodes: %v, NodeTags: %v", nodeIds, tags)
|
||||
|
||||
enable := true
|
||||
|
||||
_, nodes, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
||||
Page: 0,
|
||||
Size: 1000,
|
||||
NodeId: nodeIds,
|
||||
Enabled: &enable, // Only get enabled nodes
|
||||
})
|
||||
|
||||
if len(nodes) > 0 {
|
||||
var serverMapIds = make(map[int64]*node.Server)
|
||||
for _, n := range nodes {
|
||||
serverMapIds[n.ServerId] = nil
|
||||
}
|
||||
var serverIds []int64
|
||||
for k := range serverMapIds {
|
||||
serverIds = append(serverIds, k)
|
||||
}
|
||||
|
||||
servers, err := l.svcCtx.NodeModel.QueryServerList(l.ctx, serverIds)
|
||||
if err != nil {
|
||||
l.Errorw("[Generate Subscribe]find server details error: %v", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server details error: %v", err.Error())
|
||||
}
|
||||
|
||||
for _, s := range servers {
|
||||
serverMapIds[s.Id] = s
|
||||
}
|
||||
|
||||
for _, n := range nodes {
|
||||
server := serverMapIds[n.ServerId]
|
||||
if server == nil {
|
||||
continue
|
||||
}
|
||||
userSubscribeNode := &types.UserSubscribeNodeInfo{
|
||||
Id: n.Id,
|
||||
Name: n.Name,
|
||||
Uuid: userSub.UUID,
|
||||
Protocol: n.Protocol,
|
||||
Port: n.Port,
|
||||
Address: n.Address,
|
||||
Tags: strings.Split(n.Tags, ","),
|
||||
Country: server.Country,
|
||||
City: server.City,
|
||||
CreatedAt: n.CreatedAt.Unix(),
|
||||
}
|
||||
userSubscribeNodes = append(userSubscribeNodes, userSubscribeNode)
|
||||
}
|
||||
}
|
||||
|
||||
l.Debugf("[Query Subscribe]found servers: %v", len(nodes))
|
||||
|
||||
if err != nil {
|
||||
l.Errorw("[Generate Subscribe]find server details error: %v", logger.Field("error", err.Error()))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server details error: %v", err.Error())
|
||||
}
|
||||
logger.Debugf("[Generate Subscribe]found servers: %v", len(nodes))
|
||||
return userSubscribeNodes, nil
|
||||
}
|
||||
|
||||
func (l *QueryUserSubscribeNodeListLogic) isSubscriptionExpired(userSub *user.Subscribe) bool {
|
||||
return userSub.ExpireTime.Unix() < time.Now().Unix() && userSub.ExpireTime.Unix() != 0
|
||||
}
|
||||
|
||||
func (l *QueryUserSubscribeNodeListLogic) createExpiredServers() []*types.UserSubscribeNodeInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *QueryUserSubscribeNodeListLogic) getFirstHostLine() string {
|
||||
host := l.svcCtx.Config.Host
|
||||
lines := strings.Split(host, "\n")
|
||||
if len(lines) > 0 {
|
||||
return lines[0]
|
||||
}
|
||||
return host
|
||||
}
|
||||
func (l *QueryUserSubscribeNodeListLogic) getUserSubscribe(token string) (*user.Subscribe, error) {
|
||||
userSub, err := l.svcCtx.UserModel.FindOneSubscribeByToken(l.ctx, token)
|
||||
if err != nil {
|
||||
l.Infow("[Generate Subscribe]find subscribe error: %v", logger.Field("error", err.Error()), logger.Field("token", token))
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error())
|
||||
}
|
||||
|
||||
// Ignore expiration check
|
||||
//if userSub.Status > 1 {
|
||||
// l.Infow("[Generate Subscribe]subscribe is not available", logger.Field("status", int(userSub.Status)), logger.Field("token", token))
|
||||
// return nil, errors.Wrapf(xerr.NewErrCode(xerr.SubscribeNotAvailable), "subscribe is not available")
|
||||
//}
|
||||
|
||||
return userSub, nil
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/log"
|
||||
"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/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type CommissionWithdrawLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Commission Withdraw
|
||||
func NewCommissionWithdrawLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CommissionWithdrawLogic {
|
||||
return &CommissionWithdrawLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CommissionWithdrawLogic) CommissionWithdraw(req *types.CommissionWithdrawRequest) (resp *types.WithdrawalLog, err error) {
|
||||
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")
|
||||
}
|
||||
|
||||
if u.Commission < req.Amount {
|
||||
logger.Errorf("User %d has insufficient commission balance: %.2f, requested: %.2f", u.Id, float64(u.Commission)/100, float64(req.Amount)/100)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserCommissionNotEnough), "User %d has insufficient commission balance", u.Id)
|
||||
}
|
||||
|
||||
tx := l.svcCtx.DB.WithContext(l.ctx).Begin()
|
||||
|
||||
// update user commission balance
|
||||
u.Commission -= req.Amount
|
||||
if err = l.svcCtx.UserModel.Update(l.ctx, u, tx); err != nil {
|
||||
tx.Rollback()
|
||||
l.Errorf("Failed to update user %d commission balance: %v", u.Id, err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Failed to update user %d commission balance: %v", u.Id, err)
|
||||
}
|
||||
|
||||
// create withdrawal log
|
||||
logInfo := log.Commission{
|
||||
Type: log.CommissionTypeConvertBalance,
|
||||
Amount: req.Amount,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
}
|
||||
b, err := logInfo.Marshal()
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
l.Errorf("Failed to marshal commission log for user %d: %v", u.Id, err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Failed to marshal commission log for user %d: %v", u.Id, err)
|
||||
}
|
||||
|
||||
err = tx.Model(log.SystemLog{}).Create(&log.SystemLog{
|
||||
Type: log.TypeCommission.Uint8(),
|
||||
Date: time.Now().Format("2006-01-02"),
|
||||
ObjectID: u.Id,
|
||||
Content: string(b),
|
||||
CreatedAt: time.Now(),
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
l.Errorf("Failed to create commission log for user %d: %v", u.Id, err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Failed to create commission log for user %d: %v", u.Id, err)
|
||||
}
|
||||
|
||||
err = tx.Model(&user.Withdrawal{}).Create(&user.Withdrawal{
|
||||
UserId: u.Id,
|
||||
Amount: req.Amount,
|
||||
Content: req.Content,
|
||||
Status: 0,
|
||||
Reason: "",
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
l.Errorf("Failed to create withdrawal log for user %d: %v", u.Id, err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Failed to create withdrawal log for user %d: %v", u.Id, err)
|
||||
}
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
l.Errorf("Transaction commit failed for user %d withdrawal: %v", u.Id, err)
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Transaction commit failed for user %d withdrawal: %v", u.Id, err)
|
||||
}
|
||||
|
||||
return &types.WithdrawalLog{
|
||||
UserId: u.Id,
|
||||
Amount: req.Amount,
|
||||
Content: req.Content,
|
||||
Status: 0,
|
||||
Reason: "",
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/logger"
|
||||
"github.com/perfect-panel/server/pkg/tool"
|
||||
)
|
||||
|
||||
type GetDeviceListLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Get Device List
|
||||
func NewGetDeviceListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetDeviceListLogic {
|
||||
return &GetDeviceListLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetDeviceListLogic) GetDeviceList() (resp *types.GetDeviceListResponse, err error) {
|
||||
userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||
list, count, err := l.svcCtx.UserModel.QueryDeviceList(l.ctx, userInfo.Id)
|
||||
userRespList := make([]types.UserDevice, 0)
|
||||
tool.DeepCopy(&userRespList, list)
|
||||
resp = &types.GetDeviceListResponse{
|
||||
Total: count,
|
||||
List: userRespList,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -60,25 +60,8 @@ func (l *QueryUserSubscribeLogic) QueryUserSubscribe() (resp *types.QueryUserSub
|
||||
}
|
||||
}
|
||||
|
||||
// 计算节点数量(通过服务组关联的实际节点数量)
|
||||
if item.Subscribe != nil {
|
||||
// 获取服务组ID列表
|
||||
groupIds := tool.StringToInt64Slice(item.Subscribe.ServerGroup)
|
||||
|
||||
// 通过服务组查询关联的节点数量
|
||||
servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, groupIds)
|
||||
if err != nil {
|
||||
l.Errorw("[QueryUserSubscribeLogic] FindServerListByGroupIds error", logger.Field("error", err.Error()))
|
||||
sub.Subscribe.ServerCount = 0
|
||||
} else {
|
||||
sub.Subscribe.ServerCount = int64(len(servers))
|
||||
}
|
||||
|
||||
// 保留原始服务器ID列表用于其他用途
|
||||
serverIds := tool.StringToInt64Slice(item.Subscribe.Server)
|
||||
sub.Subscribe.Server = serverIds
|
||||
}
|
||||
|
||||
short, _ := tool.FixedUniqueString(item.Token, 8, "")
|
||||
sub.Short = short
|
||||
sub.ResetTime = calculateNextResetTime(&sub)
|
||||
resp.List = append(resp.List, sub)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
)
|
||||
|
||||
type QueryWithdrawalLogLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewQueryWithdrawalLogLogic Query Withdrawal Log
|
||||
func NewQueryWithdrawalLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryWithdrawalLogLogic {
|
||||
return &QueryWithdrawalLogLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *QueryWithdrawalLogLogic) QueryWithdrawalLog(req *types.QueryWithdrawalLogListRequest) (resp *types.QueryWithdrawalLogListResponse, err error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"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/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UnbindDeviceLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// Unbind Device
|
||||
func NewUnbindDeviceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnbindDeviceLogic {
|
||||
return &UnbindDeviceLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UnbindDeviceLogic) UnbindDevice(req *types.UnbindDeviceRequest) error {
|
||||
userInfo := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||
device, err := l.svcCtx.UserModel.FindOneDevice(l.ctx, req.Id)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DeviceNotExist), "find device")
|
||||
}
|
||||
|
||||
if device.UserId != userInfo.Id {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "device not belong to user")
|
||||
}
|
||||
|
||||
return l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
|
||||
var deleteDevice user.Device
|
||||
err = tx.Model(&deleteDevice).Where("id = ?", req.Id).First(&deleteDevice).Error
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.QueueEnqueueError), "find device err: %v", err)
|
||||
}
|
||||
err = tx.Delete(deleteDevice).Error
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete device err: %v", err)
|
||||
}
|
||||
var userAuth user.AuthMethods
|
||||
err = tx.Model(&userAuth).Where("auth_identifier = ? and auth_type = ?", deleteDevice.Identifier, "device").First(&userAuth).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find device online record err: %v", err)
|
||||
}
|
||||
|
||||
err = tx.Delete(&userAuth).Error
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete device online record err: %v", err)
|
||||
}
|
||||
sessionId := l.ctx.Value(constant.CtxKeySessionID)
|
||||
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
||||
l.svcCtx.Redis.Del(l.ctx, sessionIdCacheKey)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"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/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type UpdateUserRulesLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewUpdateUserRulesLogic Update User Rules
|
||||
func NewUpdateUserRulesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserRulesLogic {
|
||||
return &UpdateUserRulesLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UpdateUserRulesLogic) UpdateUserRules(req *types.UpdateUserRulesRequest) error {
|
||||
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||
if !ok {
|
||||
logger.Error("current user is not found in context")
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||
}
|
||||
if len(req.Rules) > 0 {
|
||||
bytes, err := json.Marshal(req.Rules)
|
||||
if err != nil {
|
||||
l.Logger.Errorf("UpdateUserRulesLogic json marshal rules error: %v", err)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "json marshal rules failed: %v", err.Error())
|
||||
}
|
||||
u.Rules = string(bytes)
|
||||
err = l.svcCtx.UserModel.Update(l.ctx, u)
|
||||
if err != nil {
|
||||
l.Logger.Errorf("UpdateUserRulesLogic UpdateUserRules error: %v", err)
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user rules failed: %v", err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
|
||||
"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/logger"
|
||||
"github.com/perfect-panel/server/pkg/tool"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type UpdateUserSubscribeNoteLogic struct {
|
||||
logger.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
// NewUpdateUserSubscribeNoteLogic Update User Subscribe Note
|
||||
func NewUpdateUserSubscribeNoteLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserSubscribeNoteLogic {
|
||||
return &UpdateUserSubscribeNoteLogic{
|
||||
Logger: logger.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UpdateUserSubscribeNoteLogic) UpdateUserSubscribeNote(req *types.UpdateUserSubscribeNoteRequest) error {
|
||||
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||
if !ok {
|
||||
logger.Error("current user is not found in context")
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||
}
|
||||
|
||||
userSub, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, req.UserSubscribeId)
|
||||
if err != nil {
|
||||
l.Errorw("FindOneUserSubscribe failed:", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneUserSubscribe failed: %v", err.Error())
|
||||
}
|
||||
|
||||
if userSub.UserId != u.Id {
|
||||
l.Errorw("UserSubscribeId does not belong to the current user")
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "UserSubscribeId does not belong to the current user")
|
||||
}
|
||||
|
||||
userSub.Note = req.Note
|
||||
var newSub user.Subscribe
|
||||
tool.DeepCopy(&newSub, userSub)
|
||||
|
||||
err = l.svcCtx.UserModel.UpdateSubscribe(l.ctx, &newSub)
|
||||
if err != nil {
|
||||
l.Errorw("UpdateSubscribe failed:", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "UpdateSubscribe failed: %v", err.Error())
|
||||
}
|
||||
|
||||
// Clear user subscription cache
|
||||
if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, &newSub); err != nil {
|
||||
l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("userSubscribeId", userSub.Id))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "ClearSubscribeCache failed: %v", err.Error())
|
||||
}
|
||||
|
||||
// Clear subscription cache
|
||||
if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSub.SubscribeId); err != nil {
|
||||
l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("subscribeId", userSub.SubscribeId))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "ClearSubscribeCache failed: %v", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -9,7 +9,9 @@ const (
|
||||
AnyTLS = "anytls"
|
||||
Tuic = "tuic"
|
||||
Hysteria = "hysteria"
|
||||
Hysteria2 = "hysteria2"
|
||||
// Deprecated: Hysteria2 is deprecated, use Hysteria instead
|
||||
// TODO: remove in future versions
|
||||
Hysteria2 = "hysteria2"
|
||||
)
|
||||
|
||||
type SecurityConfig struct {
|
||||
|
||||
@@ -57,13 +57,19 @@ func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// compatible hysteria2, remove in future versions
|
||||
protocolRequest := req.Protocol
|
||||
if protocolRequest == Hysteria2 {
|
||||
protocolRequest = Hysteria
|
||||
}
|
||||
|
||||
protocols, err := data.UnmarshalProtocols()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfg map[string]interface{}
|
||||
for _, protocol := range protocols {
|
||||
if protocol.Type == req.Protocol {
|
||||
if protocol.Type == protocolRequest {
|
||||
cfg = l.compatible(protocol)
|
||||
break
|
||||
}
|
||||
@@ -209,7 +215,7 @@ func (l *GetServerConfigLogic) compatible(config node.Protocol) map[string]inter
|
||||
RealityShortId: config.RealityShortId,
|
||||
},
|
||||
}
|
||||
case Hysteria2, Hysteria:
|
||||
case Hysteria:
|
||||
result = Hysteria2Node{
|
||||
Port: config.Port,
|
||||
HopPorts: config.HopPorts,
|
||||
|
||||
@@ -249,8 +249,9 @@ func (l *SubscribeLogic) createExpiredServers() []*node.Node {
|
||||
Port: 18080,
|
||||
Address: "127.0.0.1",
|
||||
Server: &node.Server{
|
||||
Id: 1,
|
||||
Name: "Subscribe Expired",
|
||||
Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||
Protocols: "[{\"type\":\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||
},
|
||||
Protocol: "shadowsocks",
|
||||
Enabled: &enable,
|
||||
@@ -261,8 +262,9 @@ func (l *SubscribeLogic) createExpiredServers() []*node.Node {
|
||||
Port: 18080,
|
||||
Address: "127.0.0.1",
|
||||
Server: &node.Server{
|
||||
Id: 1,
|
||||
Name: "Subscribe Expired",
|
||||
Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||
Protocols: "[{\"type\":\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||
},
|
||||
Protocol: "shadowsocks",
|
||||
Enabled: &enable,
|
||||
|
||||
Reference in New Issue
Block a user