Merge branch 'old' into main
Some checks failed
Build docker and publish / build (20.15.1) (push) Has been cancelled
Some checks failed
Build docker and publish / build (20.15.1) (push) Has been cancelled
This commit is contained in:
commit
544cfb5c99
@ -102,35 +102,53 @@ func (adapter *Adapter) Proxies(servers []*node.Node) ([]Proxy, error) {
|
|||||||
for _, protocol := range protocols {
|
for _, protocol := range protocols {
|
||||||
if protocol.Type == item.Protocol {
|
if protocol.Type == item.Protocol {
|
||||||
proxies = append(proxies, Proxy{
|
proxies = append(proxies, Proxy{
|
||||||
Sort: item.Sort,
|
Sort: item.Sort,
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
Server: item.Address,
|
Server: item.Address,
|
||||||
Port: item.Port,
|
Port: item.Port,
|
||||||
Type: item.Protocol,
|
Type: item.Protocol,
|
||||||
Tags: strings.Split(item.Tags, ","),
|
Tags: strings.Split(item.Tags, ","),
|
||||||
Security: protocol.Security,
|
Security: protocol.Security,
|
||||||
SNI: protocol.SNI,
|
SNI: protocol.SNI,
|
||||||
AllowInsecure: protocol.AllowInsecure,
|
AllowInsecure: protocol.AllowInsecure,
|
||||||
Fingerprint: protocol.Fingerprint,
|
Fingerprint: protocol.Fingerprint,
|
||||||
RealityServerAddr: protocol.RealityServerAddr,
|
RealityServerAddr: protocol.RealityServerAddr,
|
||||||
RealityServerPort: protocol.RealityServerPort,
|
RealityServerPort: protocol.RealityServerPort,
|
||||||
RealityPrivateKey: protocol.RealityPrivateKey,
|
RealityPrivateKey: protocol.RealityPrivateKey,
|
||||||
RealityPublicKey: protocol.RealityPublicKey,
|
RealityPublicKey: protocol.RealityPublicKey,
|
||||||
RealityShortId: protocol.RealityShortId,
|
RealityShortId: protocol.RealityShortId,
|
||||||
Transport: protocol.Transport,
|
Transport: protocol.Transport,
|
||||||
Host: protocol.Host,
|
Host: protocol.Host,
|
||||||
Path: protocol.Path,
|
Path: protocol.Path,
|
||||||
ServiceName: protocol.ServiceName,
|
ServiceName: protocol.ServiceName,
|
||||||
Method: protocol.Cipher,
|
Method: protocol.Cipher,
|
||||||
ServerKey: protocol.ServerKey,
|
ServerKey: protocol.ServerKey,
|
||||||
Flow: protocol.Flow,
|
Flow: protocol.Flow,
|
||||||
HopPorts: protocol.HopPorts,
|
HopPorts: protocol.HopPorts,
|
||||||
HopInterval: protocol.HopInterval,
|
HopInterval: protocol.HopInterval,
|
||||||
ObfsPassword: protocol.ObfsPassword,
|
ObfsPassword: protocol.ObfsPassword,
|
||||||
DisableSNI: protocol.DisableSNI,
|
UpMbps: protocol.UpMbps,
|
||||||
ReduceRtt: protocol.ReduceRtt,
|
DownMbps: protocol.DownMbps,
|
||||||
UDPRelayMode: protocol.UDPRelayMode,
|
DisableSNI: protocol.DisableSNI,
|
||||||
CongestionController: protocol.CongestionController,
|
ReduceRtt: protocol.ReduceRtt,
|
||||||
|
UDPRelayMode: protocol.UDPRelayMode,
|
||||||
|
CongestionController: protocol.CongestionController,
|
||||||
|
PaddingScheme: protocol.PaddingScheme,
|
||||||
|
Multiplex: protocol.Multiplex,
|
||||||
|
XhttpMode: protocol.XhttpMode,
|
||||||
|
XhttpExtra: protocol.XhttpExtra,
|
||||||
|
Encryption: protocol.Encryption,
|
||||||
|
EncryptionMode: protocol.EncryptionMode,
|
||||||
|
EncryptionRtt: protocol.EncryptionRtt,
|
||||||
|
EncryptionTicket: protocol.EncryptionTicket,
|
||||||
|
EncryptionServerPadding: protocol.EncryptionServerPadding,
|
||||||
|
EncryptionPrivateKey: protocol.EncryptionPrivateKey,
|
||||||
|
EncryptionClientPadding: protocol.EncryptionClientPadding,
|
||||||
|
EncryptionPassword: protocol.EncryptionPassword,
|
||||||
|
Ratio: protocol.Ratio,
|
||||||
|
CertMode: protocol.CertMode,
|
||||||
|
CertDNSProvider: protocol.CertDNSProvider,
|
||||||
|
CertDNSEnv: protocol.CertDNSEnv,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -102,6 +102,9 @@ type (
|
|||||||
BatchDeleteSubscribeRequest {
|
BatchDeleteSubscribeRequest {
|
||||||
Ids []int64 `json:"ids" validate:"required"`
|
Ids []int64 `json:"ids" validate:"required"`
|
||||||
}
|
}
|
||||||
|
ResetAllSubscribeTokenResponse {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
@ -157,5 +160,9 @@ service ppanel {
|
|||||||
@doc "Subscribe sort"
|
@doc "Subscribe sort"
|
||||||
@handler SubscribeSort
|
@handler SubscribeSort
|
||||||
post /sort (SubscribeSortRequest)
|
post /sort (SubscribeSortRequest)
|
||||||
|
|
||||||
|
@doc "Reset all subscribe tokens"
|
||||||
|
@handler ResetAllSubscribeToken
|
||||||
|
post /reset_all_token returns (ResetAllSubscribeTokenResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,8 +19,13 @@ type (
|
|||||||
Periods []TimePeriod `json:"periods"`
|
Periods []TimePeriod `json:"periods"`
|
||||||
}
|
}
|
||||||
PreViewNodeMultiplierResponse {
|
PreViewNodeMultiplierResponse {
|
||||||
CurrentTime string `json:"current_time"`
|
CurrentTime string `json:"current_time"`
|
||||||
Ratio float32 `json:"ratio"`
|
Ratio float32 `json:"ratio"`
|
||||||
|
}
|
||||||
|
ModuleConfig {
|
||||||
|
Secret string `json:"secret"` // 通讯密钥
|
||||||
|
ServiceName string `json:"service_name"` // 服务名称
|
||||||
|
ServiceVersion string `json:"service_version"` // 服务版本
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -125,5 +130,9 @@ service ppanel {
|
|||||||
@doc "PreView Node Multiplier"
|
@doc "PreView Node Multiplier"
|
||||||
@handler PreViewNodeMultiplier
|
@handler PreViewNodeMultiplier
|
||||||
get /node_multiplier/preview returns (PreViewNodeMultiplierResponse)
|
get /node_multiplier/preview returns (PreViewNodeMultiplierResponse)
|
||||||
|
|
||||||
|
@doc "Get Module Config"
|
||||||
|
@handler GetModuleConfig
|
||||||
|
get /module returns (ModuleConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,14 @@ type (
|
|||||||
VersionResponse {
|
VersionResponse {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
QueryIPLocationRequest {
|
||||||
|
IP string `form:"ip" validate:"required"`
|
||||||
|
}
|
||||||
|
QueryIPLocationResponse {
|
||||||
|
Country string `json:"country"`
|
||||||
|
Region string `json:"region,omitempty"`
|
||||||
|
City string `json:"city"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
@ -36,5 +44,9 @@ service ppanel {
|
|||||||
@doc "Get Version"
|
@doc "Get Version"
|
||||||
@handler GetVersion
|
@handler GetVersion
|
||||||
get /version returns (VersionResponse)
|
get /version returns (VersionResponse)
|
||||||
|
|
||||||
|
@doc "Query IP Location"
|
||||||
|
@handler QueryIPLocation
|
||||||
|
get /ip/location (QueryIPLocationRequest) returns (QueryIPLocationResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,11 +11,13 @@ info (
|
|||||||
type (
|
type (
|
||||||
// User login request
|
// User login request
|
||||||
UserLoginRequest {
|
UserLoginRequest {
|
||||||
Email string `json:"email" validate:"required"`
|
Identifier string `json:"identifier"`
|
||||||
Password string `json:"password" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
Password string `json:"password" validate:"required"`
|
||||||
UserAgent string `header:"User-Agent"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
// Check user is exist request
|
// Check user is exist request
|
||||||
CheckUserRequest {
|
CheckUserRequest {
|
||||||
@ -27,22 +29,26 @@ type (
|
|||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
UserRegisterRequest {
|
UserRegisterRequest {
|
||||||
Email string `json:"email" validate:"required"`
|
Identifier string `json:"identifier"`
|
||||||
Password string `json:"password" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Invite string `json:"invite,optional"`
|
Password string `json:"password" validate:"required"`
|
||||||
Code string `json:"code,optional"`
|
Invite string `json:"invite,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
Code string `json:"code,optional"`
|
||||||
UserAgent string `header:"User-Agent"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
ResetPasswordRequest {
|
ResetPasswordRequest {
|
||||||
Email string `json:"email" validate:"required"`
|
Identifier string `json:"identifier"`
|
||||||
Password string `json:"password" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Code string `json:"code,optional"`
|
Password string `json:"password" validate:"required"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
Code string `json:"code,optional"`
|
||||||
UserAgent string `header:"User-Agent"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
LoginResponse {
|
LoginResponse {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
@ -60,12 +66,14 @@ type (
|
|||||||
}
|
}
|
||||||
// login request
|
// login request
|
||||||
TelephoneLoginRequest {
|
TelephoneLoginRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneCode string `json:"telephone_code"`
|
TelephoneCode string `json:"telephone_code"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
// Check user is exist request
|
// Check user is exist request
|
||||||
@ -79,6 +87,7 @@ type (
|
|||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
TelephoneRegisterRequest {
|
TelephoneRegisterRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
@ -86,16 +95,19 @@ type (
|
|||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type,optional"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
TelephoneResetPasswordRequest {
|
TelephoneResetPasswordRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Code string `json:"code,optional"`
|
Code string `json:"code,optional"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
UserAgent string `header:"User-Agent"`
|
UserAgent string `header:"User-Agent"`
|
||||||
|
LoginType string `header:"Login-Type,optional"`
|
||||||
CfToken string `json:"cf_token,optional"`
|
CfToken string `json:"cf_token,optional"`
|
||||||
}
|
}
|
||||||
AppleLoginCallbackRequest {
|
AppleLoginCallbackRequest {
|
||||||
@ -107,11 +119,18 @@ type (
|
|||||||
Code string `form:"code"`
|
Code string `form:"code"`
|
||||||
State string `form:"state"`
|
State string `form:"state"`
|
||||||
}
|
}
|
||||||
|
DeviceLoginRequest {
|
||||||
|
Identifier string `json:"identifier" validate:"required"`
|
||||||
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
|
UserAgent string `json:"user_agent" validate:"required"`
|
||||||
|
CfToken string `json:"cf_token,optional"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
prefix: v1/auth
|
prefix: v1/auth
|
||||||
group: auth
|
group: auth
|
||||||
|
middleware: DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "User login"
|
@doc "User login"
|
||||||
@ -145,6 +164,10 @@ service ppanel {
|
|||||||
@doc "Reset password"
|
@doc "Reset password"
|
||||||
@handler TelephoneResetPassword
|
@handler TelephoneResetPassword
|
||||||
post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse)
|
post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse)
|
||||||
|
|
||||||
|
@doc "Device Login"
|
||||||
|
@handler DeviceLogin
|
||||||
|
post /login/device (DeviceLoginRequest) returns (LoginResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
|
|||||||
@ -87,11 +87,17 @@ type (
|
|||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
List []SubscribeClient `json:"list"`
|
List []SubscribeClient `json:"list"`
|
||||||
}
|
}
|
||||||
|
HeartbeatResponse {
|
||||||
|
Status bool `json:"status"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Timestamp int64 `json:"timestamp,omitempty"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
prefix: v1/common
|
prefix: v1/common
|
||||||
group: common
|
group: common
|
||||||
|
middleware: DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get global config"
|
@doc "Get global config"
|
||||||
@ -129,5 +135,9 @@ service ppanel {
|
|||||||
@doc "Get Client"
|
@doc "Get Client"
|
||||||
@handler GetClient
|
@handler GetClient
|
||||||
get /client returns (GetSubscribeClientResponse)
|
get /client returns (GetSubscribeClientResponse)
|
||||||
|
|
||||||
|
@doc "Heartbeat"
|
||||||
|
@handler Heartbeat
|
||||||
|
get /heartbeat returns (HeartbeatResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import "../types.api"
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/announcement
|
prefix: v1/public/announcement
|
||||||
group: public/announcement
|
group: public/announcement
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Query announcement"
|
@doc "Query announcement"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import "../types.api"
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/document
|
prefix: v1/public/document
|
||||||
group: public/document
|
group: public/document
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get document list"
|
@doc "Get document list"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import "../types.api"
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/order
|
prefix: v1/public/order
|
||||||
group: public/order
|
group: public/order
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Pre create order"
|
@doc "Pre create order"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import "../types.api"
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/payment
|
prefix: v1/public/payment
|
||||||
group: public/payment
|
group: public/payment
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get available payment methods"
|
@doc "Get available payment methods"
|
||||||
|
|||||||
@ -68,8 +68,9 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
prefix: v1/public/portal
|
prefix: v1/public/portal
|
||||||
group: public/portal
|
group: public/portal
|
||||||
|
middleware: DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get available payment methods"
|
@doc "Get available payment methods"
|
||||||
|
|||||||
@ -14,16 +14,54 @@ type (
|
|||||||
QuerySubscribeListRequest {
|
QuerySubscribeListRequest {
|
||||||
Language string `form:"language"`
|
Language string `form:"language"`
|
||||||
}
|
}
|
||||||
|
QueryUserSubscribeNodeListResponse {
|
||||||
|
List []UserSubscribeInfo `json:"list"`
|
||||||
|
}
|
||||||
|
UserSubscribeInfo {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
OrderId int64 `json:"order_id"`
|
||||||
|
SubscribeId int64 `json:"subscribe_id"`
|
||||||
|
StartTime int64 `json:"start_time"`
|
||||||
|
ExpireTime int64 `json:"expire_time"`
|
||||||
|
FinishedAt int64 `json:"finished_at"`
|
||||||
|
ResetTime int64 `json:"reset_time"`
|
||||||
|
Traffic int64 `json:"traffic"`
|
||||||
|
Download int64 `json:"download"`
|
||||||
|
Upload int64 `json:"upload"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Status uint8 `json:"status"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
IsTryOut bool `json:"is_try_out"`
|
||||||
|
Nodes []*UserSubscribeNodeInfo `json:"nodes"`
|
||||||
|
}
|
||||||
|
UserSubscribeNodeInfo {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
City string `json:"city"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
prefix: v1/public/subscribe
|
prefix: v1/public/subscribe
|
||||||
group: public/subscribe
|
group: public/subscribe
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get subscribe list"
|
@doc "Get subscribe list"
|
||||||
@handler QuerySubscribeList
|
@handler QuerySubscribeList
|
||||||
get /list (QuerySubscribeListRequest) returns (QuerySubscribeListResponse)
|
get /list (QuerySubscribeListRequest) returns (QuerySubscribeListResponse)
|
||||||
|
|
||||||
|
@doc "Get user subscribe node info"
|
||||||
|
@handler QueryUserSubscribeNodeList
|
||||||
|
get /node/list returns (QueryUserSubscribeNodeListResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ type (
|
|||||||
@server (
|
@server (
|
||||||
prefix: v1/public/ticket
|
prefix: v1/public/ticket
|
||||||
group: public/ticket
|
group: public/ticket
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Get ticket list"
|
@doc "Get ticket list"
|
||||||
|
|||||||
@ -97,12 +97,48 @@ type (
|
|||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Code string `json:"code" validate:"required"`
|
Code string `json:"code" validate:"required"`
|
||||||
}
|
}
|
||||||
|
GetDeviceListResponse {
|
||||||
|
List []UserDevice `json:"list"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
||||||
|
UnbindDeviceRequest {
|
||||||
|
Id int64 `json:"id" validate:"required"`
|
||||||
|
}
|
||||||
|
UpdateUserSubscribeNoteRequest {
|
||||||
|
UserSubscribeId int64 `json:"user_subscribe_id" validate:"required"`
|
||||||
|
Note string `json:"note" validate:"max=500"`
|
||||||
|
}
|
||||||
|
UpdateUserRulesRequest {
|
||||||
|
Rules []string `json:"rules" validate:"required"`
|
||||||
|
}
|
||||||
|
CommissionWithdrawRequest {
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
WithdrawalLog {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Status uint8 `json:"status"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
}
|
||||||
|
QueryWithdrawalLogListRequest {
|
||||||
|
Page int `form:"page"`
|
||||||
|
Size int `form:"size"`
|
||||||
|
}
|
||||||
|
QueryWithdrawalLogListResponse {
|
||||||
|
List []WithdrawalLog `json:"list"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
prefix: v1/public/user
|
prefix: v1/public/user
|
||||||
group: public/user
|
group: public/user
|
||||||
middleware: AuthMiddleware
|
middleware: AuthMiddleware,DeviceMiddleware
|
||||||
)
|
)
|
||||||
service ppanel {
|
service ppanel {
|
||||||
@doc "Query User Info"
|
@doc "Query User Info"
|
||||||
@ -192,5 +228,29 @@ service ppanel {
|
|||||||
@doc "Update Bind Email"
|
@doc "Update Bind Email"
|
||||||
@handler UpdateBindEmail
|
@handler UpdateBindEmail
|
||||||
put /bind_email (UpdateBindEmailRequest)
|
put /bind_email (UpdateBindEmailRequest)
|
||||||
|
|
||||||
|
@doc "Get Device List"
|
||||||
|
@handler GetDeviceList
|
||||||
|
get /devices returns (GetDeviceListResponse)
|
||||||
|
|
||||||
|
@doc "Unbind Device"
|
||||||
|
@handler UnbindDevice
|
||||||
|
put /unbind_device (UnbindDeviceRequest)
|
||||||
|
|
||||||
|
@doc "Update User Subscribe Note"
|
||||||
|
@handler UpdateUserSubscribeNote
|
||||||
|
put /subscribe_note (UpdateUserSubscribeNoteRequest)
|
||||||
|
|
||||||
|
@doc "Update User Rules"
|
||||||
|
@handler UpdateUserRules
|
||||||
|
put /rules (UpdateUserRulesRequest)
|
||||||
|
|
||||||
|
@doc "Commission Withdraw"
|
||||||
|
@handler CommissionWithdraw
|
||||||
|
post /commission_withdraw (CommissionWithdrawRequest) returns (WithdrawalLog)
|
||||||
|
|
||||||
|
@doc "Query Withdrawal Log"
|
||||||
|
@handler QueryWithdrawalLog
|
||||||
|
get /withdrawal_log (QueryWithdrawalLogListRequest) returns (QueryWithdrawalLogListResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@ type (
|
|||||||
EnableTradeNotify bool `json:"enable_trade_notify"`
|
EnableTradeNotify bool `json:"enable_trade_notify"`
|
||||||
AuthMethods []UserAuthMethod `json:"auth_methods"`
|
AuthMethods []UserAuthMethod `json:"auth_methods"`
|
||||||
UserDevices []UserDevice `json:"user_devices"`
|
UserDevices []UserDevice `json:"user_devices"`
|
||||||
|
Rules []string `json:"rules"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
DeletedAt int64 `json:"deleted_at,omitempty"`
|
DeletedAt int64 `json:"deleted_at,omitempty"`
|
||||||
@ -115,6 +116,7 @@ type (
|
|||||||
AuthConfig {
|
AuthConfig {
|
||||||
Mobile MobileAuthenticateConfig `json:"mobile"`
|
Mobile MobileAuthenticateConfig `json:"mobile"`
|
||||||
Email EmailAuthticateConfig `json:"email"`
|
Email EmailAuthticateConfig `json:"email"`
|
||||||
|
Device DeviceAuthticateConfig `json:"device"`
|
||||||
Register PubilcRegisterConfig `json:"register"`
|
Register PubilcRegisterConfig `json:"register"`
|
||||||
}
|
}
|
||||||
PubilcRegisterConfig {
|
PubilcRegisterConfig {
|
||||||
@ -134,6 +136,12 @@ type (
|
|||||||
EnableDomainSuffix bool `json:"enable_domain_suffix"`
|
EnableDomainSuffix bool `json:"enable_domain_suffix"`
|
||||||
DomainSuffixList string `json:"domain_suffix_list"`
|
DomainSuffixList string `json:"domain_suffix_list"`
|
||||||
}
|
}
|
||||||
|
DeviceAuthticateConfig {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
ShowAds bool `json:"show_ads"`
|
||||||
|
EnableSecurity bool `json:"enable_security"`
|
||||||
|
OnlyRealDevice bool `json:"only_real_device"`
|
||||||
|
}
|
||||||
RegisterConfig {
|
RegisterConfig {
|
||||||
StopRegister bool `json:"stop_register"`
|
StopRegister bool `json:"stop_register"`
|
||||||
EnableTrial bool `json:"enable_trial"`
|
EnableTrial bool `json:"enable_trial"`
|
||||||
@ -464,6 +472,7 @@ type (
|
|||||||
Upload int64 `json:"upload"`
|
Upload int64 `json:"upload"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
Status uint8 `json:"status"`
|
Status uint8 `json:"status"`
|
||||||
|
Short string `json:"short"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
42
cmd/migrate.go
Normal file
42
cmd/migrate.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/initialize"
|
||||||
|
"github.com/perfect-panel/server/internal/config"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/conf"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
migrateCmd.Flags().StringVar(&migrateConfigPath, "config", "etc/ppanel.yaml", "ppanel.yaml directory to read from")
|
||||||
|
rootCmd.AddCommand(migrateCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
migrateConfigPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
var migrateCmd = &cobra.Command{
|
||||||
|
Use: "migrate",
|
||||||
|
Short: "Run database migrations",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
runMigrate()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMigrate() {
|
||||||
|
var c config.Config
|
||||||
|
conf.MustLoad(migrateConfigPath, &c)
|
||||||
|
if err := logger.SetUp(c.Logger); err != nil {
|
||||||
|
fmt.Println("Logger setup failed:", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
ctx := svc.NewServiceContext(c)
|
||||||
|
initialize.Migrate(ctx)
|
||||||
|
logger.Info("[Migrate] database migration completed")
|
||||||
|
}
|
||||||
8
go.mod
8
go.mod
@ -44,7 +44,7 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk v1.29.0
|
go.opentelemetry.io/otel/sdk v1.29.0
|
||||||
go.opentelemetry.io/otel/trace v1.29.0
|
go.opentelemetry.io/otel/trace v1.29.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.35.0
|
||||||
golang.org/x/oauth2 v0.25.0
|
golang.org/x/oauth2 v0.25.0
|
||||||
golang.org/x/time v0.6.0
|
golang.org/x/time v0.6.0
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
@ -60,6 +60,7 @@ require (
|
|||||||
github.com/fatih/color v1.18.0
|
github.com/fatih/color v1.18.0
|
||||||
github.com/goccy/go-json v0.10.4
|
github.com/goccy/go-json v0.10.4
|
||||||
github.com/golang-migrate/migrate/v4 v4.18.2
|
github.com/golang-migrate/migrate/v4 v4.18.2
|
||||||
|
github.com/oschwald/geoip2-golang v1.13.0
|
||||||
github.com/spaolacci/murmur3 v1.1.0
|
github.com/spaolacci/murmur3 v1.1.0
|
||||||
google.golang.org/grpc v1.64.1
|
google.golang.org/grpc v1.64.1
|
||||||
google.golang.org/protobuf v1.36.3
|
google.golang.org/protobuf v1.36.3
|
||||||
@ -117,6 +118,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/openzipkin/zipkin-go v0.4.2 // indirect
|
github.com/openzipkin/zipkin-go v0.4.2 // indirect
|
||||||
|
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
@ -138,8 +140,8 @@ require (
|
|||||||
golang.org/x/arch v0.13.0 // indirect
|
golang.org/x/arch v0.13.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect
|
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
|
|||||||
16
go.sum
16
go.sum
@ -285,6 +285,10 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
|
|||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA=
|
github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA=
|
||||||
github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY=
|
github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY=
|
||||||
|
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
|
||||||
|
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
||||||
|
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
|
||||||
|
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@ -402,8 +406,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
|
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4=
|
||||||
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
@ -463,8 +467,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@ -478,8 +482,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/report"
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
|
|
||||||
@ -35,10 +36,30 @@ func Config(path string) (chan bool, *http.Server) {
|
|||||||
configPath = path
|
configPath = path
|
||||||
// Create a new Gin instance
|
// Create a new Gin instance
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
// get server port
|
||||||
|
port := 8080
|
||||||
|
host := "127.0.0.1"
|
||||||
|
|
||||||
|
// check gateway mode
|
||||||
|
if report.IsGatewayMode() {
|
||||||
|
// get free port
|
||||||
|
freePort, err := report.ModulePort()
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("get module port error: %s", err.Error())
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
port = freePort
|
||||||
|
// register module
|
||||||
|
err = report.RegisterModule(port)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("register module error: %s", err.Error())
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
logger.Infof("module registered on port %d", port)
|
||||||
|
}
|
||||||
// Create a new HTTP server
|
// Create a new HTTP server
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: ":8080",
|
Addr: fmt.Sprintf("%s:%d", host, port),
|
||||||
Handler: r,
|
Handler: r,
|
||||||
}
|
}
|
||||||
// Load templates
|
// Load templates
|
||||||
|
|||||||
26
initialize/device.go
Normal file
26
initialize/device.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/config"
|
||||||
|
"github.com/perfect-panel/server/internal/model/auth"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Device(ctx *svc.ServiceContext) {
|
||||||
|
logger.Debug("device config initialization")
|
||||||
|
method, err := ctx.AuthModel.FindOneByMethod(context.Background(), "device")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var cfg config.DeviceConfig
|
||||||
|
var deviceConfig auth.DeviceConfig
|
||||||
|
deviceConfig.Unmarshal(method.Config)
|
||||||
|
tool.DeepCopy(&cfg, deviceConfig)
|
||||||
|
cfg.Enable = *method.Enabled
|
||||||
|
ctx.Config.Device = cfg
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ func StartInitSystemConfig(svc *svc.ServiceContext) {
|
|||||||
Site(svc)
|
Site(svc)
|
||||||
Node(svc)
|
Node(svc)
|
||||||
Email(svc)
|
Email(svc)
|
||||||
|
Device(svc)
|
||||||
Invite(svc)
|
Invite(svc)
|
||||||
Verify(svc)
|
Verify(svc)
|
||||||
Subscribe(svc)
|
Subscribe(svc)
|
||||||
|
|||||||
0
initialize/migrate/database/02115_ads.down.sql
Normal file
0
initialize/migrate/database/02115_ads.down.sql
Normal file
20
initialize/migrate/database/02115_ads.up.sql
Normal file
20
initialize/migrate/database/02115_ads.up.sql
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
-- 只有当 ads 表中不存在 description 字段时才添加
|
||||||
|
SET
|
||||||
|
@col_exists := (
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'ads'
|
||||||
|
AND COLUMN_NAME = 'description'
|
||||||
|
);
|
||||||
|
|
||||||
|
SET
|
||||||
|
@query := IF(
|
||||||
|
@col_exists = 0,
|
||||||
|
'ALTER TABLE `ads` ADD COLUMN `description` VARCHAR(255) DEFAULT '''' COMMENT ''Description'';',
|
||||||
|
'SELECT "Column `description` already exists"'
|
||||||
|
);
|
||||||
|
|
||||||
|
PREPARE stmt FROM @query;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
3
initialize/migrate/database/02116_user_algo.down.sql
Normal file
3
initialize/migrate/database/02116_user_algo.down.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE `user`
|
||||||
|
DROP COLUMN `algo`,
|
||||||
|
DROP COLUMN `salt`;
|
||||||
35
initialize/migrate/database/02116_user_algo.up.sql
Normal file
35
initialize/migrate/database/02116_user_algo.up.sql
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
-- 添加 algo 列(如果不存在)
|
||||||
|
SET @dbname = DATABASE();
|
||||||
|
SET @tablename = 'user';
|
||||||
|
SET @colname = 'algo';
|
||||||
|
SET @sql = (
|
||||||
|
SELECT IF(
|
||||||
|
COUNT(*) = 0,
|
||||||
|
'ALTER TABLE `user` ADD COLUMN `algo` VARCHAR(20) NOT NULL DEFAULT ''default'' COMMENT ''Encryption Algorithm'' AFTER `password`;',
|
||||||
|
'SELECT "Column `algo` already exists";'
|
||||||
|
)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @dbname
|
||||||
|
AND TABLE_NAME = @tablename
|
||||||
|
AND COLUMN_NAME = @colname
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- 添加 salt 列(如果不存在)
|
||||||
|
SET @colname = 'salt';
|
||||||
|
SET @sql = (
|
||||||
|
SELECT IF(
|
||||||
|
COUNT(*) = 0,
|
||||||
|
'ALTER TABLE `user` ADD COLUMN `salt` VARCHAR(20) NOT NULL DEFAULT ''default'' COMMENT ''Password Salt'' AFTER `algo`;',
|
||||||
|
'SELECT "Column `salt` already exists";'
|
||||||
|
)
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @dbname
|
||||||
|
AND TABLE_NAME = @tablename
|
||||||
|
AND COLUMN_NAME = @colname
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
INSERT INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
|
||||||
|
SELECT 'site', 'CustomData', '{
|
||||||
|
"kr_website_id": ""
|
||||||
|
}', 'string', 'Custom Data', '2025-04-22 14:25:16.637', '2025-10-14 15:47:19.187'
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `system` WHERE `category` = 'site' AND `key` = 'CustomData'
|
||||||
|
);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
INSERT INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
|
||||||
|
SELECT 'site', 'CustomData', '{
|
||||||
|
"kr_website_id": ""
|
||||||
|
}', 'string', 'Custom Data', '2025-04-22 14:25:16.637', '2025-10-14 15:47:19.187'
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `system` WHERE `category` = 'site' AND `key` = 'CustomData'
|
||||||
|
);
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE traffic_log DROP INDEX idx_timestamp;
|
||||||
1
initialize/migrate/database/02118_traffic_log_idx.up.sql
Normal file
1
initialize/migrate/database/02118_traffic_log_idx.up.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE traffic_log ADD INDEX idx_timestamp (timestamp);
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `user_subscribe`
|
||||||
|
DROP COLUMN `note`;
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE `user_subscribe`
|
||||||
|
ADD COLUMN `note` VARCHAR(500) NOT NULL DEFAULT ''
|
||||||
|
COMMENT 'User note for subscription'
|
||||||
|
AFTER `status`;
|
||||||
2
initialize/migrate/database/02120_user_rules.down.sql
Normal file
2
initialize/migrate/database/02120_user_rules.down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `user`
|
||||||
|
DROP COLUMN IF EXISTS `rules`;
|
||||||
4
initialize/migrate/database/02120_user_rules.up.sql
Normal file
4
initialize/migrate/database/02120_user_rules.up.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE `user`
|
||||||
|
ADD COLUMN `rules` TEXT NULL
|
||||||
|
COMMENT 'User rules for subscription'
|
||||||
|
AFTER `created_at`;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
DROP TABLE IF EXISTS `withdrawals`;
|
||||||
|
|
||||||
|
DELETE FROM `system`
|
||||||
|
WHERE `category` = 'invite'
|
||||||
|
AND `key` = 'WithdrawalMethod';
|
||||||
16
initialize/migrate/database/02121_user_withdrawal.up.sql
Normal file
16
initialize/migrate/database/02121_user_withdrawal.up.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `withdrawals` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
|
||||||
|
`user_id` BIGINT NOT NULL COMMENT 'User ID',
|
||||||
|
`amount` BIGINT NOT NULL COMMENT 'Withdrawal Amount',
|
||||||
|
`content` TEXT COMMENT 'Withdrawal Content',
|
||||||
|
`status` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Withdrawal Status',
|
||||||
|
`reason` VARCHAR(500) NOT NULL DEFAULT '' COMMENT 'Rejection Reason',
|
||||||
|
`created_at` DATETIME NOT NULL COMMENT 'Creation Time',
|
||||||
|
`updated_at` DATETIME NOT NULL COMMENT 'Update Time',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_user_id` (`user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
|
||||||
|
VALUES
|
||||||
|
('invite', 'WithdrawalMethod', '', 'string', 'withdrawal method', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637');
|
||||||
@ -2,6 +2,7 @@ package initialize
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/user"
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -16,13 +17,17 @@ func Migrate(ctx *svc.ServiceContext) {
|
|||||||
mc := orm.Mysql{
|
mc := orm.Mysql{
|
||||||
Config: ctx.Config.MySQL,
|
Config: ctx.Config.MySQL,
|
||||||
}
|
}
|
||||||
|
now := time.Now()
|
||||||
if err := migrate.Migrate(mc.Dsn()).Up(); err != nil {
|
if err := migrate.Migrate(mc.Dsn()).Up(); err != nil {
|
||||||
if !errors.Is(err, migrate.NoChange) {
|
if !errors.Is(err, migrate.NoChange) {
|
||||||
logger.Errorf("[Migrate] Up error: %v", err.Error())
|
logger.Errorf("[Migrate] Up error: %v", err.Error())
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
logger.Info("[Migrate] database not change")
|
logger.Info("[Migrate] database not change")
|
||||||
|
} else {
|
||||||
|
logger.Info("[Migrate] Database change, took " + time.Since(now).String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not found admin user
|
// if not found admin user
|
||||||
err := ctx.DB.Transaction(func(tx *gorm.DB) error {
|
err := ctx.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
var count int64
|
var count int64
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
"github.com/perfect-panel/server/pkg/orm"
|
"github.com/perfect-panel/server/pkg/orm"
|
||||||
)
|
)
|
||||||
@ -20,6 +21,7 @@ type Config struct {
|
|||||||
Node NodeConfig `yaml:"Node"`
|
Node NodeConfig `yaml:"Node"`
|
||||||
Mobile MobileConfig `yaml:"Mobile"`
|
Mobile MobileConfig `yaml:"Mobile"`
|
||||||
Email EmailConfig `yaml:"Email"`
|
Email EmailConfig `yaml:"Email"`
|
||||||
|
Device DeviceConfig `yaml:"device"`
|
||||||
Verify Verify `yaml:"Verify"`
|
Verify Verify `yaml:"Verify"`
|
||||||
VerifyCode VerifyCode `yaml:"VerifyCode"`
|
VerifyCode VerifyCode `yaml:"VerifyCode"`
|
||||||
Register RegisterConfig `yaml:"Register"`
|
Register RegisterConfig `yaml:"Register"`
|
||||||
@ -95,6 +97,14 @@ type MobileConfig struct {
|
|||||||
Whitelist []string `yaml:"whitelist"`
|
Whitelist []string `yaml:"whitelist"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeviceConfig struct {
|
||||||
|
Enable bool `yaml:"enable" default:"true"`
|
||||||
|
ShowAds bool `yaml:"show_ads"`
|
||||||
|
EnableSecurity bool `yaml:"enable_security"`
|
||||||
|
OnlyRealDevice bool `yaml:"only_real_device"`
|
||||||
|
SecuritySecret string `yaml:"security_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
type SiteConfig struct {
|
type SiteConfig struct {
|
||||||
Host string `yaml:"Host" default:""`
|
Host string `yaml:"Host" default:""`
|
||||||
SiteName string `yaml:"SiteName" default:""`
|
SiteName string `yaml:"SiteName" default:""`
|
||||||
@ -190,12 +200,12 @@ type File struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type InviteConfig struct {
|
type InviteConfig struct {
|
||||||
ForcedInvite bool `yaml:"ForcedInvite" default:"false"`
|
ForcedInvite bool `yaml:"ForcedInvite" default:"false"`
|
||||||
FirstPurchasePercentage int64 `yaml:"FirstPurchasePercentage" default:"20"`
|
FirstPurchasePercentage int64 `yaml:"FirstPurchasePercentage" default:"20"`
|
||||||
FirstYearlyPurchasePercentage int64 `yaml:"FirstYearlyPurchasePercentage" default:"25"`
|
FirstYearlyPurchasePercentage int64 `yaml:"FirstYearlyPurchasePercentage" default:"25"`
|
||||||
NonFirstPurchasePercentage int64 `yaml:"NonFirstPurchasePercentage" default:"10"`
|
NonFirstPurchasePercentage int64 `yaml:"NonFirstPurchasePercentage" default:"10"`
|
||||||
OnlyFirstPurchase bool `yaml:"OnlyFirstPurchase" default:"true"`
|
ReferralPercentage uint8 `yaml:"ReferralPercentage" default:"10"`
|
||||||
ReferralPercentage int64 `yaml:"ReferralPercentage" default:"20"`
|
OnlyFirstPurchase bool `yaml:"OnlyFirstPurchase" default:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Telegram struct {
|
type Telegram struct {
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
package subscribe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/admin/subscribe"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reset all subscribe tokens
|
||||||
|
func ResetAllSubscribeTokenHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
l := subscribe.NewResetAllSubscribeTokenLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.ResetAllSubscribeToken()
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
internal/handler/admin/system/getModuleConfigHandler.go
Normal file
18
internal/handler/admin/system/getModuleConfigHandler.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/admin/system"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetModuleConfigHandler Get Module Config
|
||||||
|
func GetModuleConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
l := system.NewGetModuleConfigLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.GetModuleConfig()
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/handler/admin/tool/queryIPLocationHandler.go
Normal file
26
internal/handler/admin/tool/queryIPLocationHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueryIPLocationHandler Query IP Location
|
||||||
|
func QueryIPLocationHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.QueryIPLocationRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := tool.NewQueryIPLocationLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.QueryIPLocation(&req)
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/handler/auth/deviceLoginHandler.go
Normal file
26
internal/handler/auth/deviceLoginHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/auth"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device Login
|
||||||
|
func DeviceLoginHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.DeviceLoginRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewDeviceLoginLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.DeviceLogin(&req)
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,6 @@ import (
|
|||||||
// Get Client
|
// Get Client
|
||||||
func GetClientHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
func GetClientHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
l := common.NewGetClientLogic(c.Request.Context(), svcCtx)
|
l := common.NewGetClientLogic(c.Request.Context(), svcCtx)
|
||||||
resp, err := l.GetClient()
|
resp, err := l.GetClient()
|
||||||
result.HttpResult(c, resp, err)
|
result.HttpResult(c, resp, err)
|
||||||
|
|||||||
18
internal/handler/common/heartbeatHandler.go
Normal file
18
internal/handler/common/heartbeatHandler.go
Normal file
@ -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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Heartbeat
|
||||||
|
func HeartbeatHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
l := common.NewHeartbeatLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.Heartbeat()
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package subscribe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/subscribe"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get user subscribe node info
|
||||||
|
func QueryUserSubscribeNodeListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
l := subscribe.NewQueryUserSubscribeNodeListLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.QueryUserSubscribeNodeList()
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/handler/public/user/commissionWithdrawHandler.go
Normal file
26
internal/handler/public/user/commissionWithdrawHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commission Withdraw
|
||||||
|
func CommissionWithdrawHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.CommissionWithdrawRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewCommissionWithdrawLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.CommissionWithdraw(&req)
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
internal/handler/public/user/getDeviceListHandler.go
Normal file
18
internal/handler/public/user/getDeviceListHandler.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get Device List
|
||||||
|
func GetDeviceListHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
l := user.NewGetDeviceListLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.GetDeviceList()
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/handler/public/user/queryWithdrawalLogHandler.go
Normal file
26
internal/handler/public/user/queryWithdrawalLogHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Query Withdrawal Log
|
||||||
|
func QueryWithdrawalLogHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.QueryWithdrawalLogListRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewQueryWithdrawalLogLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.QueryWithdrawalLog(&req)
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/handler/public/user/unbindDeviceHandler.go
Normal file
26
internal/handler/public/user/unbindDeviceHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unbind Device
|
||||||
|
func UnbindDeviceHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.UnbindDeviceRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewUnbindDeviceLogic(c.Request.Context(), svcCtx)
|
||||||
|
err := l.UnbindDevice(&req)
|
||||||
|
result.HttpResult(c, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
internal/handler/public/user/updateUserRulesHandler.go
Normal file
26
internal/handler/public/user/updateUserRulesHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update User Rules
|
||||||
|
func UpdateUserRulesHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.UpdateUserRulesRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewUpdateUserRulesLogic(c.Request.Context(), svcCtx)
|
||||||
|
err := l.UpdateUserRules(&req)
|
||||||
|
result.HttpResult(c, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update User Subscribe Note
|
||||||
|
func UpdateUserSubscribeNoteHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.UpdateUserSubscribeNoteRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewUpdateUserSubscribeNoteLogic(c.Request.Context(), svcCtx)
|
||||||
|
err := l.UpdateUserSubscribeNote(&req)
|
||||||
|
result.HttpResult(c, nil, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -385,6 +385,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// Get subscribe list
|
// Get subscribe list
|
||||||
adminSubscribeGroupRouter.GET("/list", adminSubscribe.GetSubscribeListHandler(serverCtx))
|
adminSubscribeGroupRouter.GET("/list", adminSubscribe.GetSubscribeListHandler(serverCtx))
|
||||||
|
|
||||||
|
// Reset all subscribe tokens
|
||||||
|
adminSubscribeGroupRouter.POST("/reset_all_token", adminSubscribe.ResetAllSubscribeTokenHandler(serverCtx))
|
||||||
|
|
||||||
// Subscribe sort
|
// Subscribe sort
|
||||||
adminSubscribeGroupRouter.POST("/sort", adminSubscribe.SubscribeSortHandler(serverCtx))
|
adminSubscribeGroupRouter.POST("/sort", adminSubscribe.SubscribeSortHandler(serverCtx))
|
||||||
}
|
}
|
||||||
@ -408,6 +411,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// Update invite config
|
// Update invite config
|
||||||
adminSystemGroupRouter.PUT("/invite_config", adminSystem.UpdateInviteConfigHandler(serverCtx))
|
adminSystemGroupRouter.PUT("/invite_config", adminSystem.UpdateInviteConfigHandler(serverCtx))
|
||||||
|
|
||||||
|
// Get Module Config
|
||||||
|
adminSystemGroupRouter.GET("/module", adminSystem.GetModuleConfigHandler(serverCtx))
|
||||||
|
|
||||||
// Get node config
|
// Get node config
|
||||||
adminSystemGroupRouter.GET("/node_config", adminSystem.GetNodeConfigHandler(serverCtx))
|
adminSystemGroupRouter.GET("/node_config", adminSystem.GetNodeConfigHandler(serverCtx))
|
||||||
|
|
||||||
@ -487,6 +493,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
adminToolGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
adminToolGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// Query IP Location
|
||||||
|
adminToolGroupRouter.GET("/ip/location", adminTool.QueryIPLocationHandler(serverCtx))
|
||||||
|
|
||||||
// Get System Log
|
// Get System Log
|
||||||
adminToolGroupRouter.GET("/log", adminTool.GetSystemLogHandler(serverCtx))
|
adminToolGroupRouter.GET("/log", adminTool.GetSystemLogHandler(serverCtx))
|
||||||
|
|
||||||
@ -578,6 +587,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
authGroupRouter := router.Group("/v1/auth")
|
authGroupRouter := router.Group("/v1/auth")
|
||||||
|
authGroupRouter.Use(middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Check user is exist
|
// Check user is exist
|
||||||
@ -589,6 +599,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// User login
|
// User login
|
||||||
authGroupRouter.POST("/login", auth.UserLoginHandler(serverCtx))
|
authGroupRouter.POST("/login", auth.UserLoginHandler(serverCtx))
|
||||||
|
|
||||||
|
// Device Login
|
||||||
|
authGroupRouter.POST("/login/device", auth.DeviceLoginHandler(serverCtx))
|
||||||
|
|
||||||
// User Telephone login
|
// User Telephone login
|
||||||
authGroupRouter.POST("/login/telephone", auth.TelephoneLoginHandler(serverCtx))
|
authGroupRouter.POST("/login/telephone", auth.TelephoneLoginHandler(serverCtx))
|
||||||
|
|
||||||
@ -619,6 +632,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commonGroupRouter := router.Group("/v1/common")
|
commonGroupRouter := router.Group("/v1/common")
|
||||||
|
commonGroupRouter.Use(middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Get Ads
|
// Get Ads
|
||||||
@ -630,6 +644,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// Get Client
|
// Get Client
|
||||||
commonGroupRouter.GET("/client", common.GetClientHandler(serverCtx))
|
commonGroupRouter.GET("/client", common.GetClientHandler(serverCtx))
|
||||||
|
|
||||||
|
// Heartbeat
|
||||||
|
commonGroupRouter.GET("/heartbeat", common.HeartbeatHandler(serverCtx))
|
||||||
|
|
||||||
// Get verification code
|
// Get verification code
|
||||||
commonGroupRouter.POST("/send_code", common.SendEmailCodeHandler(serverCtx))
|
commonGroupRouter.POST("/send_code", common.SendEmailCodeHandler(serverCtx))
|
||||||
|
|
||||||
@ -650,7 +667,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicAnnouncementGroupRouter := router.Group("/v1/public/announcement")
|
publicAnnouncementGroupRouter := router.Group("/v1/public/announcement")
|
||||||
publicAnnouncementGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicAnnouncementGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Query announcement
|
// Query announcement
|
||||||
@ -658,7 +675,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicDocumentGroupRouter := router.Group("/v1/public/document")
|
publicDocumentGroupRouter := router.Group("/v1/public/document")
|
||||||
publicDocumentGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicDocumentGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Get document detail
|
// Get document detail
|
||||||
@ -669,7 +686,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicOrderGroupRouter := router.Group("/v1/public/order")
|
publicOrderGroupRouter := router.Group("/v1/public/order")
|
||||||
publicOrderGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicOrderGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Close order
|
// Close order
|
||||||
@ -698,7 +715,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicPaymentGroupRouter := router.Group("/v1/public/payment")
|
publicPaymentGroupRouter := router.Group("/v1/public/payment")
|
||||||
publicPaymentGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicPaymentGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Get available payment methods
|
// Get available payment methods
|
||||||
@ -706,6 +723,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicPortalGroupRouter := router.Group("/v1/public/portal")
|
publicPortalGroupRouter := router.Group("/v1/public/portal")
|
||||||
|
publicPortalGroupRouter.Use(middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Purchase Checkout
|
// Purchase Checkout
|
||||||
@ -728,15 +746,18 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicSubscribeGroupRouter := router.Group("/v1/public/subscribe")
|
publicSubscribeGroupRouter := router.Group("/v1/public/subscribe")
|
||||||
publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Get subscribe list
|
// Get subscribe list
|
||||||
publicSubscribeGroupRouter.GET("/list", publicSubscribe.QuerySubscribeListHandler(serverCtx))
|
publicSubscribeGroupRouter.GET("/list", publicSubscribe.QuerySubscribeListHandler(serverCtx))
|
||||||
|
|
||||||
|
// Get user subscribe node info
|
||||||
|
publicSubscribeGroupRouter.GET("/node/list", publicSubscribe.QueryUserSubscribeNodeListHandler(serverCtx))
|
||||||
}
|
}
|
||||||
|
|
||||||
publicTicketGroupRouter := router.Group("/v1/public/ticket")
|
publicTicketGroupRouter := router.Group("/v1/public/ticket")
|
||||||
publicTicketGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicTicketGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Update ticket status
|
// Update ticket status
|
||||||
@ -756,7 +777,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publicUserGroupRouter := router.Group("/v1/public/user")
|
publicUserGroupRouter := router.Group("/v1/public/user")
|
||||||
publicUserGroupRouter.Use(middleware.AuthMiddleware(serverCtx))
|
publicUserGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||||
|
|
||||||
{
|
{
|
||||||
// Query User Affiliate Count
|
// Query User Affiliate Count
|
||||||
@ -786,6 +807,12 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// Query User Commission Log
|
// Query User Commission Log
|
||||||
publicUserGroupRouter.GET("/commission_log", publicUser.QueryUserCommissionLogHandler(serverCtx))
|
publicUserGroupRouter.GET("/commission_log", publicUser.QueryUserCommissionLogHandler(serverCtx))
|
||||||
|
|
||||||
|
// Commission Withdraw
|
||||||
|
publicUserGroupRouter.POST("/commission_withdraw", publicUser.CommissionWithdrawHandler(serverCtx))
|
||||||
|
|
||||||
|
// Get Device List
|
||||||
|
publicUserGroupRouter.GET("/devices", publicUser.GetDeviceListHandler(serverCtx))
|
||||||
|
|
||||||
// Query User Info
|
// Query User Info
|
||||||
publicUserGroupRouter.GET("/info", publicUser.QueryUserInfoHandler(serverCtx))
|
publicUserGroupRouter.GET("/info", publicUser.QueryUserInfoHandler(serverCtx))
|
||||||
|
|
||||||
@ -801,15 +828,24 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// Update User Password
|
// Update User Password
|
||||||
publicUserGroupRouter.PUT("/password", publicUser.UpdateUserPasswordHandler(serverCtx))
|
publicUserGroupRouter.PUT("/password", publicUser.UpdateUserPasswordHandler(serverCtx))
|
||||||
|
|
||||||
|
// Update User Rules
|
||||||
|
publicUserGroupRouter.PUT("/rules", publicUser.UpdateUserRulesHandler(serverCtx))
|
||||||
|
|
||||||
// Query User Subscribe
|
// Query User Subscribe
|
||||||
publicUserGroupRouter.GET("/subscribe", publicUser.QueryUserSubscribeHandler(serverCtx))
|
publicUserGroupRouter.GET("/subscribe", publicUser.QueryUserSubscribeHandler(serverCtx))
|
||||||
|
|
||||||
// Get Subscribe Log
|
// Get Subscribe Log
|
||||||
publicUserGroupRouter.GET("/subscribe_log", publicUser.GetSubscribeLogHandler(serverCtx))
|
publicUserGroupRouter.GET("/subscribe_log", publicUser.GetSubscribeLogHandler(serverCtx))
|
||||||
|
|
||||||
|
// Update User Subscribe Note
|
||||||
|
publicUserGroupRouter.PUT("/subscribe_note", publicUser.UpdateUserSubscribeNoteHandler(serverCtx))
|
||||||
|
|
||||||
// Reset User Subscribe Token
|
// Reset User Subscribe Token
|
||||||
publicUserGroupRouter.PUT("/subscribe_token", publicUser.ResetUserSubscribeTokenHandler(serverCtx))
|
publicUserGroupRouter.PUT("/subscribe_token", publicUser.ResetUserSubscribeTokenHandler(serverCtx))
|
||||||
|
|
||||||
|
// Unbind Device
|
||||||
|
publicUserGroupRouter.PUT("/unbind_device", publicUser.UnbindDeviceHandler(serverCtx))
|
||||||
|
|
||||||
// Unbind OAuth
|
// Unbind OAuth
|
||||||
publicUserGroupRouter.POST("/unbind_oauth", publicUser.UnbindOAuthHandler(serverCtx))
|
publicUserGroupRouter.POST("/unbind_oauth", publicUser.UnbindOAuthHandler(serverCtx))
|
||||||
|
|
||||||
@ -824,6 +860,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
|
|
||||||
// Verify Email
|
// Verify Email
|
||||||
publicUserGroupRouter.POST("/verify_email", publicUser.VerifyEmailHandler(serverCtx))
|
publicUserGroupRouter.POST("/verify_email", publicUser.VerifyEmailHandler(serverCtx))
|
||||||
|
|
||||||
|
// Query Withdrawal Log
|
||||||
|
publicUserGroupRouter.GET("/withdrawal_log", publicUser.QueryWithdrawalLogHandler(serverCtx))
|
||||||
}
|
}
|
||||||
|
|
||||||
serverGroupRouter := router.Group("/v1/server")
|
serverGroupRouter := router.Group("/v1/server")
|
||||||
|
|||||||
@ -36,6 +36,12 @@ func QueryServerProtocolConfigHandler(svcCtx *svc.ServiceContext) func(c *gin.Co
|
|||||||
|
|
||||||
fmt.Printf("[QueryServerProtocolConfigHandler] - ShouldBindQuery request: %+v\n", req)
|
fmt.Printf("[QueryServerProtocolConfigHandler] - ShouldBindQuery request: %+v\n", req)
|
||||||
|
|
||||||
|
if svcCtx.Config.Node.NodeSecret != req.SecretKey {
|
||||||
|
c.String(http.StatusUnauthorized, "Unauthorized")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
l := server.NewQueryServerProtocolConfigLogic(c.Request.Context(), svcCtx)
|
l := server.NewQueryServerProtocolConfigLogic(c.Request.Context(), svcCtx)
|
||||||
resp, err := l.QueryServerProtocolConfig(&req)
|
resp, err := l.QueryServerProtocolConfig(&req)
|
||||||
result.HttpResult(c, resp, err)
|
result.HttpResult(c, resp, err)
|
||||||
|
|||||||
@ -23,6 +23,22 @@ func SubscribeHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
|||||||
ua := c.GetHeader("User-Agent")
|
ua := c.GetHeader("User-Agent")
|
||||||
req.UA = c.Request.Header.Get("User-Agent")
|
req.UA = c.Request.Header.Get("User-Agent")
|
||||||
req.Flag = c.Query("flag")
|
req.Flag = c.Query("flag")
|
||||||
|
if svcCtx.Config.Subscribe.PanDomain {
|
||||||
|
domain := c.Request.Host
|
||||||
|
domainArr := strings.Split(domain, ".")
|
||||||
|
short, err := tool.FixedUniqueString(req.Token, 8, "")
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("[SubscribeHandler] Generate short token failed: %v", err)
|
||||||
|
c.String(http.StatusInternalServerError, "Internal Server")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if short != domainArr[0] {
|
||||||
|
c.String(http.StatusForbidden, "Access denied")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if svcCtx.Config.Subscribe.UserAgentLimit {
|
if svcCtx.Config.Subscribe.UserAgentLimit {
|
||||||
if ua == "" {
|
if ua == "" {
|
||||||
|
|||||||
@ -29,10 +29,12 @@ func NewPreviewSubscribeTemplateLogic(ctx context.Context, svcCtx *svc.ServiceCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) {
|
func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) {
|
||||||
|
enable := true
|
||||||
_, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
_, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
||||||
Page: 1,
|
Page: 1,
|
||||||
Size: 1000,
|
Size: 1000,
|
||||||
Preload: true,
|
Preload: true,
|
||||||
|
Enabled: &enable,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
|
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
|
||||||
|
|||||||
@ -92,6 +92,9 @@ func (l *UpdateAuthMethodConfigLogic) UpdateGlobal(method string) {
|
|||||||
if method == "mobile" {
|
if method == "mobile" {
|
||||||
initialize.Mobile(l.svcCtx)
|
initialize.Mobile(l.svcCtx)
|
||||||
}
|
}
|
||||||
|
if method == "device" {
|
||||||
|
initialize.Device(l.svcCtx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePlatformConfig(platform string, cfg map[string]interface{}) (interface{}, error) {
|
func validatePlatformConfig(platform string, cfg map[string]interface{}) (interface{}, error) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/report"
|
||||||
paymentPlatform "github.com/perfect-panel/server/pkg/payment"
|
paymentPlatform "github.com/perfect-panel/server/pkg/payment"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/payment"
|
"github.com/perfect-panel/server/internal/model/payment"
|
||||||
@ -43,15 +44,31 @@ func (l *GetPaymentMethodListLogic) GetPaymentMethodList(req *types.GetPaymentMe
|
|||||||
Total: total,
|
Total: total,
|
||||||
List: make([]types.PaymentMethodDetail, len(list)),
|
List: make([]types.PaymentMethodDetail, len(list)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gateway mod
|
||||||
|
|
||||||
|
isGatewayMod := report.IsGatewayMode()
|
||||||
|
|
||||||
for i, v := range list {
|
for i, v := range list {
|
||||||
config := make(map[string]interface{})
|
config := make(map[string]interface{})
|
||||||
_ = json.Unmarshal([]byte(v.Config), &config)
|
_ = json.Unmarshal([]byte(v.Config), &config)
|
||||||
notifyUrl := ""
|
notifyUrl := ""
|
||||||
|
|
||||||
if paymentPlatform.ParsePlatform(v.Platform) != paymentPlatform.Balance {
|
if paymentPlatform.ParsePlatform(v.Platform) != paymentPlatform.Balance {
|
||||||
|
notifyUrl = v.Domain
|
||||||
if 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 {
|
} 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{
|
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
|
||||||
|
}
|
||||||
41
internal/logic/admin/system/getModuleConfigLogic.go
Normal file
41
internal/logic/admin/system/getModuleConfigLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
57
internal/logic/admin/tool/queryIPLocationLogic.go
Normal file
57
internal/logic/admin/tool/queryIPLocationLogic.go
Normal file
@ -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)
|
pwd := tool.EncodePassWord(req.Password)
|
||||||
newUser := &user.User{
|
newUser := &user.User{
|
||||||
Password: pwd,
|
Password: pwd,
|
||||||
|
Algo: "default",
|
||||||
ReferralPercentage: req.ReferralPercentage,
|
ReferralPercentage: req.ReferralPercentage,
|
||||||
OnlyFirstPurchase: &req.OnlyFirstPurchase,
|
OnlyFirstPurchase: &req.OnlyFirstPurchase,
|
||||||
ReferCode: req.ReferCode,
|
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")
|
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.Password = tool.EncodePassWord(req.Password)
|
||||||
|
userInfo.Algo = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l.svcCtx.UserModel.Update(l.ctx, userInfo)
|
err = l.svcCtx.UserModel.Update(l.ctx, userInfo)
|
||||||
|
|||||||
234
internal/logic/auth/bindDeviceLogic.go
Normal file
234
internal/logic/auth/bindDeviceLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
293
internal/logic/auth/deviceLoginLogic.go
Normal file
293
internal/logic/auth/deviceLoginLogic.go
Normal file
@ -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
|
// Update password
|
||||||
userInfo.Password = tool.EncodePassWord(req.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())
|
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
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -116,6 +133,7 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
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 == "" {
|
if req.TelephoneCode == "" {
|
||||||
// Verify password
|
// 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")
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -124,6 +124,23 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
|
|||||||
l.svcCtx.Redis.Del(l.ctx, cacheKey)
|
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
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -133,6 +150,7 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
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
|
// Generate password
|
||||||
pwd := tool.EncodePassWord(req.Password)
|
pwd := tool.EncodePassWord(req.Password)
|
||||||
userInfo.Password = pwd
|
userInfo.Password = pwd
|
||||||
|
userInfo.Algo = "default"
|
||||||
err = l.svcCtx.UserModel.Update(l.ctx, userInfo)
|
err = l.svcCtx.UserModel.Update(l.ctx, userInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "update user password failed: %v", err.Error())
|
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
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -92,6 +108,7 @@ func (l *TelephoneResetPasswordLogic) TelephoneResetPassword(req *types.Telephon
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorw("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
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)
|
pwd := tool.EncodePassWord(req.Password)
|
||||||
userInfo := &user.User{
|
userInfo := &user.User{
|
||||||
Password: pwd,
|
Password: pwd,
|
||||||
|
Algo: "default",
|
||||||
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
||||||
AuthMethods: []user.AuthMethods{
|
AuthMethods: []user.AuthMethods{
|
||||||
{
|
{
|
||||||
@ -138,6 +139,22 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -147,6 +164,7 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/log"
|
"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/pkg/logger"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
"github.com/perfect-panel/server/internal/config"
|
||||||
@ -76,9 +77,25 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify password
|
// 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")
|
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
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -88,6 +105,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
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)
|
pwd := tool.EncodePassWord(req.Password)
|
||||||
userInfo := &user.User{
|
userInfo := &user.User{
|
||||||
Password: pwd,
|
Password: pwd,
|
||||||
|
Algo: "default",
|
||||||
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
||||||
}
|
}
|
||||||
if referer != nil {
|
if referer != nil {
|
||||||
@ -125,6 +126,21 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
@ -134,6 +150,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
|||||||
l.svcCtx.Config.JwtAuth.AccessExpire,
|
l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||||
jwt.WithOption("UserId", userInfo.Id),
|
jwt.WithOption("UserId", userInfo.Id),
|
||||||
jwt.WithOption("SessionId", sessionId),
|
jwt.WithOption("SessionId", sessionId),
|
||||||
|
jwt.WithOption("LoginType", req.LoginType),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
@ -67,6 +68,10 @@ func (l *GetGlobalConfigLogic) GetGlobalConfig() (resp *types.GetGlobalConfigRes
|
|||||||
for _, method := range authMethods {
|
for _, method := range authMethods {
|
||||||
if *method.Enabled {
|
if *method.Enabled {
|
||||||
methods = append(methods, method.Method)
|
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
|
resp.OAuthMethods = methods
|
||||||
|
|||||||
33
internal/logic/common/heartbeatLogic.go
Normal file
33
internal/logic/common/heartbeatLogic.go
Normal file
@ -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
|
return err
|
||||||
}
|
}
|
||||||
// Verify sign
|
// 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 {
|
if !client.VerifySign(urlParamsToMap(l.ctx.Request.URL.RawQuery)) && !l.svcCtx.Config.Debug {
|
||||||
l.Logger.Error("[EPayNotify] Verify sign failed")
|
l.Logger.Error("[EPayNotify] Verify sign failed")
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/log"
|
"github.com/perfect-panel/server/internal/model/log"
|
||||||
|
"github.com/perfect-panel/server/internal/report"
|
||||||
"github.com/perfect-panel/server/pkg/constant"
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
|
|
||||||
paymentPlatform "github.com/perfect-panel/server/pkg/payment"
|
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())
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
|
||||||
}
|
}
|
||||||
// Initialize EPay client with merchant credentials
|
// 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
|
// Convert order amount to CNY using current exchange rate
|
||||||
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
||||||
@ -275,16 +276,29 @@ func (l *PurchaseCheckoutLogic) epayPayment(config *payment.Payment, info *order
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gateway mod
|
||||||
|
|
||||||
|
isGatewayMod := report.IsGatewayMode()
|
||||||
|
|
||||||
// Build notification URL for payment status callbacks
|
// Build notification URL for payment status callbacks
|
||||||
notifyUrl := ""
|
notifyUrl := ""
|
||||||
if config.Domain != "" {
|
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 {
|
} else {
|
||||||
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
|
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
host = l.svcCtx.Config.Host
|
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
|
// 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())
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Unmarshal error: %s", err.Error())
|
||||||
}
|
}
|
||||||
// Initialize EPay client with merchant credentials
|
// 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
|
// Convert order amount to CNY using current exchange rate
|
||||||
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
amount, err := l.queryExchangeRate("CNY", info.Amount)
|
||||||
@ -317,18 +331,29 @@ func (l *PurchaseCheckoutLogic) CryptoSaaSPayment(config *payment.Payment, info
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gateway mod
|
||||||
|
isGatewayMod := report.IsGatewayMode()
|
||||||
|
|
||||||
// Build notification URL for payment status callbacks
|
// Build notification URL for payment status callbacks
|
||||||
notifyUrl := ""
|
notifyUrl := ""
|
||||||
if config.Domain != "" {
|
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 {
|
} else {
|
||||||
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
|
host, ok := l.ctx.Value(constant.CtxKeyRequestHost).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
host = l.svcCtx.Config.Host
|
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
|
// Create payment URL for user redirection
|
||||||
url := client.CreatePayUrl(epay.Order{
|
url := client.CreatePayUrl(epay.Order{
|
||||||
Name: l.svcCtx.Config.Site.SiteName,
|
Name: l.svcCtx.Config.Site.SiteName,
|
||||||
@ -347,6 +372,11 @@ func (l *PurchaseCheckoutLogic) queryExchangeRate(to string, src int64) (amount
|
|||||||
// Convert cents to decimal amount
|
// Convert cents to decimal amount
|
||||||
amount = float64(src) / float64(100)
|
amount = float64(src) / float64(100)
|
||||||
|
|
||||||
|
if l.svcCtx.ExchangeRate != 0 && to == "CNY" {
|
||||||
|
amount = amount * l.svcCtx.ExchangeRate
|
||||||
|
return amount, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Retrieve system currency configuration
|
// Retrieve system currency configuration
|
||||||
currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
|
currency, err := l.svcCtx.SystemModel.GetCurrencyConfig(l.ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -83,6 +83,12 @@ func (l *PurchaseLogic) Purchase(req *types.PortalPurchaseRequest) (resp *types.
|
|||||||
if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount {
|
if couponInfo.Count != 0 && couponInfo.Count <= couponInfo.UsedCount {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponInsufficientUsage), "coupon used")
|
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)
|
couponSub := tool.StringToInt64Slice(couponInfo.Subscribe)
|
||||||
if len(couponSub) > 0 && !tool.Contains(couponSub, req.SubscribeId) {
|
if len(couponSub) > 0 && !tool.Contains(couponSub, req.SubscribeId) {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.CouponNotApplicable), "coupon not match")
|
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
|
list[i] = sub
|
||||||
// 通过服务组查询关联的节点数量
|
// 通过服务组查询关联的节点数量
|
||||||
if item.ServerGroup != "" {
|
if item.ServerGroup != "" {
|
||||||
groupIds := tool.StringToInt64Slice(item.ServerGroup)
|
servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, tool.StringToInt64Slice(item.ServerGroup))
|
||||||
servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, groupIds)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorw("[QuerySubscribeListLogic] FindServerListByGroupIds error", logger.Field("error", err.Error()))
|
l.Errorw("[QuerySubscribeListLogic] FindServerListByGroupIds error", logger.Field("error", err.Error()))
|
||||||
sub.ServerCount = 0
|
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
|
||||||
|
}
|
||||||
108
internal/logic/public/user/commissionWithdrawLogic.go
Normal file
108
internal/logic/public/user/commissionWithdrawLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
39
internal/logic/public/user/getDeviceListLogic.go
Normal file
39
internal/logic/public/user/getDeviceListLogic.go
Normal file
@ -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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算节点数量(通过服务组关联的实际节点数量)
|
short, _ := tool.FixedUniqueString(item.Token, 8, "")
|
||||||
if item.Subscribe != nil {
|
sub.Short = short
|
||||||
// 获取服务组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
|
|
||||||
}
|
|
||||||
|
|
||||||
sub.ResetTime = calculateNextResetTime(&sub)
|
sub.ResetTime = calculateNextResetTime(&sub)
|
||||||
resp.List = append(resp.List, sub)
|
resp.List = append(resp.List, sub)
|
||||||
}
|
}
|
||||||
|
|||||||
30
internal/logic/public/user/queryWithdrawalLogLogic.go
Normal file
30
internal/logic/public/user/queryWithdrawalLogLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
72
internal/logic/public/user/unbindDeviceLogic.go
Normal file
72
internal/logic/public/user/unbindDeviceLogic.go
Normal file
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
51
internal/logic/public/user/updateUserRulesLogic.go
Normal file
51
internal/logic/public/user/updateUserRulesLogic.go
Normal file
@ -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
|
||||||
|
}
|
||||||
73
internal/logic/public/user/updateUserSubscribeNoteLogic.go
Normal file
73
internal/logic/public/user/updateUserSubscribeNoteLogic.go
Normal file
@ -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"
|
AnyTLS = "anytls"
|
||||||
Tuic = "tuic"
|
Tuic = "tuic"
|
||||||
Hysteria = "hysteria"
|
Hysteria = "hysteria"
|
||||||
Hysteria2 = "hysteria2"
|
// Deprecated: Hysteria2 is deprecated, use Hysteria instead
|
||||||
|
// TODO: remove in future versions
|
||||||
|
Hysteria2 = "hysteria2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SecurityConfig struct {
|
type SecurityConfig struct {
|
||||||
|
|||||||
@ -57,13 +57,19 @@ func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compatible hysteria2, remove in future versions
|
||||||
|
protocolRequest := req.Protocol
|
||||||
|
if protocolRequest == Hysteria2 {
|
||||||
|
protocolRequest = Hysteria
|
||||||
|
}
|
||||||
|
|
||||||
protocols, err := data.UnmarshalProtocols()
|
protocols, err := data.UnmarshalProtocols()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var cfg map[string]interface{}
|
var cfg map[string]interface{}
|
||||||
for _, protocol := range protocols {
|
for _, protocol := range protocols {
|
||||||
if protocol.Type == req.Protocol {
|
if protocol.Type == protocolRequest {
|
||||||
cfg = l.compatible(protocol)
|
cfg = l.compatible(protocol)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -209,7 +215,7 @@ func (l *GetServerConfigLogic) compatible(config node.Protocol) map[string]inter
|
|||||||
RealityShortId: config.RealityShortId,
|
RealityShortId: config.RealityShortId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case Hysteria2, Hysteria:
|
case Hysteria:
|
||||||
result = Hysteria2Node{
|
result = Hysteria2Node{
|
||||||
Port: config.Port,
|
Port: config.Port,
|
||||||
HopPorts: config.HopPorts,
|
HopPorts: config.HopPorts,
|
||||||
|
|||||||
@ -249,8 +249,9 @@ func (l *SubscribeLogic) createExpiredServers() []*node.Node {
|
|||||||
Port: 18080,
|
Port: 18080,
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
Server: &node.Server{
|
Server: &node.Server{
|
||||||
|
Id: 1,
|
||||||
Name: "Subscribe Expired",
|
Name: "Subscribe Expired",
|
||||||
Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
Protocols: "[{\"type\":\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||||
},
|
},
|
||||||
Protocol: "shadowsocks",
|
Protocol: "shadowsocks",
|
||||||
Enabled: &enable,
|
Enabled: &enable,
|
||||||
@ -261,8 +262,9 @@ func (l *SubscribeLogic) createExpiredServers() []*node.Node {
|
|||||||
Port: 18080,
|
Port: 18080,
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
Server: &node.Server{
|
Server: &node.Server{
|
||||||
|
Id: 1,
|
||||||
Name: "Subscribe Expired",
|
Name: "Subscribe Expired",
|
||||||
Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
Protocols: "[{\"type\":\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||||
},
|
},
|
||||||
Protocol: "shadowsocks",
|
Protocol: "shadowsocks",
|
||||||
Enabled: &enable,
|
Enabled: &enable,
|
||||||
|
|||||||
@ -40,6 +40,11 @@ func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loginType := ""
|
||||||
|
if claims["LoginType"] != nil {
|
||||||
|
loginType = claims["LoginType"].(string)
|
||||||
|
}
|
||||||
// get user id from token
|
// get user id from token
|
||||||
userId := int64(claims["UserId"].(float64))
|
userId := int64(claims["UserId"].(float64))
|
||||||
// get session id from token
|
// get session id from token
|
||||||
@ -77,6 +82,7 @@ func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx = context.WithValue(ctx, constant.LoginType, loginType)
|
||||||
ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo)
|
ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo)
|
||||||
ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId)
|
ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId)
|
||||||
c.Request = c.Request.WithContext(ctx)
|
c.Request = c.Request.WithContext(ctx)
|
||||||
|
|||||||
295
internal/middleware/deviceMiddleware.go
Normal file
295
internal/middleware/deviceMiddleware.go
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
pkgaes "github.com/perfect-panel/server/pkg/aes"
|
||||||
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
noWritten = -1
|
||||||
|
defaultStatus = http.StatusOK
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeviceMiddleware(srvCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
if !srvCtx.Config.Device.Enable {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if srvCtx.Config.Device.SecuritySecret == "" {
|
||||||
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.SecretIsEmpty), "Secret is empty"))
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
if ctx.Value(constant.CtxKeyUser) == nil && c.GetHeader("Login-Type") != "" {
|
||||||
|
ctx = context.WithValue(ctx, constant.LoginType, c.GetHeader("Login-Type"))
|
||||||
|
c.Request = c.Request.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginType, ok := ctx.Value(constant.LoginType).(string)
|
||||||
|
if !ok || loginType != "device" {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw := NewResponseWriter(c, srvCtx)
|
||||||
|
if !rw.Decrypt() {
|
||||||
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidCiphertext), "Invalid ciphertext"))
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Writer = rw
|
||||||
|
c.Next()
|
||||||
|
rw.FlushAbort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponseWriter(c *gin.Context, srvCtx *svc.ServiceContext) (rw *ResponseWriter) {
|
||||||
|
rw = &ResponseWriter{
|
||||||
|
c: c,
|
||||||
|
body: new(bytes.Buffer),
|
||||||
|
ResponseWriter: c.Writer,
|
||||||
|
}
|
||||||
|
rw.encryptionKey = srvCtx.Config.Device.SecuritySecret
|
||||||
|
rw.encryptionMethod = "AES"
|
||||||
|
rw.encryption = true
|
||||||
|
return rw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Encrypt() {
|
||||||
|
if !rw.encryption {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := rw.body.Bytes()
|
||||||
|
params := map[string]interface{}{}
|
||||||
|
err := json.Unmarshal(buf, ¶ms)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := params["data"]
|
||||||
|
if data != nil {
|
||||||
|
var jsonData []byte
|
||||||
|
str, ok := data.(string)
|
||||||
|
if ok {
|
||||||
|
jsonData = []byte(str)
|
||||||
|
} else {
|
||||||
|
jsonData, _ = json.Marshal(data)
|
||||||
|
}
|
||||||
|
encrypt, iv, err := pkgaes.Encrypt(jsonData, rw.encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params["data"] = map[string]interface{}{
|
||||||
|
"data": encrypt,
|
||||||
|
"time": iv,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
marshal, _ := json.Marshal(params)
|
||||||
|
rw.body.Reset()
|
||||||
|
rw.body.Write(marshal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Decrypt() bool {
|
||||||
|
if !rw.encryption {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断url链接中是否存在data和iv数据,存在就进行解密并设置回去
|
||||||
|
query := rw.c.Request.URL.Query()
|
||||||
|
dataStr := query.Get("data")
|
||||||
|
timeStr := query.Get("time")
|
||||||
|
if dataStr != "" && timeStr != "" {
|
||||||
|
decrypt, err := pkgaes.Decrypt(dataStr, rw.encryptionKey, timeStr)
|
||||||
|
if err == nil {
|
||||||
|
params := map[string]interface{}{}
|
||||||
|
err = json.Unmarshal([]byte(decrypt), ¶ms)
|
||||||
|
if err == nil {
|
||||||
|
for k, v := range params {
|
||||||
|
query.Set(k, fmt.Sprintf("%v", v))
|
||||||
|
}
|
||||||
|
query.Del("data")
|
||||||
|
query.Del("time")
|
||||||
|
rw.c.Request.RequestURI = fmt.Sprintf("%s?%s", rw.c.Request.RequestURI[:strings.Index(rw.c.Request.RequestURI, "?")], query.Encode())
|
||||||
|
rw.c.Request.URL.RawQuery = query.Encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断body是否存在数据,存在就尝试解密,并设置回去
|
||||||
|
body, err := io.ReadAll(rw.c.Request.Body)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]interface{}{}
|
||||||
|
err = json.Unmarshal(body, ¶ms)
|
||||||
|
data := params["data"]
|
||||||
|
nonce := params["time"]
|
||||||
|
if err != nil || data == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := data.(string)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iv, ok := nonce.(string)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypt, err := pkgaes.Decrypt(str, rw.encryptionKey, iv)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rw.c.Request.Body = io.NopCloser(bytes.NewBuffer([]byte(decrypt)))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) FlushAbort() {
|
||||||
|
defer rw.c.Abort()
|
||||||
|
responseBody := rw.body.String()
|
||||||
|
fmt.Println("Original Response Body:", responseBody)
|
||||||
|
rw.flush = true
|
||||||
|
if rw.encryption {
|
||||||
|
rw.Encrypt()
|
||||||
|
}
|
||||||
|
_, err := rw.Write(rw.body.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
size int
|
||||||
|
status int
|
||||||
|
flush bool
|
||||||
|
body *bytes.Buffer
|
||||||
|
c *gin.Context
|
||||||
|
encryption bool
|
||||||
|
encryptionKey string
|
||||||
|
encryptionMethod string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Unwrap() http.ResponseWriter {
|
||||||
|
return rw.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:unused
|
||||||
|
func (rw *ResponseWriter) reset(writer http.ResponseWriter) {
|
||||||
|
rw.ResponseWriter = writer
|
||||||
|
rw.size = noWritten
|
||||||
|
rw.status = defaultStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) WriteHeader(code int) {
|
||||||
|
if code > 0 && rw.status != code {
|
||||||
|
if rw.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rw.status = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) WriteHeaderNow() {
|
||||||
|
if !rw.Written() {
|
||||||
|
rw.size = 0
|
||||||
|
rw.ResponseWriter.WriteHeader(rw.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Write(data []byte) (n int, err error) {
|
||||||
|
if rw.flush {
|
||||||
|
rw.WriteHeaderNow()
|
||||||
|
n, err = rw.ResponseWriter.Write(data)
|
||||||
|
rw.size += n
|
||||||
|
} else {
|
||||||
|
rw.body.Write(data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) WriteString(s string) (n int, err error) {
|
||||||
|
if rw.flush {
|
||||||
|
rw.WriteHeaderNow()
|
||||||
|
n, err = rw.ResponseWriter.Write([]byte(s))
|
||||||
|
rw.size += n
|
||||||
|
} else {
|
||||||
|
rw.body.Write([]byte(s))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Status() int {
|
||||||
|
return rw.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Size() int {
|
||||||
|
return rw.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Written() bool {
|
||||||
|
return rw.size != noWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack implements the http.Hijacker interface.
|
||||||
|
func (rw *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
if rw.size < 0 {
|
||||||
|
rw.size = 0
|
||||||
|
}
|
||||||
|
return rw.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseNotify implements the http.CloseNotifier interface.
|
||||||
|
func (rw *ResponseWriter) CloseNotify() <-chan bool {
|
||||||
|
// 通过 r.Context().Done() 来监听请求的取消
|
||||||
|
done := rw.c.Request.Context().Done()
|
||||||
|
closed := make(chan bool)
|
||||||
|
|
||||||
|
// 当上下文被取消时,通过 closed channel 发送通知
|
||||||
|
go func() {
|
||||||
|
<-done
|
||||||
|
closed <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
return closed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implements the http.Flusher interface.
|
||||||
|
func (rw *ResponseWriter) Flush() {
|
||||||
|
rw.WriteHeaderNow()
|
||||||
|
rw.ResponseWriter.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *ResponseWriter) Pusher() (pusher http.Pusher) {
|
||||||
|
if pusher, ok := rw.ResponseWriter.(http.Pusher); ok {
|
||||||
|
return pusher
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -43,7 +43,7 @@ func (m *customAnnouncementModel) GetAnnouncementListByPage(ctx context.Context,
|
|||||||
if filter.Search != "" {
|
if filter.Search != "" {
|
||||||
conn = conn.Where("`title` LIKE ? OR `content` LIKE ?", "%"+filter.Search+"%", "%"+filter.Search+"%")
|
conn = conn.Where("`title` LIKE ? OR `content` LIKE ?", "%"+filter.Search+"%", "%"+filter.Search+"%")
|
||||||
}
|
}
|
||||||
return conn.Count(&total).Offset((page - 1) * size).Limit(size).Find(v).Error
|
return conn.Count(&total).Offset((page - 1) * size).Limit(size).Find(&list).Error
|
||||||
})
|
})
|
||||||
return total, list, err
|
return total, list, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,23 +33,24 @@ const (
|
|||||||
TypeTrafficStat Type = 42 // Daily traffic statistics log
|
TypeTrafficStat Type = 42 // Daily traffic statistics log
|
||||||
)
|
)
|
||||||
const (
|
const (
|
||||||
ResetSubscribeTypeAuto uint16 = 231 // Auto reset
|
ResetSubscribeTypeAuto uint16 = 231 // Auto reset
|
||||||
ResetSubscribeTypeAdvance uint16 = 232 // Advance reset
|
ResetSubscribeTypeAdvance uint16 = 232 // Advance reset
|
||||||
ResetSubscribeTypePaid uint16 = 233 // Paid reset
|
ResetSubscribeTypePaid uint16 = 233 // Paid reset
|
||||||
ResetSubscribeTypeQuota uint16 = 234 // Quota reset
|
ResetSubscribeTypeQuota uint16 = 234 // Quota reset
|
||||||
BalanceTypeRecharge uint16 = 321 // Recharge
|
BalanceTypeRecharge uint16 = 321 // Recharge
|
||||||
BalanceTypeWithdraw uint16 = 322 // Withdraw
|
BalanceTypeWithdraw uint16 = 322 // Withdraw
|
||||||
BalanceTypePayment uint16 = 323 // Payment
|
BalanceTypePayment uint16 = 323 // Payment
|
||||||
BalanceTypeRefund uint16 = 324 // Refund
|
BalanceTypeRefund uint16 = 324 // Refund
|
||||||
BalanceTypeAdjust uint16 = 326 // Admin Adjust
|
BalanceTypeAdjust uint16 = 326 // Admin Adjust
|
||||||
BalanceTypeReward uint16 = 325 // Reward
|
BalanceTypeReward uint16 = 325 // Reward
|
||||||
CommissionTypePurchase uint16 = 331 // Purchase
|
CommissionTypePurchase uint16 = 331 // Purchase
|
||||||
CommissionTypeRenewal uint16 = 332 // Renewal
|
CommissionTypeRenewal uint16 = 332 // Renewal
|
||||||
CommissionTypeRefund uint16 = 333 // Refund
|
CommissionTypeRefund uint16 = 333 // Refund
|
||||||
commissionTypeWithdraw uint16 = 334 // withdraw
|
CommissionTypeWithdraw uint16 = 334 // withdraw
|
||||||
CommissionTypeAdjust uint16 = 335 // Admin Adjust
|
CommissionTypeAdjust uint16 = 335 // Admin Adjust
|
||||||
GiftTypeIncrease uint16 = 341 // Increase
|
CommissionTypeConvertBalance uint16 = 336 // Convert to Balance
|
||||||
GiftTypeReduce uint16 = 342 // Reduce
|
GiftTypeIncrease uint16 = 341 // Increase
|
||||||
|
GiftTypeReduce uint16 = 342 // Reduce
|
||||||
)
|
)
|
||||||
|
|
||||||
// Uint8 converts Type to uint8.
|
// Uint8 converts Type to uint8.
|
||||||
|
|||||||
@ -23,6 +23,7 @@ type (
|
|||||||
UpdateServer(ctx context.Context, data *Server, tx ...*gorm.DB) error
|
UpdateServer(ctx context.Context, data *Server, tx ...*gorm.DB) error
|
||||||
DeleteServer(ctx context.Context, id int64, tx ...*gorm.DB) error
|
DeleteServer(ctx context.Context, id int64, tx ...*gorm.DB) error
|
||||||
Transaction(ctx context.Context, fn func(db *gorm.DB) error) error
|
Transaction(ctx context.Context, fn func(db *gorm.DB) error) error
|
||||||
|
QueryServerList(ctx context.Context, ids []int64) (servers []*Server, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeModel interface {
|
NodeModel interface {
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package node
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/pkg/tool"
|
"github.com/perfect-panel/server/pkg/tool"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type customServerLogicModel interface {
|
type customServerLogicModel interface {
|
||||||
@ -63,6 +65,12 @@ func (m *customServerModel) FilterServerList(ctx context.Context, params *Filter
|
|||||||
return total, servers, err
|
return total, servers, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *customServerModel) QueryServerList(ctx context.Context, ids []int64) (servers []*Server, err error) {
|
||||||
|
query := m.WithContext(ctx).Model(&Server{})
|
||||||
|
err = query.Where("id IN (?)", ids).Find(&servers).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// FilterNodeList Filter Node List
|
// FilterNodeList Filter Node List
|
||||||
func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error) {
|
func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNodeParams) (int64, []*Node, error) {
|
||||||
var nodes []*Node
|
var nodes []*Node
|
||||||
@ -85,10 +93,7 @@ func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNo
|
|||||||
query = query.Where("server_id IN ?", params.ServerId)
|
query = query.Where("server_id IN ?", params.ServerId)
|
||||||
}
|
}
|
||||||
if len(params.Tag) > 0 {
|
if len(params.Tag) > 0 {
|
||||||
query = query.Where("1 = 0")
|
query = query.Scopes(InSet("tags", params.Tag))
|
||||||
for _, tag := range params.Tag {
|
|
||||||
query = query.Or("FIND_IN_SET(?,tags)", tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if params.Protocol != "" {
|
if params.Protocol != "" {
|
||||||
query = query.Where("protocol = ?", params.Protocol)
|
query = query.Where("protocol = ?", params.Protocol)
|
||||||
@ -165,3 +170,22 @@ func (m *customServerModel) ClearServerCache(ctx context.Context, serverId int64
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InSet 支持多值 OR 查询
|
||||||
|
func InSet(field string, values []string) func(db *gorm.DB) *gorm.DB {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
conds := make([]string, len(values))
|
||||||
|
args := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
conds[i] = "FIND_IN_SET(?, " + field + ")"
|
||||||
|
args[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用括号包裹 OR 条件,保证外层 AND 不受影响
|
||||||
|
return db.Where("("+strings.Join(conds, " OR ")+")", args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -85,9 +85,10 @@ func (l *AlipayF2FConfig) Unmarshal(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EPayConfig struct {
|
type EPayConfig struct {
|
||||||
Pid string `json:"pid"`
|
Pid string `json:"pid"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *EPayConfig) Marshal() ([]byte, error) {
|
func (l *EPayConfig) Marshal() ([]byte, error) {
|
||||||
@ -109,6 +110,7 @@ type CryptoSaaSConfig struct {
|
|||||||
Endpoint string `json:"endpoint"`
|
Endpoint string `json:"endpoint"`
|
||||||
AccountID string `json:"account_id"`
|
AccountID string `json:"account_id"`
|
||||||
SecretKey string `json:"secret_key"`
|
SecretKey string `json:"secret_key"`
|
||||||
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CryptoSaaSConfig) Marshal() ([]byte, error) {
|
func (l *CryptoSaaSConfig) Marshal() ([]byte, error) {
|
||||||
|
|||||||
@ -22,8 +22,7 @@ type Subscribe struct {
|
|||||||
Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"`
|
Quota int64 `gorm:"type:int;not null;default:0;comment:Quota"`
|
||||||
Nodes string `gorm:"type:varchar(255);comment:Node Ids"`
|
Nodes string `gorm:"type:varchar(255);comment:Node Ids"`
|
||||||
NodeTags string `gorm:"type:varchar(255);comment:Node Tags"`
|
NodeTags string `gorm:"type:varchar(255);comment:Node Tags"`
|
||||||
ServerGroup string `gorm:"type:varchar(255);comment:Server Group Ids"`
|
ServerGroup string `gorm:"type:varchar(255);comment:Server Group"`
|
||||||
Server string `gorm:"type:varchar(255);comment:Server Ids"`
|
|
||||||
Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show portal page"`
|
Show *bool `gorm:"type:tinyint(1);not null;default:0;comment:Show portal page"`
|
||||||
Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"`
|
Sell *bool `gorm:"type:tinyint(1);not null;default:0;comment:Sell"`
|
||||||
Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"`
|
Sort int64 `gorm:"type:int;not null;default:0;comment:Sort"`
|
||||||
|
|||||||
@ -46,6 +46,16 @@ func (m *customUserModel) QueryDevicePageList(ctx context.Context, userId, subsc
|
|||||||
return list, total, err
|
return list, total, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryDeviceList returns a list of records that meet the conditions.
|
||||||
|
func (m *customUserModel) QueryDeviceList(ctx context.Context, userId int64) ([]*Device, int64, error) {
|
||||||
|
var list []*Device
|
||||||
|
var total int64
|
||||||
|
err := m.QueryNoCacheCtx(ctx, &list, func(conn *gorm.DB, v interface{}) error {
|
||||||
|
return conn.Model(&Device{}).Where("`user_id` = ?", userId).Count(&total).Find(&list).Error
|
||||||
|
})
|
||||||
|
return list, total, err
|
||||||
|
}
|
||||||
|
|
||||||
func (m *customUserModel) UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error {
|
func (m *customUserModel) UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error {
|
||||||
old, err := m.FindOneDevice(ctx, data.Id)
|
old, err := m.FindOneDevice(ctx, data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,3 +86,18 @@ func (m *customUserModel) DeleteDevice(ctx context.Context, id int64, tx ...*gor
|
|||||||
}, data.GetCacheKeys()...)
|
}, data.GetCacheKeys()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *customUserModel) InsertDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error {
|
||||||
|
defer func() {
|
||||||
|
if clearErr := m.ClearDeviceCache(ctx, data); clearErr != nil {
|
||||||
|
// log cache clear error
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error {
|
||||||
|
if len(tx) > 0 {
|
||||||
|
conn = tx[0]
|
||||||
|
}
|
||||||
|
return conn.Create(data).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ type SubscribeDetails struct {
|
|||||||
Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
|
Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
|
||||||
UUID string `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"`
|
UUID string `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"`
|
||||||
Status uint8 `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired; 4: Cancelled"`
|
Status uint8 `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired; 4: Cancelled"`
|
||||||
|
Note string `gorm:"type:varchar(500);default:'';comment:User note for subscription"`
|
||||||
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||||
}
|
}
|
||||||
@ -95,10 +96,12 @@ type customUserLogicModel interface {
|
|||||||
FindUserAuthMethodByPlatform(ctx context.Context, userId int64, platform string) (*AuthMethods, error)
|
FindUserAuthMethodByPlatform(ctx context.Context, userId int64, platform string) (*AuthMethods, error)
|
||||||
FindOneByEmail(ctx context.Context, email string) (*User, error)
|
FindOneByEmail(ctx context.Context, email string) (*User, error)
|
||||||
FindOneDevice(ctx context.Context, id int64) (*Device, error)
|
FindOneDevice(ctx context.Context, id int64) (*Device, error)
|
||||||
|
QueryDeviceList(ctx context.Context, userid int64) ([]*Device, int64, error)
|
||||||
QueryDevicePageList(ctx context.Context, userid, subscribeId int64, page, size int) ([]*Device, int64, error)
|
QueryDevicePageList(ctx context.Context, userid, subscribeId int64, page, size int) ([]*Device, int64, error)
|
||||||
UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
||||||
FindOneDeviceByIdentifier(ctx context.Context, id string) (*Device, error)
|
FindOneDeviceByIdentifier(ctx context.Context, id string) (*Device, error)
|
||||||
DeleteDevice(ctx context.Context, id int64, tx ...*gorm.DB) error
|
DeleteDevice(ctx context.Context, id int64, tx ...*gorm.DB) error
|
||||||
|
InsertDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
||||||
|
|
||||||
ClearSubscribeCache(ctx context.Context, data ...*Subscribe) error
|
ClearSubscribeCache(ctx context.Context, data ...*Subscribe) error
|
||||||
ClearUserCache(ctx context.Context, data ...*User) error
|
ClearUserCache(ctx context.Context, data ...*User) error
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import (
|
|||||||
type User struct {
|
type User struct {
|
||||||
Id int64 `gorm:"primaryKey"`
|
Id int64 `gorm:"primaryKey"`
|
||||||
Password string `gorm:"type:varchar(100);not null;comment:User Password"`
|
Password string `gorm:"type:varchar(100);not null;comment:User Password"`
|
||||||
|
Algo string `gorm:"type:varchar(20);default:'default';comment:Encryption Algorithm"`
|
||||||
|
Salt string `gorm:"type:varchar(20);default:null;comment:Password Salt"`
|
||||||
Avatar string `gorm:"type:MEDIUMTEXT;comment:User Avatar"`
|
Avatar string `gorm:"type:MEDIUMTEXT;comment:User Avatar"`
|
||||||
Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount
|
Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount
|
||||||
ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"`
|
ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"`
|
||||||
@ -24,6 +26,7 @@ type User struct {
|
|||||||
EnableTradeNotify *bool `gorm:"default:false;not null;comment:Enable Trade Notifications"`
|
EnableTradeNotify *bool `gorm:"default:false;not null;comment:Enable Trade Notifications"`
|
||||||
AuthMethods []AuthMethods `gorm:"foreignKey:UserId;references:Id"`
|
AuthMethods []AuthMethods `gorm:"foreignKey:UserId;references:Id"`
|
||||||
UserDevices []Device `gorm:"foreignKey:UserId;references:Id"`
|
UserDevices []Device `gorm:"foreignKey:UserId;references:Id"`
|
||||||
|
Rules string `gorm:"type:TEXT;comment:User Rules"`
|
||||||
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||||
}
|
}
|
||||||
@ -47,6 +50,7 @@ type Subscribe struct {
|
|||||||
Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
|
Token string `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
|
||||||
UUID string `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"`
|
UUID string `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"`
|
||||||
Status uint8 `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted"`
|
Status uint8 `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted"`
|
||||||
|
Note string `gorm:"type:varchar(500);default:'';comment:User note for subscription"`
|
||||||
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||||
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||||
}
|
}
|
||||||
@ -99,3 +103,18 @@ type DeviceOnlineRecord struct {
|
|||||||
func (DeviceOnlineRecord) TableName() string {
|
func (DeviceOnlineRecord) TableName() string {
|
||||||
return "user_device_online_record"
|
return "user_device_online_record"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Withdrawal struct {
|
||||||
|
Id int64 `gorm:"primaryKey"`
|
||||||
|
UserId int64 `gorm:"index:idx_user_id;not null;comment:User ID"`
|
||||||
|
Amount int64 `gorm:"not null;comment:Withdrawal Amount"`
|
||||||
|
Content string `gorm:"type:text;comment:Withdrawal Content"`
|
||||||
|
Status uint8 `gorm:"type:tinyint(1);default:0;comment:Withdrawal Status: 0: Pending 1: Approved 2: Rejected"`
|
||||||
|
Reason string `gorm:"type:varchar(500);default:'';comment:Rejection Reason"`
|
||||||
|
CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"`
|
||||||
|
UpdatedAt time.Time `gorm:"comment:Update Time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Withdrawal) TableName() string {
|
||||||
|
return "user_withdrawal"
|
||||||
|
}
|
||||||
|
|||||||
15
internal/report/report.go
Normal file
15
internal/report/report.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package report
|
||||||
|
|
||||||
|
const (
|
||||||
|
RegisterAPI = "/basic/register" // 模块注册接口
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterResponse 模块注册响应参数
|
||||||
|
type RegisterResponse struct {
|
||||||
|
Code int `json:"code"` // 响应代码
|
||||||
|
Message string `json:"message"` // 响应信息
|
||||||
|
Data struct {
|
||||||
|
Success bool `json:"success"` // 注册是否成功
|
||||||
|
Message string `json:"message"` // 返回信息
|
||||||
|
} `json:"data"` // 响应数据
|
||||||
|
}
|
||||||
113
internal/report/tool.go
Normal file
113
internal/report/tool.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FreePort returns a free TCP port by opening a listener on port 0.
|
||||||
|
func FreePort() (int, error) {
|
||||||
|
l, err := net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
// Get the assigned port
|
||||||
|
addr := l.Addr().(*net.TCPAddr)
|
||||||
|
return addr.Port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModulePort returns the module port from the environment variable or a free port.
|
||||||
|
func ModulePort() (int, error) {
|
||||||
|
// 从环境变量获取端口号
|
||||||
|
value, exists := os.LookupEnv("PPANEL_PORT")
|
||||||
|
if exists {
|
||||||
|
var port int
|
||||||
|
_, err := fmt.Sscanf(value, "%d", &port)
|
||||||
|
if err != nil {
|
||||||
|
return FreePort()
|
||||||
|
}
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
return FreePort()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GatewayPort returns the gateway port from the environment variable or a free port.
|
||||||
|
func GatewayPort() (int, error) {
|
||||||
|
// 从环境变量获取端口号
|
||||||
|
value, exists := os.LookupEnv("GATEWAY_PORT")
|
||||||
|
if exists {
|
||||||
|
var port int
|
||||||
|
_, err := fmt.Sscanf(value, "%d", &port)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to parse GATEWAY_PORT: %v Value %s", err.Error(), value)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("could not determine gateway port")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterModule registers a module with the gateway.
|
||||||
|
func RegisterModule(port int) error {
|
||||||
|
// 从环境变量中读取网关模块端口
|
||||||
|
gatewayPort, err := GatewayPort()
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to determine GATEWAY_PORT: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从环境变量中获取通讯密钥
|
||||||
|
value, exists := os.LookupEnv("SECRET_KEY")
|
||||||
|
if !exists {
|
||||||
|
panic("could not determine secret key")
|
||||||
|
}
|
||||||
|
|
||||||
|
var response RegisterResponse
|
||||||
|
|
||||||
|
client := resty.New().SetBaseURL(fmt.Sprintf("http://127.0.0.1:%d", gatewayPort))
|
||||||
|
result, err := client.R().SetHeader("Content-Type", "application/json").SetBody(RegisterServiceRequest{
|
||||||
|
Secret: value,
|
||||||
|
ProxyPath: "/api",
|
||||||
|
ServiceURL: fmt.Sprintf("http://127.0.0.1:%d", port),
|
||||||
|
Repository: constant.Repository,
|
||||||
|
HeartbeatURL: fmt.Sprintf("http://127.0.0.1:%d/v1/common/heartbeat", port),
|
||||||
|
ServiceName: constant.ServiceName,
|
||||||
|
ServiceVersion: constant.Version,
|
||||||
|
}).SetResult(&response).Post(RegisterAPI)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to register service: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.IsError() {
|
||||||
|
return errors.New("failed to register module: " + result.Status())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response.Data.Success {
|
||||||
|
logger.Infof("Result: %v", result.String())
|
||||||
|
return errors.New("failed to register module: " + response.Message)
|
||||||
|
}
|
||||||
|
logger.Infof("Module registered successfully: %s", response.Message)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGatewayMode checks if the application is running in gateway mode.
|
||||||
|
// It returns true if GATEWAY_MODE is set to "true" and GATEWAY_PORT is valid.
|
||||||
|
func IsGatewayMode() bool {
|
||||||
|
value, exists := os.LookupEnv("GATEWAY_MODE")
|
||||||
|
if exists && value == "true" {
|
||||||
|
if _, err := GatewayPort(); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
21
internal/report/tool_test.go
Normal file
21
internal/report/tool_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFreePort(t *testing.T) {
|
||||||
|
port, err := FreePort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FreePort() error: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("FreePort: %v", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModulePort(t *testing.T) {
|
||||||
|
port, err := ModulePort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ModulePort() error: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("ModulePort: %v", port)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user